Implement Draft Pull Request Feature #2319 (#2336)

This commit is contained in:
Joobi S B
2019-07-11 21:43:29 +05:30
committed by Naoki Takezoe
parent 6a3f51a784
commit 3c8026f135
12 changed files with 217 additions and 126 deletions

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<changeSet>
<addColumn tableName="PULL_REQUEST">
<column name="IS_DRAFT" type="boolean" nullable="false" defaultValueBoolean="false" />
</addColumn>
</changeSet>

View File

@@ -63,5 +63,6 @@ object GitBucketCoreModule
new Version("4.30.1"), new Version("4.30.1"),
new Version("4.31.0", new LiquibaseMigration("update/gitbucket-core_4.31.xml")), new Version("4.31.0", new LiquibaseMigration("update/gitbucket-core_4.31.xml")),
new Version("4.31.1"), 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"))
) )

View File

@@ -69,6 +69,7 @@ trait PullRequestsControllerBase extends ControllerBase {
"requestBranch" -> trim(text(required, maxlength(100))), "requestBranch" -> trim(text(required, maxlength(100))),
"commitIdFrom" -> trim(text(required, maxlength(40))), "commitIdFrom" -> trim(text(required, maxlength(40))),
"commitIdTo" -> trim(text(required, maxlength(40))), "commitIdTo" -> trim(text(required, maxlength(40))),
"isDraft" -> trim(boolean(required)),
"assignedUserName" -> trim(optional(text())), "assignedUserName" -> trim(optional(text())),
"milestoneId" -> trim(optional(number())), "milestoneId" -> trim(optional(number())),
"priorityId" -> trim(optional(number())), "priorityId" -> trim(optional(number())),
@@ -77,7 +78,8 @@ trait PullRequestsControllerBase extends ControllerBase {
val mergeForm = mapping( val mergeForm = mapping(
"message" -> trim(label("Message", text(required))), "message" -> trim(label("Message", text(required))),
"strategy" -> trim(label("Strategy", text(required))) "strategy" -> trim(label("Strategy", text(required))),
"isDraft" -> trim(boolean(required))
)(MergeForm.apply) )(MergeForm.apply)
case class PullRequestForm( case class PullRequestForm(
@@ -90,13 +92,14 @@ trait PullRequestsControllerBase extends ControllerBase {
requestBranch: String, requestBranch: String,
commitIdFrom: String, commitIdFrom: String,
commitIdTo: String, commitIdTo: String,
isDraft: Boolean,
assignedUserName: Option[String], assignedUserName: Option[String],
milestoneId: Option[Int], milestoneId: Option[Int],
priorityId: Option[Int], priorityId: Option[Int],
labelNames: Option[String] 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 => get("/:owner/:repository/pulls")(referrersOnly { repository =>
val q = request.getParameter("q") val q = request.getParameter("q")
@@ -286,6 +289,7 @@ trait PullRequestsControllerBase extends ControllerBase {
flash += "error" -> s"""Can't delete the default branch "${pullreq.requestBranch}".""" flash += "error" -> s"""Can't delete the default branch "${pullreq.requestBranch}"."""
} }
} }
redirect(s"/${baseRepository.owner}/${baseRepository.name}/pull/${issueId}") redirect(s"/${baseRepository.owner}/${baseRepository.name}/pull/${issueId}")
}) getOrElse NotFound() }) getOrElse NotFound()
}) })
@@ -338,14 +342,26 @@ trait PullRequestsControllerBase extends ControllerBase {
}) getOrElse NotFound() }) 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) => post("/:owner/:repository/pull/:id/merge", mergeForm)(writableUsersOnly { (form, repository) =>
params("id").toIntOpt.flatMap { issueId => params("id").toIntOpt.flatMap { issueId =>
val owner = repository.owner val owner = repository.owner
val name = repository.name 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 Right(objectId) => redirect(s"/${owner}/${name}/pull/${issueId}")
case Left(message) => Some(BadRequest()) case Left(message) => Some(BadRequest(message))
} }
} getOrElse NotFound() } getOrElse NotFound()
}) })
@@ -543,6 +559,7 @@ trait PullRequestsControllerBase extends ControllerBase {
requestBranch = form.requestBranch, requestBranch = form.requestBranch,
commitIdFrom = form.commitIdFrom, commitIdFrom = form.commitIdFrom,
commitIdTo = form.commitIdTo, commitIdTo = form.commitIdTo,
isDraft = form.isDraft,
loginAccount = context.loginAccount.get loginAccount = context.loginAccount.get
) )

