summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--modules/context/pagination.go6
-rw-r--r--modules/context/repo.go12
-rw-r--r--modules/git/commit.go22
-rw-r--r--modules/git/ref.go43
-rw-r--r--modules/git/repo.go2
-rw-r--r--modules/git/repo_commit.go6
-rw-r--r--modules/gitgraph/graph.go35
-rw-r--r--modules/gitgraph/graph_models.go106
-rw-r--r--modules/gitgraph/graph_test.go8
-rw-r--r--modules/templates/helper.go21
-rw-r--r--options/locale/locale_en-US.ini2
-rw-r--r--routers/repo/commit.go56
-rw-r--r--templates/repo/graph.tmpl99
-rw-r--r--templates/repo/graph/commits.tmpl80
-rw-r--r--templates/repo/graph/div.tmpl7
-rw-r--r--templates/repo/graph/svgcontainer.tmpl24
-rw-r--r--web_src/js/features/gitgraph.js51
-rw-r--r--web_src/less/_base.less4
-rw-r--r--web_src/less/_repository.less1
-rw-r--r--web_src/less/features/gitgraph.less96
-rw-r--r--web_src/less/themes/theme-arc-green.less33
21 files changed, 588 insertions, 126 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 {
diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index 815d69c518..b1447f3108 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -822,6 +822,8 @@ audio_not_supported_in_browser = Your browser does not support the HTML5 'audio'
stored_lfs = Stored with Git LFS
symbolic_link = Symbolic link
commit_graph = Commit Graph
+commit_graph.select = Select branches
+commit_graph.hide_pr_refs = Hide Pull Requests
commit_graph.monochrome = Mono
commit_graph.color = Color
blame = Blame
diff --git a/routers/repo/commit.go b/routers/repo/commit.go
index d9547cc51d..5bb26ffe41 100644
--- a/routers/repo/commit.go
+++ b/routers/repo/commit.go
@@ -23,6 +23,7 @@ import (
const (
tplCommits base.TplName = "repo/commits"
tplGraph base.TplName = "repo/graph"
+ tplGraphDiv base.TplName = "repo/graph/div"
tplCommitPage base.TplName = "repo/commit_page"
)
@@ -88,6 +89,7 @@ func Commits(ctx *context.Context) {
// Graph render commit graph - show commits from all branches.
func Graph(ctx *context.Context) {
+ ctx.Data["Title"] = ctx.Tr("repo.commit_graph")
ctx.Data["PageIsCommits"] = true
ctx.Data["PageIsViewCode"] = true
mode := strings.ToLower(ctx.QueryTrim("mode"))
@@ -95,6 +97,18 @@ func Graph(ctx *context.Context) {
mode = "color"
}
ctx.Data["Mode"] = mode
+ hidePRRefs := ctx.QueryBool("hide-pr-refs")
+ ctx.Data["HidePRRefs"] = hidePRRefs
+ branches := ctx.QueryStrings("branch")
+ realBranches := make([]string, len(branches))
+ copy(realBranches, branches)
+ for i, branch := range realBranches {
+ if strings.HasPrefix(branch, "--") {
+ realBranches[i] = "refs/heads/" + branch
+ }
+ }
+ ctx.Data["SelectedBranches"] = realBranches
+ files := ctx.QueryStrings("file")
commitsCount, err := ctx.Repo.GetCommitsCount()
if err != nil {
@@ -102,28 +116,60 @@ func Graph(ctx *context.Context) {
return
}
- allCommitsCount, err := ctx.Repo.GitRepo.GetAllCommitsCount()
+ graphCommitsCount, err := ctx.Repo.GetCommitGraphsCount(hidePRRefs, realBranches, files)
if err != nil {
- ctx.ServerError("GetAllCommitsCount", err)
- return
+ log.Warn("GetCommitGraphsCount error for generate graph exclude prs: %t branches: %s in %-v, Will Ignore branches and try again. Underlying Error: %v", hidePRRefs, branches, ctx.Repo.Repository, err)
+ realBranches = []string{}
+ branches = []string{}
+ graphCommitsCount, err = ctx.Repo.GetCommitGraphsCount(hidePRRefs, realBranches, files)
+ if err != nil {
+ ctx.ServerError("GetCommitGraphsCount", err)
+ return
+ }
}
page := ctx.QueryInt("page")
- graph, err := gitgraph.GetCommitGraph(ctx.Repo.GitRepo, page, 0)
+ graph, err := gitgraph.GetCommitGraph(ctx.Repo.GitRepo, page, 0, hidePRRefs, realBranches, files)
if err != nil {
ctx.ServerError("GetCommitGraph", err)
return
}
+ if err := graph.LoadAndProcessCommits(ctx.Repo.Repository, ctx.Repo.GitRepo); err != nil {
+ ctx.ServerError("LoadAndProcessCommits", err)
+ return
+ }
+
ctx.Data["Graph"] = graph
+
+ gitRefs, err := ctx.Repo.GitRepo.GetRefs()
+ if err != nil {
+ ctx.ServerError("GitRepo.GetRefs", err)
+ return
+ }
+
+ ctx.Data["AllRefs"] = gitRefs
+
ctx.Data["Username"] = ctx.Repo.Owner.Name
ctx.Data["Reponame"] = ctx.Repo.Repository.Name
ctx.Data["CommitCount"] = commitsCount
ctx.Data["Branch"] = ctx.Repo.BranchName
- paginator := context.NewPagination(int(allCommitsCount), setting.UI.GraphMaxCommitNum, page, 5)
+ paginator := context.NewPagination(int(graphCommitsCount), setting.UI.GraphMaxCommitNum, page, 5)
paginator.AddParam(ctx, "mode", "Mode")
+ paginator.AddParam(ctx, "hide-pr-refs", "HidePRRefs")
+ for _, branch := range branches {
+ paginator.AddParamString("branch", branch)
+ }
+ for _, file := range files {
+ paginator.AddParamString("file", file)
+ }
ctx.Data["Page"] = paginator
+ if ctx.QueryBool("div-only") {
+ ctx.HTML(200, tplGraphDiv)
+ return
+ }
+
ctx.HTML(200, tplGraph)
}
diff --git a/templates/repo/graph.tmpl b/templates/repo/graph.tmpl
index 7d2ecb5a91..fa027adc03 100644
--- a/templates/repo/graph.tmpl
+++ b/templates/repo/graph.tmpl
@@ -3,60 +3,61 @@
{{template "repo/header" .}}
<div class="ui container">
<div id="git-graph-container" class="ui segment{{if eq .Mode "monochrome"}} monochrome{{end}}">
- <h2 class="ui header dividing">{{.i18n.Tr "repo.commit_graph"}}
- <div class="ui right">
- <div class="ui icon buttons tiny color-buttons">
- <button id="flow-color-monochrome" class="ui labelled icon button{{if eq .Mode "monochrome"}} active{{end}}" title="{{.i18n.Tr "repo.commit_graph.monochrome"}}"><span class="emoji">{{svg "material-invert-colors"}}</span> {{.i18n.Tr "repo.commit_graph.monochrome"}}</button>
- <button id="flow-color-colored" class="ui labelled icon button{{if ne .Mode "monochrome"}} active{{end}}" title="{{.i18n.Tr "repo.commit_graph.color"}}"><span class="emoji">{{svg "material-palette"}}</span> {{.i18n.Tr "repo.commit_graph.color"}}</button>
+ <h2 class="ui header dividing">
+ {{.i18n.Tr "repo.commit_graph"}}
+ <div class="ui icon buttons tiny color-buttons">
+ <div class="ui multiple selection search dropdown" id="flow-select-refs-dropdown">
+ <input type="hidden" name="flow">
+ <i class="dropdown icon"></i>
+ <div class="default text">{{.i18n.Tr "repo.commit_graph.select"}}</div>
+ <div class="menu">
+ <div class="item" data-value="...flow-hide-pr-refs">
+ <span class="truncate">
+ {{svg "octicon-eye-closed" 16 "mr-2"}}<span title="{{.i18n.Tr "repo.commit_graph.hide_pr_refs"}}">{{.i18n.Tr "repo.commit_graph.hide_pr_refs"}}</span>
+ </span>
+ </div>
+ {{range .AllRefs}}
+ {{$refGroup := .RefGroup}}
+ {{if eq $refGroup "pull"}}
+ <div class="item" data-value="{{.Name}}">
+ <span class="truncate">
+ {{svg "octicon-git-pull-request" 16 "mr-2"}}<span title="{{.ShortName}}">#{{.ShortName}}</span>
+ </span>
+ </div>
+ {{else if eq $refGroup "tags"}}
+ <div class="item" data-value="{{.Name}}">
+ <span class="truncate">
+ {{svg "octicon-tag" 16 "mr-2"}}<span title="{{.ShortName}}">{{.ShortName}}</span>
+ </span>
+ </div>
+ {{else if eq $refGroup "remotes"}}
+ <div class="item" data-value="{{.Name}}">
+ <span class="truncate">
+ {{svg "octicon-cross-reference" 16 "mr-2"}}<span title="{{.ShortName}}">{{.ShortName}}</span>
+ </span>
+ </div>
+ {{else if eq $refGroup "heads"}}
+ <div class="item" data-value="{{.Name}}">
+ <span class="truncate">
+ {{svg "octicon-git-branch" 16 "mr-2"}}<span title="{{.ShortName}}">{{.ShortName}}</span>
+ </span>
+ </div>
+ {{end}}
+ {{end}}
+ </div>
</div>
+ <button id="flow-color-monochrome" class="ui labelled icon button{{if eq .Mode "monochrome"}} active{{end}}" title="{{.i18n.Tr "repo.commit_graph.monochrome"}}">{{svg "material-invert-colors" 16 "mr-2"}}{{.i18n.Tr "repo.commit_graph.monochrome"}}</button>
+ <button id="flow-color-colored" class="ui labelled icon button{{if ne .Mode "monochrome"}} active{{end}}" title="{{.i18n.Tr "repo.commit_graph.color"}}">{{svg "material-palette" 16 "mr-2"}}{{.i18n.Tr "repo.commit_graph.color"}}</button>
</div>
</h2>
<div class="ui dividing"></div>
- <div id="rel-container">
- <svg viewbox="{{Mul .Graph.MinColumn 5}} {{Mul .Graph.MinRow 10}} {{Add (Mul .Graph.Width 5) 5}} {{Mul .Graph.Height 10}}" width="{{Add (Mul .Graph.Width 10) 10}}px">
- {{range $flowid, $flow := .Graph.Flows}}
- <g id="flow-{{$flow.ID}}" class="flow-group flow-color-{{$flow.ColorNumber}} flow-color-16-{{$flow.Color16}}" data-flow="{{$flow.ID}}" data-color="{{$flow.ColorNumber}}">
- <path d="{{range $i, $glyph := $flow.Glyphs -}}
- {{- if or (eq $glyph.Glyph '*') (eq $glyph.Glyph '|') -}}
- M {{Add (Mul $glyph.Column 5) 5}} {{Add (Mul $glyph.Row 10) 0}} v 10 {{/* */ -}}
- {{- else if eq $glyph.Glyph '/' -}}
- M {{Add (Mul $glyph.Column 5) 10}} {{Add (Mul $glyph.Row 10) 0}} l -10 10 {{/* */ -}}
- {{- else if eq $glyph.Glyph '\\' -}}
- M {{Add (Mul $glyph.Column 5) 0}} {{Add (Mul $glyph.Row 10) 0}} l 10 10 {{/* */ -}}
- {{- else if or (eq $glyph.Glyph '-') (eq $glyph.Glyph '.') -}}
- M {{Add (Mul $glyph.Column 5) 0}} {{Add (Mul $glyph.Row 10) 10}} h 5 {{/* */ -}}
- {{- else if eq $glyph.Glyph '_' -}}
- M {{Add (Mul $glyph.Column 5) 0}} {{Add (Mul $glyph.Row 10) 10}} h 10 {{/* */ -}}
- {{- end -}}
- {{- end}}" stroke-width="1" fill="none" id="flow-{{$flow.ID}}-path" stroke-linecap="round"/>
- {{range $flow.Commits}}
- <circle class="flow-commit" cx="{{Add (Mul .Column 5) 5}}" cy="{{Add (Mul .Row 10) 5}}" r="2.5" stroke="none" id="flow-commit-{{.Rev}}" data-rev="{{.Rev}}"/>
- {{end}}
- </g>
- {{end}}
- </svg>
- </div>
- <div id="rev-container">
- <ul id="rev-list">
- {{ range .Graph.Commits }}
- <li id="commit-{{.Rev}}" data-flow="{{.Flow}}">
- {{ if .OnlyRelation }}
- <span />
- {{ else }}
- <code id="{{.ShortRev}}">
- <a href="{{AppSubUrl}}/{{$.Username}}/{{$.Reponame}}/commit/{{.Rev}}">{{ .ShortRev}}</a>
- </code>
- <strong> {{.Branch}}</strong>
- <span>{{RenderCommitMessage .Subject $.RepoLink $.Repository.ComposeMetas}}</span> by
- <span class="author">{{.Author}}</span>
- <span class="time">{{.Date}}</span>
- {{ end }}
- </li>
- {{ end }}
- </ul>
- </div>
+ <div class="ui segment loading hide" id="loading-indicator"></div>
+ {{ template "repo/graph/svgcontainer" .}}
+ {{ template "repo/graph/commits" .}}
</div>
</div>
</div>
-{{template "base/paginate" .}}
+<div id="pagination">
+ {{template "base/paginate" .}}
+</div>
{{template "base/footer" .}}
diff --git a/templates/repo/graph/commits.tmpl b/templates/repo/graph/commits.tmpl
new file mode 100644
index 0000000000..108aa0aa04
--- /dev/null
+++ b/templates/repo/graph/commits.tmpl
@@ -0,0 +1,80 @@
+<div id="rev-container">
+ <ul id="rev-list">
+ {{ range $commitI, $commit := .Graph.Commits }}
+ <li id="commit-{{$commit.Rev}}" data-flow="{{$commit.Flow}}">
+ {{ if $commit.OnlyRelation }}
+ <span />
+ {{ else }}
+ <span class="sha" id="{{$commit.ShortRev}}">
+ {{$class := "ui sha label"}}
+ {{if $commit.Commit.Signature}}
+ {{$class = (printf "%s%s" $class " isSigned")}}
+ {{if $commit.Verification.Verified}}
+ {{if eq $commit.Verification.TrustStatus "trusted"}}
+ {{$class = (printf "%s%s" $class " isVerified")}}
+ {{else if eq $commit.Verification.TrustStatus "untrusted"}}
+ {{$class = (printf "%s%s" $class " isVerifiedUntrusted")}}
+ {{else}}
+ {{$class = (printf "%s%s" $class " isVerifiedUnmatched")}}
+ {{end}}
+ {{else if $commit.Verification.Warning}}
+ {{$class = (printf "%s%s" $class " isWarning")}}
+ {{end}}
+ {{end}}
+ <a href="{{AppSubUrl}}/{{$.Username}}/{{$.Reponame}}/commit/{{$commit.Rev}}" rel="nofollow" class="{{$class}}">
+ <span class="shortsha">{{ShortSha $commit.Commit.ID.String}}</span>
+ {{- if $commit.Commit.Signature -}}
+ <span class="shortsha-pad"></span>{{template "repo/shabox_badge" dict "root" $ "verification" $commit.Verification}}
+ {{- end -}}
+ </a>
+ </span>
+ <span class="message df ac mr-2">{{RenderCommitMessage $commit.Subject $.RepoLink $.Repository.ComposeMetas}}</span>
+ <span class="tags df ac">
+ {{range $commit.Refs}}
+ {{$refGroup := .RefGroup}}
+ {{if eq $refGroup "pull"}}
+ {{if $.HidePRRefs}}
+ {{if (containGeneric $.SelectedBranches .Name) }}
+ <a class="ui labelled icon button basic tiny" href="{{$.RepoLink}}/pulls/{{.ShortName|PathEscape}}">
+ {{svg "octicon-git-pull-request" 16 "mr-2"}}#{{.ShortName}}
+ </a>
+ {{end}}
+ {{else}}
+ <a class="ui labelled icon button basic tiny" href="{{$.RepoLink}}/pulls/{{.ShortName|PathEscape}}">
+ {{svg "octicon-git-pull-request" 16 "mr-2"}}#{{.ShortName}}
+ </a>
+ {{end}}
+ {{else if eq $refGroup "tags"}}
+ <a class="ui labelled icon button basic tiny" href="{{$.RepoLink}}/src/tag/{{.ShortName|PathEscape}}">
+ {{svg "octicon-tag" 16 "mr-2"}}{{.ShortName}}
+ </a>
+ {{else if eq $refGroup "remotes"}}
+ <a class="ui labelled icon button basic tiny" href="{{$.RepoLink}}/src/commit/{{$commit.Rev}}">
+ {{svg "octicon-cross-reference" 16 "mr-2"}}{{.ShortName}}
+ </a>
+ {{else if eq $refGroup "heads"}}
+ <a class="ui labelled icon button basic tiny" href="{{$.RepoLink}}/src/branch/{{.ShortName|PathEscape}}">
+ {{svg "octicon-git-branch" 16 "mr-2"}}{{.ShortName}}
+ </a>
+ {{else}}
+ <!-- Unknown ref type {{.Name}} -->
+ {{end}}
+ {{end}}
+ </span>
+ <span class="author df ac mr-2">
+ {{$userName := $commit.Commit.Author.Name}}
+ {{if $commit.User}}
+ {{if $commit.User.FullName}}
+ {{$userName = $commit.User.FullName}}
+ {{end}}
+ <img class="ui avatar image" src="{{$commit.User.RelAvatarLink}}" alt=""/><a href="{{AppSubUrl}}/{{$commit.User.Name}}">{{$userName}}</a>
+ {{else}}
+ <img class="ui avatar image" src="{{AvatarLink $commit.Commit.Author.Email}}" alt=""/>{{$userName}}
+ {{end}}
+ </span>
+ <span class="time df ac">{{$commit.Date}}</span>
+ {{ end }}
+ </li>
+ {{ end }}
+ </ul>
+</div>
diff --git a/templates/repo/graph/div.tmpl b/templates/repo/graph/div.tmpl
new file mode 100644
index 0000000000..c0bd4e269a
--- /dev/null
+++ b/templates/repo/graph/div.tmpl
@@ -0,0 +1,7 @@
+<div>
+ {{template "repo/graph/svgcontainer" .}}
+ {{template "repo/graph/commits" .}}
+ <div id="pagination">
+ {{template "base/paginate" .}}
+ </div>
+</div>
diff --git a/templates/repo/graph/svgcontainer.tmpl b/templates/repo/graph/svgcontainer.tmpl
new file mode 100644
index 0000000000..6e6046cecc
--- /dev/null
+++ b/templates/repo/graph/svgcontainer.tmpl
@@ -0,0 +1,24 @@
+<div id="rel-container">
+ <svg viewbox="{{Mul .Graph.MinColumn 5}} {{Mul .Graph.MinRow 12}} {{Add (Mul .Graph.Width 5) 5}} {{Mul .Graph.Height 12}}" width="{{Add (Mul .Graph.Width 10) 10}}px">
+ {{range $flowid, $flow := .Graph.Flows}}
+ <g id="flow-{{$flow.ID}}" class="flow-group flow-color-{{$flow.ColorNumber}} flow-color-16-{{$flow.Color16}}" data-flow="{{$flow.ID}}" data-color="{{$flow.ColorNumber}}">
+ <path d="{{range $i, $glyph := $flow.Glyphs -}}
+ {{- if or (eq $glyph.Glyph '*') (eq $glyph.Glyph '|') -}}
+ M {{Add (Mul $glyph.Column 5) 5}} {{Add (Mul $glyph.Row 12) 0}} v 12 {{/* */ -}}
+ {{- else if eq $glyph.Glyph '/' -}}
+ M {{Add (Mul $glyph.Column 5) 10}} {{Add (Mul $glyph.Row 12) 0}} l -10 12 {{/* */ -}}
+ {{- else if eq $glyph.Glyph '\\' -}}
+ M {{Add (Mul $glyph.Column 5) 0}} {{Add (Mul $glyph.Row 12) 0}} l 10 12 {{/* */ -}}
+ {{- else if or (eq $glyph.Glyph '-') (eq $glyph.Glyph '.') -}}
+ M {{Add (Mul $glyph.Column 5) 0}} {{Add (Mul $glyph.Row 12) 12}} h 5 {{/* */ -}}
+ {{- else if eq $glyph.Glyph '_' -}}
+ M {{Add (Mul $glyph.Column 5) 0}} {{Add (Mul $glyph.Row 12) 12}} h 10 {{/* */ -}}
+ {{- end -}}
+ {{- end}}" stroke-width="1" fill="none" id="flow-{{$flow.ID}}-path" stroke-linecap="round"/>
+ {{range $flow.Commits}}
+ <circle class="flow-commit" cx="{{Add (Mul .Column 5) 5}}" cy="{{Add (Mul .Row 12) 6}}" r="2.5" stroke="none" id="flow-commit-{{.Rev}}" data-rev="{{.Rev}}"/>
+ {{end}}
+ </g>
+ {{end}}
+ </svg>
+</div>
diff --git a/web_src/js/features/gitgraph.js b/web_src/js/features/gitgraph.js
index 655cfb77c2..570d16059f 100644
--- a/web_src/js/features/gitgraph.js
+++ b/web_src/js/features/gitgraph.js
@@ -46,6 +46,57 @@ export default async function initGitGraph() {
window.history.replaceState({}, '', window.location.pathname);
}
});
+ const url = new URL(window.location);
+ const params = url.searchParams;
+ const updateGraph = async () => {
+ const queryString = params.toString();
+ const ajaxUrl = new URL(url);
+ ajaxUrl.searchParams.set('div-only', 'true');
+ window.history.replaceState({}, '', queryString ? `?${queryString}` : window.location.pathname);
+ $('#pagination').empty();
+ $('#rel-container').addClass('hide');
+ $('#rev-container').addClass('hide');
+ $('#loading-indicator').removeClass('hide');
+
+ const div = $(await $.ajax(String(ajaxUrl)));
+ $('#pagination').html(div.find('#pagination').html());
+ $('#rel-container').html(div.find('#rel-container').html());
+ $('#rev-container').html(div.find('#rev-container').html());
+ $('#loading-indicator').addClass('hide');
+ $('#rel-container').removeClass('hide');
+ $('#rev-container').removeClass('hide');
+ };
+ const dropdownSelected = params.getAll('branch');
+ if (params.has('hide-pr-refs') && params.get('hide-pr-refs') === 'true') {
+ dropdownSelected.splice(0, 0, '...flow-hide-pr-refs');
+ }
+
+ $('#flow-select-refs-dropdown').dropdown('set selected', dropdownSelected);
+ $('#flow-select-refs-dropdown').dropdown({
+ clearable: true,
+ onRemove(toRemove) {
+ if (toRemove === '...flow-hide-pr-refs') {
+ params.delete('hide-pr-refs');
+ } else {
+ const branches = params.getAll('branch');
+ params.delete('branch');
+ for (const branch of branches) {
+ if (branch !== toRemove) {
+ params.append('branch', branch);
+ }
+ }
+ }
+ updateGraph();
+ },
+ onAdd(toAdd) {
+ if (toAdd === '...flow-hide-pr-refs') {
+ params.set('hide-pr-refs', true);
+ } else {
+ params.append('branch', toAdd);
+ }
+ updateGraph();
+ },
+ });
$('#git-graph-container').on('mouseenter', '#rev-list li', (e) => {
const flow = $(e.currentTarget).data('flow');
if (flow === 0) return;
diff --git a/web_src/less/_base.less b/web_src/less/_base.less
index 7a757fd922..29eca15d24 100644
--- a/web_src/less/_base.less
+++ b/web_src/less/_base.less
@@ -1391,6 +1391,10 @@ table th[data-sortt-desc] {
}
}
+.dropdown .ui.label {
+ margin-left: 0 !important;
+}
+
.ui.dropdown .menu .item {
border-radius: 0;
}
diff --git a/web_src/less/_repository.less b/web_src/less/_repository.less
index 0a2772a79e..e77fbc05a7 100644
--- a/web_src/less/_repository.less
+++ b/web_src/less/_repository.less
@@ -1462,6 +1462,7 @@
#commits-table td.sha .sha.label,
#repo-files-table .sha.label,
+ #rev-list .sha.label,
.timeline-item.commits-list .singular-commit .sha.label {
border: 1px solid #bbbbbb;
diff --git a/web_src/less/features/gitgraph.less b/web_src/less/features/gitgraph.less
index da81b13352..ee170fba7f 100644
--- a/web_src/less/features/gitgraph.less
+++ b/web_src/less/features/gitgraph.less
@@ -1,8 +1,21 @@
#git-graph-container {
float: left;
display: block;
- overflow-x: auto;
+ overflow-x: scroll;
width: 100%;
+ min-height: 350px;
+
+ > .ui.segment.loading {
+ border: 0;
+ z-index: 1;
+ min-height: 246px;
+ }
+
+ h2 {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ }
.color-buttons {
margin-right: 0;
@@ -12,11 +25,49 @@
padding-bottom: 10px;
}
+ #flow-select-refs-dropdown {
+ border-top-right-radius: 0;
+ border-bottom-right-radius: 0;
+ min-width: 250px;
+ border-right: none;
+
+ .ui.label {
+ max-width: 180px;
+ display: inline-flex !important;
+ align-items: center;
+
+ .truncate {
+ display: inline-block;
+ max-width: 140px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ vertical-align: top;
+ white-space: nowrap;
+ }
+ }
+
+ .dropdown.icon {
+ display: none;
+ }
+
+ .default.text {
+ padding-top: 4px;
+ padding-bottom: 4px;
+ }
+
+ input.search {
+ position: relative;
+ top: 1px;
+ }
+ }
+
li {
list-style-type: none;
- height: 20px;
- line-height: 20px;
+ height: 24px;
+ line-height: 24px;
white-space: nowrap;
+ display: flex;
+ align-items: center;
.node-relation {
font-family: "Bitstream Vera Sans Mono", "Courier", monospace;
@@ -31,10 +82,6 @@
font-size: 80%;
}
- a {
- color: #000000;
- }
-
a:hover {
text-decoration: underline;
}
@@ -59,16 +106,39 @@
#rev-list {
margin: 0;
- padding: 0 5px;
- min-width: 95%;
+ padding: 0;
+ width: 100%;
- li.highlight,
- li.hover {
+ li.highlight.hover {
background-color: rgba(0, 0, 0, .05);
}
- li.highlight.hover {
- background-color: rgba(0, 0, 0, .1);
+ .tags a.button {
+ padding: 2px 4px;
+ }
+
+ .sha.label {
+ padding-top: 5px;
+ padding-bottom: 3px;
+ }
+
+ .sha.label .shortsha {
+ padding-top: 0;
+ }
+
+ .sha.label .shortsha-pad {
+ padding-right: 10px;
+ }
+
+ .sha.label .ui.detail.icon.button {
+ padding-top: 3px;
+ margin-top: -5px;
+ padding-bottom: 1px;
+ }
+
+ .author .ui.avatar.image {
+ width: auto;
+ height: 18px;
}
}
diff --git a/web_src/less/themes/theme-arc-green.less b/web_src/less/themes/theme-arc-green.less
index c7fd7e4a23..5b0cbb5e96 100644
--- a/web_src/less/themes/theme-arc-green.less
+++ b/web_src/less/themes/theme-arc-green.less
@@ -1360,10 +1360,6 @@ td.blob-hunk {
}
}
-input {
- background: #2e323e;
-}
-
.settings .key.list .item:not(:first-child) {
border-top: 1px solid var(--color-secondary);
}
@@ -1608,6 +1604,12 @@ a.blob-excerpt:hover {
color: #dbdbdb;
}
+.ui.active.label {
+ background: #393d4a;
+ border-color: #393d4a;
+ color: #dbdbdb;
+}
+
a.ui.label:hover,
a.ui.labels .label:hover {
background-color: #505667 !important;
@@ -1617,6 +1619,7 @@ a.ui.labels .label:hover {
.sha.label,
.repository #repo-files-table .sha.label,
.repository #commits-table td.sha .sha.label,
+#rev-list .sha.label,
.repository .timeline-item.commits-list .singular-commit .sha.label,
.repository.view.issue .comment-list .timeline-item.commits-list .singular-commit .shabox .sha.label {
border-color: #505667;
@@ -1624,6 +1627,7 @@ a.ui.labels .label:hover {
.sha.label.isSigned .detail.icon,
.repository #commits-table td.sha .sha.label.isSigned .detail.icon,
+#rev-list .sha.label.isSigned .detail.icon,
.repository #repo-files-table .sha.label.isSigned .detail.icon,
.repository .timeline-item.commits-list .singular-commit .sha.label.isSigned .detail.icon,
.repository.view.issue .comment-list .timeline-item.commits-list .singular-commit .shabox .sha.label.isSigned .detail.icon {
@@ -1743,14 +1747,6 @@ a.ui.labels .label:hover {
color: var(--color-secondary-dark-6);
}
-#git-graph-container li a {
- color: #c79575;
-}
-
-#git-graph-container li .author {
- color: #c79575;
-}
-
.ui.header .sub.header {
color: var(--color-secondary-dark-6);
}
@@ -1970,6 +1966,10 @@ a.ui.labels .label:hover {
}
}
+.ui.loading.segment:before {
+ background: #353945;
+}
+
.ui.popup {
background-color: #383c4a;
color: var(--color-secondary-dark-6);
@@ -2053,6 +2053,10 @@ img[src$="/img/matrix.svg"] {
filter: invert(80%);
}
+#git-graph-container li .time {
+ color: #6a737d;
+}
+
#git-graph-container.monochrome #rel-container .flow-group {
stroke: dimgrey;
fill: dimgrey;
@@ -2077,11 +2081,6 @@ img[src$="/img/matrix.svg"] {
}
}
-#git-graph-container #rev-list li.highlight,
-#git-graph-container #rev-list li.hover {
- background-color: rgba(255, 255, 255, .05);
-}
-
#git-graph-container #rev-list li.highlight.hover {
background-color: rgba(255, 255, 255, .1);
}