diff --git a/modules/context/pagination.go b/modules/context/pagination.go index 9a6ad0b5c4f9..a6638f40865b 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 f34b05d1d0a1..0ef644b522b4 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 87278af9c7df..6425345ea8ee 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 67b56ac99948..2a2798b18f9a 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 644ff09284c6..ae370d3da973 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 1f123c97fbba..ee3b05447b8f 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 257e4f3af0de..85056786395c 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 ea6ba960846d..ba168ab19de3 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 ca9d653ceed3..c2726a731a45 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 03ec80f99c23..e4107dfa9a10 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 815d69c518ab..b1447f310867 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 d9547cc51de6..5bb26ffe41a9 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 7d2ecb5a914d..fa027adc033c 100644 --- a/templates/repo/graph.tmpl +++ b/templates/repo/graph.tmpl @@ -3,60 +3,61 @@ {{template "repo/header" .}}
-

{{.i18n.Tr "repo.commit_graph"}} -
-
- - +

+ {{.i18n.Tr "repo.commit_graph"}} +
+ + +

-
- - {{range $flowid, $flow := .Graph.Flows}} - - - {{range $flow.Commits}} - - {{end}} - - {{end}} - -
-
-
    - {{ range .Graph.Commits }} -
  • - {{ if .OnlyRelation }} - - {{ else }} - - {{ .ShortRev}} - - {{.Branch}} - {{RenderCommitMessage .Subject $.RepoLink $.Repository.ComposeMetas}} by - {{.Author}} - {{.Date}} - {{ end }} -
  • - {{ end }} -
-
+
+ {{ template "repo/graph/svgcontainer" .}} + {{ template "repo/graph/commits" .}}

-{{template "base/paginate" .}} + {{template "base/footer" .}} diff --git a/templates/repo/graph/commits.tmpl b/templates/repo/graph/commits.tmpl new file mode 100644 index 000000000000..108aa0aa04a5 --- /dev/null +++ b/templates/repo/graph/commits.tmpl @@ -0,0 +1,80 @@ +
+ +
diff --git a/templates/repo/graph/div.tmpl b/templates/repo/graph/div.tmpl new file mode 100644 index 000000000000..c0bd4e269a5a --- /dev/null +++ b/templates/repo/graph/div.tmpl @@ -0,0 +1,7 @@ +
+ {{template "repo/graph/svgcontainer" .}} + {{template "repo/graph/commits" .}} + +
diff --git a/templates/repo/graph/svgcontainer.tmpl b/templates/repo/graph/svgcontainer.tmpl new file mode 100644 index 000000000000..6e6046ceccfb --- /dev/null +++ b/templates/repo/graph/svgcontainer.tmpl @@ -0,0 +1,24 @@ +
+ + {{range $flowid, $flow := .Graph.Flows}} + + + {{range $flow.Commits}} + + {{end}} + + {{end}} + +
diff --git a/web_src/js/features/gitgraph.js b/web_src/js/features/gitgraph.js index 655cfb77c222..570d16059f58 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 7a757fd92209..29eca15d24a0 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 0a2772a79e3b..e77fbc05a747 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 da81b13352d4..ee170fba7fe7 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 c7fd7e4a234a..5b0cbb5e96e9 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); }