Merge remote-tracking branch 'upstream/master' into pr-implemt-apis

This commit is contained in:
KOUNOIKE
2018-11-24 02:09:05 +09:00
34 changed files with 884 additions and 786 deletions

View File

@@ -1,24 +1,24 @@
# Changelog # Changelog
All changes to the project will be documented in this file. All changes to the project will be documented in this file.
### 4.29.0 - 29 Sep 2018 ## 4.29.0 - 29 Sep 2018
- Official Docker image has been available - Official Docker image has been available
- Enhance file edit and delete buttons of the repository viewer - Enhance file edit and delete buttons of the repository viewer
- Fix Patch button to generate patches for all files in the commit - Fix Patch button to generate patches for all files in the commit
- Display confirmation dialog for Transfer Ownership and Garbage collection - Display confirmation dialog for Transfer Ownership and Garbage collection
- Fix wrong url encoding in "Compare & pull request" - Fix wrong url encoding in "Compare & pull request"
### 4.28.0 - 1 Sep 2018 ## 4.28.0 - 1 Sep 2018
- Proxy support for plugin installation - Proxy support for plugin installation
- Fix some bugs around pull requests - Fix some bugs around pull requests
### 4.27.0 - 29 Jul 2018 ## 4.27.0 - 29 Jul 2018
- Create new tag on the browser - Create new tag on the browser
- EditorConfig support - EditorConfig support
- Improve issues / pull requests search - Improve issues / pull requests search
- Some improvements and bug fixes for plugin installation via internet and pull request commenting - Some improvements and bug fixes for plugin installation via internet and pull request commenting
### 4.26.0 - 30 Jun 2018 ## 4.26.0 - 30 Jun 2018
- Installing plugins from the central registry - Installing plugins from the central registry
- Repositories tab in the dashboard - Repositories tab in the dashboard
- Fork dialog enhancement - Fork dialog enhancement
@@ -26,7 +26,7 @@ All changes to the project will be documented in this file.
- Keep showing incompleted task list - Keep showing incompleted task list
- New notification hooks - New notification hooks
### 4.25.0 - 29 May 2018 ## 4.25.0 - 29 May 2018
- Security improvements - Security improvements
- Show mail address at the profile page - Show mail address at the profile page
- Task list on commit comments - Task list on commit comments
@@ -34,10 +34,10 @@ All changes to the project will be documented in this file.
- Expose user public keys - Expose user public keys
- Download repository improvements - Download repository improvements
### 4.24.1 - 1 May 2018 ## 4.24.1 - 1 May 2018
- Fix bug in Web API authentication - Fix bug in Web API authentication
### 4.24.0 - 30 Apr 2018 ## 4.24.0 - 30 Apr 2018
- Diff for each review comment on pull requests - Diff for each review comment on pull requests
- Extra mail addresses support - Extra mail addresses support
- Show tags at the commit list - Show tags at the commit list
@@ -45,12 +45,12 @@ All changes to the project will be documented in this file.
- Renew layout of gitbucket-gist-plugin - Renew layout of gitbucket-gist-plugin
- Web API of gitbucket-ci-plugin - Web API of gitbucket-ci-plugin
### 4.23.1 - 10 Apr 2018 ## 4.23.1 - 10 Apr 2018
- Fix bug that the contents API doesn't work for the repository root - Fix bug that the contents API doesn't work for the repository root
- Fix shutdown problem in Tomcat deployment - Fix shutdown problem in Tomcat deployment
- Render by plugins at the blob view even if it's a binary file - Render by plugins at the blob view even if it's a binary file
### 4.23.0 - 31 Mar 2018 ## 4.23.0 - 31 Mar 2018
- Allow tail slash in URL - Allow tail slash in URL
- Display commit message of tags at the releases page - Display commit message of tags at the releases page
- Add labels property to issues and pull requests API response - Add labels property to issues and pull requests API response
@@ -58,26 +58,26 @@ All changes to the project will be documented in this file.
- Git authentication with personal access token - Git authentication with personal access token
- Max parallel builds and max stored history in CI plugin became configurable - Max parallel builds and max stored history in CI plugin became configurable
### 4.22.0 - 3 Mar 2018 ## 4.22.0 - 3 Mar 2018
- Pull request merge strategy settings - Pull request merge strategy settings
- Create repository with an empty commit - Create repository with an empty commit
- Improve database viewer - Improve database viewer
- Update maven-repository-plugin - Update maven-repository-plugin
### 4.21.2 - 27 Jan 2018 ## 4.21.2 - 27 Jan 2018
- Bugfix - Bugfix
### 4.21.1 - 27 Jan 2018 ## 4.21.1 - 27 Jan 2018
- Bugfix - Bugfix
### 4.21.0 - 27 Jan 2018 ## 4.21.0 - 27 Jan 2018
- Release page - Release page
- OpenID Connect support - OpenID Connect support
- New database viewer - New database viewer
- Submodule links to web page - Submodule links to web page
- Clarify close/reopen button - Clarify close/reopen button
## 4.20.0 - 23 Dec 2017 # 4.20.0 - 23 Dec 2017
- Squash and rebase merge strategy for pull requests - Squash and rebase merge strategy for pull requests
- Quick pull request creation - Quick pull request creation
- Download patch from the diff view - Download patch from the diff view

View File

@@ -6,7 +6,7 @@ val Name = "gitbucket"
val GitBucketVersion = "4.30.0-SNAPSHOT" val GitBucketVersion = "4.30.0-SNAPSHOT"
val ScalatraVersion = "2.6.3" val ScalatraVersion = "2.6.3"
val JettyVersion = "9.4.11.v20180605" val JettyVersion = "9.4.11.v20180605"
val JgitVersion = "5.1.2.201810061102-r" val JgitVersion = "5.1.3.201810200350-r"
lazy val root = (project in file(".")) lazy val root = (project in file("."))
.enablePlugins(SbtTwirl, ScalatraPlugin) .enablePlugins(SbtTwirl, ScalatraPlugin)
@@ -36,40 +36,40 @@ libraryDependencies ++= Seq(
"org.scalatra" %% "scalatra" % ScalatraVersion, "org.scalatra" %% "scalatra" % ScalatraVersion,
"org.scalatra" %% "scalatra-json" % ScalatraVersion, "org.scalatra" %% "scalatra-json" % ScalatraVersion,
"org.scalatra" %% "scalatra-forms" % ScalatraVersion, "org.scalatra" %% "scalatra-forms" % ScalatraVersion,
"org.json4s" %% "json4s-jackson" % "3.5.3", "org.json4s" %% "json4s-jackson" % "3.5.4",
"commons-io" % "commons-io" % "2.6", "commons-io" % "commons-io" % "2.6",
"io.github.gitbucket" % "solidbase" % "1.0.2", "io.github.gitbucket" % "solidbase" % "1.0.2",
"io.github.gitbucket" % "markedj" % "1.0.15", "io.github.gitbucket" % "markedj" % "1.0.15",
"org.apache.commons" % "commons-compress" % "1.18", "org.apache.commons" % "commons-compress" % "1.18",
"org.apache.commons" % "commons-email" % "1.5", "org.apache.commons" % "commons-email" % "1.5",
"org.apache.httpcomponents" % "httpclient" % "4.5.6", "org.apache.httpcomponents" % "httpclient" % "4.5.6",
"org.apache.sshd" % "apache-sshd" % "2.1.0" exclude ("org.slf4j", "slf4j-jdk14"), "org.apache.sshd" % "apache-sshd" % "1.7.0" exclude ("org.slf4j", "slf4j-jdk14"),
"org.apache.tika" % "tika-core" % "1.19.1", "org.apache.tika" % "tika-core" % "1.19.1",
"com.github.takezoe" %% "blocking-slick-32" % "0.0.10", "com.github.takezoe" %% "blocking-slick-32" % "0.0.11",
"com.novell.ldap" % "jldap" % "2009-10-07", "com.novell.ldap" % "jldap" % "2009-10-07",
"com.h2database" % "h2" % "1.4.196", "com.h2database" % "h2" % "1.4.197",
"org.mariadb.jdbc" % "mariadb-java-client" % "2.3.0", "org.mariadb.jdbc" % "mariadb-java-client" % "2.3.0",
"org.postgresql" % "postgresql" % "42.2.5", "org.postgresql" % "postgresql" % "42.2.5",
"ch.qos.logback" % "logback-classic" % "1.2.3", "ch.qos.logback" % "logback-classic" % "1.2.3",
"com.zaxxer" % "HikariCP" % "2.7.4", "com.zaxxer" % "HikariCP" % "2.7.9",
"com.typesafe" % "config" % "1.3.2", "com.typesafe" % "config" % "1.3.3",
"com.typesafe.akka" %% "akka-actor" % "2.5.8", "com.typesafe.akka" %% "akka-actor" % "2.5.17",
"fr.brouillard.oss.security.xhub" % "xhub4j-core" % "1.0.0", "fr.brouillard.oss.security.xhub" % "xhub4j-core" % "1.0.0",
"com.github.bkromhout" % "java-diff-utils" % "2.1.1", "com.github.bkromhout" % "java-diff-utils" % "2.1.1",
"org.cache2k" % "cache2k-all" % "1.0.1.Final", "org.cache2k" % "cache2k-all" % "1.0.2.Final",
"com.enragedginger" %% "akka-quartz-scheduler" % "1.6.1-akka-2.5.x" exclude ("c3p0", "c3p0"), "com.enragedginger" %% "akka-quartz-scheduler" % "1.7.0-akka-2.5.x" exclude ("c3p0", "c3p0"),
"net.coobird" % "thumbnailator" % "0.4.8", "net.coobird" % "thumbnailator" % "0.4.8",
"com.github.zafarkhaja" % "java-semver" % "0.9.0", "com.github.zafarkhaja" % "java-semver" % "0.9.0",
"com.nimbusds" % "oauth2-oidc-sdk" % "5.45", "com.nimbusds" % "oauth2-oidc-sdk" % "5.64.4",
"org.eclipse.jetty" % "jetty-webapp" % JettyVersion % "provided", "org.eclipse.jetty" % "jetty-webapp" % JettyVersion % "provided",
"javax.servlet" % "javax.servlet-api" % "3.1.0" % "provided", "javax.servlet" % "javax.servlet-api" % "3.1.0" % "provided",
"junit" % "junit" % "4.12" % "test", "junit" % "junit" % "4.12" % "test",
"org.scalatra" %% "scalatra-scalatest" % ScalatraVersion % "test", "org.scalatra" %% "scalatra-scalatest" % ScalatraVersion % "test",
"org.mockito" % "mockito-core" % "2.19.1" % "test", "org.mockito" % "mockito-core" % "2.23.0" % "test",
"com.wix" % "wix-embedded-mysql" % "3.0.0" % "test", "com.wix" % "wix-embedded-mysql" % "3.0.0" % "test",
"ru.yandex.qatools.embed" % "postgresql-embedded" % "2.6" % "test", "ru.yandex.qatools.embed" % "postgresql-embedded" % "2.9" % "test",
"net.i2p.crypto" % "eddsa" % "0.2.0", "net.i2p.crypto" % "eddsa" % "0.3.0",
"is.tagomor.woothee" % "woothee-java" % "1.7.0", "is.tagomor.woothee" % "woothee-java" % "1.8.0",
"org.ec4j.core" % "ec4j-core" % "0.0.1" "org.ec4j.core" % "ec4j-core" % "0.0.1"
) )

View File

@@ -1 +1 @@
sbt.version=1.2.3 sbt.version=1.2.6

View File

@@ -1 +1 @@
libraryDependencies += "com.eclipsesource.minimal-json" % "minimal-json" % "0.9.4" libraryDependencies += "com.eclipsesource.minimal-json" % "minimal-json" % "0.9.5"

View File

