diff --git a/CHANGELOG.md b/CHANGELOG.md index 31fd7a0..0cddb04 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,24 +1,24 @@ # Changelog 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 - Enhance file edit and delete buttons of the repository viewer - Fix Patch button to generate patches for all files in the commit - Display confirmation dialog for Transfer Ownership and Garbage collection - 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 - Fix some bugs around pull requests -### 4.27.0 - 29 Jul 2018 +## 4.27.0 - 29 Jul 2018 - Create new tag on the browser - EditorConfig support - Improve issues / pull requests search - 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 - Repositories tab in the dashboard - Fork dialog enhancement @@ -26,7 +26,7 @@ - Keep showing incompleted task list - New notification hooks -### 4.25.0 - 29 May 2018 +## 4.25.0 - 29 May 2018 - Security improvements - Show mail address at the profile page - Task list on commit comments @@ -34,10 +34,10 @@ - Expose user public keys - Download repository improvements -### 4.24.1 - 1 May 2018 +## 4.24.1 - 1 May 2018 - 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 - Extra mail addresses support - Show tags at the commit list @@ -45,12 +45,12 @@ - Renew layout of gitbucket-gist-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 shutdown problem in Tomcat deployment - 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 - Display commit message of tags at the releases page - Add labels property to issues and pull requests API response @@ -58,26 +58,26 @@ - Git authentication with personal access token - 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 - Create repository with an empty commit - Improve database viewer - Update maven-repository-plugin -### 4.21.2 - 27 Jan 2018 +## 4.21.2 - 27 Jan 2018 - Bugfix -### 4.21.1 - 27 Jan 2018 +## 4.21.1 - 27 Jan 2018 - Bugfix -### 4.21.0 - 27 Jan 2018 +## 4.21.0 - 27 Jan 2018 - Release page - OpenID Connect support - New database viewer - Submodule links to web page - Clarify close/reopen button -## 4.20.0 - 23 Dec 2017 +# 4.20.0 - 23 Dec 2017 - Squash and rebase merge strategy for pull requests - Quick pull request creation - Download patch from the diff view diff --git a/build.sbt b/build.sbt index 4ae2e3b..ad28306 100644 --- a/build.sbt +++ b/build.sbt @@ -6,7 +6,7 @@ val GitBucketVersion = "4.30.0-SNAPSHOT" val ScalatraVersion = "2.6.3" 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(".")) .enablePlugins(SbtTwirl, ScalatraPlugin) @@ -36,40 +36,40 @@ "org.scalatra" %% "scalatra" % ScalatraVersion, "org.scalatra" %% "scalatra-json" % 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", "io.github.gitbucket" % "solidbase" % "1.0.2", "io.github.gitbucket" % "markedj" % "1.0.15", "org.apache.commons" % "commons-compress" % "1.18", "org.apache.commons" % "commons-email" % "1.5", "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", - "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.h2database" % "h2" % "1.4.196", + "com.h2database" % "h2" % "1.4.197", "org.mariadb.jdbc" % "mariadb-java-client" % "2.3.0", "org.postgresql" % "postgresql" % "42.2.5", "ch.qos.logback" % "logback-classic" % "1.2.3", - "com.zaxxer" % "HikariCP" % "2.7.4", - "com.typesafe" % "config" % "1.3.2", - "com.typesafe.akka" %% "akka-actor" % "2.5.8", + "com.zaxxer" % "HikariCP" % "2.7.9", + "com.typesafe" % "config" % "1.3.3", + "com.typesafe.akka" %% "akka-actor" % "2.5.17", "fr.brouillard.oss.security.xhub" % "xhub4j-core" % "1.0.0", "com.github.bkromhout" % "java-diff-utils" % "2.1.1", - "org.cache2k" % "cache2k-all" % "1.0.1.Final", - "com.enragedginger" %% "akka-quartz-scheduler" % "1.6.1-akka-2.5.x" exclude ("c3p0", "c3p0"), + "org.cache2k" % "cache2k-all" % "1.0.2.Final", + "com.enragedginger" %% "akka-quartz-scheduler" % "1.7.0-akka-2.5.x" exclude ("c3p0", "c3p0"), "net.coobird" % "thumbnailator" % "0.4.8", "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", "javax.servlet" % "javax.servlet-api" % "3.1.0" % "provided", "junit" % "junit" % "4.12" % "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", - "ru.yandex.qatools.embed" % "postgresql-embedded" % "2.6" % "test", - "net.i2p.crypto" % "eddsa" % "0.2.0", - "is.tagomor.woothee" % "woothee-java" % "1.7.0", + "ru.yandex.qatools.embed" % "postgresql-embedded" % "2.9" % "test", + "net.i2p.crypto" % "eddsa" % "0.3.0", + "is.tagomor.woothee" % "woothee-java" % "1.8.0", "org.ec4j.core" % "ec4j-core" % "0.0.1" ) diff --git a/project/build.properties b/project/build.properties index 0cd8b07..7c58a83 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.2.3 +sbt.version=1.2.6 diff --git a/project/build.sbt b/project/build.sbt index 4d5e402..b4a7058 100644 --- a/project/build.sbt +++ b/project/build.sbt @@ -1 +1 @@ -libraryDependencies += "com.eclipsesource.minimal-json" % "minimal-json" % "0.9.4" +libraryDependencies += "com.eclipsesource.minimal-json" % "minimal-json" % "0.9.5" diff --git a/project/plugins.sbt b/project/plugins.sbt index b70719e..6d6b112 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,9 +1,9 @@ scalacOptions ++= Seq("-unchecked", "-deprecation", "-feature") addSbtPlugin("com.geirsson" % "sbt-scalafmt" % "1.5.0") -addSbtPlugin("com.typesafe.sbt" % "sbt-twirl" % "1.3.13") -addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.5") +addSbtPlugin("com.typesafe.sbt" % "sbt-twirl" % "1.3.15") +addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.8") 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 addSbtPlugin("com.typesafe.sbt" % "sbt-license-report" % "1.2.0") diff --git a/src/main/scala/gitbucket/core/controller/AccountController.scala b/src/main/scala/gitbucket/core/controller/AccountController.scala index 7663ba9..b04ce63 100644 --- a/src/main/scala/gitbucket/core/controller/AccountController.scala +++ b/src/main/scala/gitbucket/core/controller/AccountController.scala @@ -360,13 +360,7 @@ // FileUtils.deleteDirectory(getWikiRepositoryDir(userName, repositoryName)) // FileUtils.deleteDirectory(getTemporaryDir(userName, repositoryName)) // } - // Remove from GROUP_MEMBER and COLLABORATOR - removeUserRelatedData(userName) - updateAccount(account.copy(isRemoved = true)) - - // call hooks - PluginRegistry().getAccountHooks.foreach(_.deleted(userName)) - + suspendAccount(account) session.invalidate redirect("/") } diff --git a/src/main/scala/gitbucket/core/controller/ApiController.scala b/src/main/scala/gitbucket/core/controller/ApiController.scala index ccbbdd7..7e2ef39 100644 --- a/src/main/scala/gitbucket/core/controller/ApiController.scala +++ b/src/main/scala/gitbucket/core/controller/ApiController.scala @@ -36,6 +36,7 @@ with WebHookService with WebHookPullRequestService with WebHookIssueCommentService + with WebHookPullRequestReviewCommentService with WikiService with ActivityService with PrioritiesService diff --git a/src/main/scala/gitbucket/core/controller/DashboardController.scala b/src/main/scala/gitbucket/core/controller/DashboardController.scala index d73f3c7..3dc8d1a 100644 --- a/src/main/scala/gitbucket/core/controller/DashboardController.scala +++ b/src/main/scala/gitbucket/core/controller/DashboardController.scala @@ -12,9 +12,13 @@ with PullRequestService with RepositoryService with AccountService + with ActivityService with CommitsService with LabelsService with PrioritiesService + with WebHookService + with WebHookPullRequestService + with WebHookPullRequestReviewCommentService with MilestonesService with UsersAuthenticator diff --git a/src/main/scala/gitbucket/core/controller/IssuesController.scala b/src/main/scala/gitbucket/core/controller/IssuesController.scala index 90676e6..514564a 100644 --- a/src/main/scala/gitbucket/core/controller/IssuesController.scala +++ b/src/main/scala/gitbucket/core/controller/IssuesController.scala @@ -26,6 +26,7 @@ with WritableUsersAuthenticator with PullRequestService with WebHookIssueCommentService + with WebHookPullRequestReviewCommentService with CommitsService with PrioritiesService diff --git a/src/main/scala/gitbucket/core/controller/PullRequestsController.scala b/src/main/scala/gitbucket/core/controller/PullRequestsController.scala index 6d7262f..18fd38d 100644 --- a/src/main/scala/gitbucket/core/controller/PullRequestsController.scala +++ b/src/main/scala/gitbucket/core/controller/PullRequestsController.scala @@ -32,6 +32,7 @@ with CommitsService with ActivityService with WebHookPullRequestService + with WebHookPullRequestReviewCommentService with ReadableUsersAuthenticator with ReferrerAuthenticator with WritableUsersAuthenticator @@ -294,11 +295,12 @@ issueId <- params("id").toIntOpt loginAccount <- context.loginAccount (issue, pullreq) <- getPullRequest(baseRepository.owner, baseRepository.name, issueId) + repository <- getRepository(pullreq.requestUserName, pullreq.requestRepositoryName) + remoteRepository <- getRepository(pullreq.userName, pullreq.repositoryName) owner = pullreq.requestUserName name = pullreq.requestRepositoryName if hasDeveloperRole(owner, name, context.loginAccount) } yield { - val repository = getRepository(owner, name).get val branchProtection = getProtectedBranchInfo(owner, name, pullreq.requestBranch) if (branchProtection.needStatusCheck(loginAccount.userName)) { flash += "error" -> s"branch ${pullreq.requestBranch} is protected need status check." @@ -314,83 +316,19 @@ JGitUtil.getAllCommitIds(git) }.toSet pullRemote( - owner, - name, + repository, pullreq.requestBranch, - pullreq.userName, - pullreq.repositoryName, + remoteRepository, pullreq.branch, loginAccount, - s"Merge branch '${alias}' into ${pullreq.requestBranch}" + s"Merge branch '${alias}' into ${pullreq.requestBranch}", + Some(pullreq) ) match { case None => // conflict flash += "error" -> s"Can't automatic merging branch '${alias}' into ${pullreq.requestBranch}." case Some(oldId) => // update pull request - updatePullRequests(owner, name, pullreq.requestBranch) - - 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 - ) - } - } - } + updatePullRequests(owner, name, pullreq.requestBranch, loginAccount, "synchronize") flash += "info" -> s"Merge branch '${alias}' into ${pullreq.requestBranch}" } } @@ -401,119 +339,14 @@ }) post("/:owner/:repository/pull/:id/merge", mergeForm)(writableUsersOnly { (form, repository) => - params("id").toIntOpt.flatMap { - issueId => - val owner = repository.owner - 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) + params("id").toIntOpt.flatMap { issueId => + val owner = repository.owner + val name = repository.name - // record activity - recordMergeActivity(owner, name, loginAccount.userName, issueId, form.message) - - 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()) + mergePullRequest(repository, issueId, context.loginAccount.get, form.message, form.strategy) match { + case Right(objectId) => redirect(s"/${owner}/${name}/pull/${issueId}") + case Left(message) => Some(BadRequest()) + } } getOrElse NotFound() }) @@ -731,7 +564,7 @@ recordPullRequestActivity(owner, name, loginUserName, issueId, form.title) // 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 => // extract references and create refer comment diff --git a/src/main/scala/gitbucket/core/controller/RepositorySettingsController.scala b/src/main/scala/gitbucket/core/controller/RepositorySettingsController.scala index 0807cd4..521a10a 100644 --- a/src/main/scala/gitbucket/core/controller/RepositorySettingsController.scala +++ b/src/main/scala/gitbucket/core/controller/RepositorySettingsController.scala @@ -13,13 +13,11 @@ import gitbucket.core.util.Implicits._ import gitbucket.core.util.Directory._ import org.scalatra.forms._ -import org.apache.commons.io.FileUtils import org.scalatra.i18n.Messages import org.eclipse.jgit.api.Git import org.eclipse.jgit.lib.Constants import org.eclipse.jgit.lib.ObjectId import gitbucket.core.model.WebHookContentType -import gitbucket.core.plugin.PluginRegistry class RepositorySettingsController extends RepositorySettingsControllerBase @@ -148,29 +146,6 @@ if (repository.name != form.repositoryName) { // Update database 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." redirect(s"/${repository.owner}/${form.repositoryName}/settings/options") @@ -392,31 +367,7 @@ post("/:owner/:repository/settings/transfer", transferForm)(ownerOnly { (form, repository) => // Change repository owner if (repository.owner != form.newOwner) { - LockUtil.lock(s"${repository.owner}/${repository.name}") { - // Update database - 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)) - } + renameRepository(repository.owner, repository.name, form.newOwner, repository.name) } redirect(s"/${form.newOwner}/${repository.name}") }) @@ -425,19 +376,8 @@ * Delete the repository. */ post("/:owner/:repository/settings/delete")(ownerOnly { repository => - LockUtil.lock(s"${repository.owner}/${repository.name}") { - // Delete the repository and related files - deleteRepository(repository.owner, repository.name) - - 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)) - } - + // Delete the repository and related files + deleteRepository(repository.repository) redirect(s"/${repository.owner}") }) diff --git a/src/main/scala/gitbucket/core/controller/RepositoryViewerController.scala b/src/main/scala/gitbucket/core/controller/RepositoryViewerController.scala index 1054149..49cecce 100644 --- a/src/main/scala/gitbucket/core/controller/RepositoryViewerController.scala +++ b/src/main/scala/gitbucket/core/controller/RepositoryViewerController.scala @@ -7,14 +7,13 @@ import gitbucket.core.repo.html import gitbucket.core.helper import gitbucket.core.service._ +import gitbucket.core.service.RepositoryCommitFileService.CommitFile import gitbucket.core.util._ -import gitbucket.core.util.JGitUtil._ import gitbucket.core.util.StringUtil._ import gitbucket.core.util.SyntaxSugars._ import gitbucket.core.util.Implicits._ import gitbucket.core.util.Directory._ -import gitbucket.core.model.{Account, CommitState, CommitStatus, WebHook} -import gitbucket.core.service.WebHookService._ +import gitbucket.core.model.{Account, CommitState, CommitStatus} import gitbucket.core.view import gitbucket.core.view.helpers import org.apache.commons.compress.archivers.{ArchiveEntry, ArchiveOutputStream} @@ -24,15 +23,12 @@ import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream import org.apache.commons.compress.compressors.xz.XZCompressorOutputStream import org.apache.commons.compress.utils.IOUtils -import org.scalatra.forms._ 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.archive.{TgzFormat, ZipFormat} -import org.eclipse.jgit.dircache.{DirCache, DirCacheBuilder} import org.eclipse.jgit.errors.MissingObjectException import org.eclipse.jgit.lib._ -import org.eclipse.jgit.transport.{ReceiveCommand, ReceivePack} import org.eclipse.jgit.treewalk.TreeWalk import org.eclipse.jgit.treewalk.filter.PathFilter import org.json4s.jackson.Serialization @@ -42,6 +38,7 @@ class RepositoryViewerController extends RepositoryViewerControllerBase with RepositoryService + with RepositoryCommitFileService with AccountService with ActivityService with IssuesService @@ -64,6 +61,7 @@ */ trait RepositoryViewerControllerBase extends ControllerBase { self: RepositoryService + with RepositoryCommitFileService with AccountService with ActivityService with IssuesService @@ -319,13 +317,34 @@ 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( repository = repository, branch = form.branch, path = form.path, 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) { redirect(s"/${repository.owner}/${repository.name}/tree/${form.branch}") @@ -394,7 +413,8 @@ content = appendNewLine(convertLineSeparator(form.content, form.lineSeparator), form.lineSeparator), charset = form.charset, message = form.message.getOrElse(s"Create ${form.newFileName}"), - commit = form.commit + commit = form.commit, + loginAccount = context.loginAccount.get ) redirect( @@ -417,7 +437,8 @@ } else { form.message.getOrElse(s"Rename ${form.oldFileName.get} to ${form.newFileName}") }, - commit = form.commit + commit = form.commit, + loginAccount = context.loginAccount.get ) redirect( @@ -436,7 +457,8 @@ content = "", charset = "", message = form.message.getOrElse(s"Delete ${form.fileName}"), - commit = form.commit + commit = form.commit, + loginAccount = context.loginAccount.get ) println(form.path) @@ -593,50 +615,17 @@ post("/:owner/:repository/commit/:id/comment/new", commentForm)(readableUsersOnly { (form, repository) => val id = params("id") createCommitComment( - repository.owner, - repository.name, + repository, id, - context.loginAccount.get.userName, + context.loginAccount.get, form.content, form.fileName, form.oldLineNumber, form.newLineNumber, + form.diff, 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}") }) @@ -661,64 +650,18 @@ ajaxPost("/:owner/:repository/commit/:id/comment/_data/new", commentForm)(readableUsersOnly { (form, repository) => val id = params("id") val commentId = createCommitComment( - repository.owner, - repository.name, + repository, id, - context.loginAccount.get.userName, + context.loginAccount.get, form.content, form.fileName, form.oldLineNumber, form.newLineNumber, + form.diff, 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 - 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 .commitcomment(comment, hasDeveloperRole(repository.owner, repository.name, context.loginAccount), repository) }) @@ -930,185 +873,6 @@ 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 => s"readme.${extension}" } ++ Seq("readme.txt", "readme") diff --git a/src/main/scala/gitbucket/core/plugin/Plugin.scala b/src/main/scala/gitbucket/core/plugin/Plugin.scala index 06355f2..92a51ea 100644 --- a/src/main/scala/gitbucket/core/plugin/Plugin.scala +++ b/src/main/scala/gitbucket/core/plugin/Plugin.scala @@ -8,7 +8,7 @@ import gitbucket.core.service.SystemSettingsService.SystemSettings import gitbucket.core.util.SyntaxSugars._ 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 /** diff --git a/src/main/scala/gitbucket/core/plugin/PluginRegistry.scala b/src/main/scala/gitbucket/core/plugin/PluginRegistry.scala index c37f1ae..6964732 100644 --- a/src/main/scala/gitbucket/core/plugin/PluginRegistry.scala +++ b/src/main/scala/gitbucket/core/plugin/PluginRegistry.scala @@ -24,7 +24,7 @@ import io.github.gitbucket.solidbase.model.Module import org.apache.commons.io.FileUtils 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 play.twirl.api.Html diff --git a/src/main/scala/gitbucket/core/service/AccountService.scala b/src/main/scala/gitbucket/core/service/AccountService.scala index f5f9cdb..151edb0 100644 --- a/src/main/scala/gitbucket/core/service/AccountService.scala +++ b/src/main/scala/gitbucket/core/service/AccountService.scala @@ -7,6 +7,7 @@ import gitbucket.core.model.Profile.dateColumnType import gitbucket.core.util.{LDAPUtil, StringUtil} import StringUtil._ +import gitbucket.core.plugin.PluginRegistry import gitbucket.core.service.SystemSettingsService.SystemSettings trait AccountService { @@ -183,6 +184,15 @@ 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 = Accounts .filter { a => @@ -281,6 +291,15 @@ 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] = { List(userName) ++ Collaborators.filter(_.collaboratorName === userName.bind).sortBy(_.userName).map(_.userName).list.distinct diff --git a/src/main/scala/gitbucket/core/service/ActivityService.scala b/src/main/scala/gitbucket/core/service/ActivityService.scala index ef83188..f48126d 100644 --- a/src/main/scala/gitbucket/core/service/ActivityService.scala +++ b/src/main/scala/gitbucket/core/service/ActivityService.scala @@ -360,7 +360,7 @@ repositoryName, activityUserName, "release", - s"[user:${activityUserName}] released ${name} at [repo:${userName}/${repositoryName}]", + s"[user:${activityUserName}] released [release:${userName}/${repositoryName}/${name}] at [repo:${userName}/${repositoryName}]", None, currentDate ) diff --git a/src/main/scala/gitbucket/core/service/CommitsService.scala b/src/main/scala/gitbucket/core/service/CommitsService.scala index b59c929..f3b8e84 100644 --- a/src/main/scala/gitbucket/core/service/CommitsService.scala +++ b/src/main/scala/gitbucket/core/service/CommitsService.scala @@ -2,15 +2,20 @@ 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.profile.blockingApi._ 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.{FileUtil, StringUtil} import org.apache.commons.io.FileUtils trait CommitsService { + self: ActivityService with PullRequestService with WebHookPullRequestReviewCommentService => def getCommitComments(owner: String, repository: String, commitId: String, includePullRequest: Boolean)( implicit s: Session @@ -28,21 +33,21 @@ None def createCommitComment( - owner: String, - repository: String, + repository: RepositoryInfo, commitId: String, - loginUser: String, + loginAccount: Account, content: String, fileName: Option[String], oldLine: Option[Int], newLine: Option[Int], + diff: Option[String], issueId: Option[Int] - )(implicit s: Session): Int = - CommitComments returning CommitComments.map(_.commentId) insert CommitComment( - userName = owner, - repositoryName = repository, + )(implicit s: Session, c: JsonFormat.Context, context: Context): Int = { + val commentId = CommitComments returning CommitComments.map(_.commentId) insert CommitComment( + userName = repository.owner, + repositoryName = repository.name, commitId = commitId, - commentedUserName = loginUser, + commentedUserName = loginAccount.userName, content = content, fileName = fileName, oldLine = oldLine, @@ -55,6 +60,56 @@ 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])( implicit s: Session ): Unit = diff --git a/src/main/scala/gitbucket/core/service/HandleCommentService.scala b/src/main/scala/gitbucket/core/service/HandleCommentService.scala index edd968f..28a2a4e 100644 --- a/src/main/scala/gitbucket/core/service/HandleCommentService.scala +++ b/src/main/scala/gitbucket/core/service/HandleCommentService.scala @@ -87,9 +87,9 @@ case "reopen" => "reopened" } if (issue.isPullRequest) - callPullRequestWebHook(webHookAction, repository, issue.issueId, context.baseUrl, loginAccount) + callPullRequestWebHook(webHookAction, repository, issue.issueId, loginAccount) else - callIssuesWebHook(webHookAction, repository, issue, context.baseUrl, loginAccount) + callIssuesWebHook(webHookAction, repository, issue, loginAccount) } // call hooks diff --git a/src/main/scala/gitbucket/core/service/IssueCreationService.scala b/src/main/scala/gitbucket/core/service/IssueCreationService.scala index a769fb8..0c223c0 100644 --- a/src/main/scala/gitbucket/core/service/IssueCreationService.scala +++ b/src/main/scala/gitbucket/core/service/IssueCreationService.scala @@ -57,7 +57,7 @@ createReferComment(owner, name, issue, title + " " + body.getOrElse(""), loginAccount) // call web hooks - callIssuesWebHook("opened", repository, issue, context.baseUrl, loginAccount) + callIssuesWebHook("opened", repository, issue, loginAccount) // call hooks PluginRegistry().getIssueHooks.foreach(_.created(issue, repository)) diff --git a/src/main/scala/gitbucket/core/service/MergeService.scala b/src/main/scala/gitbucket/core/service/MergeService.scala index 3eab6b3..c737a67 100644 --- a/src/main/scala/gitbucket/core/service/MergeService.scala +++ b/src/main/scala/gitbucket/core/service/MergeService.scala @@ -1,8 +1,16 @@ 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.{JGitUtil, LockUtil} 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.api.Git import org.eclipse.jgit.transport.RefSpec @@ -13,6 +21,13 @@ import scala.collection.JavaConverters._ trait MergeService { + self: AccountService + with ActivityService + with IssuesService + with RepositoryService + with PullRequestService + with WebHookPullRequestService => + import MergeService._ /** @@ -43,7 +58,13 @@ } /** 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) } @@ -54,12 +75,18 @@ issueId: Int, commits: Seq[RevCommit], committer: PersonIdent - ): Unit = { + ): ObjectId = { new MergeCacheInfo(git, branch, issueId).rebase(committer, commits) } /** 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) } @@ -136,27 +163,223 @@ tryMergeRemote(userName, repositoryName, branch, requestUserName, requestRepositoryName, requestBranch).left.toOption def pullRemote( - localUserName: String, - localRepositoryName: String, + localRepository: RepositoryInfo, localBranch: String, - remoteUserName: String, - remoteRepositoryName: String, + remoteRepository: RepositoryInfo, remoteBranch: String, loginAccount: Account, - message: String - ): Option[ObjectId] = { + message: String, + 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 { case (newTreeId, oldBaseId, oldHeadId) => using(Git.open(getRepositoryDir(localUserName, localRepositoryName))) { git => + val existIds = JGitUtil.getAllCommitIds(git).toSet + val committer = new PersonIdent(loginAccount.fullName, loginAccount.mailAddress) val newCommit = Util.createMergeCommit(git.getRepository, newTreeId, committer, message, Seq(oldBaseId, oldHeadId)) 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 }.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 { @@ -191,13 +414,15 @@ force: Boolean, committer: PersonIdent, refLogMessage: Option[String] = None - ): Unit = { + ): ObjectId = { val refUpdate = repository.updateRef(ref) refUpdate.setNewObjectId(newObjectId) refUpdate.setForceUpdate(force) refUpdate.setRefLogIdent(committer) refLogMessage.foreach(refUpdate.setRefLogMessage(_, true)) refUpdate.update() + + newObjectId } } @@ -265,7 +490,7 @@ } // update branch from cache - def merge(message: String, committer: PersonIdent) = { + def merge(message: String, committer: PersonIdent): ObjectId = { if (checkConflict().isDefined) { throw new RuntimeException("This pull request can't merge automatically.") } @@ -278,7 +503,7 @@ 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) { throw new RuntimeException("This pull request can't merge automatically.") } @@ -310,7 +535,7 @@ 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) { throw new RuntimeException("This pull request can't merge automatically.") } diff --git a/src/main/scala/gitbucket/core/service/PullRequestService.scala b/src/main/scala/gitbucket/core/service/PullRequestService.scala index 8297048..7517a1d 100644 --- a/src/main/scala/gitbucket/core/service/PullRequestService.scala +++ b/src/main/scala/gitbucket/core/service/PullRequestService.scala @@ -5,6 +5,7 @@ import gitbucket.core.model.Profile.profile.blockingApi._ import difflib.{Delta, DiffUtils} import gitbucket.core.service.RepositoryService.RepositoryInfo +import gitbucket.core.api.JsonFormat import gitbucket.core.util.SyntaxSugars._ import gitbucket.core.util.Directory._ import gitbucket.core.util.Implicits._ @@ -17,7 +18,8 @@ import scala.collection.JavaConverters._ -trait PullRequestService { self: IssuesService with CommitsService => +trait PullRequestService { + self: IssuesService with CommitsService with WebHookService with WebHookPullRequestService with RepositoryService => import PullRequestService._ def getPullRequest(owner: String, repository: String, issueId: Int)( @@ -166,7 +168,10 @@ /** * 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 => if (Repositories.filter(_.byRepository(pullreq.userName, pullreq.repositoryName)).exists.run) { // Update the git repository @@ -206,8 +211,17 @@ // Update commit id in the PULL_REQUEST table updateCommitId(pullreq.userName, pullreq.repositoryName, pullreq.issueId, commitIdTo, commitIdFrom) + + // call web hook + callPullRequestWebHookByRequestBranch( + action, + getRepository(owner, repository).get, + pullreq.requestBranch, + loginAccount + ) } } + } def getPullRequestByRequestCommit( userName: String, diff --git a/src/main/scala/gitbucket/core/service/RepositoryCommitFileService.scala b/src/main/scala/gitbucket/core/service/RepositoryCommitFileService.scala new file mode 100644 index 0000000..55bf9fb --- /dev/null +++ b/src/main/scala/gitbucket/core/service/RepositoryCommitFileService.scala @@ -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) +} diff --git a/src/main/scala/gitbucket/core/service/RepositoryService.scala b/src/main/scala/gitbucket/core/service/RepositoryService.scala index e5295ed..2f152db 100644 --- a/src/main/scala/gitbucket/core/service/RepositoryService.scala +++ b/src/main/scala/gitbucket/core/service/RepositoryService.scala @@ -1,16 +1,25 @@ package gitbucket.core.service +import gitbucket.core.api.JsonFormat import gitbucket.core.controller.Context import gitbucket.core.util._ 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.profile.blockingApi._ 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.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._ /** @@ -68,181 +77,232 @@ (Repositories filter { t => t.byRepository(oldUserName, oldRepositoryName) } firstOption).foreach { repository => - Repositories insert repository.copy(userName = newUserName, repositoryName = newRepositoryName) + LockUtil.lock(s"${repository.userName}/${repository.repositoryName}") { + Repositories insert repository.copy(userName = newUserName, repositoryName = newRepositoryName) - val webHooks = RepositoryWebHooks.filter(_.byRepository(oldUserName, oldRepositoryName)).list - val webHookEvents = RepositoryWebHookEvents.filter(_.byRepository(oldUserName, oldRepositoryName)).list - val milestones = Milestones.filter(_.byRepository(oldUserName, oldRepositoryName)).list - val issueId = IssueId.filter(_.byRepository(oldUserName, oldRepositoryName)).list - val issues = Issues.filter(_.byRepository(oldUserName, oldRepositoryName)).list - val pullRequests = PullRequests.filter(_.byRepository(oldUserName, oldRepositoryName)).list - val labels = Labels.filter(_.byRepository(oldUserName, oldRepositoryName)).list - val priorities = Priorities.filter(_.byRepository(oldUserName, oldRepositoryName)).list - val issueComments = IssueComments.filter(_.byRepository(oldUserName, oldRepositoryName)).list - val issueLabels = IssueLabels.filter(_.byRepository(oldUserName, oldRepositoryName)).list - val commitComments = CommitComments.filter(_.byRepository(oldUserName, oldRepositoryName)).list - val commitStatuses = CommitStatuses.filter(_.byRepository(oldUserName, oldRepositoryName)).list - val collaborators = Collaborators.filter(_.byRepository(oldUserName, oldRepositoryName)).list - val protectedBranches = ProtectedBranches.filter(_.byRepository(oldUserName, oldRepositoryName)).list - val protectedBranchContexts = - ProtectedBranchContexts.filter(_.byRepository(oldUserName, oldRepositoryName)).list - val deployKeys = DeployKeys.filter(_.byRepository(oldUserName, oldRepositoryName)).list - val releases = ReleaseTags.filter(_.byRepository(oldUserName, oldRepositoryName)).list - val releaseAssets = ReleaseAssets.filter(_.byRepository(oldUserName, oldRepositoryName)).list + val webHooks = RepositoryWebHooks.filter(_.byRepository(oldUserName, oldRepositoryName)).list + val webHookEvents = RepositoryWebHookEvents.filter(_.byRepository(oldUserName, oldRepositoryName)).list + val milestones = Milestones.filter(_.byRepository(oldUserName, oldRepositoryName)).list + val issueId = IssueId.filter(_.byRepository(oldUserName, oldRepositoryName)).list + val issues = Issues.filter(_.byRepository(oldUserName, oldRepositoryName)).list + val pullRequests = PullRequests.filter(_.byRepository(oldUserName, oldRepositoryName)).list + val labels = Labels.filter(_.byRepository(oldUserName, oldRepositoryName)).list + val priorities = Priorities.filter(_.byRepository(oldUserName, oldRepositoryName)).list + val issueComments = IssueComments.filter(_.byRepository(oldUserName, oldRepositoryName)).list + val issueLabels = IssueLabels.filter(_.byRepository(oldUserName, oldRepositoryName)).list + val commitComments = CommitComments.filter(_.byRepository(oldUserName, oldRepositoryName)).list + val commitStatuses = CommitStatuses.filter(_.byRepository(oldUserName, oldRepositoryName)).list + val collaborators = Collaborators.filter(_.byRepository(oldUserName, oldRepositoryName)).list + val protectedBranches = ProtectedBranches.filter(_.byRepository(oldUserName, oldRepositoryName)).list + val protectedBranchContexts = + ProtectedBranchContexts.filter(_.byRepository(oldUserName, oldRepositoryName)).list + val deployKeys = DeployKeys.filter(_.byRepository(oldUserName, oldRepositoryName)).list + val releases = ReleaseTags.filter(_.byRepository(oldUserName, oldRepositoryName)).list + val releaseAssets = ReleaseAssets.filter(_.byRepository(oldUserName, oldRepositoryName)).list - Repositories - .filter { t => - (t.originUserName === oldUserName.bind) && (t.originRepositoryName === oldRepositoryName.bind) - } - .map { t => - t.originUserName -> t.originRepositoryName - } - .update(newUserName, newRepositoryName) - - Repositories - .filter { t => - (t.parentUserName === oldUserName.bind) && (t.parentRepositoryName === oldRepositoryName.bind) - } - .map { t => - t.parentUserName -> t.parentRepositoryName - } - .update(newUserName, newRepositoryName) - - // Updates activity fk before deleting repository because activity is sorted by activityId - // and it can't be changed by deleting-and-inserting record. - Activities.filter(_.byRepository(oldUserName, oldRepositoryName)).list.foreach { activity => - Activities - .filter(_.activityId === activity.activityId.bind) - .map(x => (x.userName, x.repositoryName)) - .update(newUserName, newRepositoryName) - } - - deleteRepository(oldUserName, oldRepositoryName) - - RepositoryWebHooks.insertAll( - webHooks.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _* - ) - RepositoryWebHookEvents.insertAll( - webHookEvents.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _* - ) - Milestones.insertAll(milestones.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _*) - Priorities.insertAll(priorities.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _*) - IssueId.insertAll(issueId.map(_.copy(_1 = newUserName, _2 = newRepositoryName)): _*) - - val newMilestones = Milestones.filter(_.byRepository(newUserName, newRepositoryName)).list - val newPriorities = Priorities.filter(_.byRepository(newUserName, newRepositoryName)).list - Issues.insertAll(issues.map { x => - x.copy( - userName = newUserName, - repositoryName = newRepositoryName, - milestoneId = x.milestoneId.map { id => - newMilestones.find(_.title == milestones.find(_.milestoneId == id).get.title).get.milestoneId - }, - priorityId = x.priorityId.map { id => - newPriorities.find(_.priorityName == priorities.find(_.priorityId == id).get.priorityName).get.priorityId + Repositories + .filter { t => + (t.originUserName === oldUserName.bind) && (t.originRepositoryName === oldRepositoryName.bind) } + .map { t => + t.originUserName -> t.originRepositoryName + } + .update(newUserName, newRepositoryName) + + Repositories + .filter { t => + (t.parentUserName === oldUserName.bind) && (t.parentRepositoryName === oldRepositoryName.bind) + } + .map { t => + t.parentUserName -> t.parentRepositoryName + } + .update(newUserName, newRepositoryName) + + // Updates activity fk before deleting repository because activity is sorted by activityId + // and it can't be changed by deleting-and-inserting record. + Activities.filter(_.byRepository(oldUserName, oldRepositoryName)).list.foreach { activity => + Activities + .filter(_.activityId === activity.activityId.bind) + .map(x => (x.userName, x.repositoryName)) + .update(newUserName, newRepositoryName) + } + + deleteRepositoryOnModel(oldUserName, oldRepositoryName) + + RepositoryWebHooks.insertAll( + webHooks.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _* ) - }: _*) + RepositoryWebHookEvents.insertAll( + webHookEvents.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _* + ) + Milestones.insertAll(milestones.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _*) + Priorities.insertAll(priorities.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _*) + IssueId.insertAll(issueId.map(_.copy(_1 = newUserName, _2 = newRepositoryName)): _*) - PullRequests.insertAll(pullRequests.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _*) - IssueComments.insertAll( - issueComments.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _* - ) - Labels.insertAll(labels.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _*) - CommitComments.insertAll( - commitComments.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _* - ) - CommitStatuses.insertAll( - commitStatuses.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _* - ) - ProtectedBranches.insertAll( - protectedBranches.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _* - ) - ProtectedBranchContexts.insertAll( - protectedBranchContexts.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _* - ) - DeployKeys.insertAll(deployKeys.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _*) - ReleaseTags.insertAll(releases.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _*) - ReleaseAssets.insertAll( - releaseAssets.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _* - ) - - // Update source repository of pull requests - PullRequests - .filter { t => - (t.requestUserName === oldUserName.bind) && (t.requestRepositoryName === oldRepositoryName.bind) - } - .map { t => - t.requestUserName -> t.requestRepositoryName - } - .update(newUserName, newRepositoryName) - - // Convert labelId - val oldLabelMap = labels.map(x => (x.labelId, x.labelName)).toMap - val newLabelMap = - Labels.filter(_.byRepository(newUserName, newRepositoryName)).map(x => (x.labelName, x.labelId)).list.toMap - IssueLabels.insertAll( - issueLabels.map( - x => - x.copy( - labelId = newLabelMap(oldLabelMap(x.labelId)), - userName = newUserName, - repositoryName = newRepositoryName + val newMilestones = Milestones.filter(_.byRepository(newUserName, newRepositoryName)).list + val newPriorities = Priorities.filter(_.byRepository(newUserName, newRepositoryName)).list + Issues.insertAll(issues.map { x => + x.copy( + userName = newUserName, + repositoryName = newRepositoryName, + milestoneId = x.milestoneId.map { id => + newMilestones.find(_.title == milestones.find(_.milestoneId == id).get.title).get.milestoneId + }, + priorityId = x.priorityId.map { id => + newPriorities + .find(_.priorityName == priorities.find(_.priorityId == id).get.priorityName) + .get + .priorityId + } ) - ): _* - ) + }: _*) - // TODO Drop transferred owner from collaborators? - Collaborators.insertAll( - collaborators.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _* - ) + PullRequests.insertAll( + pullRequests.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _* + ) + IssueComments.insertAll( + issueComments.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _* + ) + Labels.insertAll(labels.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _*) + CommitComments.insertAll( + commitComments.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _* + ) + CommitStatuses.insertAll( + commitStatuses.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _* + ) + ProtectedBranches.insertAll( + protectedBranches.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _* + ) + ProtectedBranchContexts.insertAll( + protectedBranchContexts.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _* + ) + DeployKeys.insertAll(deployKeys.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _*) + ReleaseTags.insertAll(releases.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _*) + ReleaseAssets.insertAll( + releaseAssets.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _* + ) - // Update activity messages - Activities - .filter { t => - (t.message like s"%:${oldUserName}/${oldRepositoryName}]%") || - (t.message like s"%:${oldUserName}/${oldRepositoryName}#%") || - (t.message like s"%:${oldUserName}/${oldRepositoryName}@%") + // Update source repository of pull requests + PullRequests + .filter { t => + (t.requestUserName === oldUserName.bind) && (t.requestRepositoryName === oldRepositoryName.bind) + } + .map { t => + t.requestUserName -> t.requestRepositoryName + } + .update(newUserName, newRepositoryName) + + // Convert labelId + val oldLabelMap = labels.map(x => (x.labelId, x.labelName)).toMap + val newLabelMap = + Labels.filter(_.byRepository(newUserName, newRepositoryName)).map(x => (x.labelName, x.labelId)).list.toMap + IssueLabels.insertAll( + issueLabels.map( + x => + x.copy( + labelId = newLabelMap(oldLabelMap(x.labelId)), + userName = newUserName, + repositoryName = newRepositoryName + ) + ): _* + ) + + // TODO Drop transferred owner from collaborators? + Collaborators.insertAll( + collaborators.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _* + ) + + // Update activity messages + Activities + .filter { t => + (t.message like s"%:${oldUserName}/${oldRepositoryName}]%") || + (t.message like s"%:${oldUserName}/${oldRepositoryName}#%") || + (t.message like s"%:${oldUserName}/${oldRepositoryName}@%") + } + .map { t => + t.activityId -> t.message + } + .list + .foreach { + case (activityId, message) => + Activities + .filter(_.activityId === activityId.bind) + .map(_.message) + .update( + message + .replace( + s"[repo:${oldUserName}/${oldRepositoryName}]", + s"[repo:${newUserName}/${newRepositoryName}]" + ) + .replace( + s"[branch:${oldUserName}/${oldRepositoryName}#", + s"[branch:${newUserName}/${newRepositoryName}#" + ) + .replace( + s"[tag:${oldUserName}/${oldRepositoryName}#", + s"[tag:${newUserName}/${newRepositoryName}#" + ) + .replace( + s"[pullreq:${oldUserName}/${oldRepositoryName}#", + s"[pullreq:${newUserName}/${newRepositoryName}#" + ) + .replace( + s"[issue:${oldUserName}/${oldRepositoryName}#", + s"[issue:${newUserName}/${newRepositoryName}#" + ) + .replace( + s"[commit:${oldUserName}/${oldRepositoryName}@", + s"[commit:${newUserName}/${newRepositoryName}@" + ) + ) + } + // Move git repository + defining(getRepositoryDir(oldUserName, oldRepositoryName)) { dir => + if (dir.isDirectory) { + FileUtils.moveDirectory(dir, getRepositoryDir(newUserName, newRepositoryName)) + } } - .map { t => - t.activityId -> t.message + // Move wiki repository + defining(getWikiRepositoryDir(oldUserName, oldRepositoryName)) { dir => + if (dir.isDirectory) { + FileUtils.moveDirectory(dir, getWikiRepositoryDir(newUserName, newRepositoryName)) + } } - .list - .foreach { - case (activityId, message) => - Activities - .filter(_.activityId === activityId.bind) - .map(_.message) - .update( - message - .replace( - s"[repo:${oldUserName}/${oldRepositoryName}]", - s"[repo:${newUserName}/${newRepositoryName}]" - ) - .replace( - s"[branch:${oldUserName}/${oldRepositoryName}#", - s"[branch:${newUserName}/${newRepositoryName}#" - ) - .replace(s"[tag:${oldUserName}/${oldRepositoryName}#", s"[tag:${newUserName}/${newRepositoryName}#") - .replace( - s"[pullreq:${oldUserName}/${oldRepositoryName}#", - s"[pullreq:${newUserName}/${newRepositoryName}#" - ) - .replace( - s"[issue:${oldUserName}/${oldRepositoryName}#", - s"[issue:${newUserName}/${newRepositoryName}#" - ) - .replace( - s"[commit:${oldUserName}/${oldRepositoryName}@", - s"[commit:${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 Collaborators.filter(_.byRepository(userName, repositoryName)).delete CommitComments.filter(_.byRepository(userName, repositoryName)).delete @@ -718,7 +778,6 @@ } object RepositoryService { - case class RepositoryInfo( owner: String, name: String, diff --git a/src/main/scala/gitbucket/core/service/WebHookService.scala b/src/main/scala/gitbucket/core/service/WebHookService.scala index d8c0e48..a47ef6a 100644 --- a/src/main/scala/gitbucket/core/service/WebHookService.scala +++ b/src/main/scala/gitbucket/core/service/WebHookService.scala @@ -311,7 +311,6 @@ action: String, repository: RepositoryService.RepositoryInfo, issue: Issue, - baseUrl: String, sender: Account )(implicit s: Session, context: JsonFormat.Context): Unit = { callWebHookOf(repository.owner, repository.name, WebHook.Issues) { @@ -341,7 +340,6 @@ action: String, repository: RepositoryService.RepositoryInfo, issueId: Int, - baseUrl: String, sender: Account )(implicit s: Session, c: JsonFormat.Context): Unit = { import WebHookService._ @@ -404,7 +402,6 @@ action: String, requestRepository: RepositoryService.RepositoryInfo, requestBranch: String, - baseUrl: String, sender: Account )(implicit s: Session, c: JsonFormat.Context): Unit = { import WebHookService._ @@ -450,7 +447,6 @@ repository: RepositoryService.RepositoryInfo, issue: Issue, pullRequest: PullRequest, - baseUrl: String, sender: Account )(implicit s: Session, c: JsonFormat.Context): Unit = { import WebHookService._ diff --git a/src/main/scala/gitbucket/core/servlet/GitRepositoryServlet.scala b/src/main/scala/gitbucket/core/servlet/GitRepositoryServlet.scala index 722bf5d..46dbcc4 100644 --- a/src/main/scala/gitbucket/core/servlet/GitRepositoryServlet.scala +++ b/src/main/scala/gitbucket/core/servlet/GitRepositoryServlet.scala @@ -221,6 +221,7 @@ with PrioritiesService with MilestonesService with WebHookPullRequestService + with WebHookPullRequestReviewCommentService with CommitsService { private val logger = LoggerFactory.getLogger(classOf[CommitLogHook]) @@ -299,7 +300,7 @@ getAccountByUserName(pusher).foreach { pusherAccount => closeIssuesFromMessage(commit.fullMessage, pusher, owner, repository).foreach { issueId => getIssue(owner, repository, issueId.toString).foreach { issue => - callIssuesWebHook("closed", repositoryInfo, issue, baseUrl, pusherAccount) + callIssuesWebHook("closed", repositoryInfo, issue, pusherAccount) PluginRegistry().getIssueHooks .foreach(_.closedByCommitComment(issue, repositoryInfo, commit.fullMessage, pusherAccount)) } @@ -319,7 +320,7 @@ }.isDefined) { markMergeAndClosePullRequest(pusher, owner, repository, pull) getAccountByUserName(pusher).foreach { pusherAccount => - callPullRequestWebHook("closed", repositoryInfo, pull.issueId, baseUrl, pusherAccount) + callPullRequestWebHook("closed", repositoryInfo, pull.issueId, pusherAccount) } } } @@ -346,15 +347,8 @@ command.getType match { case ReceiveCommand.Type.CREATE | ReceiveCommand.Type.UPDATE | ReceiveCommand.Type.UPDATE_NONFASTFORWARD => - updatePullRequests(owner, repository, branchName) getAccountByUserName(pusher).foreach { pusherAccount => - callPullRequestWebHookByRequestBranch( - "synchronize", - repositoryInfo, - branchName, - baseUrl, - pusherAccount - ) + updatePullRequests(owner, repository, branchName, pusherAccount, "synchronize") } case _ => } diff --git a/src/main/scala/gitbucket/core/ssh/GitCommand.scala b/src/main/scala/gitbucket/core/ssh/GitCommand.scala index 8e8cfe5..19dcdbf 100644 --- a/src/main/scala/gitbucket/core/ssh/GitCommand.scala +++ b/src/main/scala/gitbucket/core/ssh/GitCommand.scala @@ -6,7 +6,7 @@ import gitbucket.core.servlet.{CommitLogHook, Database} import gitbucket.core.util.{SyntaxSugars, Directory} 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.slf4j.LoggerFactory import java.io.{File, InputStream, OutputStream} @@ -16,7 +16,7 @@ import Directory._ import gitbucket.core.ssh.PublicKeyAuthenticator.AuthType 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 object GitCommand { diff --git a/src/main/scala/gitbucket/core/ssh/NoShell.scala b/src/main/scala/gitbucket/core/ssh/NoShell.scala index b350e26..35f276a 100644 --- a/src/main/scala/gitbucket/core/ssh/NoShell.scala +++ b/src/main/scala/gitbucket/core/ssh/NoShell.scala @@ -3,7 +3,7 @@ import gitbucket.core.service.SystemSettingsService.SshAddress import org.apache.sshd.common.Factory 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 org.eclipse.jgit.lib.Constants diff --git a/src/main/scala/gitbucket/core/view/helpers.scala b/src/main/scala/gitbucket/core/view/helpers.scala index e3a8d6a..ab7d5bd 100644 --- a/src/main/scala/gitbucket/core/view/helpers.scala +++ b/src/main/scala/gitbucket/core/view/helpers.scala @@ -232,6 +232,12 @@ s"""${m.group(1)}/${m .group(2)}@${m.group(3).substring(0, 7)}""" ) + .replaceAll( + "\\[release:([^\\s]+?)/([^\\s]+?)/([^\\s]+?)\\]", + (m: Match) => + s"""${m + .group(3)}""" + ) ) /** diff --git a/src/main/twirl/gitbucket/core/admin/plugins.scala.html b/src/main/twirl/gitbucket/core/admin/plugins.scala.html index afe2f83..b923ec5 100644 --- a/src/main/twirl/gitbucket/core/admin/plugins.scala.html +++ b/src/main/twirl/gitbucket/core/admin/plugins.scala.html @@ -41,10 +41,6 @@ @plugin.pluginVersion
- - @plugin.pluginName -
-
@plugin.description
diff --git a/src/test/scala/gitbucket/core/service/MergeServiceSpec.scala b/src/test/scala/gitbucket/core/service/MergeServiceSpec.scala index 95d87a2..ffac0c0 100644 --- a/src/test/scala/gitbucket/core/service/MergeServiceSpec.scala +++ b/src/test/scala/gitbucket/core/service/MergeServiceSpec.scala @@ -12,7 +12,9 @@ import java.io.File 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 issueId = 10 def initRepository(owner: String, name: String): File = { diff --git a/src/test/scala/gitbucket/core/service/PullRequestServiceSpec.scala b/src/test/scala/gitbucket/core/service/PullRequestServiceSpec.scala index 40ff25e..bba472c 100644 --- a/src/test/scala/gitbucket/core/service/PullRequestServiceSpec.scala +++ b/src/test/scala/gitbucket/core/service/PullRequestServiceSpec.scala @@ -9,11 +9,15 @@ with PullRequestService with IssuesService with AccountService + with ActivityService with RepositoryService with CommitsService with LabelsService with MilestonesService - with PrioritiesService { + with PrioritiesService + with WebHookService + with WebHookPullRequestService + with WebHookPullRequestReviewCommentService { def swap(r: (Issue, PullRequest)) = (r._2 -> r._1) diff --git a/src/test/scala/gitbucket/core/service/ServiceSpecBase.scala b/src/test/scala/gitbucket/core/service/ServiceSpecBase.scala index f7ff3be..86b3f26 100644 --- a/src/test/scala/gitbucket/core/service/ServiceSpecBase.scala +++ b/src/test/scala/gitbucket/core/service/ServiceSpecBase.scala @@ -43,8 +43,10 @@ def user(name: String)(implicit s: Session): Account = AccountService.getAccountByUserName(name).get - lazy val dummyService = new RepositoryService with AccountService with IssuesService with PullRequestService - with CommitsService with CommitStatusService with LabelsService with MilestonesService with PrioritiesService() {} + lazy val dummyService = new RepositoryService with AccountService with ActivityService with IssuesService + 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 = { val ac = AccountService.getAccountByUserName(userName).getOrElse(generateNewAccount(userName)) diff --git a/src/test/scala/gitbucket/core/service/WebHookServiceSpec.scala b/src/test/scala/gitbucket/core/service/WebHookServiceSpec.scala index 9f14a24..957aa93 100644 --- a/src/test/scala/gitbucket/core/service/WebHookServiceSpec.scala +++ b/src/test/scala/gitbucket/core/service/WebHookServiceSpec.scala @@ -5,8 +5,9 @@ import gitbucket.core.model.WebHookContentType class WebHookServiceSpec extends FunSuite with ServiceSpecBase { - lazy val service = new WebHookPullRequestService with AccountService with RepositoryService with PullRequestService - with IssuesService with CommitsService with LabelsService with MilestonesService with PrioritiesService + lazy val service = new WebHookPullRequestService with AccountService with ActivityService with RepositoryService + with PullRequestService with IssuesService with CommitsService with LabelsService with MilestonesService + with PrioritiesService with WebHookPullRequestReviewCommentService test("WebHookPullRequestService.getPullRequestsByRequestForWebhook") { withTestDB { implicit session => diff --git a/src/test/scala/gitbucket/core/ssh/GitCommandSpec.scala b/src/test/scala/gitbucket/core/ssh/GitCommandSpec.scala index 93dd0d4..9051c57 100644 --- a/src/test/scala/gitbucket/core/ssh/GitCommandSpec.scala +++ b/src/test/scala/gitbucket/core/ssh/GitCommandSpec.scala @@ -1,6 +1,6 @@ package gitbucket.core.ssh -import org.apache.sshd.server.shell.UnknownCommand +import org.apache.sshd.server.scp.UnknownCommand import org.scalatest.FunSpec class GitCommandFactorySpec extends FunSpec {