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 2019-05-05 18:25:25 +02:00 committed by Lauris BH
parent c1da790cee
commit 55a8e12d85
7 changed files with 132 additions and 6 deletions

View File

@ -9,9 +9,11 @@ import (
"bytes" "bytes"
"container/list" "container/list"
"errors" "errors"
"fmt"
"os" "os"
"path" "path"
"path/filepath" "path/filepath"
"strconv"
"strings" "strings"
"time" "time"
@ -306,3 +308,40 @@ func GetLatestCommitTime(repoPath string) (time.Time, error) {
commitTime := strings.TrimSpace(stdout) commitTime := strings.TrimSpace(stdout)
return time.Parse(GitTimeLayout, commitTime) 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
}

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
}

View File

@ -223,6 +223,13 @@ func NewFuncMap() []template.FuncMap {
} }
return dict, nil 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)
},
}} }}
} }

File diff suppressed because one or more lines are too long

View File

@ -963,6 +963,42 @@
margin-top: 1px!important; 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 { &.commits {
.header { .header {
.search { .search {

View File

@ -14,6 +14,7 @@ import (
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/repofiles"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
) )
@ -28,6 +29,8 @@ type Branch struct {
IsProtected bool IsProtected bool
IsDeleted bool IsDeleted bool
DeletedBranch *models.DeletedBranch DeletedBranch *models.DeletedBranch
CommitsAhead int
CommitsBehind int
} }
// Branches render repository branch page // Branches render repository branch page
@ -168,16 +171,25 @@ func loadBranches(ctx *context.Context) []*Branch {
return nil 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 { if err != nil {
ctx.ServerError("IsProtectedBranch", err) ctx.ServerError("IsProtectedBranch", err)
return nil return nil
} }
divergence, divergenceError := repofiles.CountDivergingCommits(ctx.Repo.Repository, branchName)
if divergenceError != nil {
ctx.ServerError("CountDivergingCommits", divergenceError)
return nil
}
branches[i] = &Branch{ branches[i] = &Branch{
Name: rawBranches[i].Name, Name: branchName,
Commit: commit, Commit: commit,
IsProtected: isProtected, IsProtected: isProtected,
CommitsAhead: divergence.Ahead,
CommitsBehind: divergence.Behind,
} }
} }

View File

@ -26,7 +26,8 @@
<table class="ui very basic striped fixed table single line"> <table class="ui very basic striped fixed table single line">
<thead> <thead>
<tr> <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)}} {{if and $.IsWriter (not $.IsMirror)}}
<th class="one wide right aligned">{{.i18n.Tr "repo.branch.delete_head"}}</th> <th class="one wide right aligned">{{.i18n.Tr "repo.branch.delete_head"}}</th>
{{end}} {{end}}
@ -45,6 +46,18 @@
<p class="time">{{$.i18n.Tr "org.repo_updated"}} {{TimeSince .Commit.Committer.When $.i18n.Lang}}</p> <p class="time">{{$.i18n.Tr "org.repo_updated"}} {{TimeSince .Commit.Committer.When $.i18n.Lang}}</p>
</td> </td>
{{end}} {{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)}} {{if and $.IsWriter (not $.IsMirror)}}
<td class="right aligned"> <td class="right aligned">
{{if .IsProtected}} {{if .IsProtected}}