diff options
author | zeripath <art27@cantab.net> | 2020-11-08 17:21:54 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-11-08 12:21:54 -0500 |
commit | c05a8abc762f868e67dd131d34f45218a0fb95ab (patch) | |
tree | 9aa0909c736933adc895ef830941dca85206ee07 /modules | |
parent | d4e0b286558a68c96b0001a0676099c06067511b (diff) | |
download | gitea-c05a8abc762f868e67dd131d34f45218a0fb95ab.tar.gz gitea-c05a8abc762f868e67dd131d34f45218a0fb95ab.zip |
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 <art27@cantab.net>
* as per @silverwind
Signed-off-by: Andrew Thornton <art27@cantab.net>
* as per @silverwind
Signed-off-by: Andrew Thornton <art27@cantab.net>
* Only show refs in dropdown we display on the graph
Signed-off-by: Andrew Thornton <art27@cantab.net>
* as per @silverwind
Signed-off-by: Andrew Thornton <art27@cantab.net>
* use flexbox for ui header
Signed-off-by: Andrew Thornton <art27@cantab.net>
* Move Hide Pull Request button to the dropdown
Signed-off-by: Andrew Thornton <art27@cantab.net>
* Add SHA and user pictures
Signed-off-by: Andrew Thornton <art27@cantab.net>
* fix test
Signed-off-by: Andrew Thornton <art27@cantab.net>
* fix test 2
Signed-off-by: Andrew Thornton <art27@cantab.net>
* fixes
* async
* more tweaks
* use tabs in tmpl
Signed-off-by: Andrew Thornton <art27@cantab.net>
* remove commented thing
Signed-off-by: Andrew Thornton <art27@cantab.net>
* fix linting
Signed-off-by: Andrew Thornton <art27@cantab.net>
* Update web_src/js/features/gitgraph.js
Co-authored-by: silverwind <me@silverwind.io>
* graph tweaks
* more tweaks
* add title
Signed-off-by: Andrew Thornton <art27@cantab.net>
* fix loading indicator z-index and position
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: techknowlogick <techknowlogick@gitea.io>
Co-authored-by: Lauris BH <lauris@nix.lv>
Diffstat (limited to 'modules')
-rw-r--r-- | modules/context/pagination.go | 6 | ||||
-rw-r--r-- | modules/context/repo.go | 12 | ||||
-rw-r--r-- | modules/git/commit.go | 22 | ||||
-rw-r--r-- | modules/git/ref.go | 43 | ||||
-rw-r--r-- | modules/git/repo.go | 2 | ||||
-rw-r--r-- | modules/git/repo_commit.go | 6 | ||||
-rw-r--r-- | modules/gitgraph/graph.go | 35 | ||||
-rw-r--r-- | modules/gitgraph/graph_models.go | 106 | ||||
-rw-r--r-- | modules/gitgraph/graph_test.go | 8 | ||||
-rw-r--r-- | modules/templates/helper.go | 21 |
10 files changed, 219 insertions, 42 deletions
diff --git a/modules/context/pagination.go b/modules/context/pagination.go index 9a6ad0b5c4..a6638f4086 100644 --- a/modules/context/pagination.go +++ b/modules/context/pagination.go @@ -37,6 +37,12 @@ func (p *Pagination) AddParam(ctx *Context, paramKey string, ctxKey string) { p.urlParams = append(p.urlParams, urlParam) } +// AddParamString adds a string parameter directly +func (p *Pagination) AddParamString(key string, value string) { + urlParam := fmt.Sprintf("%s=%v", url.QueryEscape(key), url.QueryEscape(value)) + p.urlParams = append(p.urlParams, urlParam) +} + // GetParams returns the configured URL params func (p *Pagination) GetParams() template.URL { return template.URL(strings.Join(p.urlParams, "&")) diff --git a/modules/context/repo.go b/modules/context/repo.go index f34b05d1d0..0ef644b522 100644 --- a/modules/context/repo.go +++ b/modules/context/repo.go @@ -171,6 +171,18 @@ func (r *Repository) GetCommitsCount() (int64, error) { }) } +// GetCommitGraphsCount returns cached commit count for current view +func (r *Repository) GetCommitGraphsCount(hidePRRefs bool, branches []string, files []string) (int64, error) { + cacheKey := fmt.Sprintf("commits-count-%d-graph-%t-%s-%s", r.Repository.ID, hidePRRefs, branches, files) + + return cache.GetInt64(cacheKey, func() (int64, error) { + if len(branches) == 0 { + return git.AllCommitsCount(r.Repository.RepoPath(), hidePRRefs, files...) + } + return git.CommitsCountFiles(r.Repository.RepoPath(), branches, files) + }) +} + // BranchNameSubURL sub-URL for the BranchName field func (r *Repository) BranchNameSubURL() string { switch { diff --git a/modules/git/commit.go b/modules/git/commit.go index 87278af9c7..6425345ea8 100644 --- a/modules/git/commit.go +++ b/modules/git/commit.go @@ -262,8 +262,19 @@ func CommitChangesWithArgs(repoPath string, args []string, opts CommitChangesOpt } // AllCommitsCount returns count of all commits in repository -func AllCommitsCount(repoPath string) (int64, error) { - stdout, err := NewCommand("rev-list", "--all", "--count").RunInDir(repoPath) +func AllCommitsCount(repoPath string, hidePRRefs bool, files ...string) (int64, error) { + args := []string{"--all", "--count"} + if hidePRRefs { + args = append([]string{"--exclude=refs/pull/*"}, args...) + } + cmd := NewCommand("rev-list") + cmd.AddArguments(args...) + if len(files) > 0 { + cmd.AddArguments("--") + cmd.AddArguments(files...) + } + + stdout, err := cmd.RunInDir(repoPath) if err != nil { return 0, err } @@ -271,7 +282,8 @@ func AllCommitsCount(repoPath string) (int64, error) { return strconv.ParseInt(strings.TrimSpace(stdout), 10, 64) } -func commitsCount(repoPath string, revision, relpath []string) (int64, error) { +// CommitsCountFiles returns number of total commits of until given revision. +func CommitsCountFiles(repoPath string, revision, relpath []string) (int64, error) { cmd := NewCommand("rev-list", "--count") cmd.AddArguments(revision...) if len(relpath) > 0 { @@ -288,8 +300,8 @@ func commitsCount(repoPath string, revision, relpath []string) (int64, error) { } // CommitsCount returns number of total commits of until given revision. -func CommitsCount(repoPath, revision string) (int64, error) { - return commitsCount(repoPath, []string{revision}, []string{}) +func CommitsCount(repoPath string, revision ...string) (int64, error) { + return CommitsCountFiles(repoPath, revision, []string{}) } // CommitsCount returns number of total commits of until current revision. diff --git a/modules/git/ref.go b/modules/git/ref.go index 67b56ac999..2a2798b18f 100644 --- a/modules/git/ref.go +++ b/modules/git/ref.go @@ -4,6 +4,8 @@ package git +import "strings" + // Reference represents a Git ref. type Reference struct { Name string @@ -16,3 +18,44 @@ type Reference struct { func (ref *Reference) Commit() (*Commit, error) { return ref.repo.getCommit(ref.Object) } + +// ShortName returns the short name of the reference +func (ref *Reference) ShortName() string { + if ref == nil { + return "" + } + if strings.HasPrefix(ref.Name, "refs/heads/") { + return ref.Name[11:] + } + if strings.HasPrefix(ref.Name, "refs/tags/") { + return ref.Name[10:] + } + if strings.HasPrefix(ref.Name, "refs/remotes/") { + return ref.Name[13:] + } + if strings.HasPrefix(ref.Name, "refs/pull/") && strings.IndexByte(ref.Name[10:], '/') > -1 { + return ref.Name[10 : strings.IndexByte(ref.Name[10:], '/')+10] + } + + return ref.Name +} + +// RefGroup returns the group type of the reference +func (ref *Reference) RefGroup() string { + if ref == nil { + return "" + } + if strings.HasPrefix(ref.Name, "refs/heads/") { + return "heads" + } + if strings.HasPrefix(ref.Name, "refs/tags/") { + return "tags" + } + if strings.HasPrefix(ref.Name, "refs/remotes/") { + return "remotes" + } + if strings.HasPrefix(ref.Name, "refs/pull/") && strings.IndexByte(ref.Name[10:], '/') > -1 { + return "pull" + } + return "" +} diff --git a/modules/git/repo.go b/modules/git/repo.go index 644ff09284..ae370d3da9 100644 --- a/modules/git/repo.go +++ b/modules/git/repo.go @@ -49,7 +49,7 @@ const prettyLogFormat = `--pretty=format:%H` // GetAllCommitsCount returns count of all commits in repository func (repo *Repository) GetAllCommitsCount() (int64, error) { - return AllCommitsCount(repo.Path) + return AllCommitsCount(repo.Path, false) } func (repo *Repository) parsePrettyFormatLogToList(logs []byte) (*list.List, error) { diff --git a/modules/git/repo_commit.go b/modules/git/repo_commit.go index 1f123c97fb..ee3b05447b 100644 --- a/modules/git/repo_commit.go +++ b/modules/git/repo_commit.go @@ -318,7 +318,7 @@ func (repo *Repository) FileChangedBetweenCommits(filename, id1, id2 string) (bo // FileCommitsCount return the number of files at a revison func (repo *Repository) FileCommitsCount(revision, file string) (int64, error) { - return commitsCount(repo.Path, []string{revision}, []string{file}) + return CommitsCountFiles(repo.Path, []string{revision}, []string{file}) } // CommitsByFileAndRange return the commits according revison file and the page @@ -413,11 +413,11 @@ func (repo *Repository) CommitsBetweenIDs(last, before string) (*list.List, erro // CommitsCountBetween return numbers of commits between two commits func (repo *Repository) CommitsCountBetween(start, end string) (int64, error) { - count, err := commitsCount(repo.Path, []string{start + "..." + end}, []string{}) + count, err := CommitsCountFiles(repo.Path, []string{start + "..." + end}, []string{}) if err != nil && strings.Contains(err.Error(), "no merge base") { // future versions of git >= 2.28 are likely to return an error if before and last have become unrelated. // previously it would return the results of git rev-list before last so let's try that... - return commitsCount(repo.Path, []string{start, end}, []string{}) + return CommitsCountFiles(repo.Path, []string{start, end}, []string{}) } return count, err diff --git a/modules/gitgraph/graph.go b/modules/gitgraph/graph.go index 257e4f3af0..8505678639 100644 --- a/modules/gitgraph/graph.go +++ b/modules/gitgraph/graph.go @@ -17,23 +17,42 @@ import ( ) // GetCommitGraph return a list of commit (GraphItems) from all branches -func GetCommitGraph(r *git.Repository, page int, maxAllowedColors int) (*Graph, error) { - format := "DATA:%d|%H|%ad|%an|%ae|%h|%s" +func GetCommitGraph(r *git.Repository, page int, maxAllowedColors int, hidePRRefs bool, branches, files []string) (*Graph, error) { + format := "DATA:%D|%H|%ad|%h|%s" if page == 0 { page = 1 } - graphCmd := git.NewCommand("log") - graphCmd.AddArguments("--graph", - "--date-order", - "--all", + args := make([]string, 0, 12+len(branches)+len(files)) + + args = append(args, "--graph", "--date-order", "--decorate=full") + + if hidePRRefs { + args = append(args, "--exclude=refs/pull/*") + } + + if len(branches) == 0 { + args = append(args, "--all") + } + + args = append(args, "-C", "-M", fmt.Sprintf("-n %d", setting.UI.GraphMaxCommitNum*page), "--date=iso", - fmt.Sprintf("--pretty=format:%s", format), - ) + fmt.Sprintf("--pretty=format:%s", format)) + + if len(branches) > 0 { + args = append(args, branches...) + } + args = append(args, "--") + if len(files) > 0 { + args = append(args, files...) + } + + graphCmd := git.NewCommand("log") + graphCmd.AddArguments(args...) graph := NewGraph() stderr := new(strings.Builder) diff --git a/modules/gitgraph/graph_models.go b/modules/gitgraph/graph_models.go index ea6ba96084..ba168ab19d 100644 --- a/modules/gitgraph/graph_models.go +++ b/modules/gitgraph/graph_models.go @@ -7,6 +7,10 @@ package gitgraph import ( "bytes" "fmt" + + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/log" ) // NewGraph creates a basic graph @@ -77,6 +81,48 @@ func (graph *Graph) AddCommit(row, column int, flowID int64, data []byte) error return nil } +// LoadAndProcessCommits will load the git.Commits for each commit in the graph, +// the associate the commit with the user author, and check the commit verification +// before finally retrieving the latest status +func (graph *Graph) LoadAndProcessCommits(repository *models.Repository, gitRepo *git.Repository) error { + var err error + + var ok bool + + emails := map[string]*models.User{} + keyMap := map[string]bool{} + + for _, c := range graph.Commits { + if len(c.Rev) == 0 { + continue + } + c.Commit, err = gitRepo.GetCommit(c.Rev) + if err != nil { + return fmt.Errorf("GetCommit: %s Error: %w", c.Rev, err) + } + + if c.Commit.Author != nil { + email := c.Commit.Author.Email + if c.User, ok = emails[email]; !ok { + c.User, _ = models.GetUserByEmail(email) + emails[email] = c.User + } + } + + c.Verification = models.ParseCommitWithSignature(c.Commit) + + _ = models.CalculateTrustStatus(c.Verification, repository, &keyMap) + + statuses, err := models.GetLatestCommitStatus(repository, c.Commit.ID.String(), 0) + if err != nil { + log.Error("GetLatestCommitStatus: %v", err) + } else { + c.Status = models.CalcCommitStatus(statuses) + } + } + return nil +} + // NewFlow creates a new flow func NewFlow(flowID int64, color, row, column int) *Flow { return &Flow{ @@ -142,42 +188,60 @@ var RelationCommit = &Commit{ // NewCommit creates a new commit from a provided line func NewCommit(row, column int, line []byte) (*Commit, error) { - data := bytes.SplitN(line, []byte("|"), 7) - if len(data) < 7 { + data := bytes.SplitN(line, []byte("|"), 5) + if len(data) < 5 { return nil, fmt.Errorf("malformed data section on line %d with commit: %s", row, string(line)) } return &Commit{ Row: row, Column: column, // 0 matches git log --pretty=format:%d => ref names, like the --decorate option of git-log(1) - Branch: string(data[0]), + Refs: newRefsFromRefNames(data[0]), // 1 matches git log --pretty=format:%H => commit hash Rev: string(data[1]), // 2 matches git log --pretty=format:%ad => author date (format respects --date= option) Date: string(data[2]), - // 3 matches git log --pretty=format:%an => author name - Author: string(data[3]), - // 4 matches git log --pretty=format:%ae => author email - AuthorEmail: string(data[4]), - // 5 matches git log --pretty=format:%h => abbreviated commit hash - ShortRev: string(data[5]), - // 6 matches git log --pretty=format:%s => subject - Subject: string(data[6]), + // 3 matches git log --pretty=format:%h => abbreviated commit hash + ShortRev: string(data[3]), + // 4 matches git log --pretty=format:%s => subject + Subject: string(data[4]), }, nil } +func newRefsFromRefNames(refNames []byte) []git.Reference { + refBytes := bytes.Split(refNames, []byte{',', ' '}) + refs := make([]git.Reference, 0, len(refBytes)) + for _, refNameBytes := range refBytes { + if len(refNameBytes) == 0 { + continue + } + refName := string(refNameBytes) + if refName[0:5] == "tag: " { + refName = refName[5:] + } else if refName[0:8] == "HEAD -> " { + refName = refName[8:] + } + refs = append(refs, git.Reference{ + Name: refName, + }) + } + return refs +} + // Commit represents a commit at co-ordinate X, Y with the data type Commit struct { - Flow int64 - Row int - Column int - Branch string - Rev string - Date string - Author string - AuthorEmail string - ShortRev string - Subject string + Commit *git.Commit + User *models.User + Verification *models.CommitVerification + Status *models.CommitStatus + Flow int64 + Row int + Column int + Refs []git.Reference + Rev string + Date string + ShortRev string + Subject string } // OnlyRelation returns whether this a relation only commit diff --git a/modules/gitgraph/graph_test.go b/modules/gitgraph/graph_test.go index ca9d653cee..c2726a731a 100644 --- a/modules/gitgraph/graph_test.go +++ b/modules/gitgraph/graph_test.go @@ -22,7 +22,7 @@ func BenchmarkGetCommitGraph(b *testing.B) { defer currentRepo.Close() for i := 0; i < b.N; i++ { - graph, err := GetCommitGraph(currentRepo, 1, 0) + graph, err := GetCommitGraph(currentRepo, 1, 0, false, nil, nil) if err != nil { b.Error("Could get commit graph") } @@ -34,7 +34,7 @@ func BenchmarkGetCommitGraph(b *testing.B) { } func BenchmarkParseCommitString(b *testing.B) { - testString := "* DATA:|4e61bacab44e9b4730e44a6615d04098dd3a8eaf|2016-12-20 21:10:41 +0100|Kjell Kvinge|kjell@kvinge.biz|4e61bac|Add route for graph" + testString := "* DATA:|4e61bacab44e9b4730e44a6615d04098dd3a8eaf|2016-12-20 21:10:41 +0100|4e61bac|Add route for graph" parser := &Parser{} parser.Reset() @@ -44,7 +44,7 @@ func BenchmarkParseCommitString(b *testing.B) { if err := parser.AddLineToGraph(graph, 0, []byte(testString)); err != nil { b.Error("could not parse teststring") } - if graph.Flows[1].Commits[0].Author != "Kjell Kvinge" { + if graph.Flows[1].Commits[0].Rev != "4e61bacab44e9b4730e44a6615d04098dd3a8eaf" { b.Error("Did not get expected data") } } @@ -244,7 +244,7 @@ func TestParseGlyphs(t *testing.T) { } func TestCommitStringParsing(t *testing.T) { - dataFirstPart := "* DATA:|4e61bacab44e9b4730e44a6615d04098dd3a8eaf|2016-12-20 21:10:41 +0100|Author|user@mail.something|4e61bac|" + dataFirstPart := "* DATA:|4e61bacab44e9b4730e44a6615d04098dd3a8eaf|2016-12-20 21:10:41 +0100|4e61bac|" tests := []struct { shouldPass bool testName string diff --git a/modules/templates/helper.go b/modules/templates/helper.go index 03ec80f99c..e4107dfa9a 100644 --- a/modules/templates/helper.go +++ b/modules/templates/helper.go @@ -16,6 +16,7 @@ import ( "mime" "net/url" "path/filepath" + "reflect" "regexp" "runtime" "strings" @@ -310,6 +311,26 @@ func NewFuncMap() []template.FuncMap { "EventSourceUpdateTime": int(setting.UI.Notification.EventSourceUpdateTime / time.Millisecond), } }, + "containGeneric": func(arr interface{}, v interface{}) bool { + arrV := reflect.ValueOf(arr) + if arrV.Kind() == reflect.String && reflect.ValueOf(v).Kind() == reflect.String { + return strings.Contains(arr.(string), v.(string)) + } + + if arrV.Kind() == reflect.Slice { + for i := 0; i < arrV.Len(); i++ { + iV := arrV.Index(i) + if !iV.CanInterface() { + continue + } + if iV.Interface() == v { + return true + } + } + } + + return false + }, "contain": func(s []int64, id int64) bool { for i := 0; i < len(s); i++ { if s[i] == id { |