View File

@@ -114,6 +114,7 @@ trait ApiPullRequestControllerBase extends ControllerBase {
requestBranch = reqBranch, requestBranch = reqBranch,
commitIdFrom = commitIdFrom.getName, commitIdFrom = commitIdFrom.getName,
commitIdTo = commitIdTo.getName, commitIdTo = commitIdTo.getName,
isDraft = false,
loginAccount = context.loginAccount.get loginAccount = context.loginAccount.get
) )
getApiPullRequest(repository, issueId).map(JsonFormat(_)) getApiPullRequest(repository, issueId).map(JsonFormat(_))
@@ -141,6 +142,7 @@ trait ApiPullRequestControllerBase extends ControllerBase {
requestBranch = reqBranch, requestBranch = reqBranch,
commitIdFrom = commitIdFrom.getName, commitIdFrom = commitIdFrom.getName,
commitIdTo = commitIdTo.getName, commitIdTo = commitIdTo.getName,
isDraft = false,
loginAccount = context.loginAccount.get loginAccount = context.loginAccount.get
) )
getApiPullRequest(repository, createPullReqAlt.issue).map(JsonFormat(_)) getApiPullRequest(repository, createPullReqAlt.issue).map(JsonFormat(_))

View File

@@ -12,6 +12,7 @@ trait PullRequestComponent extends TemplateComponent { self: Profile =>
val requestBranch = column[String]("REQUEST_BRANCH") val requestBranch = column[String]("REQUEST_BRANCH")
val commitIdFrom = column[String]("COMMIT_ID_FROM") val commitIdFrom = column[String]("COMMIT_ID_FROM")
val commitIdTo = column[String]("COMMIT_ID_TO") val commitIdTo = column[String]("COMMIT_ID_TO")
val isDraft = column[Boolean]("IS_DRAFT")
def * = def * =
( (
userName, userName,
@@ -22,7 +23,8 @@ trait PullRequestComponent extends TemplateComponent { self: Profile =>
requestRepositoryName, requestRepositoryName,
requestBranch, requestBranch,
commitIdFrom, commitIdFrom,
commitIdTo commitIdTo,
isDraft
) <> (PullRequest.tupled, PullRequest.unapply) ) <> (PullRequest.tupled, PullRequest.unapply)
def byPrimaryKey(userName: String, repositoryName: String, issueId: Int) = def byPrimaryKey(userName: String, repositoryName: String, issueId: Int) =
@@ -41,5 +43,6 @@ case class PullRequest(
requestRepositoryName: String, requestRepositoryName: String,
requestBranch: String, requestBranch: String,
commitIdFrom: String, commitIdFrom: String,
commitIdTo: String commitIdTo: String,
isDraft: Boolean
) )

View File

@@ -252,8 +252,10 @@ trait MergeService {
issueId: Int, issueId: Int,
loginAccount: Account, loginAccount: Account,
message: String, message: String,
strategy: String strategy: String,
isDraft: Boolean
)(implicit s: Session, c: JsonFormat.Context, context: Context): Either[String, ObjectId] = { )(implicit s: Session, c: JsonFormat.Context, context: Context): Either[String, ObjectId] = {
if (!isDraft) {
if (repository.repository.options.mergeOptions.split(",").contains(strategy)) { if (repository.repository.options.mergeOptions.split(",").contains(strategy)) {
LockUtil.lock(s"${repository.owner}/${repository.name}") { LockUtil.lock(s"${repository.owner}/${repository.name}") {
getPullRequest(repository.owner, repository.name, issueId) getPullRequest(repository.owner, repository.name, issueId)
@@ -380,7 +382,7 @@ trait MergeService {
.getOrElse(Left("Pull request not found")) .getOrElse(Left("Pull request not found"))
} }
} else Left("Strategy not allowed") } else Left("Strategy not allowed")
} else Left("Draft pull requests cannot be merged")
} }
} }

View File