@@ -1,9 +1,9 @@
scalacOptions ++= Seq("-unchecked", "-deprecation", "-feature") scalacOptions ++= Seq("-unchecked", "-deprecation", "-feature")
addSbtPlugin("com.geirsson" % "sbt-scalafmt" % "1.5.0") addSbtPlugin("com.geirsson" % "sbt-scalafmt" % "1.5.0")
addSbtPlugin("com.typesafe.sbt" % "sbt-twirl" % "1.3.13") addSbtPlugin("com.typesafe.sbt" % "sbt-twirl" % "1.3.15")
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.5") addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.8")
addSbtPlugin("org.scalatra.sbt" % "sbt-scalatra" % "1.0.1") addSbtPlugin("org.scalatra.sbt" % "sbt-scalatra" % "1.0.1")
addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.1.0") addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.1.2")
addSbtCoursier addSbtCoursier
addSbtPlugin("com.typesafe.sbt" % "sbt-license-report" % "1.2.0") addSbtPlugin("com.typesafe.sbt" % "sbt-license-report" % "1.2.0")

View File

@@ -360,13 +360,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
// FileUtils.deleteDirectory(getWikiRepositoryDir(userName, repositoryName)) // FileUtils.deleteDirectory(getWikiRepositoryDir(userName, repositoryName))
// FileUtils.deleteDirectory(getTemporaryDir(userName, repositoryName)) // FileUtils.deleteDirectory(getTemporaryDir(userName, repositoryName))
// } // }
// Remove from GROUP_MEMBER and COLLABORATOR suspendAccount(account)
removeUserRelatedData(userName)
updateAccount(account.copy(isRemoved = true))
// call hooks
PluginRegistry().getAccountHooks.foreach(_.deleted(userName))
session.invalidate session.invalidate
redirect("/") redirect("/")
} }

View File

@@ -36,6 +36,7 @@ class ApiController
with WebHookService with WebHookService
with WebHookPullRequestService with WebHookPullRequestService
with WebHookIssueCommentService with WebHookIssueCommentService
with WebHookPullRequestReviewCommentService
with WikiService with WikiService
with ActivityService with ActivityService
with PrioritiesService with PrioritiesService

View File

@@ -12,9 +12,13 @@ class DashboardController
with PullRequestService with PullRequestService
with RepositoryService with RepositoryService
with AccountService with AccountService
with ActivityService
with CommitsService with CommitsService
with LabelsService with LabelsService
with PrioritiesService with PrioritiesService
with WebHookService
with WebHookPullRequestService
with WebHookPullRequestReviewCommentService
with MilestonesService with MilestonesService
with UsersAuthenticator with UsersAuthenticator

View File

@@ -26,6 +26,7 @@ class IssuesController
with WritableUsersAuthenticator with WritableUsersAuthenticator
with PullRequestService with PullRequestService
with WebHookIssueCommentService with WebHookIssueCommentService
with WebHookPullRequestReviewCommentService
with CommitsService with CommitsService
with PrioritiesService with PrioritiesService

View File

