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,
@@ -123,10 +123,6 @@ object ProtectedBrancheService {
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;">
@@ -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,34 +1,30 @@
@(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-@{state.name}">@commitStateIcon(state)</span> <span class="build-status-icon text-@{summaryState.name}">@commitStateIcon(summaryState)</span>
<strong class="text-@{state.name}">@commitStateText(state, pullreq.commitIdTo)</strong> <strong class="text-@{summaryState.name}">@commitStateText(summaryState, pullreq.commitIdTo)</strong>
<span class="text-@{state.name}">— @{stateMap.map{ case (keyState, states) => states.size+" "+keyState.name }.mkString(", ")} checks</span> <span class="text-@{summaryState.name}">— @summary checks</span>
</div> </div>
<div class="build-statuses-list" style="@if(state==CommitState.SUCCESS){ display:none; }else{ }"> }
@statuses.map{ status => <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="build-status-item">
<div class="pull-right"> <div class="pull-right">
@branchProtection.contexts.find(_==status.context).map{ url => <span class="label">Required</span> } @if(required){ <span class="label">Required</span> }
@status.targetUrl.map{ url => <a href="@url">Details</a> } @status.targetUrl.map{ url => <a href="@url">Details</a> }
</div> </div>
<span class="build-status-icon text-@{status.state.name}">@commitStateIcon(status.state)</span> <span class="build-status-icon text-@{status.state.name}">@commitStateIcon(status.state)</span>
@@ -37,21 +33,34 @@
</div> </div>
} }
</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">
@if(status.hasMergePermission){
<a href="#" class="show-command-line">Use the command line</a> to resolve conflicts before continuing. <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,18 +68,26 @@
</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(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">
Merging can be performed automatically. Merging can be performed automatically.
</div> </div>
} } } else {
<span class="strong">This branch has no conflicts with the base branch.</span>
<div class="small">
Only those with write access to this repository can merge pull requests.
</div> </div>
}
} } }
</div>
@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(!hasProblem){ btn-success }" id="merge-pull-request-button" value="Merge pull request"@if(hasConflict||hasRequiredStatusProblem){ disabled="true"}/> <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>. 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 />
@if(hasConflict){ @if(status.hasConflict){
<span class="strong">Checkout via command line</span> <span class="strong">Checkout via command line</span>
<p> <p>
If you cannot merge a pull request automatically here, you have the option of checking If you cannot merge a pull request automatically here, you have the option of checking
@@ -90,7 +107,7 @@
<button class="btn btn-small" type="button" id="repository-url-ssh" style="border-radius: 0px;">SSH</button> <button class="btn btn-small" type="button" id="repository-url-ssh" style="border-radius: 0px;">SSH</button>
} }
</div> </div>
<input type="text" style="width: 500px;" value="@forkedRepository.httpUrl" id="repository-url" readonly> <input type="text" style="width: 500px;" value="@forkedRepository.httpUrl" id="repository-url" readonly />
} }
<div> <div>
<p> <p>
@@ -116,6 +133,7 @@
</div> </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">