diff --git a/src/main/resources/update/gitbucket-core_4.32.xml b/src/main/resources/update/gitbucket-core_4.32.xml
new file mode 100644
index 0000000..8104a3d
--- /dev/null
+++ b/src/main/resources/update/gitbucket-core_4.32.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/src/main/scala/gitbucket/core/GitBucketCoreModule.scala b/src/main/scala/gitbucket/core/GitBucketCoreModule.scala
index 8e8c420..4a3003c 100644
--- a/src/main/scala/gitbucket/core/GitBucketCoreModule.scala
+++ b/src/main/scala/gitbucket/core/GitBucketCoreModule.scala
@@ -63,5 +63,6 @@
new Version("4.30.1"),
new Version("4.31.0", new LiquibaseMigration("update/gitbucket-core_4.31.xml")),
new Version("4.31.1"),
- new Version("4.31.2")
+ new Version("4.31.2"),
+ new Version("4.32.0", new LiquibaseMigration("update/gitbucket-core_4.32.xml"))
)
diff --git a/src/main/scala/gitbucket/core/controller/PullRequestsController.scala b/src/main/scala/gitbucket/core/controller/PullRequestsController.scala
index 62bf4be..2910ac7 100644
--- a/src/main/scala/gitbucket/core/controller/PullRequestsController.scala
+++ b/src/main/scala/gitbucket/core/controller/PullRequestsController.scala
@@ -69,6 +69,7 @@
"requestBranch" -> trim(text(required, maxlength(100))),
"commitIdFrom" -> trim(text(required, maxlength(40))),
"commitIdTo" -> trim(text(required, maxlength(40))),
+ "isDraft" -> trim(boolean(required)),
"assignedUserName" -> trim(optional(text())),
"milestoneId" -> trim(optional(number())),
"priorityId" -> trim(optional(number())),
@@ -77,7 +78,8 @@
val mergeForm = mapping(
"message" -> trim(label("Message", text(required))),
- "strategy" -> trim(label("Strategy", text(required)))
+ "strategy" -> trim(label("Strategy", text(required))),
+ "isDraft" -> trim(boolean(required))
)(MergeForm.apply)
case class PullRequestForm(
@@ -90,13 +92,14 @@
requestBranch: String,
commitIdFrom: String,
commitIdTo: String,
+ isDraft: Boolean,
assignedUserName: Option[String],
milestoneId: Option[Int],
priorityId: Option[Int],
labelNames: Option[String]
)
- case class MergeForm(message: String, strategy: String)
+ case class MergeForm(message: String, strategy: String, isDraft: Boolean)
get("/:owner/:repository/pulls")(referrersOnly { repository =>
val q = request.getParameter("q")
@@ -286,6 +289,7 @@
flash += "error" -> s"""Can't delete the default branch "${pullreq.requestBranch}"."""
}
}
+
redirect(s"/${baseRepository.owner}/${baseRepository.name}/pull/${issueId}")
}) getOrElse NotFound()
})
@@ -338,14 +342,26 @@
}) getOrElse NotFound()
})
+ post("/:owner/:repository/pull/:id/update_draft")(readableUsersOnly { baseRepository =>
+ (for {
+ issueId <- params("id").toIntOpt
+ (_, pullreq) <- getPullRequest(baseRepository.owner, baseRepository.name, issueId)
+ owner = pullreq.requestUserName
+ name = pullreq.requestRepositoryName
+ if hasDeveloperRole(owner, name, context.loginAccount)
+ } yield {
+ updateDraftToPullRequest(baseRepository.owner, baseRepository.name, issueId)
+ }) getOrElse NotFound()
+ })
+
post("/:owner/:repository/pull/:id/merge", mergeForm)(writableUsersOnly { (form, repository) =>
params("id").toIntOpt.flatMap { issueId =>
val owner = repository.owner
val name = repository.name
- mergePullRequest(repository, issueId, context.loginAccount.get, form.message, form.strategy) match {
+ mergePullRequest(repository, issueId, context.loginAccount.get, form.message, form.strategy, form.isDraft) match {
case Right(objectId) => redirect(s"/${owner}/${name}/pull/${issueId}")
- case Left(message) => Some(BadRequest())
+ case Left(message) => Some(BadRequest(message))
}
} getOrElse NotFound()
})
@@ -543,6 +559,7 @@
requestBranch = form.requestBranch,
commitIdFrom = form.commitIdFrom,
commitIdTo = form.commitIdTo,
+ isDraft = form.isDraft,
loginAccount = context.loginAccount.get
)
diff --git a/src/main/scala/gitbucket/core/controller/api/ApiPullRequestControllerBase.scala b/src/main/scala/gitbucket/core/controller/api/ApiPullRequestControllerBase.scala
index 14aaccb..9872d07 100644
--- a/src/main/scala/gitbucket/core/controller/api/ApiPullRequestControllerBase.scala
+++ b/src/main/scala/gitbucket/core/controller/api/ApiPullRequestControllerBase.scala
@@ -114,6 +114,7 @@
requestBranch = reqBranch,
commitIdFrom = commitIdFrom.getName,
commitIdTo = commitIdTo.getName,
+ isDraft = false,
loginAccount = context.loginAccount.get
)
getApiPullRequest(repository, issueId).map(JsonFormat(_))
@@ -141,6 +142,7 @@
requestBranch = reqBranch,
commitIdFrom = commitIdFrom.getName,
commitIdTo = commitIdTo.getName,
+ isDraft = false,
loginAccount = context.loginAccount.get
)
getApiPullRequest(repository, createPullReqAlt.issue).map(JsonFormat(_))
diff --git a/src/main/scala/gitbucket/core/model/PullRequest.scala b/src/main/scala/gitbucket/core/model/PullRequest.scala
index 5c37691..1e3adc0 100644
--- a/src/main/scala/gitbucket/core/model/PullRequest.scala
+++ b/src/main/scala/gitbucket/core/model/PullRequest.scala
@@ -12,6 +12,7 @@
val requestBranch = column[String]("REQUEST_BRANCH")
val commitIdFrom = column[String]("COMMIT_ID_FROM")
val commitIdTo = column[String]("COMMIT_ID_TO")
+ val isDraft = column[Boolean]("IS_DRAFT")
def * =
(
userName,
@@ -22,7 +23,8 @@
requestRepositoryName,
requestBranch,
commitIdFrom,
- commitIdTo
+ commitIdTo,
+ isDraft
) <> (PullRequest.tupled, PullRequest.unapply)
def byPrimaryKey(userName: String, repositoryName: String, issueId: Int) =
@@ -41,5 +43,6 @@
requestRepositoryName: String,
requestBranch: String,
commitIdFrom: String,
- commitIdTo: String
+ commitIdTo: String,
+ isDraft: Boolean
)
diff --git a/src/main/scala/gitbucket/core/service/MergeService.scala b/src/main/scala/gitbucket/core/service/MergeService.scala
index 169b5d1..6122374 100644
--- a/src/main/scala/gitbucket/core/service/MergeService.scala
+++ b/src/main/scala/gitbucket/core/service/MergeService.scala
@@ -252,80 +252,96 @@
issueId: Int,
loginAccount: Account,
message: String,
- strategy: String
+ strategy: String,
+ isDraft: Boolean
)(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)
+ if (!isDraft) {
+ 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)
+ // 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 (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
+ 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)
+ // 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 "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 "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 =>
+ 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(
- commit.fullMessage,
+ issueContent,
loginAccount.userName,
repository.owner,
repository.name
@@ -333,54 +349,40 @@
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))
}
}
- }
+ 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))
+ }
+ }
+ }
- callPullRequestWebHook("closed", repository, issueId, context.loginAccount.get)
+ callPullRequestWebHook("closed", repository, issueId, context.loginAccount.get)
- updatePullRequests(repository.owner, repository.name, pullreq.branch, loginAccount, "closed")
+ 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)
- }
+ // call hooks
+ PluginRegistry().getPullRequestHooks.foreach { h =>
+ h.addedComment(commentId, message, issue, repository)
+ h.merged(issue, repository)
+ }
- Right(newCommitId)
- case None =>
- Left("Unknown strategy")
+ Right(newCommitId)
+ case None =>
+ Left("Unknown strategy")
+ }
}
- }
- case _ => Left("Unknown error")
- }
- .getOrElse(Left("Pull request not found"))
- }
- } else Left("Strategy not allowed")
-
+ case _ => Left("Unknown error")
+ }
+ .getOrElse(Left("Pull request not found"))
+ }
+ } else Left("Strategy not allowed")
+ } else Left("Draft pull requests cannot be merged")
}
}
diff --git a/src/main/scala/gitbucket/core/service/PullRequestService.scala b/src/main/scala/gitbucket/core/service/PullRequestService.scala
index 2a3cd80..2323bab 100644
--- a/src/main/scala/gitbucket/core/service/PullRequestService.scala
+++ b/src/main/scala/gitbucket/core/service/PullRequestService.scala
@@ -48,6 +48,14 @@
.map(pr => pr.commitIdTo -> pr.commitIdFrom)
.update((commitIdTo, commitIdFrom))
+ def updateDraftToPullRequest(owner: String, repository: String, issueId: Int)(
+ implicit s: Session
+ ): Unit =
+ PullRequests
+ .filter(_.byPrimaryKey(owner, repository, issueId))
+ .map(pr => pr.isDraft)
+ .update(false)
+
def getPullRequestCountGroupByUser(closed: Boolean, owner: Option[String], repository: Option[String])(
implicit s: Session
): List[PullRequestCount] =
@@ -97,6 +105,7 @@
requestBranch: String,
commitIdFrom: String,
commitIdTo: String,
+ isDraft: Boolean,
loginAccount: Account
)(implicit s: Session, context: Context): Unit = {
getIssue(originRepository.owner, originRepository.name, issueId.toString).foreach { baseIssue =>
@@ -109,7 +118,8 @@
requestRepositoryName,
requestBranch,
commitIdFrom,
- commitIdTo
+ commitIdTo,
+ isDraft
)
// fetch requested branch
@@ -215,6 +225,7 @@
/**
* Fetch pull request contents into refs/pull/${issueId}/head and update pull request table.
+ *
*/
def updatePullRequests(owner: String, repository: String, branch: String, loginAccount: Account, action: String)(
implicit s: Session,
diff --git a/src/main/twirl/gitbucket/core/pulls/compare.scala.html b/src/main/twirl/gitbucket/core/pulls/compare.scala.html
index 94effec..4018b29 100644
--- a/src/main/twirl/gitbucket/core/pulls/compare.scala.html
+++ b/src/main/twirl/gitbucket/core/pulls/compare.scala.html
@@ -79,6 +79,7 @@
completionContext = "issues",
style = "height: 200px;"
)
+
@@ -159,6 +172,19 @@
}
}