diff --git a/src/main/scala/app/RepositoryViewerController.scala b/src/main/scala/app/RepositoryViewerController.scala index 57b1027..8faf1ad 100644 --- a/src/main/scala/app/RepositoryViewerController.scala +++ b/src/main/scala/app/RepositoryViewerController.scala @@ -322,14 +322,11 @@ * Displays branches. */ get("/:owner/:repository/branches")(referrersOnly { repository => - using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git => - // retrieve latest update date of each branch - val branchInfo = repository.branchList.map { branchName => - val revCommit = git.log.add(git.getRepository.resolve(branchName)).setMaxCount(1).call.iterator.next - (branchName, revCommit.getCommitterIdent.getWhen) - } - repo.html.branches(branchInfo, hasWritePermission(repository.owner, repository.name, context.loginAccount), repository) - } + val branches = JGitUtil.getBranches(repository.owner, repository.name, repository.repository.defaultBranch) + .sortBy(br => (br.mergeInfo.isEmpty, br.commitTime)) + .map(br => br -> getPullRequestByRequestCommit(repository.owner, repository.name, repository.repository.defaultBranch, br.name, br.commitId)) + .reverse + repo.html.branches(branches, hasWritePermission(repository.owner, repository.name, context.loginAccount), repository) }) /** diff --git a/src/main/scala/service/PullRequestService.scala b/src/main/scala/service/PullRequestService.scala index 68121f9..1f09d7f 100644 --- a/src/main/scala/service/PullRequestService.scala +++ b/src/main/scala/service/PullRequestService.scala @@ -94,6 +94,26 @@ updateCommitId(pullreq.userName, pullreq.repositoryName, pullreq.issueId, commitIdTo, commitIdFrom) } } + + def getPullRequestByRequestCommit(userName: String, repositoryName: String, toBranch:String, fromBranch: String, commitId: String) + (implicit s: Session): Option[(PullRequest, Issue)] = { + if(toBranch == fromBranch){ + None + } else { + PullRequests + .innerJoin(Issues).on { (t1, t2) => t1.byPrimaryKey(t2.userName, t2.repositoryName, t2.issueId) } + .filter { case (t1, t2) => + (t1.userName === userName.bind) && + (t1.repositoryName === repositoryName.bind) && + (t1.branch === toBranch.bind) && + (t1.requestUserName === userName.bind) && + (t1.requestRepositoryName === repositoryName.bind) && + (t1.requestBranch === fromBranch.bind) && + (t1.commitIdTo === commitId.bind) + } + .firstOption + } + } } object PullRequestService { diff --git a/src/main/scala/util/JGitUtil.scala b/src/main/scala/util/JGitUtil.scala index 0f76f1e..5c7be3f 100644 --- a/src/main/scala/util/JGitUtil.scala +++ b/src/main/scala/util/JGitUtil.scala @@ -134,6 +134,10 @@ */ case class SubmoduleInfo(name: String, path: String, url: String) + case class BranchMergeInfo(ahead: Int, behind: Int, isMerged: Boolean) + + case class BranchInfo(name: String, committerName: String, commitTime: Date, committerEmailAddress:String, mergeInfo: Option[BranchMergeInfo], commitId: String) + /** * Returns RevCommit from the commit or tag id. * @@ -705,4 +709,43 @@ return git.log.add(startCommit).addPath(path).setMaxCount(1).call.iterator.next } + def getBranches(owner: String, name: String, defaultBranch: String): Seq[BranchInfo] = { + using(Git.open(getRepositoryDir(owner, name))){ git => + val repo = git.getRepository + val defaultObject = repo.resolve(defaultBranch) + git.branchList.call.asScala.map { ref => + val walk = new RevWalk(repo) + try{ + val defaultCommit = walk.parseCommit(defaultObject) + val branchName = ref.getName.stripPrefix("refs/heads/") + val branchCommit = if(branchName == defaultBranch){ + defaultCommit + }else{ + walk.parseCommit(ref.getObjectId) + } + val when = branchCommit.getCommitterIdent.getWhen + val committer = branchCommit.getCommitterIdent.getName + val committerEmail = branchCommit.getCommitterIdent.getEmailAddress + val mergeInfo = if(branchName==defaultBranch){ + None + }else{ + walk.reset() + walk.setRevFilter( RevFilter.MERGE_BASE ) + walk.markStart(branchCommit) + walk.markStart(defaultCommit) + val mergeBase = walk.next() + walk.reset() + walk.setRevFilter(RevFilter.ALL) + Some(BranchMergeInfo( + ahead = RevWalkUtils.count(walk, branchCommit, mergeBase), + behind = RevWalkUtils.count(walk, defaultCommit, mergeBase), + isMerged = walk.isMergedInto(branchCommit, defaultCommit))) + } + BranchInfo(branchName, committer, when, committerEmail, mergeInfo, ref.getObjectId.name) + } finally { + walk.dispose(); + } + } + } + } } diff --git a/src/main/twirl/pulls/compare.scala.html b/src/main/twirl/pulls/compare.scala.html index b3f2483..b606715 100644 --- a/src/main/twirl/pulls/compare.scala.html +++ b/src/main/twirl/pulls/compare.scala.html @@ -126,6 +126,9 @@ $(this).hide(); $('#pull-request-form').show(); }); + if(location.search.substr(1).split("&").indexOf("expand=1")!=-1){ + $('#show-form').click(); + } @if(hasWritePermission){ function checkConflict(from, to, noConflictHandler, hasConflictHandler){ diff --git a/src/main/twirl/repo/branches.scala.html b/src/main/twirl/repo/branches.scala.html index 9857684..7f152f4 100644 --- a/src/main/twirl/repo/branches.scala.html +++ b/src/main/twirl/repo/branches.scala.html @@ -1,4 +1,4 @@ -@(branchInfo: Seq[(String, java.util.Date)], +@(branchInfo: Seq[(util.JGitUtil.BranchInfo, Option[(model.PullRequest, model.Issue)])], hasWritePermission: Boolean, repository: service.RepositoryService.RepositoryInfo)(implicit context: app.Context) @import context._ @@ -6,35 +6,67 @@ @html.main(s"${repository.owner}/${repository.name}", Some(repository)) { @html.menu("code", repository){

Branches

- - - - - - - - @branchInfo.map { case (branchName, latestUpdateDate) => +
BranchLast updateCompareDownload
+ - + + + + @branchInfo.map { case (branch, prs) => + - - - + }
- @branchName - @if(hasWritePermission && repository.repository.defaultBranch != branchName){ - Delete branch + All branches
+
+ @branch.mergeInfo.map{ info => + @prs.map{ case (pull, issue) => + #@issue.issueId + @if(issue.closed) { + @if(info.isMerged){ + Merged + }else{ + Closed + } + } else { + Open + } + }.getOrElse{ + @if(context.loginAccount.isDefined){ + New Pull Request + }else{ + Compare + } + } + @if(hasWritePermission){ + @if(prs.map(!_._2.closed).getOrElse(false)){ + + }else{ + + } + } + } +
+
+ @branch.name + + Updated @helper.html.datetimeago(branch.commitTime, false) + by @user(branch.committerName, branch.committerEmailAddress, "muted-link") + + +
+
+ @if(branch.mergeInfo.isEmpty){ + Default + }else{ + @branch.mergeInfo.map{ info => +
+
+
@info.ahead
+
@info.behind
+
+
+
+ } } -
- @helper.html.datetimeago(latestUpdateDate, false) - - @if(repository.repository.defaultBranch == branchName){ - Base branch - } else { - to @{repository.repository.defaultBranch} - } - - ZIP - TAR.GZ -
@@ -46,5 +78,6 @@ var branchName = $(e.target).data('name'); return confirm('Are you sure you want to remove the ' + branchName + ' branch?'); }); + $('*[data-toggle=tooltip]').tooltip().css("white-space","nowrap"); }); - \ No newline at end of file + diff --git a/src/main/webapp/assets/common/css/gitbucket.css b/src/main/webapp/assets/common/css/gitbucket.css index ff180c8..6888dfd 100644 --- a/src/main/webapp/assets/common/css/gitbucket.css +++ b/src/main/webapp/assets/common/css/gitbucket.css @@ -472,6 +472,62 @@ margin-left: 40px; } +.branches .muted-link{ + color: #777; +} +.branches .muted-link:hover{ + color: #4183c4; +} +.branches .branch-details{ + display: inline-block; + width: 490px; + margin-right: 10px; +} +.branches .branch-name{ + color: #4183c4; + display: inline-block; + padding: 2px 6px; + font: 12px Consolas, "Liberation Mono", Menlo, Courier, monospace; + background-color: rgba(209,227,237,0.5); + border-radius: 3px; +} +.branches .branch-meta{ + color: #aaa; + font-size: 12px; + line-height: 20px; +} +.branches .branch-action{ + display: inline-block; + float: right; + position: relative; + top: -3px; + height: 20px; +} +.branches .branch-a-b-count{ + display: inline-block; + vertical-align: middle; + width: 181px; + text-align: center; + color: rgba(0,0,0,0.5); +} +.branches .a-b-count-widget{ + font-size: 10px; +} +.branches .a-b-count-widget .count-half{ + width:90px; + position: relative; + text-align: right; + float: left; +} +.branches .a-b-count-widget .count-half:last-child { + text-align: left; + border-left: 1px solid #bbb; +} +.branches .a-b-count-widget .count-half .count-value{ + padding: 0 3px; +} + + /****************************************************************************/ /* Activity */ /****************************************************************************/