@@ -48,6 +48,14 @@ trait PullRequestService {
.map(pr => pr.commitIdTo -> pr.commitIdFrom) .map(pr => pr.commitIdTo -> pr.commitIdFrom)
.update((commitIdTo, 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])( def getPullRequestCountGroupByUser(closed: Boolean, owner: Option[String], repository: Option[String])(
implicit s: Session implicit s: Session
): List[PullRequestCount] = ): List[PullRequestCount] =
@@ -97,6 +105,7 @@ trait PullRequestService {
requestBranch: String, requestBranch: String,
commitIdFrom: String, commitIdFrom: String,
commitIdTo: String, commitIdTo: String,
isDraft: Boolean,
loginAccount: Account loginAccount: Account
)(implicit s: Session, context: Context): Unit = { )(implicit s: Session, context: Context): Unit = {
getIssue(originRepository.owner, originRepository.name, issueId.toString).foreach { baseIssue => getIssue(originRepository.owner, originRepository.name, issueId.toString).foreach { baseIssue =>
@@ -109,7 +118,8 @@ trait PullRequestService {
requestRepositoryName, requestRepositoryName,
requestBranch, requestBranch,
commitIdFrom, commitIdFrom,
commitIdTo commitIdTo,
isDraft
) )
// fetch requested branch // fetch requested branch
@@ -215,6 +225,7 @@ trait PullRequestService {
/** /**
* Fetch pull request contents into refs/pull/${issueId}/head and update pull request table. * Fetch pull request contents into refs/pull/${issueId}/head and update pull request table.
*
*/ */
def updatePullRequests(owner: String, repository: String, branch: String, loginAccount: Account, action: String)( def updatePullRequests(owner: String, repository: String, branch: String, loginAccount: Account, action: String)(
implicit s: Session, implicit s: Session,

View File

@@ -79,6 +79,7 @@
completionContext = "issues", completionContext = "issues",
style = "height: 200px;" style = "height: 200px;"
) )
<div class="text-right">
<input type="hidden" name="targetUserName" value="@originRepository.owner"/> <input type="hidden" name="targetUserName" value="@originRepository.owner"/>
<input type="hidden" name="targetBranch" value="@originId"/> <input type="hidden" name="targetBranch" value="@originId"/>
<input type="hidden" name="requestUserName" value="@forkedRepository.owner"/> <input type="hidden" name="requestUserName" value="@forkedRepository.owner"/>
@@ -86,8 +87,20 @@
<input type="hidden" name="requestBranch" value="@forkedId"/> <input type="hidden" name="requestBranch" value="@forkedId"/>
<input type="hidden" name="commitIdFrom" value="@sourceId"/> <input type="hidden" name="commitIdFrom" value="@sourceId"/>
<input type="hidden" name="commitIdTo" value="@commitId"/> <input type="hidden" name="commitIdTo" value="@commitId"/>
<div class="align-right"> <input type="hidden" id="is-draft" name="isDraft" value=false />
<input type="submit" class="btn btn-success" value="Create pull request"/>
<!-- <input type="submit" class="btn btn-success" value="Create pull request"/>-->
<!-- formaction="@context.path/@originRepository.owner/@originRepository.name/pulls/new"-->
<div class="btn-group dropup">
<input type="submit" class="btn btn-success" tabindex="2" value="Create pull request" id="submit-button" validate="true" formaction="@context.path/@originRepository.owner/@originRepository.name/pulls/new"/>
<button type="button" class="btn btn-success dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span class="caret"></span>
</button>
<ul class="dropdown-menu dropdown-menu-right">
<li><a id="pull-request">Create pull request</a></li>
<li><a id="draft-request">Create draft request</a></li>
</ul>
</div>
</div> </div>
</div> </div>
<div class="col-md-3"> <div class="col-md-3">
@@ -159,6 +172,19 @@
} }
} }
<script> <script>
$(function(){
$('#draft-request').click(function(){
$("#is-draft").val(true);
$('#submit-button').attr('value', 'Create draft request')
});
$('#pull-request').click(function(){
$("#is-draft").val(false);
$('#submit-button').attr('value', 'Create pull request')
});
});
$(function(){ $(function(){
function updateSelector(e){ function updateSelector(e){
e.parents('ul').find('i').attr('class', 'octicon'); e.parents('ul').find('i').attr('class', 'octicon');

View File

@@ -1,4 +1,4 @@
@(issue: gitbucket.core.model.Issue, @(issue: gitbucket.core.model.Issue,
pullreq: gitbucket.core.model.PullRequest, pullreq: gitbucket.core.model.PullRequest,
commits: Seq[gitbucket.core.util.JGitUtil.CommitInfo], commits: Seq[gitbucket.core.util.JGitUtil.CommitInfo],
comments: Seq[gitbucket.core.model.Comment], comments: Seq[gitbucket.core.model.Comment],

View File

@@ -76,6 +76,16 @@
</div> </div>
} else { } else {
<div class="merge-indicator merge-indicator-success"><span class="octicon octicon-check"></span></div> <div class="merge-indicator merge-indicator-success"><span class="octicon octicon-check"></span></div>
@if(pullreq.isDraft){
<span class="strong">This pull request is still a work in progress.</span>
<div class="pull-right">
<input type="button" class="btn btn-default" value="Ready for review" id="ready-for-review" />
</div>
<div class="small">
Draft pull requests cannot be merged.
</div>
} else {
@if(status.hasMergePermission){ @if(status.hasMergePermission){
<span class="strong">Merging can be performed automatically.</span> <span class="strong">Merging can be performed automatically.</span>
<div class="small"> <div class="small">
@@ -90,10 +100,11 @@
} }
} }
} }
}
</div> </div>
@if(status.hasMergePermission){ @if(status.hasMergePermission){
<div style="padding:15px; border-top:solid 1px #e5e5e5; background:#fafafa"> <div style="padding:15px; border-top:solid 1px #e5e5e5; background:#fafafa">
<input type="button" class="btn @if(!status.hasProblem){btn-success} else {btn-default}" id="merge-pull-request-button" value="Merge pull request"@if(!status.canMerge){ disabled="true"}/> <input type="button" class="btn @if(!status.hasProblem){btn-success} else {btn-default}" id="merge-pull-request-button" value="Merge pull request"@if(!status.canMerge || pullreq.isDraft){ disabled="true"}/>
&nbsp;&nbsp;You can also merge branches on the <a href="#" class="show-command-line">command line</a>. &nbsp;&nbsp;You can also merge branches on the <a href="#" class="show-command-line">command line</a>.
<div id="command-line" style="display: none;margin-top: 15px;"> <div id="command-line" style="display: none;margin-top: 15px;">
<hr /> <hr />
@@ -191,6 +202,7 @@
<input type="button" class="btn btn-default" value="Cancel" id="cancel-merge-pull-request"/> <input type="button" class="btn btn-default" value="Cancel" id="cancel-merge-pull-request"/>
<input type="submit" class="btn btn-success" value="Confirm merge"/> <input type="submit" class="btn btn-success" value="Confirm merge"/>
<input type="hidden" name="strategy" value="@originRepository.repository.options.defaultMergeOption"/> <input type="hidden" name="strategy" value="@originRepository.repository.options.defaultMergeOption"/>
<input type="hidden" name="isDraft" value="@pullreq.isDraft" />
</div> </div>
</div> </div>
</form> </form>
@@ -199,6 +211,15 @@
</div> </div>
<script> <script>
$(function(){
$('#ready-for-review').click(function(){
$.post('@helpers.url(originRepository)/pull/@issue.issueId/update_draft', function(data, status){
location.reload();
})
});
});
$(function(){ $(function(){
$('.show-command-line').click(function(){ $('.show-command-line').click(function(){
$('#command-line').toggle(); $('#command-line').toggle();

View File

@@ -139,7 +139,8 @@ object ApiSpecModels {
requestRepositoryName = repo1Name.name, requestRepositoryName = repo1Name.name,
requestBranch = "new-topic", requestBranch = "new-topic",
commitIdFrom = sha1, commitIdFrom = sha1,
commitIdTo = sha1 commitIdTo = sha1,
isDraft = true
) )
val commitComment = CommitComment( val commitComment = CommitComment(

View File

@@ -138,6 +138,7 @@ trait ServiceSpecBase extends MockitoSugar {
requestBranch = requestBranch, requestBranch = requestBranch,
commitIdFrom = baesBranch, commitIdFrom = baesBranch,
commitIdTo = requestBranch, commitIdTo = requestBranch,
isDraft = false,
loginAccount = loginAccount.get loginAccount = loginAccount.get
) )
dummyService.getPullRequest(baseUserName, baseRepositoryName, issueId).get dummyService.getPullRequest(baseUserName, baseRepositoryName, issueId).get