Number of commits ahead/behind in branch overview (#6695)

* Call Git API to determine divergence of a branch and its base branch

Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com>

* Show commit divergance in branch list

Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com>

* Adds missing comment

Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com>

* Adds test for diverging commits

Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com>

* Try comparing commits instead of branches

Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com>

* Removes test as CI can't run it

Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com>

* Adjusts signature of percentage function to allow providing multiple integers as numerator

Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com>

* Moves CountDivergingCommits function into repofiles module

Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com>
This commit is contained in:
Mario Lubenka authored and Lauris BH committed 2019-05-05 19:25:25 +03:00
1 parent c1da790cee
commit 55a8e12d85
7 files changed
+132 -6

No files matched your search

+39
View File
@@ -9,9 +9,11 @@ import (
"bytes"
"container/list"
"errors"
"fmt"
"os"
"path"
"path/filepath"
"strconv"
"strings"
"time"
@@ -306,3 +308,40 @@ func GetLatestCommitTime(repoPath string) (time.Time, error) {
commitTime := strings.TrimSpace(stdout)
return time.Parse(GitTimeLayout, commitTime)
}
// DivergeObject represents commit count diverging commits
type DivergeObject struct {
Ahead int
Behind int
}
func checkDivergence(repoPath string, baseBranch string, targetBranch string) (int, error) {
branches := fmt.Sprintf("%s..%s", baseBranch, targetBranch)
cmd := NewCommand("rev-list", "--count", branches)
stdout, err := cmd.RunInDir(repoPath)
if err != nil {
return -1, err
}
outInteger, errInteger := strconv.Atoi(strings.Trim(stdout, "\n"))
if errInteger != nil {
return -1, errInteger
}
return outInteger, nil
}
// GetDivergingCommits returns the number of commits a targetBranch is ahead or behind a baseBranch
func GetDivergingCommits(repoPath string, baseBranch string, targetBranch string) (DivergeObject, error) {
// $(git rev-list --count master..feature) commits ahead of master
ahead, errorAhead := checkDivergence(repoPath, baseBranch, targetBranch)
if errorAhead != nil {
return DivergeObject{}, errorAhead
}
// $(git rev-list --count feature..master) commits behind master
behind, errorBehind := checkDivergence(repoPath, targetBranch, baseBranch)
if errorBehind != nil {
return DivergeObject{}, errorBehind
}
return DivergeObject{ahead, behind}, nil
}
+19
View File
@@ -0,0 +1,19 @@
// Copyright 2019 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package repofiles
import (
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/git"
)
// CountDivergingCommits determines how many commits a branch is ahead or behind the repository's base branch
func CountDivergingCommits(repo *models.Repository, branch string) (*git.DivergeObject, error) {
divergence, err := git.GetDivergingCommits(repo.RepoPath(), repo.DefaultBranch, branch)
if err != nil {
return nil, err
}
return &divergence, nil
}
+7
View File
@@ -223,6 +223,13 @@ func NewFuncMap() []template.FuncMap {
}
return dict, nil
},
"percentage": func(n int, values ...int) float32 {
var sum = 0
for i := 0; i < len(values); i++ {
sum += values[i]
}
return float32(n) * 100 / float32(sum)
},
}}
}
+1 -1
View File
@@ -1 +1 @@
.tribute-container{box-shadow:0 1px 3px 1px #c7c7c7}.tribute-container ul{background:#fff}.tribute-container li{padding:8px 12px;border-bottom:1px solid #dcdcdc}.tribute-container li img{display:inline-block;vertical-align:middle;width:28px;height:28px;margin-right:5px}.tribute-container li span.fullname{font-weight:400;font-size:.8rem;margin-left:3px}.tribute-container li.highlight,.tribute-container li:hover{background:#2185D0;color:#fff}.emoji{width:1.5em;height:1.5em;display:inline-block;background-size:contain}.ui.label .emoji{height:1.2em!important}@font-face{font-family:Lato;src:url(../vendor/assets/lato-fonts/lato-regular.eot);src:url(../vendor/assets/lato-fonts/lato-regular.eot?#iefix) format('embedded-opentype'),url(../vendor/assets/lato-fonts/lato-regular.woff2) format('woff2'),url(../vendor/assets/lato-fonts/lato-regular.woff) format('woff'),url(../vendor/assets/lato-fonts/lato-regular.ttf) format('truetype');font-weight:400;font-style:normal}@font-face{font-family:Lato;src:url(../vendor/assets/lato-fonts/lato-italic.eot);src:url(../vendor/assets/lato-fonts/lato-italic.eot?#iefix) format('embedded-opentype'),url(../vendor/assets/lato-fonts/lato-italic.woff2) format('woff2'),url(../vendor/assets/lato-fonts/lato-italic.woff) format('woff'),url(../vendor/assets/lato-fonts/lato-italic.ttf) format('truetype');font-weight:400;font-style:italic}@font-face{font-family:Lato;src:url(../vendor/assets/lato-fonts/lato-bold.eot);src:url(../vendor/assets/lato-fonts/lato-bold.eot?#iefix) format('embedded-opentype'),url(../vendor/assets/lato-fonts/lato-bold.woff2) format('woff2'),url(../vendor/assets/lato-fonts/lato-bold.woff) format('woff'),url(../vendor/assets/lato-fonts/lato-bold.ttf) format('truetype');font-weight:700;font-style:normal}@font-face{font-family:Lato;src:url(../vendor/assets/lato-fonts/lato-bolditalic.eot);src:url(../vendor/assets/lato-fonts/lato-bolditalic.eot?#iefix) format('embedded-opentype'),url(../vendor/assets/lato-fonts/lato-bolditalic.woff2) format('woff2'),url(../vendor/assets/lato-fonts/lato-bolditalic.woff) format('woff'),url(../vendor/assets/lato-fonts/lato-bolditalic.ttf) format('truetype');font-weight:700;font-style:italic}@font-face{font-family:'Yu Gothic';src:local('Yu Gothic Medium');font-weight:400}@font-face{font-family:'Yu Gothic';src:local('Yu Gothic Bold');font-weight:700}textarea{font-family:-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,sans-serif}.markdown:not(code){font-family:-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,sans-serif}h1,h2,h3,h4,h5{font-family:Lato,-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,sans-serif}.home .hero h1,.home .hero h2{font-family:'PT Sans Narrow',Lato,-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,sans-serif}.ui.accordion .title:not(.ui),.ui.button,.ui.card>.content>.header.ui.card>.content>.header,.ui.category.search>.results .category>.name,.ui.form input:not([type]),.ui.form input[type=date],.ui.form input[type=datetime-local],.ui.form input[type=email],.ui.form input[type=file],.ui.form input[type=number],.ui.form input[type=password],.ui.form input[type=search],.ui.form input[type=tel],.ui.form input[type=text],.ui.form input[type=time],.ui.form input[type=url],.ui.header,.ui.input input,.ui.input>input,.ui.items>.item>.content>.header,.ui.language>.menu>.item,.ui.list .list>.item .header,.ui.list>.item .header,.ui.menu,.ui.message .header,.ui.modal>.header,.ui.popup>.header,.ui.search>.results .result .title,.ui.search>.results>.message .header,.ui.statistic>.label,.ui.statistic>.value,.ui.statistics .statistic>.label,.ui.statistics .statistic>.value,.ui.steps .step .title,.ui.text.container,body{font-family:Lato,-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,sans-serif}body{background-color:#fff;overflow-y:auto;-webkit-font-smoothing:antialiased;display:flex;flex-direction:column}:lang(ja) textarea{font-family:-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,'Hiragino Kaku Gothic ProN','Yu Gothic','Source Han Sans JP','Noto Sans CJK JP','Droid Sans Japanese',Meiryo,'MS PGothic',sans-serif}:lang(ja) .markdown:not(code){font-family:-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,'Hiragino Kaku Gothic ProN','Yu Gothic','Source Han Sans JP','Noto Sans CJK JP','Droid Sans Japanese',Meiryo,'MS PGothic',sans-serif}:lang(ja) h1,:lang(ja) h2,:lang(ja) h3,:lang(ja) h4,:lang(ja) h5{font-family:Lato,-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,'Hiragino Kaku Gothic ProN','Yu Gothic','Source Han Sans JP','Noto Sans CJK JP','Droid Sans Japanese',Meiryo,'MS PGothic',sans-serif}:lang(ja) .home .hero h1,:lang(ja) .home .hero h2{font-family:'PT Sans Narrow',Lato,-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,'Hiragino Kaku Gothic ProN','Yu Gothic','Source Han Sans JP','Noto Sans CJLine truncated
.tribute-container{box-shadow:0 1px 3px 1px #c7c7c7}.tribute-container ul{background:#fff}.tribute-container li{padding:8px 12px;border-bottom:1px solid #dcdcdc}.tribute-container li img{display:inline-block;vertical-align:middle;width:28px;height:28px;margin-right:5px}.tribute-container li span.fullname{font-weight:400;font-size:.8rem;margin-left:3px}.tribute-container li.highlight,.tribute-container li:hover{background:#2185D0;color:#fff}.emoji{width:1.5em;height:1.5em;display:inline-block;background-size:contain}.ui.label .emoji{height:1.2em!important}@font-face{font-family:Lato;src:url(../vendor/assets/lato-fonts/lato-regular.eot);src:url(../vendor/assets/lato-fonts/lato-regular.eot?#iefix) format('embedded-opentype'),url(../vendor/assets/lato-fonts/lato-regular.woff2) format('woff2'),url(../vendor/assets/lato-fonts/lato-regular.woff) format('woff'),url(../vendor/assets/lato-fonts/lato-regular.ttf) format('truetype');font-weight:400;font-style:normal}@font-face{font-family:Lato;src:url(../vendor/assets/lato-fonts/lato-italic.eot);src:url(../vendor/assets/lato-fonts/lato-italic.eot?#iefix) format('embedded-opentype'),url(../vendor/assets/lato-fonts/lato-italic.woff2) format('woff2'),url(../vendor/assets/lato-fonts/lato-italic.woff) format('woff'),url(../vendor/assets/lato-fonts/lato-italic.ttf) format('truetype');font-weight:400;font-style:italic}@font-face{font-family:Lato;src:url(../vendor/assets/lato-fonts/lato-bold.eot);src:url(../vendor/assets/lato-fonts/lato-bold.eot?#iefix) format('embedded-opentype'),url(../vendor/assets/lato-fonts/lato-bold.woff2) format('woff2'),url(../vendor/assets/lato-fonts/lato-bold.woff) format('woff'),url(../vendor/assets/lato-fonts/lato-bold.ttf) format('truetype');font-weight:700;font-style:normal}@font-face{font-family:Lato;src:url(../vendor/assets/lato-fonts/lato-bolditalic.eot);src:url(../vendor/assets/lato-fonts/lato-bolditalic.eot?#iefix) format('embedded-opentype'),url(../vendor/assets/lato-fonts/lato-bolditalic.woff2) format('woff2'),url(../vendor/assets/lato-fonts/lato-bolditalic.woff) format('woff'),url(../vendor/assets/lato-fonts/lato-bolditalic.ttf) format('truetype');font-weight:700;font-style:italic}@font-face{font-family:'Yu Gothic';src:local('Yu Gothic Medium');font-weight:400}@font-face{font-family:'Yu Gothic';src:local('Yu Gothic Bold');font-weight:700}textarea{font-family:-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,sans-serif}.markdown:not(code){font-family:-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,sans-serif}h1,h2,h3,h4,h5{font-family:Lato,-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,sans-serif}.home .hero h1,.home .hero h2{font-family:'PT Sans Narrow',Lato,-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,sans-serif}.ui.accordion .title:not(.ui),.ui.button,.ui.card>.content>.header.ui.card>.content>.header,.ui.category.search>.results .category>.name,.ui.form input:not([type]),.ui.form input[type=date],.ui.form input[type=datetime-local],.ui.form input[type=email],.ui.form input[type=file],.ui.form input[type=number],.ui.form input[type=password],.ui.form input[type=search],.ui.form input[type=tel],.ui.form input[type=text],.ui.form input[type=time],.ui.form input[type=url],.ui.header,.ui.input input,.ui.input>input,.ui.items>.item>.content>.header,.ui.language>.menu>.item,.ui.list .list>.item .header,.ui.list>.item .header,.ui.menu,.ui.message .header,.ui.modal>.header,.ui.popup>.header,.ui.search>.results .result .title,.ui.search>.results>.message .header,.ui.statistic>.label,.ui.statistic>.value,.ui.statistics .statistic>.label,.ui.statistics .statistic>.value,.ui.steps .step .title,.ui.text.container,body{font-family:Lato,-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,sans-serif}body{background-color:#fff;overflow-y:auto;-webkit-font-smoothing:antialiased;display:flex;flex-direction:column}:lang(ja) textarea{font-family:-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,'Hiragino Kaku Gothic ProN','Yu Gothic','Source Han Sans JP','Noto Sans CJK JP','Droid Sans Japanese',Meiryo,'MS PGothic',sans-serif}:lang(ja) .markdown:not(code){font-family:-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,'Hiragino Kaku Gothic ProN','Yu Gothic','Source Han Sans JP','Noto Sans CJK JP','Droid Sans Japanese',Meiryo,'MS PGothic',sans-serif}:lang(ja) h1,:lang(ja) h2,:lang(ja) h3,:lang(ja) h4,:lang(ja) h5{font-family:Lato,-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,'Hiragino Kaku Gothic ProN','Yu Gothic','Source Han Sans JP','Noto Sans CJK JP','Droid Sans Japanese',Meiryo,'MS PGothic',sans-serif}:lang(ja) .home .hero h1,:lang(ja) .home .hero h2{font-family:'PT Sans Narrow',Lato,-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,'Hiragino Kaku Gothic ProN','Yu Gothic','Source Han Sans JP','Noto Sans CJLine truncated
+36
View File
@@ -963,6 +963,42 @@
margin-top: 1px!important;
}
&.branches {
.commit-divergence {
.bar-group {
position: relative;
float: left;
padding-bottom: 6px;
width: 90px;
&:last-child {
border-left: 1px solid #b4b4b4;
}
}
.count {
margin: 0 3px;
&.count-ahead {
text-align: left;
}
&.count-behind {
text-align: right;
}
}
.bar {
height: 4px;
position: absolute;
background-color: #d4d4d5;
&.bar-behind {
right: 0;
}
&.bar-ahead {
left: 0;
}
}
}
}
&.commits {
.header {
.search {
+16 -4
View File
@@ -14,6 +14,7 @@ import (
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/repofiles"
"code.gitea.io/gitea/modules/util"
)
@@ -28,6 +29,8 @@ type Branch struct {
IsProtected bool
IsDeleted bool
DeletedBranch *models.DeletedBranch
CommitsAhead int
CommitsBehind int
}
// Branches render repository branch page
@@ -168,16 +171,25 @@ func loadBranches(ctx *context.Context) []*Branch {
return nil
}
isProtected, err := ctx.Repo.Repository.IsProtectedBranch(rawBranches[i].Name, ctx.User)
branchName := rawBranches[i].Name
isProtected, err := ctx.Repo.Repository.IsProtectedBranch(branchName, ctx.User)
if err != nil {
ctx.ServerError("IsProtectedBranch", err)
return nil
}
divergence, divergenceError := repofiles.CountDivergingCommits(ctx.Repo.Repository, branchName)
if divergenceError != nil {
ctx.ServerError("CountDivergingCommits", divergenceError)
return nil
}
branches[i] = &Branch{
Name: rawBranches[i].Name,
Commit: commit,
IsProtected: isProtected,
Name: branchName,
Commit: commit,
IsProtected: isProtected,
CommitsAhead: divergence.Ahead,
CommitsBehind: divergence.Behind,
}
}
+14 -1
View File
@@ -26,7 +26,8 @@
<table class="ui very basic striped fixed table single line">
<thead>
<tr>
<th class="nine wide">{{.i18n.Tr "repo.branch.name"}}</th>
<th class="seven wide">{{.i18n.Tr "repo.branch.name"}}</th>
<th class="two wide"></th>
{{if and $.IsWriter (not $.IsMirror)}}
<th class="one wide right aligned">{{.i18n.Tr "repo.branch.delete_head"}}</th>
{{end}}
@@ -45,6 +46,18 @@
<p class="time">{{$.i18n.Tr "org.repo_updated"}} {{TimeSince .Commit.Committer.When $.i18n.Lang}}</p>
</td>
{{end}}
<td class="ui">
<div class="commit-divergence">
<div class="bar-group">
<div class="count count-behind">{{.CommitsBehind}}</div>
<div class="bar bar-behind" style="width: {{percentage .CommitsBehind .CommitsBehind .CommitsAhead}}%"></div>
</div>
<div class="bar-group">
<div class="count count-ahead">{{.CommitsAhead}}</div>
<div class="bar bar-ahead" style="width: {{percentage .CommitsAhead .CommitsBehind .CommitsAhead}}%"></div>
</div>
</div>
</td>
{{if and $.IsWriter (not $.IsMirror)}}
<td class="right aligned">
{{if .IsProtected}}