Skip to content

Commit c05a8ab

Browse files
zeripathsilverwindtechknowlogicklafriks
authored
Multiple GitGraph improvements: Exclude PR heads, Add branch/PR links, Show only certain branches, (#12766)
* Multiple GitGraph improvements. Add backend support for excluding PRs, selecting branches and files. Fix #10327 Signed-off-by: Andrew Thornton <[email protected]> * as per @silverwind Signed-off-by: Andrew Thornton <[email protected]> * as per @silverwind Signed-off-by: Andrew Thornton <[email protected]> * Only show refs in dropdown we display on the graph Signed-off-by: Andrew Thornton <[email protected]> * as per @silverwind Signed-off-by: Andrew Thornton <[email protected]> * use flexbox for ui header Signed-off-by: Andrew Thornton <[email protected]> * Move Hide Pull Request button to the dropdown Signed-off-by: Andrew Thornton <[email protected]> * Add SHA and user pictures Signed-off-by: Andrew Thornton <[email protected]> * fix test Signed-off-by: Andrew Thornton <[email protected]> * fix test 2 Signed-off-by: Andrew Thornton <[email protected]> * fixes * async * more tweaks * use tabs in tmpl Signed-off-by: Andrew Thornton <[email protected]> * remove commented thing Signed-off-by: Andrew Thornton <[email protected]> * fix linting Signed-off-by: Andrew Thornton <[email protected]> * Update web_src/js/features/gitgraph.js Co-authored-by: silverwind <[email protected]> * graph tweaks * more tweaks * add title Signed-off-by: Andrew Thornton <[email protected]> * fix loading indicator z-index and position Co-authored-by: silverwind <[email protected]> Co-authored-by: techknowlogick <[email protected]> Co-authored-by: Lauris BH <[email protected]>
1 parent d4e0b28 commit c05a8ab

File tree

21 files changed

+588
-126
lines changed

21 files changed

+588
-126
lines changed

modules/context/pagination.go

+6
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,12 @@ func (p *Pagination) AddParam(ctx *Context, paramKey string, ctxKey string) {
3737
p.urlParams = append(p.urlParams, urlParam)
3838
}
3939

40+
// AddParamString adds a string parameter directly
41+
func (p *Pagination) AddParamString(key string, value string) {
42+
urlParam := fmt.Sprintf("%s=%v", url.QueryEscape(key), url.QueryEscape(value))
43+
p.urlParams = append(p.urlParams, urlParam)
44+
}
45+
4046
// GetParams returns the configured URL params
4147
func (p *Pagination) GetParams() template.URL {
4248
return template.URL(strings.Join(p.urlParams, "&"))

modules/context/repo.go

+12
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,18 @@ func (r *Repository) GetCommitsCount() (int64, error) {
171171
})
172172
}
173173

