* 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>tags/v1.21.12.1
| @@ -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 | |||||
| } | |||||
| @@ -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 | |||||
| } | |||||
| @@ -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) | |||||
| }, | |||||
| }} | }} | ||||
| } | } | ||||
| @@ -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 { | ||||
| @@ -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, | |||||
| Commit: commit, | |||||
| IsProtected: isProtected, | |||||
| Name: branchName, | |||||
| Commit: commit, | |||||
| IsProtected: isProtected, | |||||
| CommitsAhead: divergence.Ahead, | |||||
| CommitsBehind: divergence.Behind, | |||||
| } | } | ||||
| } | } | ||||
| @@ -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}} | ||||