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;" ) +
@@ -86,8 +87,20 @@ -
- + + + + +
@@ -159,6 +172,19 @@ } }