mirror of
https://github.com/go-gitea/gitea.git
synced 2026-10-03 04:31:52 +02:00
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:
1 parent
c1da790cee
commit
55a8e12d85
7 files changed
+132
-6
No files matched your search
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
},
|
||||
"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 @@
|
||||
.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
|
||||
@@ -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
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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}}
|
||||
|
||||
Reference in new issue
Block a user