@@ -32,6 +32,7 @@ class PullRequestsController
with CommitsService with CommitsService
with ActivityService with ActivityService
with WebHookPullRequestService with WebHookPullRequestService
with WebHookPullRequestReviewCommentService
with ReadableUsersAuthenticator with ReadableUsersAuthenticator
with ReferrerAuthenticator with ReferrerAuthenticator
with WritableUsersAuthenticator with WritableUsersAuthenticator
@@ -294,11 +295,12 @@ trait PullRequestsControllerBase extends ControllerBase {
issueId <- params("id").toIntOpt issueId <- params("id").toIntOpt
loginAccount <- context.loginAccount loginAccount <- context.loginAccount
(issue, pullreq) <- getPullRequest(baseRepository.owner, baseRepository.name, issueId) (issue, pullreq) <- getPullRequest(baseRepository.owner, baseRepository.name, issueId)
repository <- getRepository(pullreq.requestUserName, pullreq.requestRepositoryName)
remoteRepository <- getRepository(pullreq.userName, pullreq.repositoryName)
owner = pullreq.requestUserName owner = pullreq.requestUserName
name = pullreq.requestRepositoryName name = pullreq.requestRepositoryName
if hasDeveloperRole(owner, name, context.loginAccount) if hasDeveloperRole(owner, name, context.loginAccount)
} yield { } yield {
val repository = getRepository(owner, name).get
val branchProtection = getProtectedBranchInfo(owner, name, pullreq.requestBranch) val branchProtection = getProtectedBranchInfo(owner, name, pullreq.requestBranch)
if (branchProtection.needStatusCheck(loginAccount.userName)) { if (branchProtection.needStatusCheck(loginAccount.userName)) {
flash += "error" -> s"branch ${pullreq.requestBranch} is protected need status check." flash += "error" -> s"branch ${pullreq.requestBranch} is protected need status check."
@@ -314,83 +316,19 @@ trait PullRequestsControllerBase extends ControllerBase {
JGitUtil.getAllCommitIds(git) JGitUtil.getAllCommitIds(git)
}.toSet }.toSet
pullRemote( pullRemote(
owner, repository,
name,
pullreq.requestBranch, pullreq.requestBranch,
pullreq.userName, remoteRepository,
pullreq.repositoryName,
pullreq.branch, pullreq.branch,
loginAccount, loginAccount,
s"Merge branch '${alias}' into ${pullreq.requestBranch}" s"Merge branch '${alias}' into ${pullreq.requestBranch}",
Some(pullreq)
) match { ) match {
case None => // conflict case None => // conflict
flash += "error" -> s"Can't automatic merging branch '${alias}' into ${pullreq.requestBranch}." flash += "error" -> s"Can't automatic merging branch '${alias}' into ${pullreq.requestBranch}."
case Some(oldId) => case Some(oldId) =>
// update pull request // update pull request
updatePullRequests(owner, name, pullreq.requestBranch) updatePullRequests(owner, name, pullreq.requestBranch, loginAccount, "synchronize")
using(Git.open(Directory.getRepositoryDir(owner, name))) {
git =>
// after update branch
val newCommitId = git.getRepository.resolve(s"refs/heads/${pullreq.requestBranch}")
val commits = git.log
.addRange(oldId, newCommitId)
.call
.iterator
.asScala
.map(c => new JGitUtil.CommitInfo(c))
.toList
commits.foreach { commit =>
if (!existIds.contains(commit.id)) {
createIssueComment(owner, name, commit)
}
}
// record activity
recordPushActivity(owner, name, loginAccount.userName, pullreq.branch, commits)
// close issue by commit message
if (pullreq.requestBranch == repository.repository.defaultBranch) {
commits.foreach { commit =>
closeIssuesFromMessage(commit.fullMessage, loginAccount.userName, owner, name).foreach {
issueId =>
getIssue(repository.owner, repository.name, issueId.toString).foreach { issue =>
callIssuesWebHook("closed", repository, issue, baseUrl, loginAccount)
PluginRegistry().getIssueHooks
.foreach(
_.closedByCommitComment(issue, repository, commit.fullMessage, loginAccount)
)
}
}
}
}
// call web hook
callPullRequestWebHookByRequestBranch(
"synchronize",
repository,
pullreq.requestBranch,
baseUrl,
loginAccount
)
callWebHookOf(owner, name, WebHook.Push) {
for {
ownerAccount <- getAccountByUserName(owner)
} yield {
WebHookService.WebHookPushPayload(
git,
loginAccount,
pullreq.requestBranch,
repository,
commits,
ownerAccount,
oldId = oldId,
newId = newCommitId
)
}
}
}
flash += "info" -> s"Merge branch '${alias}' into ${pullreq.requestBranch}" flash += "info" -> s"Merge branch '${alias}' into ${pullreq.requestBranch}"
} }
} }
@@ -401,119 +339,14 @@ trait PullRequestsControllerBase extends ControllerBase {
}) })
post("/:owner/:repository/pull/:id/merge", mergeForm)(writableUsersOnly { (form, repository) => post("/:owner/:repository/pull/:id/merge", mergeForm)(writableUsersOnly { (form, repository) =>
params("id").toIntOpt.flatMap { params("id").toIntOpt.flatMap { issueId =>
issueId =>
val owner = repository.owner val owner = repository.owner
val name = repository.name val name = repository.name
if (repository.repository.options.mergeOptions.split(",").contains(form.strategy)) {
LockUtil.lock(s"${owner}/${name}") {
getPullRequest(owner, name, issueId).map {
case (issue, pullreq) =>
using(Git.open(getRepositoryDir(owner, name))) {
git =>
// mark issue as merged and close.
val loginAccount = context.loginAccount.get
val commentId = createComment(owner, name, loginAccount.userName, issueId, form.message, "merge")
createComment(owner, name, loginAccount.userName, issueId, "Close", "close")
updateClosed(owner, name, issueId, true)
// record activity mergePullRequest(repository, issueId, context.loginAccount.get, form.message, form.strategy) match {
recordMergeActivity(owner, name, loginAccount.userName, issueId, form.message) case Right(objectId) => redirect(s"/${owner}/${name}/pull/${issueId}")
case Left(message) => Some(BadRequest())
val (commits, _) = getRequestCompareInfo(
owner,
name,
pullreq.commitIdFrom,
pullreq.requestUserName,
pullreq.requestRepositoryName,
pullreq.commitIdTo
)
val revCommits = using(new RevWalk(git.getRepository)) { revWalk =>
commits.flatten.map { commit =>
revWalk.parseCommit(git.getRepository.resolve(commit.id))
} }
}.reverse
// merge git repository
form.strategy match {
case "merge-commit" =>
mergePullRequest(
git,
pullreq.branch,
issueId,
s"Merge pull request #${issueId} from ${pullreq.requestUserName}/${pullreq.requestBranch}\n\n" + form.message,
new PersonIdent(loginAccount.fullName, loginAccount.mailAddress)
)
case "rebase" =>
rebasePullRequest(
git,
pullreq.branch,
issueId,
revCommits,
new PersonIdent(loginAccount.fullName, loginAccount.mailAddress)
)
case "squash" =>
squashPullRequest(
git,
pullreq.branch,
issueId,
s"${issue.title} (#${issueId})\n\n" + form.message,
new PersonIdent(loginAccount.fullName, loginAccount.mailAddress)
)
}
// close issue by content of pull request
val defaultBranch = getRepository(owner, name).get.repository.defaultBranch
if (pullreq.branch == defaultBranch) {
commits.flatten.foreach { commit =>
closeIssuesFromMessage(commit.fullMessage, loginAccount.userName, owner, name).foreach {
issueId =>
getIssue(owner, name, issueId.toString).foreach { issue =>
callIssuesWebHook("closed", repository, issue, baseUrl, loginAccount)
PluginRegistry().getIssueHooks
.foreach(_.closedByCommitComment(issue, repository, commit.fullMessage, loginAccount))
}
}
}
val issueContent = issue.title + " " + issue.content.getOrElse("")
closeIssuesFromMessage(
issueContent,
loginAccount.userName,
owner,
name
).foreach { issueId =>
getIssue(owner, name, issueId.toString).foreach { issue =>
callIssuesWebHook("closed", repository, issue, baseUrl, loginAccount)
PluginRegistry().getIssueHooks
.foreach(_.closedByCommitComment(issue, repository, issueContent, loginAccount))
}
}
closeIssuesFromMessage(form.message, loginAccount.userName, owner, name).foreach { issueId =>
getIssue(owner, name, issueId.toString).foreach { issue =>
callIssuesWebHook("closed", repository, issue, baseUrl, loginAccount)
PluginRegistry().getIssueHooks
.foreach(_.closedByCommitComment(issue, repository, issueContent, loginAccount))
}
}
}
updatePullRequests(owner, name, pullreq.branch)
// call web hook
callPullRequestWebHook("closed", repository, issueId, context.baseUrl, context.loginAccount.get)
// call hooks
PluginRegistry().getPullRequestHooks.foreach { h =>
h.addedComment(commentId, form.message, issue, repository)
h.merged(issue, repository)
}
redirect(s"/${owner}/${name}/pull/${issueId}")
}
}
}
} else Some(BadRequest())
} getOrElse NotFound() } getOrElse NotFound()
}) })
@@ -731,7 +564,7 @@ trait PullRequestsControllerBase extends ControllerBase {
recordPullRequestActivity(owner, name, loginUserName, issueId, form.title) recordPullRequestActivity(owner, name, loginUserName, issueId, form.title)
// call web hook // call web hook
callPullRequestWebHook("opened", repository, issueId, context.baseUrl, context.loginAccount.get) callPullRequestWebHook("opened", repository, issueId, context.loginAccount.get)
getIssue(owner, name, issueId.toString) foreach { issue => getIssue(owner, name, issueId.toString) foreach { issue =>
// extract references and create refer comment // extract references and create refer comment

View File

@@ -13,13 +13,11 @@ import gitbucket.core.util.SyntaxSugars._
import gitbucket.core.util.Implicits._ import gitbucket.core.util.Implicits._
import gitbucket.core.util.Directory._ import gitbucket.core.util.Directory._
import org.scalatra.forms._ import org.scalatra.forms._
import org.apache.commons.io.FileUtils
import org.scalatra.i18n.Messages import org.scalatra.i18n.Messages
import org.eclipse.jgit.api.Git import org.eclipse.jgit.api.Git
import org.eclipse.jgit.lib.Constants import org.eclipse.jgit.lib.Constants
import org.eclipse.jgit.lib.ObjectId import org.eclipse.jgit.lib.ObjectId
import gitbucket.core.model.WebHookContentType import gitbucket.core.model.WebHookContentType
import gitbucket.core.plugin.PluginRegistry
class RepositorySettingsController class RepositorySettingsController
extends RepositorySettingsControllerBase extends RepositorySettingsControllerBase
@@ -148,29 +146,6 @@ trait RepositorySettingsControllerBase extends ControllerBase {
if (repository.name != form.repositoryName) { if (repository.name != form.repositoryName) {
// Update database // Update database
renameRepository(repository.owner, repository.name, repository.owner, form.repositoryName) renameRepository(repository.owner, repository.name, repository.owner, form.repositoryName)
// Move git repository
defining(getRepositoryDir(repository.owner, repository.name)) { dir =>
if (dir.isDirectory) {
FileUtils.moveDirectory(dir, getRepositoryDir(repository.owner, form.repositoryName))
}
}
// Move wiki repository
defining(getWikiRepositoryDir(repository.owner, repository.name)) { dir =>
if (dir.isDirectory) {
FileUtils.moveDirectory(dir, getWikiRepositoryDir(repository.owner, form.repositoryName))
}
}
// Move files directory
defining(getRepositoryFilesDir(repository.owner, repository.name)) { dir =>
if (dir.isDirectory) {
FileUtils.moveDirectory(dir, getRepositoryFilesDir(repository.owner, form.repositoryName))
}
}
// Delete parent directory
FileUtil.deleteDirectoryIfEmpty(getRepositoryFilesDir(repository.owner, repository.name))
// Call hooks
PluginRegistry().getRepositoryHooks.foreach(_.renamed(repository.owner, repository.name, form.repositoryName))
} }
flash += "info" -> "Repository settings has been updated." flash += "info" -> "Repository settings has been updated."
redirect(s"/${repository.owner}/${form.repositoryName}/settings/options") redirect(s"/${repository.owner}/${form.repositoryName}/settings/options")
@@ -392,31 +367,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
post("/:owner/:repository/settings/transfer", transferForm)(ownerOnly { (form, repository) => post("/:owner/:repository/settings/transfer", transferForm)(ownerOnly { (form, repository) =>
// Change repository owner // Change repository owner
if (repository.owner != form.newOwner) { if (repository.owner != form.newOwner) {
LockUtil.lock(s"${repository.owner}/${repository.name}") {
// Update database
renameRepository(repository.owner, repository.name, form.newOwner, repository.name) renameRepository(repository.owner, repository.name, form.newOwner, repository.name)
// Move git repository
defining(getRepositoryDir(repository.owner, repository.name)) { dir =>
if (dir.isDirectory) {
FileUtils.moveDirectory(dir, getRepositoryDir(form.newOwner, repository.name))
}
}
// Move wiki repository
defining(getWikiRepositoryDir(repository.owner, repository.name)) { dir =>
if (dir.isDirectory) {
FileUtils.moveDirectory(dir, getWikiRepositoryDir(form.newOwner, repository.name))
}
}
// Move files directory
defining(getRepositoryFilesDir(repository.owner, repository.name)) { dir =>
if (dir.isDirectory) {
FileUtils.moveDirectory(dir, getRepositoryFilesDir(form.newOwner, repository.name))
}
}
// Call hooks
PluginRegistry().getRepositoryHooks.foreach(_.transferred(repository.owner, form.newOwner, repository.name))
}
} }
redirect(s"/${form.newOwner}/${repository.name}") redirect(s"/${form.newOwner}/${repository.name}")
}) })
@@ -425,19 +376,8 @@ trait RepositorySettingsControllerBase extends ControllerBase {
* Delete the repository. * Delete the repository.
*/ */
post("/:owner/:repository/settings/delete")(ownerOnly { repository => post("/:owner/:repository/settings/delete")(ownerOnly { repository =>
LockUtil.lock(s"${repository.owner}/${repository.name}") {
// Delete the repository and related files // Delete the repository and related files
deleteRepository(repository.owner, repository.name) deleteRepository(repository.repository)
FileUtils.deleteDirectory(getRepositoryDir(repository.owner, repository.name))
FileUtils.deleteDirectory(getWikiRepositoryDir(repository.owner, repository.name))
FileUtils.deleteDirectory(getTemporaryDir(repository.owner, repository.name))
FileUtils.deleteDirectory(getRepositoryFilesDir(repository.owner, repository.name))
// Call hooks
PluginRegistry().getRepositoryHooks.foreach(_.deleted(repository.owner, repository.name))
}
redirect(s"/${repository.owner}") redirect(s"/${repository.owner}")
}) })

View File

@@ -7,14 +7,13 @@ import gitbucket.core.plugin.PluginRegistry
import gitbucket.core.repo.html import gitbucket.core.repo.html
import gitbucket.core.helper import gitbucket.core.helper
import gitbucket.core.service._ import gitbucket.core.service._
import gitbucket.core.service.RepositoryCommitFileService.CommitFile
import gitbucket.core.util._ import gitbucket.core.util._
import gitbucket.core.util.JGitUtil._
import gitbucket.core.util.StringUtil._ import gitbucket.core.util.StringUtil._
import gitbucket.core.util.SyntaxSugars._ import gitbucket.core.util.SyntaxSugars._
import gitbucket.core.util.Implicits._ import gitbucket.core.util.Implicits._
import gitbucket.core.util.Directory._ import gitbucket.core.util.Directory._
import gitbucket.core.model.{Account, CommitState, CommitStatus, WebHook} import gitbucket.core.model.{Account, CommitState, CommitStatus}
import gitbucket.core.service.WebHookService._
import gitbucket.core.view import gitbucket.core.view
import gitbucket.core.view.helpers import gitbucket.core.view.helpers
import org.apache.commons.compress.archivers.{ArchiveEntry, ArchiveOutputStream} import org.apache.commons.compress.archivers.{ArchiveEntry, ArchiveOutputStream}
@@ -24,15 +23,12 @@ import org.apache.commons.compress.compressors.bzip2.BZip2CompressorOutputStream
import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream
import org.apache.commons.compress.compressors.xz.XZCompressorOutputStream import org.apache.commons.compress.compressors.xz.XZCompressorOutputStream
import org.apache.commons.compress.utils.IOUtils import org.apache.commons.compress.utils.IOUtils
import org.scalatra.forms._
import org.apache.commons.io.FileUtils import org.apache.commons.io.FileUtils
import org.ec4j.core.model.PropertyType import org.scalatra.forms._
import org.eclipse.jgit.api.{ArchiveCommand, Git} import org.eclipse.jgit.api.{ArchiveCommand, Git}
import org.eclipse.jgit.archive.{TgzFormat, ZipFormat} import org.eclipse.jgit.archive.{TgzFormat, ZipFormat}
import org.eclipse.jgit.dircache.{DirCache, DirCacheBuilder}
import org.eclipse.jgit.errors.MissingObjectException import org.eclipse.jgit.errors.MissingObjectException
import org.eclipse.jgit.lib._ import org.eclipse.jgit.lib._
import org.eclipse.jgit.transport.{ReceiveCommand, ReceivePack}
import org.eclipse.jgit.treewalk.TreeWalk import org.eclipse.jgit.treewalk.TreeWalk
import org.eclipse.jgit.treewalk.filter.PathFilter import org.eclipse.jgit.treewalk.filter.PathFilter
import org.json4s.jackson.Serialization import org.json4s.jackson.Serialization
@@ -42,6 +38,7 @@ import org.scalatra.i18n.Messages
class RepositoryViewerController class RepositoryViewerController
extends RepositoryViewerControllerBase extends RepositoryViewerControllerBase
with RepositoryService with RepositoryService
with RepositoryCommitFileService
with AccountService with AccountService
with ActivityService with ActivityService
with IssuesService with IssuesService
@@ -64,6 +61,7 @@ class RepositoryViewerController
*/ */
trait RepositoryViewerControllerBase extends ControllerBase { trait RepositoryViewerControllerBase extends ControllerBase {
self: RepositoryService self: RepositoryService
with RepositoryCommitFileService
with AccountService with AccountService
with ActivityService with ActivityService
with IssuesService with IssuesService
@@ -319,13 +317,34 @@ trait RepositoryViewerControllerBase extends ControllerBase {
CommitFile(line.substring(0, i).trim, line.substring(i + 1).trim) CommitFile(line.substring(0, i).trim, line.substring(i + 1).trim)
} }
val newFiles = files.map { file =>
file.copy(name = if (form.path.length == 0) file.name else s"${form.path}/${file.name}")
}
commitFiles( commitFiles(
repository = repository, repository = repository,
branch = form.branch, branch = form.branch,
path = form.path, path = form.path,
files = files, files = files,
message = form.message.getOrElse("Add files via upload") message = form.message.getOrElse("Add files via upload"),
loginAccount = context.loginAccount.get
) {
case (git, headTip, builder, inserter) =>
JGitUtil.processTree(git, headTip) { (path, tree) =>
if (!newFiles.exists(_.name.contains(path))) {
builder.add(JGitUtil.createDirCacheEntry(path, tree.getEntryFileMode, tree.getEntryObjectId))
}
}
newFiles.foreach { file =>
val bytes =
FileUtils.readFileToByteArray(new File(getTemporaryDir(session.getId), FileUtil.checkFilename(file.id)))
builder.add(
JGitUtil.createDirCacheEntry(file.name, FileMode.REGULAR_FILE, inserter.insert(Constants.OBJ_BLOB, bytes))
) )
builder.finish()
}
}
if (form.path.length == 0) { if (form.path.length == 0) {
redirect(s"/${repository.owner}/${repository.name}/tree/${form.branch}") redirect(s"/${repository.owner}/${repository.name}/tree/${form.branch}")
@@ -394,7 +413,8 @@ trait RepositoryViewerControllerBase extends ControllerBase {
content = appendNewLine(convertLineSeparator(form.content, form.lineSeparator), form.lineSeparator), content = appendNewLine(convertLineSeparator(form.content, form.lineSeparator), form.lineSeparator),
charset = form.charset, charset = form.charset,
message = form.message.getOrElse(s"Create ${form.newFileName}"), message = form.message.getOrElse(s"Create ${form.newFileName}"),
commit = form.commit commit = form.commit,
loginAccount = context.loginAccount.get
) )
redirect( redirect(
@@ -417,7 +437,8 @@ trait RepositoryViewerControllerBase extends ControllerBase {
} else { } else {
form.message.getOrElse(s"Rename ${form.oldFileName.get} to ${form.newFileName}") form.message.getOrElse(s"Rename ${form.oldFileName.get} to ${form.newFileName}")
}, },
commit = form.commit commit = form.commit,
loginAccount = context.loginAccount.get
) )
redirect( redirect(
@@ -436,7 +457,8 @@ trait RepositoryViewerControllerBase extends ControllerBase {
content = "", content = "",
charset = "", charset = "",
message = form.message.getOrElse(s"Delete ${form.fileName}"), message = form.message.getOrElse(s"Delete ${form.fileName}"),
commit = form.commit commit = form.commit,
loginAccount = context.loginAccount.get
) )
println(form.path) println(form.path)
@@ -593,50 +615,17 @@ trait RepositoryViewerControllerBase extends ControllerBase {
post("/:owner/:repository/commit/:id/comment/new", commentForm)(readableUsersOnly { (form, repository) => post("/:owner/:repository/commit/:id/comment/new", commentForm)(readableUsersOnly { (form, repository) =>
val id = params("id") val id = params("id")
createCommitComment( createCommitComment(
repository.owner, repository,
repository.name,
id, id,
context.loginAccount.get.userName, context.loginAccount.get,
form.content, form.content,
form.fileName, form.fileName,
form.oldLineNumber, form.oldLineNumber,
form.newLineNumber, form.newLineNumber,
form.diff,
form.issueId form.issueId
) )
for {
fileName <- form.fileName
diff <- form.diff
} {
saveCommitCommentDiff(
repository.owner,
repository.name,
id,
fileName,
form.oldLineNumber,
form.newLineNumber,
diff
)
}
form.issueId match {
case Some(issueId) =>
recordCommentPullRequestActivity(
repository.owner,
repository.name,
context.loginAccount.get.userName,
issueId,
form.content
)
case None =>
recordCommentCommitActivity(
repository.owner,
repository.name,
context.loginAccount.get.userName,
id,
form.content
)
}
redirect(s"/${repository.owner}/${repository.name}/commit/${id}") redirect(s"/${repository.owner}/${repository.name}/commit/${id}")
}) })
@@ -661,64 +650,18 @@ trait RepositoryViewerControllerBase extends ControllerBase {
ajaxPost("/:owner/:repository/commit/:id/comment/_data/new", commentForm)(readableUsersOnly { (form, repository) => ajaxPost("/:owner/:repository/commit/:id/comment/_data/new", commentForm)(readableUsersOnly { (form, repository) =>
val id = params("id") val id = params("id")
val commentId = createCommitComment( val commentId = createCommitComment(
repository.owner, repository,
repository.name,
id, id,
context.loginAccount.get.userName, context.loginAccount.get,
form.content, form.content,
form.fileName, form.fileName,
form.oldLineNumber, form.oldLineNumber,
form.newLineNumber, form.newLineNumber,
form.diff,
form.issueId form.issueId
) )
for {
fileName <- form.fileName
diff <- form.diff
} {
saveCommitCommentDiff(
repository.owner,
repository.name,
id,
fileName,
form.oldLineNumber,
form.newLineNumber,
diff
)
}
val comment = getCommitComment(repository.owner, repository.name, commentId.toString).get val comment = getCommitComment(repository.owner, repository.name, commentId.toString).get
form.issueId match {
case Some(issueId) =>
getPullRequest(repository.owner, repository.name, issueId).foreach {
case (issue, pullRequest) =>
recordCommentPullRequestActivity(
repository.owner,
repository.name,
context.loginAccount.get.userName,
issueId,
form.content
)
PluginRegistry().getPullRequestHooks.foreach(_.addedComment(commentId, form.content, issue, repository))
callPullRequestReviewCommentWebHook(
"create",
comment,
repository,
issue,
pullRequest,
context.baseUrl,
context.loginAccount.get
)
}
case None =>
recordCommentCommitActivity(
repository.owner,
repository.name,
context.loginAccount.get.userName,
id,
form.content
)
}
helper.html helper.html
.commitcomment(comment, hasDeveloperRole(repository.owner, repository.name, context.loginAccount), repository) .commitcomment(comment, hasDeveloperRole(repository.owner, repository.name, context.loginAccount), repository)
}) })
@@ -930,185 +873,6 @@ trait RepositoryViewerControllerBase extends ControllerBase {
lazy val isValid: Boolean = fileIds.nonEmpty lazy val isValid: Boolean = fileIds.nonEmpty
} }
case class CommitFile(id: String, name: String)
private def commitFiles(
repository: RepositoryService.RepositoryInfo,
files: Seq[CommitFile],
branch: String,
path: String,
message: String
) = {
// prepend path to the filename
val newFiles = files.map { file =>
file.copy(name = if (path.length == 0) file.name else s"${path}/${file.name}")
}
_commitFile(repository, branch, message) {
case (git, headTip, builder, inserter) =>
JGitUtil.processTree(git, headTip) { (path, tree) =>
if (!newFiles.exists(_.name.contains(path))) {
builder.add(JGitUtil.createDirCacheEntry(path, tree.getEntryFileMode, tree.getEntryObjectId))
}
}
newFiles.foreach { file =>
val bytes =
FileUtils.readFileToByteArray(new File(getTemporaryDir(session.getId), FileUtil.checkFilename(file.id)))
builder.add(
JGitUtil.createDirCacheEntry(file.name, FileMode.REGULAR_FILE, inserter.insert(Constants.OBJ_BLOB, bytes))
)
builder.finish()
}
}
}
private def commitFile(
repository: RepositoryService.RepositoryInfo,
branch: String,
path: String,
newFileName: Option[String],
oldFileName: Option[String],
content: String,
charset: String,
message: String,
commit: String
) = {
val newPath = newFileName.map { newFileName =>
if (path.length == 0) newFileName else s"${path}/${newFileName}"
}
val oldPath = oldFileName.map { oldFileName =>
if (path.length == 0) oldFileName else s"${path}/${oldFileName}"
}
_commitFile(repository, branch, message) {
case (git, headTip, builder, inserter) =>
if (headTip.getName == commit) {
val permission = JGitUtil
.processTree(git, headTip) { (path, tree) =>
// Add all entries except the editing file
if (!newPath.contains(path) && !oldPath.contains(path)) {
builder.add(JGitUtil.createDirCacheEntry(path, tree.getEntryFileMode, tree.getEntryObjectId))
}
// Retrieve permission if file exists to keep it
oldPath.collect { case x if x == path => tree.getEntryFileMode.getBits }
}
.flatten
.headOption
newPath.foreach { newPath =>
builder.add(JGitUtil.createDirCacheEntry(newPath, permission.map { bits =>
FileMode.fromBits(bits)
} getOrElse FileMode.REGULAR_FILE, inserter.insert(Constants.OBJ_BLOB, content.getBytes(charset))))
}
builder.finish()
}
}
}
private def _commitFile(repository: RepositoryService.RepositoryInfo, branch: String, message: String)(
f: (Git, ObjectId, DirCacheBuilder, ObjectInserter) => Unit
) = {
LockUtil.lock(s"${repository.owner}/${repository.name}") {
using(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
val loginAccount = context.loginAccount.get
val builder = DirCache.newInCore.builder()
val inserter = git.getRepository.newObjectInserter()
val headName = s"refs/heads/${branch}"
val headTip = git.getRepository.resolve(headName)
f(git, headTip, builder, inserter)
val commitId = JGitUtil.createNewCommit(
git,
inserter,
headTip,
builder.getDirCache.writeTree(inserter),
headName,
loginAccount.fullName,
loginAccount.mailAddress,
message
)
inserter.flush()
inserter.close()
val receivePack = new ReceivePack(git.getRepository)
val receiveCommand = new ReceiveCommand(headTip, commitId, headName)
// call post commit hook
val error = PluginRegistry().getReceiveHooks.flatMap { hook =>
hook.preReceive(repository.owner, repository.name, receivePack, receiveCommand, loginAccount.userName)
}.headOption
error match {
case Some(error) =>
// commit is rejected
// TODO Notify commit failure to edited user
val refUpdate = git.getRepository.updateRef(headName)
refUpdate.setNewObjectId(headTip)
refUpdate.setForceUpdate(true)
refUpdate.update()
case None =>
// update refs
val refUpdate = git.getRepository.updateRef(headName)
refUpdate.setNewObjectId(commitId)
refUpdate.setForceUpdate(false)
refUpdate.setRefLogIdent(new PersonIdent(loginAccount.fullName, loginAccount.mailAddress))
refUpdate.update()
// update pull request
updatePullRequests(repository.owner, repository.name, branch)
// record activity
val commitInfo = new CommitInfo(JGitUtil.getRevCommitFromId(git, commitId))
recordPushActivity(repository.owner, repository.name, loginAccount.userName, branch, List(commitInfo))
// create issue comment by commit message
createIssueComment(repository.owner, repository.name, commitInfo)
// close issue by commit message
if (branch == repository.repository.defaultBranch) {
closeIssuesFromMessage(message, loginAccount.userName, repository.owner, repository.name).foreach {
issueId =>
getIssue(repository.owner, repository.name, issueId.toString).foreach { issue =>
callIssuesWebHook("closed", repository, issue, baseUrl, loginAccount)
PluginRegistry().getIssueHooks
.foreach(_.closedByCommitComment(issue, repository, message, loginAccount))
}
}
}
// call post commit hook
PluginRegistry().getReceiveHooks.foreach { hook =>
hook.postReceive(repository.owner, repository.name, receivePack, receiveCommand, loginAccount.userName)
}
//call web hook
callPullRequestWebHookByRequestBranch("synchronize", repository, branch, context.baseUrl, loginAccount)
val commit = new JGitUtil.CommitInfo(JGitUtil.getRevCommitFromId(git, commitId))
callWebHookOf(repository.owner, repository.name, WebHook.Push) {
getAccountByUserName(repository.owner).map { ownerAccount =>
WebHookPushPayload(
git,
loginAccount,
headName,
repository,
List(commit),
ownerAccount,
oldId = headTip,
newId = commitId
)
}
}
}
}
}
}
private val readmeFiles = PluginRegistry().renderableExtensions.map { extension => private val readmeFiles = PluginRegistry().renderableExtensions.map { extension =>
s"readme.${extension}" s"readme.${extension}"
} ++ Seq("readme.txt", "readme") } ++ Seq("readme.txt", "readme")

View File

@@ -8,7 +8,7 @@ import gitbucket.core.service.RepositoryService.RepositoryInfo
import gitbucket.core.service.SystemSettingsService.SystemSettings import gitbucket.core.service.SystemSettingsService.SystemSettings
import gitbucket.core.util.SyntaxSugars._ import gitbucket.core.util.SyntaxSugars._
import io.github.gitbucket.solidbase.model.Version import io.github.gitbucket.solidbase.model.Version
import org.apache.sshd.server.command.Command import org.apache.sshd.server.Command
import play.twirl.api.Html import play.twirl.api.Html
/** /**

View File

@@ -24,7 +24,7 @@ import io.github.gitbucket.solidbase.manager.JDBCVersionManager
import io.github.gitbucket.solidbase.model.Module import io.github.gitbucket.solidbase.model.Module
import org.apache.commons.io.FileUtils import org.apache.commons.io.FileUtils
import org.apache.http.client.methods.HttpGet import org.apache.http.client.methods.HttpGet
import org.apache.sshd.server.command.Command import org.apache.sshd.server.Command
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import play.twirl.api.Html import play.twirl.api.Html

View File

@@ -7,6 +7,7 @@ import gitbucket.core.model.Profile.profile.blockingApi._
import gitbucket.core.model.Profile.dateColumnType import gitbucket.core.model.Profile.dateColumnType
import gitbucket.core.util.{LDAPUtil, StringUtil} import gitbucket.core.util.{LDAPUtil, StringUtil}
import StringUtil._ import StringUtil._
import gitbucket.core.plugin.PluginRegistry
import gitbucket.core.service.SystemSettingsService.SystemSettings import gitbucket.core.service.SystemSettingsService.SystemSettings
trait AccountService { trait AccountService {
@@ -183,6 +184,15 @@ trait AccountService {
account account
} }
def suspendAccount(account: Account)(implicit s: Session): Unit = {
// Remove from GROUP_MEMBER and COLLABORATOR
removeUserRelatedData(account.userName)
updateAccount(account.copy(isRemoved = true))
// call hooks
PluginRegistry().getAccountHooks.foreach(_.deleted(account.userName))
}
def updateAccount(account: Account)(implicit s: Session): Unit = def updateAccount(account: Account)(implicit s: Session): Unit =
Accounts Accounts
.filter { a => .filter { a =>
@@ -281,6 +291,15 @@ trait AccountService {
Collaborators.filter(_.collaboratorName === userName.bind).delete Collaborators.filter(_.collaboratorName === userName.bind).delete
} }
def removeUser(account: Account)(implicit s: Session): Unit = {
// Remove from GROUP_MEMBER and COLLABORATOR
removeUserRelatedData(account.userName)
updateAccount(account.copy(isRemoved = true))
// call hooks
PluginRegistry().getAccountHooks.foreach(_.deleted(account.userName))
}
def getGroupNames(userName: String)(implicit s: Session): List[String] = { def getGroupNames(userName: String)(implicit s: Session): List[String] = {
List(userName) ++ List(userName) ++
Collaborators.filter(_.collaboratorName === userName.bind).sortBy(_.userName).map(_.userName).list.distinct Collaborators.filter(_.collaboratorName === userName.bind).sortBy(_.userName).map(_.userName).list.distinct

View File

@@ -360,7 +360,7 @@ trait ActivityService {
repositoryName, repositoryName,
activityUserName, activityUserName,
"release", "release",
s"[user:${activityUserName}] released ${name} at [repo:${userName}/${repositoryName}]", s"[user:${activityUserName}] released [release:${userName}/${repositoryName}/${name}] at [repo:${userName}/${repositoryName}]",
None, None,
currentDate currentDate
) )

View File

@@ -2,15 +2,20 @@ package gitbucket.core.service
import java.io.File import java.io.File
import gitbucket.core.model.CommitComment import gitbucket.core.api.JsonFormat
import gitbucket.core.controller.Context
import gitbucket.core.model.{Account, CommitComment}
import gitbucket.core.model.Profile._ import gitbucket.core.model.Profile._
import gitbucket.core.model.Profile.profile.blockingApi._ import gitbucket.core.model.Profile.profile.blockingApi._
import gitbucket.core.model.Profile.dateColumnType import gitbucket.core.model.Profile.dateColumnType
import gitbucket.core.plugin.PluginRegistry
import gitbucket.core.service.RepositoryService.RepositoryInfo
import gitbucket.core.util.Directory._ import gitbucket.core.util.Directory._
import gitbucket.core.util.{FileUtil, StringUtil} import gitbucket.core.util.{FileUtil, StringUtil}
import org.apache.commons.io.FileUtils import org.apache.commons.io.FileUtils
trait CommitsService { trait CommitsService {
self: ActivityService with PullRequestService with WebHookPullRequestReviewCommentService =>
def getCommitComments(owner: String, repository: String, commitId: String, includePullRequest: Boolean)( def getCommitComments(owner: String, repository: String, commitId: String, includePullRequest: Boolean)(
implicit s: Session implicit s: Session
@@ -28,21 +33,21 @@ trait CommitsService {
None None
def createCommitComment( def createCommitComment(
owner: String, repository: RepositoryInfo,
repository: String,
commitId: String, commitId: String,
loginUser: String, loginAccount: Account,
content: String, content: String,
fileName: Option[String], fileName: Option[String],
oldLine: Option[Int], oldLine: Option[Int],
newLine: Option[Int], newLine: Option[Int],
diff: Option[String],
issueId: Option[Int] issueId: Option[Int]
)(implicit s: Session): Int = )(implicit s: Session, c: JsonFormat.Context, context: Context): Int = {
CommitComments returning CommitComments.map(_.commentId) insert CommitComment( val commentId = CommitComments returning CommitComments.map(_.commentId) insert CommitComment(
userName = owner, userName = repository.owner,
repositoryName = repository, repositoryName = repository.name,
commitId = commitId, commitId = commitId,
commentedUserName = loginUser, commentedUserName = loginAccount.userName,
content = content, content = content,
fileName = fileName, fileName = fileName,
oldLine = oldLine, oldLine = oldLine,
@@ -55,6 +60,56 @@ trait CommitsService {
originalNewLine = newLine originalNewLine = newLine
) )
for {
fileName <- fileName
diff <- diff
} {
saveCommitCommentDiff(
repository.owner,
repository.name,
commitId,
fileName,
oldLine,
newLine,
diff
)
}
val comment = getCommitComment(repository.owner, repository.name, commentId.toString).get
issueId match {
case Some(issueId) =>
getPullRequest(repository.owner, repository.name, issueId).foreach {
case (issue, pullRequest) =>
recordCommentPullRequestActivity(
repository.owner,
repository.name,
loginAccount.userName,
issueId,
content
)
PluginRegistry().getPullRequestHooks.foreach(_.addedComment(commentId, content, issue, repository))
callPullRequestReviewCommentWebHook(
"create",
comment,
repository,
issue,
pullRequest,
loginAccount
)
}
case None =>
recordCommentCommitActivity(
repository.owner,
repository.name,
loginAccount.userName,
commitId,
content
)
}
commentId
}
def updateCommitCommentPosition(commentId: Int, commitId: String, oldLine: Option[Int], newLine: Option[Int])( def updateCommitCommentPosition(commentId: Int, commitId: String, oldLine: Option[Int], newLine: Option[Int])(
implicit s: Session implicit s: Session
): Unit = ): Unit =

View File

@@ -87,9 +87,9 @@ trait HandleCommentService {
case "reopen" => "reopened" case "reopen" => "reopened"
} }
if (issue.isPullRequest) if (issue.isPullRequest)
callPullRequestWebHook(webHookAction, repository, issue.issueId, context.baseUrl, loginAccount) callPullRequestWebHook(webHookAction, repository, issue.issueId, loginAccount)
else else
callIssuesWebHook(webHookAction, repository, issue, context.baseUrl, loginAccount) callIssuesWebHook(webHookAction, repository, issue, loginAccount)
} }
// call hooks // call hooks

View File

@@ -57,7 +57,7 @@ trait IssueCreationService {
createReferComment(owner, name, issue, title + " " + body.getOrElse(""), loginAccount) createReferComment(owner, name, issue, title + " " + body.getOrElse(""), loginAccount)
// call web hooks // call web hooks
callIssuesWebHook("opened", repository, issue, context.baseUrl, loginAccount) callIssuesWebHook("opened", repository, issue, loginAccount)
// call hooks // call hooks
PluginRegistry().getIssueHooks.foreach(_.created(issue, repository)) PluginRegistry().getIssueHooks.foreach(_.created(issue, repository))

View File

@@ -1,8 +1,16 @@
package gitbucket.core.service package gitbucket.core.service
import gitbucket.core.model.Account import gitbucket.core.api.JsonFormat
import gitbucket.core.controller.Context
import gitbucket.core.model.{Account, PullRequest, WebHook}
import gitbucket.core.plugin.PluginRegistry
import gitbucket.core.service.RepositoryService.RepositoryInfo
import gitbucket.core.util.Directory._ import gitbucket.core.util.Directory._
import gitbucket.core.util.{JGitUtil, LockUtil}
import gitbucket.core.util.SyntaxSugars._ import gitbucket.core.util.SyntaxSugars._
import gitbucket.core.model.Profile._
import gitbucket.core.model.Profile.profile._
import gitbucket.core.model.Profile.profile.blockingApi._
import org.eclipse.jgit.merge.{MergeStrategy, Merger, RecursiveMerger} import org.eclipse.jgit.merge.{MergeStrategy, Merger, RecursiveMerger}
import org.eclipse.jgit.api.Git import org.eclipse.jgit.api.Git
import org.eclipse.jgit.transport.RefSpec import org.eclipse.jgit.transport.RefSpec
@@ -13,6 +21,13 @@ import org.eclipse.jgit.revwalk.{RevCommit, RevWalk}
import scala.collection.JavaConverters._ import scala.collection.JavaConverters._
trait MergeService { trait MergeService {
self: AccountService
with ActivityService
with IssuesService
with RepositoryService
with PullRequestService
with WebHookPullRequestService =>
import MergeService._ import MergeService._
/** /**
@@ -43,7 +58,13 @@ trait MergeService {
} }
/** merge the pull request with a merge commit */ /** merge the pull request with a merge commit */
def mergePullRequest(git: Git, branch: String, issueId: Int, message: String, committer: PersonIdent): Unit = { def mergePullRequest(
git: Git,
branch: String,
issueId: Int,
message: String,
committer: PersonIdent
): ObjectId = {
new MergeCacheInfo(git, branch, issueId).merge(message, committer) new MergeCacheInfo(git, branch, issueId).merge(message, committer)
} }
@@ -54,12 +75,18 @@ trait MergeService {
issueId: Int, issueId: Int,
commits: Seq[RevCommit], commits: Seq[RevCommit],
committer: PersonIdent committer: PersonIdent
): Unit = { ): ObjectId = {
new MergeCacheInfo(git, branch, issueId).rebase(committer, commits) new MergeCacheInfo(git, branch, issueId).rebase(committer, commits)
} }
/** squash commits in the pull request and append it */ /** squash commits in the pull request and append it */
def squashPullRequest(git: Git, branch: String, issueId: Int, message: String, committer: PersonIdent): Unit = { def squashPullRequest(
git: Git,
branch: String,
issueId: Int,
message: String,
committer: PersonIdent
): ObjectId = {
new MergeCacheInfo(git, branch, issueId).squash(message, committer) new MergeCacheInfo(git, branch, issueId).squash(message, committer)
} }
@@ -136,27 +163,223 @@ trait MergeService {
tryMergeRemote(userName, repositoryName, branch, requestUserName, requestRepositoryName, requestBranch).left.toOption tryMergeRemote(userName, repositoryName, branch, requestUserName, requestRepositoryName, requestBranch).left.toOption
def pullRemote( def pullRemote(
localUserName: String, localRepository: RepositoryInfo,
localRepositoryName: String,
localBranch: String, localBranch: String,
remoteUserName: String, remoteRepository: RepositoryInfo,
remoteRepositoryName: String,
remoteBranch: String, remoteBranch: String,
loginAccount: Account, loginAccount: Account,
message: String message: String,
): Option[ObjectId] = { pullreq: Option[PullRequest]
)(implicit s: Session, c: JsonFormat.Context): Option[ObjectId] = {
val localUserName = localRepository.owner
val localRepositoryName = localRepository.name
val remoteUserName = remoteRepository.owner
val remoteRepositoryName = remoteRepository.name
tryMergeRemote(localUserName, localRepositoryName, localBranch, remoteUserName, remoteRepositoryName, remoteBranch).map { tryMergeRemote(localUserName, localRepositoryName, localBranch, remoteUserName, remoteRepositoryName, remoteBranch).map {
case (newTreeId, oldBaseId, oldHeadId) => case (newTreeId, oldBaseId, oldHeadId) =>
using(Git.open(getRepositoryDir(localUserName, localRepositoryName))) { git => using(Git.open(getRepositoryDir(localUserName, localRepositoryName))) { git =>
val existIds = JGitUtil.getAllCommitIds(git).toSet
val committer = new PersonIdent(loginAccount.fullName, loginAccount.mailAddress) val committer = new PersonIdent(loginAccount.fullName, loginAccount.mailAddress)
val newCommit = val newCommit =
Util.createMergeCommit(git.getRepository, newTreeId, committer, message, Seq(oldBaseId, oldHeadId)) Util.createMergeCommit(git.getRepository, newTreeId, committer, message, Seq(oldBaseId, oldHeadId))
Util.updateRefs(git.getRepository, s"refs/heads/${localBranch}", newCommit, false, committer, Some("merge")) Util.updateRefs(git.getRepository, s"refs/heads/${localBranch}", newCommit, false, committer, Some("merge"))
val commits = git.log
.addRange(oldBaseId, newCommit)
.call
.iterator
.asScala
.map(c => new JGitUtil.CommitInfo(c))
.toList
commits.foreach { commit =>
if (!existIds.contains(commit.id)) {
createIssueComment(localUserName, localRepositoryName, commit)
}
}
// record activity
recordPushActivity(
localUserName,
localRepositoryName,
loginAccount.userName,
localBranch,
commits
)
// close issue by commit message
if (localBranch == localRepository.repository.defaultBranch) {
commits.foreach { commit =>
closeIssuesFromMessage(commit.fullMessage, loginAccount.userName, localUserName, localRepositoryName)
.foreach { issueId =>
getIssue(localRepository.owner, localRepository.name, issueId.toString).foreach { issue =>
callIssuesWebHook("closed", localRepository, issue, loginAccount)
PluginRegistry().getIssueHooks
.foreach(
_.closedByCommitComment(issue, localRepository, commit.fullMessage, loginAccount)
)
}
}
}
}
pullreq.foreach { pullreq =>
callWebHookOf(localRepository.owner, localRepository.name, WebHook.Push) {
for {
ownerAccount <- getAccountByUserName(localRepository.owner)
} yield {
WebHookService.WebHookPushPayload(
git,
loginAccount,
pullreq.requestBranch,
localRepository,
commits,
ownerAccount,
oldId = oldBaseId,
newId = newCommit
)
}
}
}
} }
oldBaseId oldBaseId
}.toOption }.toOption
} }
def mergePullRequest(
repository: RepositoryInfo,
issueId: Int,
loginAccount: Account,
message: String,
strategy: String
)(implicit s: Session, c: JsonFormat.Context, context: Context): Either[String, ObjectId] = {
if (repository.repository.options.mergeOptions.split(",").contains(strategy)) {
LockUtil.lock(s"${repository.owner}/${repository.name}") {
getPullRequest(repository.owner, repository.name, issueId)
.map {
case (issue, pullreq) =>
using(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
// mark issue as merged and close.
val commentId =
createComment(repository.owner, repository.name, loginAccount.userName, issueId, message, "merge")
createComment(repository.owner, repository.name, loginAccount.userName, issueId, "Close", "close")
updateClosed(repository.owner, repository.name, issueId, true)
// record activity
recordMergeActivity(repository.owner, repository.name, loginAccount.userName, issueId, message)
val (commits, _) = getRequestCompareInfo(
repository.owner,
repository.name,
pullreq.commitIdFrom,
pullreq.requestUserName,
pullreq.requestRepositoryName,
pullreq.commitIdTo
)
val revCommits = using(new RevWalk(git.getRepository)) { revWalk =>
commits.flatten.map { commit =>
revWalk.parseCommit(git.getRepository.resolve(commit.id))
}
}.reverse
// merge git repository
(strategy match {
case "merge-commit" =>
Some(
mergePullRequest(
git,
pullreq.branch,
issueId,
s"Merge pull request #${issueId} from ${pullreq.requestUserName}/${pullreq.requestBranch}\n\n" + message,
new PersonIdent(loginAccount.fullName, loginAccount.mailAddress)
)
)
case "rebase" =>
Some(
rebasePullRequest(
git,
pullreq.branch,
issueId,
revCommits,
new PersonIdent(loginAccount.fullName, loginAccount.mailAddress)
)
)
case "squash" =>
Some(
squashPullRequest(
git,
pullreq.branch,
issueId,
s"${issue.title} (#${issueId})\n\n" + message,
new PersonIdent(loginAccount.fullName, loginAccount.mailAddress)
)
)
case _ =>
None
}) match {
case Some(newCommitId) =>
// close issue by content of pull request
val defaultBranch = getRepository(repository.owner, repository.name).get.repository.defaultBranch
if (pullreq.branch == defaultBranch) {
commits.flatten.foreach { commit =>
closeIssuesFromMessage(
commit.fullMessage,
loginAccount.userName,
repository.owner,
repository.name
).foreach { issueId =>
getIssue(repository.owner, repository.name, issueId.toString).foreach { issue =>
callIssuesWebHook("closed", repository, issue, loginAccount)
PluginRegistry().getIssueHooks
.foreach(_.closedByCommitComment(issue, repository, commit.fullMessage, loginAccount))
}
}
}
val issueContent = issue.title + " " + issue.content.getOrElse("")
closeIssuesFromMessage(
issueContent,
loginAccount.userName,
repository.owner,
repository.name
).foreach { issueId =>
getIssue(repository.owner, repository.name, issueId.toString).foreach { issue =>
callIssuesWebHook("closed", repository, issue, loginAccount)
PluginRegistry().getIssueHooks
.foreach(_.closedByCommitComment(issue, repository, issueContent, loginAccount))
}
}
closeIssuesFromMessage(message, loginAccount.userName, repository.owner, repository.name)
.foreach { issueId =>
getIssue(repository.owner, repository.name, issueId.toString).foreach { issue =>
callIssuesWebHook("closed", repository, issue, loginAccount)
PluginRegistry().getIssueHooks
.foreach(_.closedByCommitComment(issue, repository, issueContent, loginAccount))
}
}
}
updatePullRequests(repository.owner, repository.name, pullreq.branch, loginAccount, "closed")
// call hooks
PluginRegistry().getPullRequestHooks.foreach { h =>
h.addedComment(commentId, message, issue, repository)
h.merged(issue, repository)
}
Right(newCommitId)
case None =>
Left("Unknown strategy")
}
}
case _ => Left("Unknown error")
}
.getOrElse(Left("Pull request not found"))
}
} else Left("Strategy not allowed")
}
} }
object MergeService { object MergeService {
@@ -191,13 +414,15 @@ object MergeService {
force: Boolean, force: Boolean,
committer: PersonIdent, committer: PersonIdent,
refLogMessage: Option[String] = None refLogMessage: Option[String] = None
): Unit = { ): ObjectId = {
val refUpdate = repository.updateRef(ref) val refUpdate = repository.updateRef(ref)
refUpdate.setNewObjectId(newObjectId) refUpdate.setNewObjectId(newObjectId)
refUpdate.setForceUpdate(force) refUpdate.setForceUpdate(force)
refUpdate.setRefLogIdent(committer) refUpdate.setRefLogIdent(committer)
refLogMessage.foreach(refUpdate.setRefLogMessage(_, true)) refLogMessage.foreach(refUpdate.setRefLogMessage(_, true))
refUpdate.update() refUpdate.update()
newObjectId
} }
} }
@@ -265,7 +490,7 @@ object MergeService {
} }
// update branch from cache // update branch from cache
def merge(message: String, committer: PersonIdent) = { def merge(message: String, committer: PersonIdent): ObjectId = {
if (checkConflict().isDefined) { if (checkConflict().isDefined) {
throw new RuntimeException("This pull request can't merge automatically.") throw new RuntimeException("This pull request can't merge automatically.")
} }
@@ -278,7 +503,7 @@ object MergeService {
Util.updateRefs(repository, s"refs/heads/${branch}", mergeCommitId, false, committer, Some("merged")) Util.updateRefs(repository, s"refs/heads/${branch}", mergeCommitId, false, committer, Some("merged"))
} }
def rebase(committer: PersonIdent, commits: Seq[RevCommit]): Unit = { def rebase(committer: PersonIdent, commits: Seq[RevCommit]): ObjectId = {
if (checkConflict().isDefined) { if (checkConflict().isDefined) {
throw new RuntimeException("This pull request can't merge automatically.") throw new RuntimeException("This pull request can't merge automatically.")
} }
@@ -310,7 +535,7 @@ object MergeService {
Util.updateRefs(repository, s"refs/heads/${branch}", previousId, false, committer, Some("rebased")) Util.updateRefs(repository, s"refs/heads/${branch}", previousId, false, committer, Some("rebased"))
} }
def squash(message: String, committer: PersonIdent): Unit = { def squash(message: String, committer: PersonIdent): ObjectId = {
if (checkConflict().isDefined) { if (checkConflict().isDefined) {
throw new RuntimeException("This pull request can't merge automatically.") throw new RuntimeException("This pull request can't merge automatically.")
} }

View File

@@ -5,6 +5,7 @@ import gitbucket.core.model.Profile._
import gitbucket.core.model.Profile.profile.blockingApi._ import gitbucket.core.model.Profile.profile.blockingApi._
import difflib.{Delta, DiffUtils} import difflib.{Delta, DiffUtils}
import gitbucket.core.service.RepositoryService.RepositoryInfo import gitbucket.core.service.RepositoryService.RepositoryInfo
import gitbucket.core.api.JsonFormat
import gitbucket.core.util.SyntaxSugars._ import gitbucket.core.util.SyntaxSugars._
import gitbucket.core.util.Directory._ import gitbucket.core.util.Directory._
import gitbucket.core.util.Implicits._ import gitbucket.core.util.Implicits._
@@ -17,7 +18,8 @@ import org.eclipse.jgit.lib.ObjectId
import scala.collection.JavaConverters._ import scala.collection.JavaConverters._
trait PullRequestService { self: IssuesService with CommitsService => trait PullRequestService {
self: IssuesService with CommitsService with WebHookService with WebHookPullRequestService with RepositoryService =>
import PullRequestService._ import PullRequestService._
def getPullRequest(owner: String, repository: String, issueId: Int)( def getPullRequest(owner: String, repository: String, issueId: Int)(
@@ -166,7 +168,10 @@ trait PullRequestService { self: IssuesService with CommitsService =>
/** /**
* Fetch pull request contents into refs/pull/${issueId}/head and update pull request table. * Fetch pull request contents into refs/pull/${issueId}/head and update pull request table.
*/ */
def updatePullRequests(owner: String, repository: String, branch: String)(implicit s: Session): Unit = def updatePullRequests(owner: String, repository: String, branch: String, loginAccount: Account, action: String)(
implicit s: Session,
c: JsonFormat.Context
): Unit = {
getPullRequestsByRequest(owner, repository, branch, Some(false)).foreach { pullreq => getPullRequestsByRequest(owner, repository, branch, Some(false)).foreach { pullreq =>
if (Repositories.filter(_.byRepository(pullreq.userName, pullreq.repositoryName)).exists.run) { if (Repositories.filter(_.byRepository(pullreq.userName, pullreq.repositoryName)).exists.run) {
// Update the git repository // Update the git repository
@@ -206,6 +211,15 @@ trait PullRequestService { self: IssuesService with CommitsService =>
// Update commit id in the PULL_REQUEST table // Update commit id in the PULL_REQUEST table
updateCommitId(pullreq.userName, pullreq.repositoryName, pullreq.issueId, commitIdTo, commitIdFrom) updateCommitId(pullreq.userName, pullreq.repositoryName, pullreq.issueId, commitIdTo, commitIdFrom)
// call web hook
callPullRequestWebHookByRequestBranch(
action,
getRepository(owner, repository).get,
pullreq.requestBranch,
loginAccount
)
}
} }
} }

View File

@@ -0,0 +1,188 @@
package gitbucket.core.service
import gitbucket.core.api.JsonFormat
import gitbucket.core.model.{Account, WebHook}
import gitbucket.core.model.Profile._
import gitbucket.core.model.Profile.profile.blockingApi._
import gitbucket.core.plugin.PluginRegistry
import gitbucket.core.service.WebHookService.WebHookPushPayload
import gitbucket.core.util.Directory.getRepositoryDir
import gitbucket.core.util.JGitUtil.CommitInfo
import gitbucket.core.util.{JGitUtil, LockUtil}
import gitbucket.core.util.SyntaxSugars.using
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.dircache.{DirCache, DirCacheBuilder}
import org.eclipse.jgit.lib._
import org.eclipse.jgit.transport.{ReceiveCommand, ReceivePack}
trait RepositoryCommitFileService {
self: AccountService with ActivityService with IssuesService with PullRequestService with WebHookPullRequestService =>
import RepositoryCommitFileService._
def commitFiles(
repository: RepositoryService.RepositoryInfo,
files: Seq[CommitFile],
branch: String,
path: String,
message: String,
loginAccount: Account
)(
f: (Git, ObjectId, DirCacheBuilder, ObjectInserter) => Unit
)(implicit s: Session, c: JsonFormat.Context) = {
// prepend path to the filename
_commitFile(repository, branch, message, loginAccount)(f)
}
def commitFile(
repository: RepositoryService.RepositoryInfo,
branch: String,
path: String,
newFileName: Option[String],
oldFileName: Option[String],
content: String,
charset: String,
message: String,
commit: String,
loginAccount: Account
)(implicit s: Session, c: JsonFormat.Context) = {
val newPath = newFileName.map { newFileName =>
if (path.length == 0) newFileName else s"${path}/${newFileName}"
}
val oldPath = oldFileName.map { oldFileName =>
if (path.length == 0) oldFileName else s"${path}/${oldFileName}"
}
_commitFile(repository, branch, message, loginAccount) {
case (git, headTip, builder, inserter) =>
if (headTip.getName == commit) {
val permission = JGitUtil
.processTree(git, headTip) { (path, tree) =>
// Add all entries except the editing file
if (!newPath.contains(path) && !oldPath.contains(path)) {
builder.add(JGitUtil.createDirCacheEntry(path, tree.getEntryFileMode, tree.getEntryObjectId))
}
// Retrieve permission if file exists to keep it
oldPath.collect { case x if x == path => tree.getEntryFileMode.getBits }
}
.flatten
.headOption
newPath.foreach { newPath =>
builder.add(JGitUtil.createDirCacheEntry(newPath, permission.map { bits =>
FileMode.fromBits(bits)
} getOrElse FileMode.REGULAR_FILE, inserter.insert(Constants.OBJ_BLOB, content.getBytes(charset))))
}
builder.finish()
}
}
}
private def _commitFile(
repository: RepositoryService.RepositoryInfo,
branch: String,
message: String,
loginAccount: Account
)(
f: (Git, ObjectId, DirCacheBuilder, ObjectInserter) => Unit
)(implicit s: Session, c: JsonFormat.Context) = {
LockUtil.lock(s"${repository.owner}/${repository.name}") {
using(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
val builder = DirCache.newInCore.builder()
val inserter = git.getRepository.newObjectInserter()
val headName = s"refs/heads/${branch}"
val headTip = git.getRepository.resolve(headName)
f(git, headTip, builder, inserter)
val commitId = JGitUtil.createNewCommit(
git,
inserter,
headTip,
builder.getDirCache.writeTree(inserter),
headName,
loginAccount.fullName,
loginAccount.mailAddress,
message
)
inserter.flush()
inserter.close()
val receivePack = new ReceivePack(git.getRepository)
val receiveCommand = new ReceiveCommand(headTip, commitId, headName)
// call post commit hook
val error = PluginRegistry().getReceiveHooks.flatMap { hook =>
hook.preReceive(repository.owner, repository.name, receivePack, receiveCommand, loginAccount.userName)
}.headOption
error match {
case Some(error) =>
// commit is rejected
// TODO Notify commit failure to edited user
val refUpdate = git.getRepository.updateRef(headName)
refUpdate.setNewObjectId(headTip)
refUpdate.setForceUpdate(true)
refUpdate.update()
case None =>
// update refs
val refUpdate = git.getRepository.updateRef(headName)
refUpdate.setNewObjectId(commitId)
refUpdate.setForceUpdate(false)
refUpdate.setRefLogIdent(new PersonIdent(loginAccount.fullName, loginAccount.mailAddress))
refUpdate.update()
// update pull request
updatePullRequests(repository.owner, repository.name, branch, loginAccount, "synchronize")
// record activity
val commitInfo = new CommitInfo(JGitUtil.getRevCommitFromId(git, commitId))
recordPushActivity(repository.owner, repository.name, loginAccount.userName, branch, List(commitInfo))
// create issue comment by commit message
createIssueComment(repository.owner, repository.name, commitInfo)
// close issue by commit message
if (branch == repository.repository.defaultBranch) {
closeIssuesFromMessage(message, loginAccount.userName, repository.owner, repository.name).foreach {
issueId =>
getIssue(repository.owner, repository.name, issueId.toString).foreach { issue =>
callIssuesWebHook("closed", repository, issue, loginAccount)
PluginRegistry().getIssueHooks
.foreach(_.closedByCommitComment(issue, repository, message, loginAccount))
}
}
}
// call post commit hook
PluginRegistry().getReceiveHooks.foreach { hook =>
hook.postReceive(repository.owner, repository.name, receivePack, receiveCommand, loginAccount.userName)
}
val commit = new JGitUtil.CommitInfo(JGitUtil.getRevCommitFromId(git, commitId))
callWebHookOf(repository.owner, repository.name, WebHook.Push) {
getAccountByUserName(repository.owner).map { ownerAccount =>
WebHookPushPayload(
git,
loginAccount,
headName,
repository,
List(commit),
ownerAccount,
oldId = headTip,
newId = commitId
)
}
}
}
}
}
}
}
object RepositoryCommitFileService {
case class CommitFile(id: String, name: String)
}

View File

@@ -1,16 +1,25 @@
package gitbucket.core.service package gitbucket.core.service
import gitbucket.core.api.JsonFormat
import gitbucket.core.controller.Context import gitbucket.core.controller.Context
import gitbucket.core.util._ import gitbucket.core.util._
import gitbucket.core.util.SyntaxSugars._ import gitbucket.core.util.SyntaxSugars._
import gitbucket.core.model.{Account, Collaborator, Repository, RepositoryOptions, Role, ReleaseTag} import gitbucket.core.model.{CommitComments => _, Session => _, _}
import gitbucket.core.model.Profile._ import gitbucket.core.model.Profile._
import gitbucket.core.model.Profile.profile.blockingApi._ import gitbucket.core.model.Profile.profile.blockingApi._
import gitbucket.core.model.Profile.dateColumnType import gitbucket.core.model.Profile.dateColumnType
import gitbucket.core.util.JGitUtil.FileInfo import gitbucket.core.plugin.PluginRegistry
import gitbucket.core.service.WebHookService.WebHookPushPayload
import gitbucket.core.util.Directory.{getRepositoryDir, getRepositoryFilesDir, getTemporaryDir, getWikiRepositoryDir}
import gitbucket.core.util.JGitUtil.{CommitInfo, FileInfo}
import org.apache.commons.io.FileUtils
import org.eclipse.jgit.api.Git import org.eclipse.jgit.api.Git
import org.eclipse.jgit.dircache.{DirCache, DirCacheBuilder}
import org.eclipse.jgit.lib.{Repository => _, _}
import org.eclipse.jgit.transport.{ReceiveCommand, ReceivePack}
trait RepositoryService { self: AccountService => trait RepositoryService {
self: AccountService =>
import RepositoryService._ import RepositoryService._
/** /**
@@ -68,6 +77,7 @@ trait RepositoryService { self: AccountService =>
(Repositories filter { t => (Repositories filter { t =>
t.byRepository(oldUserName, oldRepositoryName) t.byRepository(oldUserName, oldRepositoryName)
} firstOption).foreach { repository => } firstOption).foreach { repository =>
LockUtil.lock(s"${repository.userName}/${repository.repositoryName}") {
Repositories insert repository.copy(userName = newUserName, repositoryName = newRepositoryName) Repositories insert repository.copy(userName = newUserName, repositoryName = newRepositoryName)
val webHooks = RepositoryWebHooks.filter(_.byRepository(oldUserName, oldRepositoryName)).list val webHooks = RepositoryWebHooks.filter(_.byRepository(oldUserName, oldRepositoryName)).list
@@ -117,7 +127,7 @@ trait RepositoryService { self: AccountService =>
.update(newUserName, newRepositoryName) .update(newUserName, newRepositoryName)
} }
deleteRepository(oldUserName, oldRepositoryName) deleteRepositoryOnModel(oldUserName, oldRepositoryName)
RepositoryWebHooks.insertAll( RepositoryWebHooks.insertAll(
webHooks.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _* webHooks.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _*
@@ -139,12 +149,17 @@ trait RepositoryService { self: AccountService =>
newMilestones.find(_.title == milestones.find(_.milestoneId == id).get.title).get.milestoneId newMilestones.find(_.title == milestones.find(_.milestoneId == id).get.title).get.milestoneId
}, },
priorityId = x.priorityId.map { id => priorityId = x.priorityId.map { id =>
newPriorities.find(_.priorityName == priorities.find(_.priorityId == id).get.priorityName).get.priorityId newPriorities
.find(_.priorityName == priorities.find(_.priorityId == id).get.priorityName)
.get
.priorityId
} }
) )
}: _*) }: _*)
PullRequests.insertAll(pullRequests.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _*) PullRequests.insertAll(
pullRequests.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _*
)
IssueComments.insertAll( IssueComments.insertAll(
issueComments.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _* issueComments.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _*
) )
@@ -223,7 +238,10 @@ trait RepositoryService { self: AccountService =>
s"[branch:${oldUserName}/${oldRepositoryName}#", s"[branch:${oldUserName}/${oldRepositoryName}#",
s"[branch:${newUserName}/${newRepositoryName}#" s"[branch:${newUserName}/${newRepositoryName}#"
) )
.replace(s"[tag:${oldUserName}/${oldRepositoryName}#", s"[tag:${newUserName}/${newRepositoryName}#") .replace(
s"[tag:${oldUserName}/${oldRepositoryName}#",
s"[tag:${newUserName}/${newRepositoryName}#"
)
.replace( .replace(
s"[pullreq:${oldUserName}/${oldRepositoryName}#", s"[pullreq:${oldUserName}/${oldRepositoryName}#",
s"[pullreq:${newUserName}/${newRepositoryName}#" s"[pullreq:${newUserName}/${newRepositoryName}#"
@@ -238,11 +256,53 @@ trait RepositoryService { self: AccountService =>
) )
) )
} }
// Move git repository
defining(getRepositoryDir(oldUserName, oldRepositoryName)) { dir =>
if (dir.isDirectory) {
FileUtils.moveDirectory(dir, getRepositoryDir(newUserName, newRepositoryName))
}
}
// Move wiki repository
defining(getWikiRepositoryDir(oldUserName, oldRepositoryName)) { dir =>
if (dir.isDirectory) {
FileUtils.moveDirectory(dir, getWikiRepositoryDir(newUserName, newRepositoryName))
}
}
// Move files directory
defining(getRepositoryFilesDir(oldUserName, oldRepositoryName)) { dir =>
if (dir.isDirectory) {
FileUtils.moveDirectory(dir, getRepositoryFilesDir(newUserName, newRepositoryName))
}
}
// Delete parent directory
FileUtil.deleteDirectoryIfEmpty(getRepositoryFilesDir(oldUserName, oldRepositoryName))
// Call hooks
if (oldUserName == newUserName) {
PluginRegistry().getRepositoryHooks.foreach(_.renamed(oldUserName, oldRepositoryName, newRepositoryName))
} else {
PluginRegistry().getRepositoryHooks.foreach(_.transferred(oldUserName, newUserName, newRepositoryName))
}
}
} }
} }
} }
def deleteRepository(userName: String, repositoryName: String)(implicit s: Session): Unit = { def deleteRepository(repository: Repository)(implicit s: Session): Unit = {
LockUtil.lock(s"${repository.userName}/${repository.repositoryName}") {
deleteRepositoryOnModel(repository.userName, repository.repositoryName)
FileUtils.deleteDirectory(getRepositoryDir(repository.userName, repository.repositoryName))
FileUtils.deleteDirectory(getWikiRepositoryDir(repository.userName, repository.repositoryName))
FileUtils.deleteDirectory(getTemporaryDir(repository.userName, repository.repositoryName))
FileUtils.deleteDirectory(getRepositoryFilesDir(repository.userName, repository.repositoryName))
// Call hooks
PluginRegistry().getRepositoryHooks.foreach(_.deleted(repository.userName, repository.repositoryName))
}
}
private def deleteRepositoryOnModel(userName: String, repositoryName: String)(implicit s: Session): Unit = {
Activities.filter(_.byRepository(userName, repositoryName)).delete Activities.filter(_.byRepository(userName, repositoryName)).delete
Collaborators.filter(_.byRepository(userName, repositoryName)).delete Collaborators.filter(_.byRepository(userName, repositoryName)).delete
CommitComments.filter(_.byRepository(userName, repositoryName)).delete CommitComments.filter(_.byRepository(userName, repositoryName)).delete
@@ -718,7 +778,6 @@ trait RepositoryService { self: AccountService =>
} }
object RepositoryService { object RepositoryService {
case class RepositoryInfo( case class RepositoryInfo(
owner: String, owner: String,
name: String, name: String,

View File

@@ -311,7 +311,6 @@ trait WebHookPullRequestService extends WebHookService {
action: String, action: String,
repository: RepositoryService.RepositoryInfo, repository: RepositoryService.RepositoryInfo,
issue: Issue, issue: Issue,
baseUrl: String,
sender: Account sender: Account
)(implicit s: Session, context: JsonFormat.Context): Unit = { )(implicit s: Session, context: JsonFormat.Context): Unit = {
callWebHookOf(repository.owner, repository.name, WebHook.Issues) { callWebHookOf(repository.owner, repository.name, WebHook.Issues) {
@@ -341,7 +340,6 @@ trait WebHookPullRequestService extends WebHookService {
action: String, action: String,
repository: RepositoryService.RepositoryInfo, repository: RepositoryService.RepositoryInfo,
issueId: Int, issueId: Int,
baseUrl: String,
sender: Account sender: Account
)(implicit s: Session, c: JsonFormat.Context): Unit = { )(implicit s: Session, c: JsonFormat.Context): Unit = {
import WebHookService._ import WebHookService._
@@ -404,7 +402,6 @@ trait WebHookPullRequestService extends WebHookService {
action: String, action: String,
requestRepository: RepositoryService.RepositoryInfo, requestRepository: RepositoryService.RepositoryInfo,
requestBranch: String, requestBranch: String,
baseUrl: String,
sender: Account sender: Account
)(implicit s: Session, c: JsonFormat.Context): Unit = { )(implicit s: Session, c: JsonFormat.Context): Unit = {
import WebHookService._ import WebHookService._
@@ -450,7 +447,6 @@ trait WebHookPullRequestReviewCommentService extends WebHookService {
repository: RepositoryService.RepositoryInfo, repository: RepositoryService.RepositoryInfo,
issue: Issue, issue: Issue,
pullRequest: PullRequest, pullRequest: PullRequest,
baseUrl: String,
sender: Account sender: Account
)(implicit s: Session, c: JsonFormat.Context): Unit = { )(implicit s: Session, c: JsonFormat.Context): Unit = {
import WebHookService._ import WebHookService._

View File

@@ -221,6 +221,7 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
with PrioritiesService with PrioritiesService
with MilestonesService with MilestonesService
with WebHookPullRequestService with WebHookPullRequestService
with WebHookPullRequestReviewCommentService
with CommitsService { with CommitsService {
private val logger = LoggerFactory.getLogger(classOf[CommitLogHook]) private val logger = LoggerFactory.getLogger(classOf[CommitLogHook])
@@ -299,7 +300,7 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
getAccountByUserName(pusher).foreach { pusherAccount => getAccountByUserName(pusher).foreach { pusherAccount =>
closeIssuesFromMessage(commit.fullMessage, pusher, owner, repository).foreach { issueId => closeIssuesFromMessage(commit.fullMessage, pusher, owner, repository).foreach { issueId =>
getIssue(owner, repository, issueId.toString).foreach { issue => getIssue(owner, repository, issueId.toString).foreach { issue =>
callIssuesWebHook("closed", repositoryInfo, issue, baseUrl, pusherAccount) callIssuesWebHook("closed", repositoryInfo, issue, pusherAccount)
PluginRegistry().getIssueHooks PluginRegistry().getIssueHooks
.foreach(_.closedByCommitComment(issue, repositoryInfo, commit.fullMessage, pusherAccount)) .foreach(_.closedByCommitComment(issue, repositoryInfo, commit.fullMessage, pusherAccount))
} }
@@ -319,7 +320,7 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
}.isDefined) { }.isDefined) {
markMergeAndClosePullRequest(pusher, owner, repository, pull) markMergeAndClosePullRequest(pusher, owner, repository, pull)
getAccountByUserName(pusher).foreach { pusherAccount => getAccountByUserName(pusher).foreach { pusherAccount =>
callPullRequestWebHook("closed", repositoryInfo, pull.issueId, baseUrl, pusherAccount) callPullRequestWebHook("closed", repositoryInfo, pull.issueId, pusherAccount)
} }
} }
} }
@@ -346,15 +347,8 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
command.getType match { command.getType match {
case ReceiveCommand.Type.CREATE | ReceiveCommand.Type.UPDATE | case ReceiveCommand.Type.CREATE | ReceiveCommand.Type.UPDATE |
ReceiveCommand.Type.UPDATE_NONFASTFORWARD => ReceiveCommand.Type.UPDATE_NONFASTFORWARD =>
updatePullRequests(owner, repository, branchName)
getAccountByUserName(pusher).foreach { pusherAccount => getAccountByUserName(pusher).foreach { pusherAccount =>
callPullRequestWebHookByRequestBranch( updatePullRequests(owner, repository, branchName, pusherAccount, "synchronize")
"synchronize",
repositoryInfo,
branchName,
baseUrl,
pusherAccount
)
} }
case _ => case _ =>
} }

View File

@@ -6,7 +6,7 @@ import gitbucket.core.service.{AccountService, DeployKeyService, RepositoryServi
import gitbucket.core.servlet.{CommitLogHook, Database} import gitbucket.core.servlet.{CommitLogHook, Database}
import gitbucket.core.util.{SyntaxSugars, Directory} import gitbucket.core.util.{SyntaxSugars, Directory}
import org.apache.sshd.server.{Environment, ExitCallback, SessionAware} import org.apache.sshd.server.{Environment, ExitCallback, SessionAware}
import org.apache.sshd.server.command.{Command, CommandFactory} import org.apache.sshd.server.{Command, CommandFactory}
import org.apache.sshd.server.session.ServerSession import org.apache.sshd.server.session.ServerSession
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import java.io.{File, InputStream, OutputStream} import java.io.{File, InputStream, OutputStream}
@@ -16,7 +16,7 @@ import org.eclipse.jgit.api.Git
import Directory._ import Directory._
import gitbucket.core.ssh.PublicKeyAuthenticator.AuthType import gitbucket.core.ssh.PublicKeyAuthenticator.AuthType
import org.eclipse.jgit.transport.{ReceivePack, UploadPack} import org.eclipse.jgit.transport.{ReceivePack, UploadPack}
import org.apache.sshd.server.shell.UnknownCommand import org.apache.sshd.server.scp.UnknownCommand
import org.eclipse.jgit.errors.RepositoryNotFoundException import org.eclipse.jgit.errors.RepositoryNotFoundException
object GitCommand { object GitCommand {

View File

@@ -3,7 +3,7 @@ package gitbucket.core.ssh
import gitbucket.core.service.SystemSettingsService.SshAddress import gitbucket.core.service.SystemSettingsService.SshAddress
import org.apache.sshd.common.Factory import org.apache.sshd.common.Factory
import org.apache.sshd.server.{Environment, ExitCallback} import org.apache.sshd.server.{Environment, ExitCallback}
import org.apache.sshd.server.command.Command import org.apache.sshd.server.Command
import java.io.{OutputStream, InputStream} import java.io.{OutputStream, InputStream}
import org.eclipse.jgit.lib.Constants import org.eclipse.jgit.lib.Constants

View File

@@ -232,6 +232,12 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache
s"""<a href="${context.path}/${m.group(1)}/${m.group(2)}/commit/${m.group(3)}">${m.group(1)}/${m s"""<a href="${context.path}/${m.group(1)}/${m.group(2)}/commit/${m.group(3)}">${m.group(1)}/${m
.group(2)}@${m.group(3).substring(0, 7)}</a>""" .group(2)}@${m.group(3).substring(0, 7)}</a>"""
) )
.replaceAll(
"\\[release:([^\\s]+?)/([^\\s]+?)/([^\\s]+?)\\]",
(m: Match) =>
s"""<a href="${context.path}/${m.group(1)}/${m.group(2)}/releases/${encodeRefName(m.group(3))}">${m
.group(3)}</a>"""
)
) )
/** /**

View File

@@ -40,10 +40,6 @@
<label class="col-md-2">Version</label> <label class="col-md-2">Version</label>
<span class="col-md-10">@plugin.pluginVersion</span> <span class="col-md-10">@plugin.pluginVersion</span>
</div> </div>
<div class="row">
<label class="col-md-2">Name</label>
<span class="col-md-10">@plugin.pluginName</span>
</div>
<div class="row"> <div class="row">
<label class="col-md-2">Description</label> <label class="col-md-2">Description</label>
<span class="col-md-10 muted">@plugin.description</span> <span class="col-md-10 muted">@plugin.description</span>

View File

@@ -12,7 +12,9 @@ import org.scalatest.FunSpec
import java.io.File import java.io.File
class MergeServiceSpec extends FunSpec { class MergeServiceSpec extends FunSpec {
val service = new MergeService {} val service = new MergeService with AccountService with ActivityService with IssuesService with LabelsService
with MilestonesService with RepositoryService with PrioritiesService with PullRequestService with CommitsService
with WebHookPullRequestService with WebHookPullRequestReviewCommentService {}
val branch = "master" val branch = "master"
val issueId = 10 val issueId = 10
def initRepository(owner: String, name: String): File = { def initRepository(owner: String, name: String): File = {

View File

@@ -9,11 +9,15 @@ class PullRequestServiceSpec
with PullRequestService with PullRequestService
with IssuesService with IssuesService
with AccountService with AccountService
with ActivityService
with RepositoryService with RepositoryService
with CommitsService with CommitsService
with LabelsService with LabelsService
with MilestonesService with MilestonesService
with PrioritiesService { with PrioritiesService
with WebHookService
with WebHookPullRequestService
with WebHookPullRequestReviewCommentService {
def swap(r: (Issue, PullRequest)) = (r._2 -> r._1) def swap(r: (Issue, PullRequest)) = (r._2 -> r._1)

View File

@@ -43,8 +43,10 @@ trait ServiceSpecBase {
def user(name: String)(implicit s: Session): Account = AccountService.getAccountByUserName(name).get def user(name: String)(implicit s: Session): Account = AccountService.getAccountByUserName(name).get
lazy val dummyService = new RepositoryService with AccountService with IssuesService with PullRequestService lazy val dummyService = new RepositoryService with AccountService with ActivityService with IssuesService
with CommitsService with CommitStatusService with LabelsService with MilestonesService with PrioritiesService() {} with PullRequestService with CommitsService with CommitStatusService with LabelsService with MilestonesService
with PrioritiesService with WebHookService with WebHookPullRequestService
with WebHookPullRequestReviewCommentService {}
def generateNewUserWithDBRepository(userName: String, repositoryName: String)(implicit s: Session): Account = { def generateNewUserWithDBRepository(userName: String, repositoryName: String)(implicit s: Session): Account = {
val ac = AccountService.getAccountByUserName(userName).getOrElse(generateNewAccount(userName)) val ac = AccountService.getAccountByUserName(userName).getOrElse(generateNewAccount(userName))

View File

@@ -5,8 +5,9 @@ import org.scalatest.FunSuite
import gitbucket.core.model.WebHookContentType import gitbucket.core.model.WebHookContentType
class WebHookServiceSpec extends FunSuite with ServiceSpecBase { class WebHookServiceSpec extends FunSuite with ServiceSpecBase {
lazy val service = new WebHookPullRequestService with AccountService with RepositoryService with PullRequestService lazy val service = new WebHookPullRequestService with AccountService with ActivityService with RepositoryService
with IssuesService with CommitsService with LabelsService with MilestonesService with PrioritiesService with PullRequestService with IssuesService with CommitsService with LabelsService with MilestonesService
with PrioritiesService with WebHookPullRequestReviewCommentService
test("WebHookPullRequestService.getPullRequestsByRequestForWebhook") { test("WebHookPullRequestService.getPullRequestsByRequestForWebhook") {
withTestDB { implicit session => withTestDB { implicit session =>

View File

@@ -1,6 +1,6 @@
package gitbucket.core.ssh package gitbucket.core.ssh
import org.apache.sshd.server.shell.UnknownCommand import org.apache.sshd.server.scp.UnknownCommand
import org.scalatest.FunSpec import org.scalatest.FunSpec
class GitCommandFactorySpec extends FunSpec { class GitCommandFactorySpec extends FunSpec {