update button

This commit is contained in:
nazoking
2015-12-16 21:27:47 +09:00
parent 645af4d2c0
commit 2d8aa4f8b5
8 changed files with 315 additions and 151 deletions

View File

@@ -1,7 +1,7 @@
package gitbucket.core.controller package gitbucket.core.controller
import gitbucket.core.api._ import gitbucket.core.api._
import gitbucket.core.model.{Account, CommitState, Repository, PullRequest, Issue} import gitbucket.core.model.{Account, CommitStatus, CommitState, Repository, PullRequest, Issue, WebHook}
import gitbucket.core.pulls.html import gitbucket.core.pulls.html
import gitbucket.core.service.CommitStatusService import gitbucket.core.service.CommitStatusService
import gitbucket.core.service.MergeService import gitbucket.core.service.MergeService
@@ -119,7 +119,8 @@ trait PullRequestsControllerBase extends ControllerBase {
commits, commits,
diffs, diffs,
hasWritePermission(owner, name, context.loginAccount), hasWritePermission(owner, name, context.loginAccount),
repository) repository,
flash.toMap.map(f => f._1 -> f._2.toString))
} }
} }
} getOrElse NotFound } getOrElse NotFound
@@ -166,26 +167,31 @@ trait PullRequestsControllerBase extends ControllerBase {
} getOrElse NotFound } getOrElse NotFound
}) })
ajaxGet("/:owner/:repository/pull/:id/mergeguide")(collaboratorsOnly { repository => ajaxGet("/:owner/:repository/pull/:id/mergeguide")(referrersOnly { 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
getPullRequest(owner, name, issueId) map { case(issue, pullreq) => getPullRequest(owner, name, issueId) map { case(issue, pullreq) =>
val branchProtection = getProtectedBranchInfo(owner, name, pullreq.branch) val hasConflict = LockUtil.lock(s"${owner}/${name}"){
val statuses = branchProtection.withRequireStatues(getCommitStatues(owner, name, pullreq.commitIdTo))
val hasConfrict = LockUtil.lock(s"${owner}/${name}"){
checkConflict(owner, name, pullreq.branch, issueId) checkConflict(owner, name, pullreq.branch, issueId)
} }
val hasRequiredStatusProblem = branchProtection.hasProblem(statuses, pullreq.commitIdTo, context.loginAccount.get.userName) val hasMergePermission = hasWritePermission(owner, name, context.loginAccount)
val hasProblem = hasRequiredStatusProblem || hasConfrict || (!statuses.isEmpty && CommitState.combine(statuses.map(_.state).toSet) != CommitState.SUCCESS) val branchProtection = getProtectedBranchInfo(owner, name, pullreq.branch)
val state = PullRequestsController.MergeStatus(
hasConflict = hasConflict,
commitStatues = getCommitStatues(owner, name, pullreq.commitIdTo),
branchProtection = branchProtection,
branchIsOutOfDate = JGitUtil.getShaByRef(owner, name, pullreq.branch) != Some(pullreq.commitIdFrom),
needStatusCheck = branchProtection.needStatusCheck(context.loginAccount.map(_.userName)),
hasUpdatePermission = hasWritePermission(pullreq.requestUserName, pullreq.requestRepositoryName, context.loginAccount) &&
!getProtectedBranchInfo(pullreq.requestUserName, pullreq.requestRepositoryName, pullreq.requestBranch)
.needStatusCheck(context.loginAccount.map(_.userName)),
hasMergePermission = hasMergePermission,
commitIdTo = pullreq.commitIdTo)
html.mergeguide( html.mergeguide(
hasConfrict, state,
hasProblem,
issue, issue,
pullreq, pullreq,
statuses,
branchProtection,
hasRequiredStatusProblem,
repository, repository,
getRepository(pullreq.requestUserName, pullreq.requestRepositoryName, context.baseUrl).get) getRepository(pullreq.requestUserName, pullreq.requestRepositoryName, context.baseUrl).get)
} }
@@ -207,6 +213,75 @@ trait PullRequestsControllerBase extends ControllerBase {
} getOrElse NotFound } getOrElse NotFound
}) })
post("/:owner/:repository/pull/:id/update_branch")(referrersOnly { baseRepository =>
(for {
issueId <- params("id").toIntOpt
loginAccount <- context.loginAccount
(issue, pullreq) <- getPullRequest(baseRepository.owner, baseRepository.name, issueId)
owner = pullreq.requestUserName
name = pullreq.requestRepositoryName
if hasWritePermission(owner, name, context.loginAccount)
} yield {
val branchProtection = getProtectedBranchInfo(owner, name, pullreq.requestBranch)
if(branchProtection.needStatusCheck(loginAccount.userName)){
flash += "error" -> s"branch ${pullreq.requestBranch} is protected need status check."
} else {
val repository = getRepository(owner, name, context.baseUrl).get
LockUtil.lock(s"${owner}/${name}"){
val alias = if(pullreq.repositoryName == pullreq.requestRepositoryName && pullreq.userName == pullreq.requestUserName){
pullreq.branch
}else{
s"${pullreq.userName}:${pullreq.branch}"
}
val existIds = using(Git.open(Directory.getRepositoryDir(owner, name))) { git => JGitUtil.getAllCommitIds(git) }.toSet
pullRemote(owner, name, pullreq.requestBranch, pullreq.userName, pullreq.repositoryName, pullreq.branch, loginAccount,
"Merge branch '${alias}' into ${pullreq.requestBranch}") 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.map{ commit =>
closeIssuesFromMessage(commit.fullMessage, loginAccount.userName, owner, name)
}
}
// 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)
}
}
}
flash += "info" -> s"Merge branch '${alias}' into ${pullreq.requestBranch}"
}
}
redirect(s"/${repository.owner}/${repository.name}/pull/${issueId}")
}
}) getOrElse NotFound
})
post("/:owner/:repository/pull/:id/merge", mergeForm)(collaboratorsOnly { (form, repository) => post("/:owner/:repository/pull/:id/merge", mergeForm)(collaboratorsOnly { (form, repository) =>
params("id").toIntOpt.flatMap { issueId => params("id").toIntOpt.flatMap { issueId =>
val owner = repository.owner val owner = repository.owner
@@ -532,4 +607,43 @@ trait PullRequestsControllerBase extends ControllerBase {
repository, repository,
hasWritePermission(owner, repoName, context.loginAccount)) hasWritePermission(owner, repoName, context.loginAccount))
} }
// TODO: same as gitbucket.core.servlet.CommitLogHook ...
private def createIssueComment(owner: String, repository: String, commit: CommitInfo) = {
StringUtil.extractIssueId(commit.fullMessage).foreach { issueId =>
if(getIssue(owner, repository, issueId).isDefined){
getAccountByMailAddress(commit.committerEmailAddress).foreach { account =>
createComment(owner, repository, account.userName, issueId.toInt, commit.fullMessage + " " + commit.id, "commit")
}
}
}
}
}
object PullRequestsController {
case class MergeStatus(
hasConflict: Boolean,
commitStatues:List[CommitStatus],
branchProtection: ProtectedBrancheService.ProtectedBranchInfo,
branchIsOutOfDate: Boolean,
hasUpdatePermission: Boolean,
needStatusCheck: Boolean,
hasMergePermission: Boolean,
commitIdTo: String){
val statuses: List[CommitStatus] =
commitStatues ++ (branchProtection.contexts.toSet -- commitStatues.map(_.context).toSet).map(branchProtection.pendingCommitStatus(_))
val hasRequiredStatusProblem = needStatusCheck && branchProtection.contexts.exists(context => statuses.find(_.context == context).map(_.state) != Some(CommitState.SUCCESS))
val hasProblem = hasRequiredStatusProblem || hasConflict || (!statuses.isEmpty && CommitState.combine(statuses.map(_.state).toSet) != CommitState.SUCCESS)
val canUpdate = branchIsOutOfDate && !hasConflict
val canMerge = hasMergePermission && !hasConflict && !hasRequiredStatusProblem
lazy val commitStateSummary:(CommitState, String) = {
val stateMap = statuses.groupBy(_.state)
val state = CommitState.combine(stateMap.keySet)
val summary = stateMap.map{ case (keyState, states) => states.size+" "+keyState.name }.mkString(", ")
state -> summary
}
lazy val statusesAndRequired:List[(CommitStatus, Boolean)] = statuses.map{ s => s -> branchProtection.contexts.exists(_==s.context) }
lazy val isAllSuccess = commitStateSummary._1==CommitState.SUCCESS
}
} }

View File

@@ -14,7 +14,6 @@ import gitbucket.core.util.ControlUtil._
import gitbucket.core.util.Implicits._ import gitbucket.core.util.Implicits._
import gitbucket.core.util.Directory._ import gitbucket.core.util.Directory._
import gitbucket.core.model.{Account, CommitState, WebHook} import gitbucket.core.model.{Account, CommitState, WebHook}
import gitbucket.core.service.{CommitStatusService, ProtectedBrancheService}
import gitbucket.core.service.WebHookService._ import gitbucket.core.service.WebHookService._
import gitbucket.core.view import gitbucket.core.view
import gitbucket.core.view.helpers import gitbucket.core.view.helpers

View File

@@ -10,10 +10,9 @@ import org.eclipse.jgit.merge.MergeStrategy
import org.eclipse.jgit.api.Git import org.eclipse.jgit.api.Git
import org.eclipse.jgit.transport.RefSpec import org.eclipse.jgit.transport.RefSpec
import org.eclipse.jgit.errors.NoMergeBaseException import org.eclipse.jgit.errors.NoMergeBaseException
import org.eclipse.jgit.lib.{ObjectId, CommitBuilder, PersonIdent} import org.eclipse.jgit.lib.{ObjectId, CommitBuilder, PersonIdent, Repository}
import org.eclipse.jgit.revwalk.RevWalk import org.eclipse.jgit.revwalk.RevWalk
trait MergeService { trait MergeService {
import MergeService._ import MergeService._
/** /**
@@ -52,26 +51,30 @@ trait MergeService {
/** /**
* Checks whether conflict will be caused in merging. Returns true if conflict will be caused. * Checks whether conflict will be caused in merging. Returns true if conflict will be caused.
*/ */
def checkConflict(userName: String, repositoryName: String, branch: String, def tryMergeRemote(localUserName: String, localRepositoryName: String, localBranch: String,
requestUserName: String, requestRepositoryName: String, requestBranch: String): Boolean = { remoteUserName: String, remoteRepositoryName: String, remoteBranch: String): Option[(ObjectId, ObjectId, ObjectId)] = {
using(Git.open(getRepositoryDir(requestUserName, requestRepositoryName))) { git => using(Git.open(getRepositoryDir(localUserName, localRepositoryName))) { git =>
val remoteRefName = s"refs/heads/${branch}" val remoteRefName = s"refs/heads/${remoteBranch}"
val tmpRefName = s"refs/merge-check/${userName}/${branch}" val tmpRefName = s"refs/remote-temp/${remoteUserName}/${remoteRepositoryName}/${remoteBranch}"
val refSpec = new RefSpec(s"${remoteRefName}:${tmpRefName}").setForceUpdate(true) val refSpec = new RefSpec(s"${remoteRefName}:${tmpRefName}").setForceUpdate(true)
try { try {
// fetch objects from origin repository branch // fetch objects from origin repository branch
git.fetch git.fetch
.setRemote(getRepositoryDir(userName, repositoryName).toURI.toString) .setRemote(getRepositoryDir(remoteUserName, remoteRepositoryName).toURI.toString)
.setRefSpecs(refSpec) .setRefSpecs(refSpec)
.call .call
// merge conflict check // merge conflict check
val merger = MergeStrategy.RECURSIVE.newMerger(git.getRepository, true) val merger = MergeStrategy.RECURSIVE.newMerger(git.getRepository, true)
val mergeBaseTip = git.getRepository.resolve(s"refs/heads/${requestBranch}") val mergeBaseTip = git.getRepository.resolve(s"refs/heads/${localBranch}")
val mergeTip = git.getRepository.resolve(tmpRefName) val mergeTip = git.getRepository.resolve(tmpRefName)
try { try {
!merger.merge(mergeBaseTip, mergeTip) if(merger.merge(mergeBaseTip, mergeTip)){
Some((merger.getResultTreeId, mergeBaseTip, mergeTip))
}else{
None
}
} catch { } catch {
case e: NoMergeBaseException => true case e: NoMergeBaseException => None
} }
} finally { } finally {
val refUpdate = git.getRepository.updateRef(refSpec.getDestination) val refUpdate = git.getRepository.updateRef(refSpec.getDestination)
@@ -80,8 +83,54 @@ trait MergeService {
} }
} }
} }
/**
* Checks whether conflict will be caused in merging. Returns true if conflict will be caused.
*/
def checkConflict(userName: String, repositoryName: String, branch: String,
requestUserName: String, requestRepositoryName: String, requestBranch: String): Boolean =
tryMergeRemote(userName, repositoryName, branch, requestUserName, requestRepositoryName, requestBranch).isEmpty
def pullRemote(localUserName: String, localRepositoryName: String, localBranch: String,
remoteUserName: String, remoteRepositoryName: String, remoteBranch: String,
loginAccount: Account, message: String): Option[ObjectId] = {
tryMergeRemote(localUserName, localRepositoryName, localBranch, remoteUserName, remoteRepositoryName, remoteBranch).map{ case (newTreeId, oldBaseId, oldHeadId) =>
using(Git.open(getRepositoryDir(localUserName, localRepositoryName))) { git =>
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"))
}
oldBaseId
}
}
} }
object MergeService{ object MergeService{
object Util{
// return treeId
def createMergeCommit(repository: Repository, treeId: ObjectId, committer: PersonIdent, message: String, parents: Seq[ObjectId]): ObjectId = {
val mergeCommit = new CommitBuilder()
mergeCommit.setTreeId(treeId)
mergeCommit.setParentIds(parents:_*)
mergeCommit.setAuthor(committer)
mergeCommit.setCommitter(committer)
mergeCommit.setMessage(message)
// insertObject and got mergeCommit Object Id
val inserter = repository.newObjectInserter
val mergeCommitId = inserter.insert(mergeCommit)
inserter.flush()
inserter.close()
mergeCommitId
}
def updateRefs(repository: Repository, ref: String, newObjectId: ObjectId, force: Boolean, committer: PersonIdent, refLogMessage: Option[String] = None):Unit = {
// update refs
val refUpdate = repository.updateRef(ref)
refUpdate.setNewObjectId(newObjectId)
refUpdate.setForceUpdate(force)
refUpdate.setRefLogIdent(committer)
refLogMessage.map(refUpdate.setRefLogMessage(_, true))
refUpdate.update()
}
}
case class MergeCacheInfo(git:Git, branch:String, issueId:Int){ case class MergeCacheInfo(git:Git, branch:String, issueId:Int){
val repository = git.getRepository val repository = git.getRepository
val mergedBranchName = s"refs/pull/${issueId}/merge" val mergedBranchName = s"refs/pull/${issueId}/merge"
@@ -120,12 +169,7 @@ object MergeService{
def updateBranch(treeId:ObjectId, message:String, branchName:String){ def updateBranch(treeId:ObjectId, message:String, branchName:String){
// creates merge commit // creates merge commit
val mergeCommitId = createMergeCommit(treeId, committer, message) val mergeCommitId = createMergeCommit(treeId, committer, message)
// update refs Util.updateRefs(repository, branchName, mergeCommitId, true, committer)
val refUpdate = repository.updateRef(branchName)
refUpdate.setNewObjectId(mergeCommitId)
refUpdate.setForceUpdate(true)
refUpdate.setRefLogIdent(committer)
refUpdate.update()
} }
if(!conflicted){ if(!conflicted){
updateBranch(merger.getResultTreeId, s"Merge ${mergeTip.name} into ${mergeBaseTip.name}", mergedBranchName) updateBranch(merger.getResultTreeId, s"Merge ${mergeTip.name} into ${mergeBaseTip.name}", mergedBranchName)
@@ -145,28 +189,12 @@ object MergeService{
// creates merge commit // creates merge commit
val mergeCommitId = createMergeCommit(mergeResultCommit.getTree().getId(), committer, message) val mergeCommitId = createMergeCommit(mergeResultCommit.getTree().getId(), committer, message)
// update refs // update refs
val refUpdate = repository.updateRef(s"refs/heads/${branch}") Util.updateRefs(repository, s"refs/heads/${branch}", mergeCommitId, false, committer, Some("merged"))
refUpdate.setNewObjectId(mergeCommitId)
refUpdate.setForceUpdate(false)
refUpdate.setRefLogIdent(committer)
refUpdate.setRefLogMessage("merged", true)
refUpdate.update()
} }
// return treeId // return treeId
private def createMergeCommit(treeId:ObjectId, committer:PersonIdent, message:String) = { private def createMergeCommit(treeId: ObjectId, committer: PersonIdent, message: String) =
val mergeCommit = new CommitBuilder() Util.createMergeCommit(repository, treeId, committer, message, Seq[ObjectId](mergeBaseTip, mergeTip))
mergeCommit.setTreeId(treeId)
mergeCommit.setParentIds(Array[ObjectId](mergeBaseTip, mergeTip): _*)
mergeCommit.setAuthor(committer)
mergeCommit.setCommitter(committer)
mergeCommit.setMessage(message)
// insertObject and got mergeCommit Object Id
val inserter = repository.newObjectInserter
val mergeCommitId = inserter.insert(mergeCommit)
inserter.flush()
inserter.close()
mergeCommitId
}
private def parseCommit(id:ObjectId) = using(new RevWalk( repository ))(_.parseCommit(id)) private def parseCommit(id:ObjectId) = using(new RevWalk( repository ))(_.parseCommit(id))
} }
} }

View File

@@ -101,6 +101,7 @@ object ProtectedBrancheService {
} else { } else {
contexts.toSet -- getCommitStatues(owner, repository, sha1).filter(_.state == CommitState.SUCCESS).map(_.context).toSet contexts.toSet -- getCommitStatues(owner, repository, sha1).filter(_.state == CommitState.SUCCESS).map(_.context).toSet
} }
def needStatusCheck(pusher: Option[String])(implicit session: Session): Boolean = pusher.map(needStatusCheck).getOrElse(false)
def needStatusCheck(pusher: String)(implicit session: Session): Boolean = def needStatusCheck(pusher: String)(implicit session: Session): Boolean =
if(!enabled || contexts.isEmpty){ if(!enabled || contexts.isEmpty){
false false
@@ -109,8 +110,7 @@ object ProtectedBrancheService {
}else{ }else{
!isAdministrator(pusher) !isAdministrator(pusher)
} }
def withRequireStatues(statuses: List[CommitStatus]): List[CommitStatus] = { def pendingCommitStatus(context: String) = CommitStatus(
statuses ++ (contexts.toSet -- statuses.map(_.context).toSet).map{ context => CommitStatus(
commitStatusId = 0, commitStatusId = 0,
userName = owner, userName = owner,
repositoryName = repository, repositoryName = repository,
@@ -122,10 +122,6 @@ object ProtectedBrancheService {
creator = "", creator = "",
registeredDate = new java.util.Date(), registeredDate = new java.util.Date(),
updatedDate = new java.util.Date()) updatedDate = new java.util.Date())
}
}
def hasProblem(statuses: List[CommitStatus], sha1: String, account: String)(implicit session: Session): Boolean =
needStatusCheck(account) && contexts.exists(context => statuses.find(_.context == context).map(_.state) != Some(CommitState.SUCCESS))
} }
object ProtectedBranchInfo{ object ProtectedBranchInfo{
def disabled(owner: String, repository: String): ProtectedBranchInfo = ProtectedBranchInfo(owner, repository, false, Nil, false) def disabled(owner: String, repository: String): ProtectedBranchInfo = ProtectedBranchInfo(owner, repository, false, Nil, false)

View File

@@ -1,6 +1,6 @@
package gitbucket.core.service package gitbucket.core.service
import gitbucket.core.model.{Account, Issue, PullRequest, WebHook} import gitbucket.core.model.{Account, Issue, PullRequest, WebHook, CommitStatus, CommitState}
import gitbucket.core.model.Profile._ import gitbucket.core.model.Profile._
import gitbucket.core.util.JGitUtil import gitbucket.core.util.JGitUtil
import profile.simple._ import profile.simple._

View File

@@ -20,7 +20,7 @@
case comment: gitbucket.core.model.IssueComment => Some(comment) case comment: gitbucket.core.model.IssueComment => Some(comment)
case other => None case other => None
}.exists(_.action == "merge")){ merged => }.exists(_.action == "merge")){ merged =>
@if(hasWritePermission && !issue.closed){ @if(!issue.closed){
<div class="check-conflict" style="display: none;"> <div class="check-conflict" style="display: none;">
<div class="box issue-comment-box" style="background-color: #fbeed5"> <div class="box issue-comment-box" style="background-color: #fbeed5">
<div class="box-content"class="issue-content" style="border: 1px solid #c09853; padding: 10px;"> <div class="box-content"class="issue-content" style="border: 1px solid #c09853; padding: 10px;">
@@ -40,9 +40,9 @@
<span class="small muted">You're all set-the <span class="label label-info monospace">@pullreq.requestBranch</span> branch can be safely deleted.</span> <span class="small muted">You're all set-the <span class="label label-info monospace">@pullreq.requestBranch</span> branch can be safely deleted.</span>
</div> </div>
</div> </div>
}
@issues.html.commentform(issue, !merged, hasWritePermission, repository)
} }
@issues.html.commentform(issue, !merged, hasWritePermission, repository)
}
</div> </div>
<div class="col-md-2"> <div class="col-md-2">
@issues.html.issueinfo(Some(issue), comments, issueLabels, collaborators, milestones, labels, hasWritePermission, repository) @issues.html.issueinfo(Some(issue), comments, issueLabels, collaborators, milestones, labels, hasWritePermission, repository)
@@ -55,10 +55,12 @@ $(function(){
$('#merge-pull-request').show(); $('#merge-pull-request').show();
}); });
@if(hasWritePermission){ var checkConflict = $('.check-conflict').show();
$('.check-conflict').show(); if(checkConflict.length){
$.get('@url(repository)/pull/@issue.issueId/mergeguide', function(data){ $('.check-conflict').html(data); }); $.get('@url(repository)/pull/@issue.issueId/mergeguide', function(data){ $('.check-conflict').html(data); });
}
@if(hasWritePermission){
$('.delete-branch').click(function(e){ $('.delete-branch').click(function(e){
var branchName = $(e.target).data('name'); var branchName = $(e.target).data('name');
return confirm('Are you sure you want to remove the ' + branchName + ' branch?'); return confirm('Are you sure you want to remove the ' + branchName + ' branch?');

View File

@@ -1,57 +1,66 @@
@(hasConflict: Boolean, @(status: gitbucket.core.controller.PullRequestsController.MergeStatus,
hasProblem: Boolean,
issue: gitbucket.core.model.Issue, issue: gitbucket.core.model.Issue,
pullreq: gitbucket.core.model.PullRequest, pullreq: gitbucket.core.model.PullRequest,
statuses: List[model.CommitStatus],
branchProtection: gitbucket.core.service.ProtectedBrancheService.ProtectedBranchInfo,
hasRequiredStatusProblem: Boolean,
originRepository: gitbucket.core.service.RepositoryService.RepositoryInfo, originRepository: gitbucket.core.service.RepositoryService.RepositoryInfo,
forkedRepository: gitbucket.core.service.RepositoryService.RepositoryInfo)(implicit context: gitbucket.core.controller.Context) forkedRepository: gitbucket.core.service.RepositoryService.RepositoryInfo)(implicit context: gitbucket.core.controller.Context)
@import gitbucket.core.service.SystemSettingsService @import gitbucket.core.service.SystemSettingsService
@import context._ @import context._
@import gitbucket.core.view.helpers._ @import gitbucket.core.view.helpers._
@import model.CommitState @import model.CommitState
<div class="box issue-comment-box" style="background-color: @if(hasProblem){ #fbeed5 }else{ #d8f5cd };"> <div class="box issue-comment-box" style="background-color: @if(status.hasProblem){ #fbeed5 }else{ #d8f5cd };">
<div class="box-content issue-content" style="border: 1px solid @if(hasProblem){ #c09853 }else{ #95c97e }; padding: 10px;"> <div class="box-content issue-content" style="border: 1px solid @if(status.hasProblem){ #c09853 }else{ #95c97e };padding:0">
<div id="merge-pull-request"> <div id="merge-pull-request">
@if(!statuses.isEmpty){ @if(!status.statuses.isEmpty){
<div class="build-statuses"> <div class="build-statuses">
@defining(statuses.groupBy(_.state)){ stateMap => @defining(status.commitStateSummary){ case (summaryState, summary) =>
@defining(CommitState.combine(stateMap.keySet)){ state => <div class="build-status-item-header">
<div class="build-status-item-header"> <a class="pull-right" id="toggle-all-checks"></a>
<a class="pull-right" id="toggle-all-checks"></a> <span class="build-status-icon text-@{summaryState.name}">@commitStateIcon(summaryState)</span>
<span class="build-status-icon text-@{state.name}">@commitStateIcon(state)</span> <strong class="text-@{summaryState.name}">@commitStateText(summaryState, pullreq.commitIdTo)</strong>
<strong class="text-@{state.name}">@commitStateText(state, pullreq.commitIdTo)</strong> <span class="text-@{summaryState.name}">— @summary checks</span>
<span class="text-@{state.name}">— @{stateMap.map{ case (keyState, states) => states.size+" "+keyState.name }.mkString(", ")} checks</span> </div>
</div>
<div class="build-statuses-list" style="@if(state==CommitState.SUCCESS){ display:none; }else{ }">
@statuses.map{ status =>
<div class="build-status-item">
<div class="pull-right">
@branchProtection.contexts.find(_==status.context).map{ url => <span class="label">Required</span> }
@status.targetUrl.map{ url => <a href="@url">Details</a> }
</div>
<span class="build-status-icon text-@{status.state.name}">@commitStateIcon(status.state)</span>
<strong>@status.context</strong>
@status.description.map{ desc => <span class="muted">— @desc</span> }
</div>
}
</div>
}
} }
<div class="build-statuses-list" style="@if(status.isAllSuccess){ display:none; }else{ }">
@status.statusesAndRequired.map{ case (status, required) =>
<div class="build-status-item">
<div class="pull-right">
@if(required){ <span class="label">Required</span> }
@status.targetUrl.map{ url => <a href="@url">Details</a> }
</div>
<span class="build-status-icon text-@{status.state.name}">@commitStateIcon(status.state)</span>
<strong>@status.context</strong>
@status.description.map{ desc => <span class="muted">— @desc</span> }
</div>
}
</div>
</div> </div>
} }
<div class="pull-right"> <div style="padding:15px">
<input type="button" class="btn @if(!hasProblem){ btn-success }else{ btn-default }" id="merge-pull-request-button" value="Merge pull request"@if(hasConflict){ disabled="true"}/> @if(status.hasConflict){
</div>
<div>
@if(hasConflict){
<div class="merge-indicator merge-indicator-alert"><span class="octicon octicon-alert"></span></div> <div class="merge-indicator merge-indicator-alert"><span class="octicon octicon-alert"></span></div>
<span class="strong">This branch has conflicts that must be resolved</span> <span class="strong">This branch has conflicts that must be resolved</span>
<div class="small"> <div class="small">
<a href="#" class="show-command-line">Use the command line</a> to resolve conflicts before continuing. @if(status.hasMergePermission){
<a href="#" class="show-command-line">Use the command line</a> to resolve conflicts before continuing.
} else {
Only those with write access to this repository can merge pull requests.
}
</div> </div>
} else { @if(hasRequiredStatusProblem) { } else { @if(status.branchIsOutOfDate){
@if(status.hasUpdatePermission){
<div class="pull-right">
<form method="POST" action="@url(originRepository)/pull/@pullreq.issueId/update_branch">
<input type="hidden" name="expected_head_oid" value="@pullreq.commitIdFrom">
<button class="btn"@if(!status.canUpdate){ disabled="true"} id="update-branch-button">Update branch</button>
</form>
</div>
}
<div class="merge-indicator merge-indicator-alert"><span class="octicon octicon-alert"></span></div>
<span class="strong">This branch is out-of-date with the base branch</span>
<div class="small">
Merge the latest changes from <code>@pullreq.branch</code> into this branch.
</div>
} else { @if(status.hasRequiredStatusProblem) {
<div class="merge-indicator merge-indicator-warning"><span class="octicon octicon-primitive-dot"></span></div> <div class="merge-indicator merge-indicator-warning"><span class="octicon octicon-primitive-dot"></span></div>
<span class="strong">Required statuses must pass before merging.</span> <span class="strong">Required statuses must pass before merging.</span>
<div class="small"> <div class="small">
@@ -59,63 +68,72 @@
</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>
<span class="strong">Merging can be performed automatically.</span> @if(status.hasMergePermission){
<div class="small"> <span class="strong">Merging can be performed automatically.</span>
Merging can be performed automatically. <div class="small">
</div> Merging can be performed automatically.
} } </div>
</div>
<div style="padding:15px;border-top:solid 1px #e5e5e5;background:#fafafa">
<input type="button" class="btn @if(!hasProblem){ btn-success }" id="merge-pull-request-button" value="Merge pull request"@if(hasConflict||hasRequiredStatusProblem){ disabled="true"}/>
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;">
<hr>
@if(hasConflict){
<span class="strong">Checkout via command line</span>
<p>
If you cannot merge a pull request automatically here, you have the option of checking
it out via command line to resolve conflicts and perform a manual merge.
</p>
} else { } else {
<span class="strong">Merging via command line</span> <span class="strong">This branch has no conflicts with the base branch.</span>
<p> <div class="small">
If you do not want to use the merge button or an automatic merge cannot be performed, Only those with write access to this repository can merge pull requests.
you can perform a manual merge on the command line. </div>
</p>
} }
@helper.html.copy("repository-url-copy", forkedRepository.httpUrl, true){ } } }
<div class="btn-group" data-toggle="buttons-radio"> </div>
<button class="btn btn-small active" type="button" id="repository-url-http">HTTP</button> @if(status.hasMergePermission){
@if(settings.ssh && loginAccount.isDefined){ <div style="padding:15px;border-top:solid 1px #e5e5e5;background:#fafafa">
<button class="btn btn-small" type="button" id="repository-url-ssh" style="border-radius: 0px;">SSH</button> <input type="button" class="btn @if(!status.hasProblem){ btn-success }" id="merge-pull-request-button" value="Merge pull request"@if(!status.canMerge){ disabled="true"}/>
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;">
<hr />
@if(status.hasConflict){
<span class="strong">Checkout via command line</span>
<p>
If you cannot merge a pull request automatically here, you have the option of checking
it out via command line to resolve conflicts and perform a manual merge.
</p>
} else {
<span class="strong">Merging via command line</span>
<p>
If you do not want to use the merge button or an automatic merge cannot be performed,
you can perform a manual merge on the command line.
</p>
}
@helper.html.copy("repository-url-copy", forkedRepository.httpUrl, true){
<div class="btn-group" data-toggle="buttons-radio">
<button class="btn btn-small active" type="button" id="repository-url-http">HTTP</button>
@if(settings.ssh && loginAccount.isDefined){
<button class="btn btn-small" type="button" id="repository-url-ssh" style="border-radius: 0px;">SSH</button>
}
</div>
<input type="text" style="width: 500px;" value="@forkedRepository.httpUrl" id="repository-url" readonly />
}
<div>
<p>
<span class="strong">Step 1:</span> From your project repository, check out a new branch and test the changes.
</p>
@defining(s"git checkout -b ${pullreq.requestUserName}-${pullreq.requestBranch} ${pullreq.branch}\n" +
s"git pull ${forkedRepository.httpUrl} ${pullreq.requestBranch}"){ command =>
@helper.html.copy("merge-command-copy-1", command){
<pre style="width: 600px; float: left; font-size: 12px; border-radius: 3px 0px 3px 3px;" id="merge-command">@Html(command)</pre>
}
} }
</div> </div>
<input type="text" style="width: 500px;" value="@forkedRepository.httpUrl" id="repository-url" readonly> <div>
} <p>
<div> <span class="strong">Step 2:</span> Merge the changes and update on the server.
<p> </p>
<span class="strong">Step 1:</span> From your project repository, check out a new branch and test the changes. @defining(s"git checkout ${pullreq.branch}\ngit merge --no-ff ${pullreq.requestUserName}-${pullreq.requestBranch}\n" +
</p> s"git push origin ${pullreq.branch}"){ command =>
@defining(s"git checkout -b ${pullreq.requestUserName}-${pullreq.requestBranch} ${pullreq.branch}\n" + @helper.html.copy("merge-command-copy-2", command){
s"git pull ${forkedRepository.httpUrl} ${pullreq.requestBranch}"){ command => <pre style="width: 600px; float: left; font-size: 12px; border-radius: 3px 0px 3px 3px;">@command</pre>
@helper.html.copy("merge-command-copy-1", command){ }
<pre style="width: 600px; float: left; font-size: 12px; border-radius: 3px 0px 3px 3px;" id="merge-command">@Html(command)</pre>
} }
} </div>
</div>
<div>
<p>
<span class="strong">Step 2:</span> Merge the changes and update on the server.
</p>
@defining(s"git checkout ${pullreq.branch}\ngit merge --no-ff ${pullreq.requestUserName}-${pullreq.requestBranch}\n" +
s"git push origin ${pullreq.branch}"){ command =>
@helper.html.copy("merge-command-copy-2", command){
<pre style="width: 600px; float: left; font-size: 12px; border-radius: 3px 0px 3px 3px;">@command</pre>
}
}
</div> </div>
</div> </div>
</div> }
</div> </div>
<div id="confirm-merge-form" style="display: none;"> <div id="confirm-merge-form" style="display: none;">
<form method="POST" action="@url(originRepository)/pull/@pullreq.issueId/merge"> <form method="POST" action="@url(originRepository)/pull/@pullreq.issueId/merge">

View File

@@ -8,7 +8,8 @@
dayByDayCommits: Seq[Seq[gitbucket.core.util.JGitUtil.CommitInfo]], dayByDayCommits: Seq[Seq[gitbucket.core.util.JGitUtil.CommitInfo]],
diffs: Seq[gitbucket.core.util.JGitUtil.DiffInfo], diffs: Seq[gitbucket.core.util.JGitUtil.DiffInfo],
hasWritePermission: Boolean, hasWritePermission: Boolean,
repository: gitbucket.core.service.RepositoryService.RepositoryInfo)(implicit context: gitbucket.core.controller.Context) repository: gitbucket.core.service.RepositoryService.RepositoryInfo,
flash: Map[String, String])(implicit context: gitbucket.core.controller.Context)
@import context._ @import context._
@import gitbucket.core.view.helpers._ @import gitbucket.core.view.helpers._
@import gitbucket.core.model._ @import gitbucket.core.model._
@@ -73,6 +74,12 @@
</ul> </ul>
<div class="tab-content fill-width pull-left"> <div class="tab-content fill-width pull-left">
<div class="tab-pane active" id="conversation"> <div class="tab-pane active" id="conversation">
@flash.get("error").map{ error =>
<div class="alert alert-error">@error</div>
}
@flash.get("info").map{ info =>
<div class="alert alert-info">@info</div>
}
@pulls.html.conversation(issue, pullreq, comments, issueLabels, collaborators, milestones, labels, hasWritePermission, repository) @pulls.html.conversation(issue, pullreq, comments, issueLabels, collaborators, milestones, labels, hasWritePermission, repository)
</div> </div>
<div class="tab-pane" id="commits"> <div class="tab-pane" id="commits">