mirror of
https://github.com/gitbucket/gitbucket.git
synced 2026-05-07 16:25:59 +02:00
Call webhook when pull request is merged (#2611)
This commit is contained in:
@@ -10,6 +10,7 @@ import gitbucket.core.util.{JGitUtil, LockUtil}
|
||||
import gitbucket.core.model.Profile.profile.blockingApi._
|
||||
import gitbucket.core.model.activity.{CloseIssueInfo, MergeInfo, PushInfo}
|
||||
import gitbucket.core.service.SystemSettingsService.SystemSettings
|
||||
import gitbucket.core.service.WebHookService.WebHookPushPayload
|
||||
import gitbucket.core.util.JGitUtil.CommitInfo
|
||||
import org.eclipse.jgit.merge.{MergeStrategy, Merger, RecursiveMerger}
|
||||
import org.eclipse.jgit.api.Git
|
||||
@@ -27,7 +28,8 @@ trait MergeService {
|
||||
with IssuesService
|
||||
with RepositoryService
|
||||
with PullRequestService
|
||||
with WebHookPullRequestService =>
|
||||
with WebHookPullRequestService
|
||||
with WebHookService =>
|
||||
|
||||
import MergeService._
|
||||
|
||||
@@ -61,40 +63,91 @@ trait MergeService {
|
||||
/** merge the pull request with a merge commit */
|
||||
def mergeWithMergeCommit(
|
||||
git: Git,
|
||||
userName: String,
|
||||
repositoryName: String,
|
||||
repository: RepositoryInfo,
|
||||
branch: String,
|
||||
issueId: Int,
|
||||
message: String,
|
||||
committer: PersonIdent
|
||||
)(implicit s: Session): ObjectId = {
|
||||
new MergeCacheInfo(git, userName, repositoryName, branch, issueId, getReceiveHooks()).merge(message, committer)
|
||||
loginAccount: Account,
|
||||
settings: SystemSettings
|
||||
)(implicit s: Session, c: JsonFormat.Context): ObjectId = {
|
||||
val beforeCommitId = git.getRepository.resolve(s"refs/heads/${branch}")
|
||||
val afterCommitId = new MergeCacheInfo(git, repository.owner, repository.name, branch, issueId, getReceiveHooks())
|
||||
.merge(message, new PersonIdent(loginAccount.fullName, loginAccount.mailAddress))
|
||||
callWebHook(git, repository, branch, beforeCommitId, afterCommitId, loginAccount, settings)
|
||||
afterCommitId
|
||||
}
|
||||
|
||||
/** rebase to the head of the pull request branch */
|
||||
def mergeWithRebase(
|
||||
git: Git,
|
||||
userName: String,
|
||||
repositoryName: String,
|
||||
repository: RepositoryInfo,
|
||||
branch: String,
|
||||
issueId: Int,
|
||||
commits: Seq[RevCommit],
|
||||
committer: PersonIdent
|
||||
)(implicit s: Session): ObjectId = {
|
||||
new MergeCacheInfo(git, userName, repositoryName, branch, issueId, getReceiveHooks()).rebase(committer, commits)
|
||||
loginAccount: Account,
|
||||
settings: SystemSettings
|
||||
)(implicit s: Session, c: JsonFormat.Context): ObjectId = {
|
||||
val beforeCommitId = git.getRepository.resolve(s"refs/heads/${branch}")
|
||||
val afterCommitId =
|
||||
new MergeCacheInfo(git, repository.owner, repository.name, branch, issueId, getReceiveHooks())
|
||||
.rebase(new PersonIdent(loginAccount.fullName, loginAccount.mailAddress), commits)
|
||||
callWebHook(git, repository, branch, beforeCommitId, afterCommitId, loginAccount, settings)
|
||||
afterCommitId
|
||||
}
|
||||
|
||||
/** squash commits in the pull request and append it */
|
||||
def mergeWithSquash(
|
||||
git: Git,
|
||||
userName: String,
|
||||
repositoryName: String,
|
||||
repository: RepositoryInfo,
|
||||
branch: String,
|
||||
issueId: Int,
|
||||
message: String,
|
||||
committer: PersonIdent
|
||||
)(implicit s: Session): ObjectId = {
|
||||
new MergeCacheInfo(git, userName, repositoryName, branch, issueId, getReceiveHooks()).squash(message, committer)
|
||||
loginAccount: Account,
|
||||
settings: SystemSettings
|
||||
)(implicit s: Session, c: JsonFormat.Context): ObjectId = {
|
||||
val beforeCommitId = git.getRepository.resolve(s"refs/heads/${branch}")
|
||||
val afterCommitId =
|
||||
new MergeCacheInfo(git, repository.owner, repository.name, branch, issueId, getReceiveHooks())
|
||||
.squash(message, new PersonIdent(loginAccount.fullName, loginAccount.mailAddress))
|
||||
callWebHook(git, repository, branch, beforeCommitId, afterCommitId, loginAccount, settings)
|
||||
afterCommitId
|
||||
}
|
||||
|
||||
private def callWebHook(
|
||||
git: Git,
|
||||
repository: RepositoryInfo,
|
||||
branch: String,
|
||||
beforeCommitId: ObjectId,
|
||||
afterCommitId: ObjectId,
|
||||
loginAccount: Account,
|
||||
settings: SystemSettings
|
||||
)(
|
||||
implicit s: Session,
|
||||
c: JsonFormat.Context
|
||||
): Unit = {
|
||||
callWebHookOf(repository.owner, repository.name, WebHook.Push, settings) {
|
||||
getAccountByUserName(repository.owner).map { ownerAccount =>
|
||||
WebHookPushPayload(
|
||||
git,
|
||||
loginAccount,
|
||||
s"refs/heads/${branch}",
|
||||
repository,
|
||||
git
|
||||
.log()
|
||||
.addRange(beforeCommitId, afterCommitId)
|
||||
.call()
|
||||
.asScala
|
||||
.map { commit =>
|
||||
new JGitUtil.CommitInfo(commit)
|
||||
}
|
||||
.toList
|
||||
.reverse,
|
||||
ownerAccount,
|
||||
oldId = beforeCommitId,
|
||||
newId = afterCommitId
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** fetch remote branch to my repository refs/pull/{issueId}/head */
|
||||
@@ -303,7 +356,8 @@ trait MergeService {
|
||||
message,
|
||||
strategy,
|
||||
commits,
|
||||
getReceiveHooks()
|
||||
getReceiveHooks(),
|
||||
settings
|
||||
) match {
|
||||
case Some(newCommitId) =>
|
||||
// mark issue as merged and close.
|
||||
@@ -428,8 +482,9 @@ trait MergeService {
|
||||
message: String,
|
||||
strategy: String,
|
||||
commits: Seq[Seq[CommitInfo]],
|
||||
receiveHooks: Seq[ReceiveHook]
|
||||
)(implicit s: Session): Option[ObjectId] = {
|
||||
receiveHooks: Seq[ReceiveHook],
|
||||
settings: SystemSettings
|
||||
)(implicit s: Session, c: JsonFormat.Context): Option[ObjectId] = {
|
||||
val revCommits = Using
|
||||
.resource(new RevWalk(git.getRepository)) { revWalk =>
|
||||
commits.flatten.map { commit =>
|
||||
@@ -443,36 +498,36 @@ trait MergeService {
|
||||
Some(
|
||||
mergeWithMergeCommit(
|
||||
git,
|
||||
repository.owner,
|
||||
repository.name,
|
||||
repository,
|
||||
pullRequest.branch,
|
||||
issue.issueId,
|
||||
s"Merge pull request #${issue.issueId} from ${pullRequest.requestUserName}/${pullRequest.requestBranch}\n\n" + message,
|
||||
new PersonIdent(loginAccount.fullName, loginAccount.mailAddress)
|
||||
loginAccount,
|
||||
settings
|
||||
)
|
||||
)
|
||||
case "rebase" =>
|
||||
Some(
|
||||
mergeWithRebase(
|
||||
git,
|
||||
repository.owner,
|
||||
repository.name,
|
||||
repository,
|
||||
pullRequest.branch,
|
||||
issue.issueId,
|
||||
revCommits,
|
||||
new PersonIdent(loginAccount.fullName, loginAccount.mailAddress)
|
||||
loginAccount,
|
||||
settings
|
||||
)
|
||||
)
|
||||
case "squash" =>
|
||||
Some(
|
||||
mergeWithSquash(
|
||||
git,
|
||||
repository.owner,
|
||||
repository.name,
|
||||
repository,
|
||||
pullRequest.branch,
|
||||
issue.issueId,
|
||||
s"${issue.title} (#${issue.issueId})\n\n" + message,
|
||||
new PersonIdent(loginAccount.fullName, loginAccount.mailAddress)
|
||||
loginAccount,
|
||||
settings
|
||||
)
|
||||
)
|
||||
case _ =>
|
||||
|
||||
@@ -257,11 +257,11 @@ trait WebHookService {
|
||||
)(implicit s: Session, c: JsonFormat.Context): Unit = {
|
||||
val webHooks = getWebHooksByEvent(owner, repository, event)
|
||||
if (webHooks.nonEmpty) {
|
||||
makePayload.map(callWebHook(event, webHooks, _, settings))
|
||||
makePayload.foreach(callWebHook(event, webHooks, _, settings))
|
||||
}
|
||||
val accountWebHooks = getAccountWebHooksByEvent(owner, event)
|
||||
if (accountWebHooks.nonEmpty) {
|
||||
makePayload.map(callWebHook(event, accountWebHooks, _, settings))
|
||||
makePayload.foreach(callWebHook(event, accountWebHooks, _, settings))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,18 +1,34 @@
|
||||
package gitbucket.core.service
|
||||
|
||||
import gitbucket.core.util.Directory._
|
||||
import gitbucket.core.util.GitSpecUtil._
|
||||
import org.eclipse.jgit.api.Git
|
||||
import org.eclipse.jgit.lib._
|
||||
import org.eclipse.jgit.revwalk._
|
||||
import org.eclipse.jetty.server.handler.AbstractHandler
|
||||
import org.scalatest.funspec.AnyFunSpec
|
||||
import java.io.File
|
||||
import java.util.Date
|
||||
import java.net.InetSocketAddress
|
||||
import java.nio.charset.StandardCharsets
|
||||
|
||||
import gitbucket.core.plugin.ReceiveHook
|
||||
import javax.servlet.http.{HttpServletRequest, HttpServletResponse}
|
||||
|
||||
import scala.util.Using
|
||||
import scala.jdk.CollectionConverters._
|
||||
import gitbucket.core.controller.Context
|
||||
import gitbucket.core.plugin.ReceiveHook
|
||||
import gitbucket.core.util.Directory._
|
||||
import gitbucket.core.util.GitSpecUtil._
|
||||
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
||||
import gitbucket.core.model._
|
||||
import gitbucket.core.model.Profile._
|
||||
import gitbucket.core.model.Profile.profile.blockingApi._
|
||||
import gitbucket.core.service.WebHookService.WebHookPushPayload
|
||||
import org.eclipse.jetty.webapp.WebAppContext
|
||||
import org.eclipse.jetty.server.{Request, Server}
|
||||
import org.json4s.jackson.JsonMethods._
|
||||
import MergeServiceSpec._
|
||||
import org.json4s.JsonAST.{JArray, JString}
|
||||
|
||||
class MergeServiceSpec extends AnyFunSpec {
|
||||
class MergeServiceSpec extends AnyFunSpec with ServiceSpecBase {
|
||||
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 with RequestCache {
|
||||
@@ -115,22 +131,161 @@ class MergeServiceSpec extends AnyFunSpec {
|
||||
}
|
||||
describe("mergePullRequest") {
|
||||
it("can merge") {
|
||||
val repo8Dir = initRepository("user1", "repo8")
|
||||
Using.resource(Git.open(repo8Dir)) { git =>
|
||||
createFile(git, s"refs/pull/${issueId}/head", "test.txt", "hoge2")
|
||||
val committer = new PersonIdent("dummy2", "dummy2@example.com")
|
||||
assert(getFile(git, branch, "test.txt").content.get == "hoge")
|
||||
val requestBranchId = git.getRepository.resolve(s"refs/pull/${issueId}/head")
|
||||
val masterId = git.getRepository.resolve(branch)
|
||||
service.mergeWithMergeCommit(git, "user1", "repo8", branch, issueId, "merged", committer)(null)
|
||||
val lastCommitId = git.getRepository.resolve(branch)
|
||||
val commit = Using.resource(new RevWalk(git.getRepository))(_.parseCommit(lastCommitId))
|
||||
assert(commit.getCommitterIdent() == committer)
|
||||
assert(commit.getAuthorIdent() == committer)
|
||||
assert(commit.getFullMessage() == "merged")
|
||||
assert(commit.getParents.toSet == Set(requestBranchId, masterId))
|
||||
assert(getFile(git, branch, "test.txt").content.get == "hoge2")
|
||||
implicit val jsonFormats = gitbucket.core.api.JsonFormat.jsonFormats
|
||||
import gitbucket.core.util.Implicits._
|
||||
|
||||
withTestDB { implicit session =>
|
||||
generateNewUserWithDBRepository("user1", "repo8")
|
||||
initRepository("user1", "repo8")
|
||||
|
||||
implicit val context = Context(
|
||||
createSystemSettings(),
|
||||
Some(createAccount("dummy2", "dummy2-fullname", "dummy2@example.com")),
|
||||
request
|
||||
)
|
||||
|
||||
Using.resource(Git.open(getRepositoryDir("user1", "repo8"))) { git =>
|
||||
val commitId = createFile(git, s"refs/pull/${issueId}/head", "test.txt", "hoge2")
|
||||
assert(getFile(git, branch, "test.txt").content.get == "hoge")
|
||||
|
||||
val requestBranchId = git.getRepository.resolve(s"refs/pull/${issueId}/head")
|
||||
val masterId = git.getRepository.resolve(branch)
|
||||
val repository = createRepositoryInfo("user1", "repo8")
|
||||
|
||||
registerWebHook("user1", "repo8", "http://localhost:9999")
|
||||
|
||||
Using.resource(new TestServer()) { server =>
|
||||
service.mergeWithMergeCommit(
|
||||
git,
|
||||
repository,
|
||||
branch,
|
||||
issueId,
|
||||
"merged",
|
||||
context.loginAccount.get,
|
||||
context.settings
|
||||
)
|
||||
|
||||
Thread.sleep(5000)
|
||||
|
||||
val json = parse(new String(server.lastRequestContent, StandardCharsets.UTF_8))
|
||||
// 2 commits (create file + merge commit)
|
||||
assert((json \ "commits").asInstanceOf[JArray].arr.length == 2)
|
||||
// verify id of file creation commit
|
||||
assert((json \ "commits" \ "id").asInstanceOf[JArray].arr(0).asInstanceOf[JString].s == commitId.getName)
|
||||
}
|
||||
|
||||
val lastCommitId = git.getRepository.resolve(branch)
|
||||
val commit = Using.resource(new RevWalk(git.getRepository))(_.parseCommit(lastCommitId))
|
||||
assert(commit.getCommitterIdent().getName == "dummy2-fullname")
|
||||
assert(commit.getCommitterIdent().getEmailAddress == "dummy2@example.com")
|
||||
assert(commit.getAuthorIdent().getName == "dummy2-fullname")
|
||||
assert(commit.getAuthorIdent().getEmailAddress == "dummy2@example.com")
|
||||
assert(commit.getFullMessage() == "merged")
|
||||
assert(commit.getParents.toSet == Set(requestBranchId, masterId))
|
||||
assert(getFile(git, branch, "test.txt").content.get == "hoge2")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def registerWebHook(userName: String, repositoryName: String, url: String)(implicit s: Session): Unit = {
|
||||
RepositoryWebHooks insert RepositoryWebHook(
|
||||
userName = userName,
|
||||
repositoryName = repositoryName,
|
||||
url = url,
|
||||
ctype = WebHookContentType.JSON,
|
||||
token = None
|
||||
)
|
||||
RepositoryWebHookEvents insert RepositoryWebHookEvent(userName, repositoryName, url, WebHook.Push)
|
||||
}
|
||||
|
||||
private def createAccount(userName: String, fullName: String, mailAddress: String): Account =
|
||||
Account(
|
||||
userName = userName,
|
||||
fullName = fullName,
|
||||
mailAddress = mailAddress,
|
||||
password = "password",
|
||||
isAdmin = false,
|
||||
url = None,
|
||||
registeredDate = new Date(),
|
||||
updatedDate = new Date(),
|
||||
lastLoginDate = None,
|
||||
image = None,
|
||||
isGroupAccount = false,
|
||||
isRemoved = false,
|
||||
description = None
|
||||
)
|
||||
|
||||
private def createRepositoryInfo(userName: String, repositoryName: String): RepositoryInfo =
|
||||
RepositoryInfo(
|
||||
owner = userName,
|
||||
name = repositoryName,
|
||||
repository = gitbucket.core.model.Repository(
|
||||
userName = userName,
|
||||
repositoryName = repositoryName,
|
||||
isPrivate = false,
|
||||
description = None,
|
||||
defaultBranch = "master",
|
||||
registeredDate = new Date(),
|
||||
updatedDate = new Date(),
|
||||
lastActivityDate = new Date(),
|
||||
originUserName = None,
|
||||
originRepositoryName = None,
|
||||
parentUserName = None,
|
||||
parentRepositoryName = None,
|
||||
options = RepositoryOptions(
|
||||
issuesOption = "PUBLIC",
|
||||
externalIssuesUrl = None,
|
||||
wikiOption = "PUBLIC",
|
||||
externalWikiUrl = None,
|
||||
allowFork = true,
|
||||
mergeOptions = "merge-commit,squash,rebase",
|
||||
defaultMergeOption = "merge-commit"
|
||||
)
|
||||
),
|
||||
issueCount = 0,
|
||||
pullCount = 0,
|
||||
forkedCount = 0,
|
||||
milestoneCount = 0,
|
||||
branchList = Nil,
|
||||
tags = Nil,
|
||||
managers = Nil
|
||||
)
|
||||
}
|
||||
|
||||
object MergeServiceSpec {
|
||||
class TestServer extends AutoCloseable {
|
||||
var lastRequestURI: String = null
|
||||
var lastRequestHeaders: Map[String, String] = null
|
||||
var lastRequestContent: Array[Byte] = null
|
||||
|
||||
val server = new Server(new InetSocketAddress(9999))
|
||||
val context = new WebAppContext()
|
||||
context.setServer(server)
|
||||
server.setStopAtShutdown(true)
|
||||
server.setStopTimeout(500)
|
||||
server.setHandler(new AbstractHandler {
|
||||
override def handle(
|
||||
target: String,
|
||||
baseRequest: Request,
|
||||
request: HttpServletRequest,
|
||||
response: HttpServletResponse
|
||||
): Unit = {
|
||||
lastRequestURI = request.getRequestURI
|
||||
lastRequestHeaders = request.getHeaderNames.asScala.map { key =>
|
||||
key -> request.getHeader(key)
|
||||
}.toMap
|
||||
val bytes = new Array[Byte](request.getContentLength)
|
||||
if (bytes.length > 0) {
|
||||
request.getInputStream.read(bytes)
|
||||
lastRequestContent = bytes
|
||||
}
|
||||
}
|
||||
})
|
||||
server.start()
|
||||
|
||||
override def close(): Unit = {
|
||||
server.stop()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ trait ServiceSpecBase {
|
||||
when(request.getContextPath).thenReturn("")
|
||||
when(request.getSession).thenReturn(session)
|
||||
|
||||
private def createSystemSettings() =
|
||||
def createSystemSettings() =
|
||||
SystemSettings(
|
||||
baseUrl = None,
|
||||
information = None,
|
||||
|
||||
@@ -46,7 +46,7 @@ object GitSpecUtil {
|
||||
authorName: String = "dummy",
|
||||
authorEmail: String = "dummy@example.com",
|
||||
message: String = "test commit"
|
||||
): Unit = {
|
||||
): ObjectId = {
|
||||
val builder = DirCache.newInCore.builder()
|
||||
val inserter = git.getRepository.newObjectInserter()
|
||||
val headId = git.getRepository.resolve(branch + "^{commit}")
|
||||
@@ -65,7 +65,7 @@ object GitSpecUtil {
|
||||
)
|
||||
)
|
||||
builder.finish()
|
||||
JGitUtil.createNewCommit(
|
||||
val commitId = JGitUtil.createNewCommit(
|
||||
git,
|
||||
inserter,
|
||||
headId,
|
||||
@@ -77,6 +77,7 @@ object GitSpecUtil {
|
||||
)
|
||||
inserter.flush()
|
||||
inserter.close()
|
||||
commitId
|
||||
}
|
||||
|
||||
def getFile(git: Git, branch: String, path: String) = {
|
||||
|
||||
Reference in New Issue
Block a user