174+
// GetCommitGraphsCount returns cached commit count for current view
175+
func (r *Repository) GetCommitGraphsCount(hidePRRefs bool, branches []string, files []string) (int64, error) {
176+
cacheKey := fmt.Sprintf("commits-count-%d-graph-%t-%s-%s", r.Repository.ID, hidePRRefs, branches, files)
177+
178+
return cache.GetInt64(cacheKey, func() (int64, error) {
179+
if len(branches) == 0 {
180+
return git.AllCommitsCount(r.Repository.RepoPath(), hidePRRefs, files...)
181+
}
182+
return git.CommitsCountFiles(r.Repository.RepoPath(), branches, files)
183+
})
184+
}
185+
174186
// BranchNameSubURL sub-URL for the BranchName field
175187
func (r *Repository) BranchNameSubURL() string {
176188
switch {

modules/git/commit.go

+17-5
Original file line numberDiff line numberDiff line change
@@ -262,16 +262,28 @@ func CommitChangesWithArgs(repoPath string, args []string, opts CommitChangesOpt
262262
}
263263

264264
// AllCommitsCount returns count of all commits in repository
265-
func AllCommitsCount(repoPath string) (int64, error) {
266-
stdout, err := NewCommand("rev-list", "--all", "--count").RunInDir(repoPath)
265+
func AllCommitsCount(repoPath string, hidePRRefs bool, files ...string) (int64, error) {
266+
args := []string{"--all", "--count"}
267+
if hidePRRefs {
268+
args = append([]string{"--exclude=refs/pull/*"}, args...)
269+
}
270+
cmd := NewCommand("rev-list")
271+
cmd.AddArguments(args...)
272+
if len(files) > 0 {
273+
cmd.AddArguments("--")
274+
cmd.AddArguments(files...)
275+
}
276+
277+
stdout, err := cmd.RunInDir(repoPath)
267278
if err != nil {
268279
return 0, err
269280
}
270281

271282
return strconv.ParseInt(strings.TrimSpace(stdout), 10, 64)
272283
}
273284

274-
func commitsCount(repoPath string, revision, relpath []string) (int64, error) {
285+
// CommitsCountFiles returns number of total commits of until given revision.
286+
func CommitsCountFiles(repoPath string, revision, relpath []string) (int64, error) {
275287
cmd := NewCommand("rev-list", "--count")
276288
cmd.AddArguments(revision...)
277289
if len(relpath) > 0 {
@@ -288,8 +300,8 @@ func commitsCount(repoPath string, revision, relpath []string) (int64, error) {
288300
}
289301

290302
// CommitsCount returns number of total commits of until given revision.
291-
func CommitsCount(repoPath, revision string) (int64, error) {
292-
return commitsCount(repoPath, []string{revision}, []string{})
303+
func CommitsCount(repoPath string, revision ...string) (int64, error) {
304+
return CommitsCountFiles(repoPath, revision, []string{})
293305
}
294306

295307
// CommitsCount returns number of total commits of until current revision.

modules/git/ref.go

+43
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
package git
66

7+
import "strings"
8+
79
// Reference represents a Git ref.
810
type Reference struct {
911
Name string
@@ -16,3 +18,44 @@ type Reference struct {
1618
func (ref *Reference) Commit() (*Commit, error) {
1719
return ref.repo.getCommit(ref.Object)
1820
}
21+
22+
// ShortName returns the short name of the reference
23+
func (ref *Reference) ShortName() string {
24+
if ref == nil {
25+
return ""
26+
}
27+
if strings.HasPrefix(ref.Name, "refs/heads/") {
28+
return ref.Name[11:]
29+
}
30+
if strings.HasPrefix(ref.Name, "refs/tags/") {
31+
return ref.Name[10:]
32+
}
33+
if strings.HasPrefix(ref.Name, "refs/remotes/") {
34+
return ref.Name[13:]
35+
}
36+
if strings.HasPrefix(ref.Name, "refs/pull/") && strings.IndexByte(ref.Name[10:], '/') > -1 {
37+
return ref.Name[10 : strings.IndexByte(ref.Name[10:], '/')+10]
38+
}
39+
40+
return ref.Name
41+
}
42+
43+
// RefGroup returns the group type of the reference
44+
func (ref *Reference) RefGroup() string {
45+
if ref == nil {
46+
return ""
47+
}
48+
if strings.HasPrefix(ref.Name, "refs/heads/") {
49+
return "heads"
50+
}
51+
if strings.HasPrefix(ref.Name, "refs/tags/") {
52+
return "tags"
53+
}
54+
if strings.HasPrefix(ref.Name, "refs/remotes/") {
55+
return "remotes"
56+
}
57+
if strings.HasPrefix(ref.Name, "refs/pull/") && strings.IndexByte(ref.Name[10:], '/') > -1 {
58+
return "pull"
59+
}
60+
return ""
61+
}

modules/git/repo.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ const prettyLogFormat = `--pretty=format:%H`
4949

5050
// GetAllCommitsCount returns count of all commits in repository
5151
func (repo *Repository) GetAllCommitsCount() (int64, error) {
52-
return AllCommitsCount(repo.Path)
52+
return AllCommitsCount(repo.Path, false)
5353
}
5454

5555
func (repo *Repository) parsePrettyFormatLogToList(logs []byte) (*list.List, error) {

modules/git/repo_commit.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,7 @@ func (repo *Repository) FileChangedBetweenCommits(filename, id1, id2 string) (bo
318318

319319
// FileCommitsCount return the number of files at a revison
320320
func (repo *Repository) FileCommitsCount(revision, file string) (int64, error) {
321-
return commitsCount(repo.Path, []string{revision}, []string{file})
321+
return CommitsCountFiles(repo.Path, []string{revision}, []string{file})
322322
}
323323

324324
// CommitsByFileAndRange return the commits according revison file and the page
@@ -413,11 +413,11 @@ func (repo *Repository) CommitsBetweenIDs(last, before string) (*list.List, erro
413413

414414
// CommitsCountBetween return numbers of commits between two commits
415415
func (repo *Repository) CommitsCountBetween(start, end string) (int64, error) {
416-
count, err := commitsCount(repo.Path, []string{start + "..." + end}, []string{})
416+
count, err := CommitsCountFiles(repo.Path, []string{start + "..." + end}, []string{})
417417
if err != nil && strings.Contains(err.Error(), "no merge base") {
418418
// future versions of git >= 2.28 are likely to return an error if before and last have become unrelated.
419419
// previously it would return the results of git rev-list before last so let's try that...
420-
return commitsCount(repo.Path, []string{start, end}, []string{})
420+
return CommitsCountFiles(repo.Path, []string{start, end}, []string{})
421421
}
422422

423423
return count, err

modules/gitgraph/graph.go

+27-8
Original file line numberDiff line numberDiff line change
@@ -17,23 +17,42 @@ import (
1717
)
1818

1919
// GetCommitGraph return a list of commit (GraphItems) from all branches
20-
func GetCommitGraph(r *git.Repository, page int, maxAllowedColors int) (*Graph, error) {
21-
format := "DATA:%d|%H|%ad|%an|%ae|%h|%s"
20+
func GetCommitGraph(r *git.Repository, page int, maxAllowedColors int, hidePRRefs bool, branches, files []string) (*Graph, error) {
21+
format := "DATA:%D|%H|%ad|%h|%s"
2222

2323
if page == 0 {
2424
page = 1
2525
}
2626

27-
graphCmd := git.NewCommand("log")
28-
graphCmd.AddArguments("--graph",
29-
"--date-order",
30-
"--all",
27+
args := make([]string, 0, 12+len(branches)+len(files))
28+
29+
args = append(args, "--graph", "--date-order", "--decorate=full")
30+
31+
if hidePRRefs {
32+
args = append(args, "--exclude=refs/pull/*")
33+
}
34+
35+
if len(branches) == 0 {
36+
args = append(args, "--all")
37+
}
38+
39+
args = append(args,
3140
"-C",
3241
"-M",
3342
fmt.Sprintf("-n %d", setting.UI.GraphMaxCommitNum*page),
3443
"--date=iso",
35-
fmt.Sprintf("--pretty=format:%s", format),
36-
)
44+
fmt.Sprintf("--pretty=format:%s", format))
45+
46+
if len(branches) > 0 {
47+
args = append(args, branches...)
48+
}
49+
args = append(args, "--")
50+
if len(files) > 0 {
51+
args = append(args, files...)
52+
}
53+
54+
graphCmd := git.NewCommand("log")
55+
graphCmd.AddArguments(args...)
3756
graph := NewGraph()
3857

3958
stderr := new(strings.Builder)

modules/gitgraph/graph_models.go

+85-21
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ package gitgraph
77
import (
88
"bytes"
99
"fmt"
10+
11+
"code.gitea.io/gitea/models"
12+
"code.gitea.io/gitea/modules/git"
13+
"code.gitea.io/gitea/modules/log"
1014
)
1115

1216
// NewGraph creates a basic graph
@@ -77,6 +81,48 @@ func (graph *Graph) AddCommit(row, column int, flowID int64, data []byte) error
7781
return nil
7882
}
7983

84+
// LoadAndProcessCommits will load the git.Commits for each commit in the graph,
85+
// the associate the commit with the user author, and check the commit verification
86+
// before finally retrieving the latest status
87+
func (graph *Graph) LoadAndProcessCommits(repository *models.Repository, gitRepo *git.Repository) error {
88+
var err error
89+
90+
var ok bool
91+
92+
emails := map[string]*models.User{}
93+
keyMap := map[string]bool{}
94+
95+
for _, c := range graph.Commits {
96+
if len(c.Rev) == 0 {
97+
continue
98+
}
99+
c.Commit, err = gitRepo.GetCommit(c.Rev)
100+
if err != nil {
101+
return fmt.Errorf("GetCommit: %s Error: %w", c.Rev, err)
102+
}
103+
104+
if c.Commit.Author != nil {
105+
email := c.Commit.Author.Email
106+
if c.User, ok = emails[email]; !ok {
107+
c.User, _ = models.GetUserByEmail(email)
108+
emails[email] = c.User
109+
}
110+
}
111+
112+
c.Verification = models.ParseCommitWithSignature(c.Commit)
113+
114+
_ = models.CalculateTrustStatus(c.Verification, repository, &keyMap)
115+
116+
statuses, err := models.GetLatestCommitStatus(repository, c.Commit.ID.String(), 0)
117+
if err != nil {
118+
log.Error("GetLatestCommitStatus: %v", err)
119+
} else {
120+
c.Status = models.CalcCommitStatus(statuses)
121+
}
122+
}
123+
return nil
124+
}
125+
80126
// NewFlow creates a new flow
81127
func NewFlow(flowID int64, color, row, column int) *Flow {
82128
return &Flow{
@@ -142,42 +188,60 @@ var RelationCommit = &Commit{
142188

143189
// NewCommit creates a new commit from a provided line
144190
func NewCommit(row, column int, line []byte) (*Commit, error) {
145-
data := bytes.SplitN(line, []byte("|"), 7)
146-
if len(data) < 7 {
191+
data := bytes.SplitN(line, []byte("|"), 5)
192+
if len(data) < 5 {
147193
return nil, fmt.Errorf("malformed data section on line %d with commit: %s", row, string(line))
148194
}
149195
return &Commit{
150196
Row: row,
151197
Column: column,
152198
// 0 matches git log --pretty=format:%d => ref names, like the --decorate option of git-log(1)
153-
Branch: string(data[0]),
199+
Refs: newRefsFromRefNames(data[0]),
154200
// 1 matches git log --pretty=format:%H => commit hash
155201
Rev: string(data[1]),
156202
// 2 matches git log --pretty=format:%ad => author date (format respects --date= option)
157203
Date: string(data[2]),
158-
// 3 matches git log --pretty=format:%an => author name
159-
Author: string(data[3]),
160-
// 4 matches git log --pretty=format:%ae => author email
161-
AuthorEmail: string(data[4]),
162-
// 5 matches git log --pretty=format:%h => abbreviated commit hash
163-
ShortRev: string(data[5]),
164-
// 6 matches git log --pretty=format:%s => subject
165-
Subject: string(data[6]),
204+
// 3 matches git log --pretty=format:%h => abbreviated commit hash
205+
ShortRev: string(data[3]),
206+
// 4 matches git log --pretty=format:%s => subject
207+
Subject: string(data[4]),
166208
}, nil
167209
}
168210

211+
func newRefsFromRefNames(refNames []byte) []git.Reference {
212+
refBytes := bytes.Split(refNames, []byte{',', ' '})
213+
refs := make([]git.Reference, 0, len(refBytes))
214+
for _, refNameBytes := range refBytes {
215+
if len(refNameBytes) == 0 {
216+
continue
217+
}
218+
refName := string(refNameBytes)
219+
if refName[0:5] == "tag: " {
220+
refName = refName[5:]
221+
} else if refName[0:8] == "HEAD -> " {
222+
refName = refName[8:]
223+
}
224+
refs = append(refs, git.Reference{
225+
Name: refName,
226+
})
227+
}
228+
return refs
229+
}
230+
169231
// Commit represents a commit at co-ordinate X, Y with the data
170232
type Commit struct {
171-
Flow int64
172-
Row int
173-
Column int
174-
Branch string
175-
Rev string
176-
Date string
177-
Author string
178-
AuthorEmail string
179-
ShortRev string
180-
Subject string
233+
Commit *git.Commit
234+
User *models.User
235+
Verification *models.CommitVerification
236+
Status *models.CommitStatus
237+
Flow int64
238+
Row int
239+
Column int
240+
Refs []git.Reference
241+
Rev string
242+
Date string
243+
ShortRev string
244+
Subject string
181245
}
182246

183247
// OnlyRelation returns whether this a relation only commit

modules/gitgraph/graph_test.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ func BenchmarkGetCommitGraph(b *testing.B) {
2222
defer currentRepo.Close()
2323

2424
for i := 0; i < b.N; i++ {
25-
graph, err := GetCommitGraph(currentRepo, 1, 0)
25+
graph, err := GetCommitGraph(currentRepo, 1, 0, false, nil, nil)
2626
if err != nil {
2727
b.Error("Could get commit graph")
2828
}
@@ -34,7 +34,7 @@ func BenchmarkGetCommitGraph(b *testing.B) {
3434
}
3535

3636
func BenchmarkParseCommitString(b *testing.B) {
37-
testString := "* DATA:|4e61bacab44e9b4730e44a6615d04098dd3a8eaf|2016-12-20 21:10:41 +0100|Kjell Kvinge|[email protected]|4e61bac|Add route for graph"
37+
testString := "* DATA:|4e61bacab44e9b4730e44a6615d04098dd3a8eaf|2016-12-20 21:10:41 +0100|4e61bac|Add route for graph"
3838

3939
parser := &Parser{}
4040
parser.Reset()
@@ -44,7 +44,7 @@ func BenchmarkParseCommitString(b *testing.B) {
4444
if err := parser.AddLineToGraph(graph, 0, []byte(testString)); err != nil {
4545
b.Error("could not parse teststring")
4646
}
47-
if graph.Flows[1].Commits[0].Author != "Kjell Kvinge" {
47+
if graph.Flows[1].Commits[0].Rev != "4e61bacab44e9b4730e44a6615d04098dd3a8eaf" {
4848
b.Error("Did not get expected data")
4949
}
5050
}
@@ -244,7 +244,7 @@ func TestParseGlyphs(t *testing.T) {
244244
}
245245

246246
func TestCommitStringParsing(t *testing.T) {
247-
dataFirstPart := "* DATA:|4e61bacab44e9b4730e44a6615d04098dd3a8eaf|2016-12-20 21:10:41 +0100|Author|[email protected]|4e61bac|"
247+
dataFirstPart := "* DATA:|4e61bacab44e9b4730e44a6615d04098dd3a8eaf|2016-12-20 21:10:41 +0100|4e61bac|"
248248
tests := []struct {
249249
shouldPass bool
250250
testName string

0 commit comments

Comments
 (0)