mirror of
				https://gitee.com/gitea/gitea
				synced 2025-11-04 08:30:25 +08:00 
			
		
		
		
	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>
This commit is contained in:
		@@ -37,6 +37,12 @@ func (p *Pagination) AddParam(ctx *Context, paramKey string, ctxKey string) {
 | 
				
			|||||||
	p.urlParams = append(p.urlParams, urlParam)
 | 
						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
 | 
					// GetParams returns the configured URL params
 | 
				
			||||||
func (p *Pagination) GetParams() template.URL {
 | 
					func (p *Pagination) GetParams() template.URL {
 | 
				
			||||||
	return template.URL(strings.Join(p.urlParams, "&"))
 | 
						return template.URL(strings.Join(p.urlParams, "&"))
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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
 | 
					// BranchNameSubURL sub-URL for the BranchName field
 | 
				
			||||||
func (r *Repository) BranchNameSubURL() string {
 | 
					func (r *Repository) BranchNameSubURL() string {
 | 
				
			||||||
	switch {
 | 
						switch {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -262,8 +262,19 @@ func CommitChangesWithArgs(repoPath string, args []string, opts CommitChangesOpt
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// AllCommitsCount returns count of all commits in repository
 | 
					// AllCommitsCount returns count of all commits in repository
 | 
				
			||||||
func AllCommitsCount(repoPath string) (int64, error) {
 | 
					func AllCommitsCount(repoPath string, hidePRRefs bool, files ...string) (int64, error) {
 | 
				
			||||||
	stdout, err := NewCommand("rev-list", "--all", "--count").RunInDir(repoPath)
 | 
						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 {
 | 
						if err != nil {
 | 
				
			||||||
		return 0, err
 | 
							return 0, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -271,7 +282,8 @@ func AllCommitsCount(repoPath string) (int64, error) {
 | 
				
			|||||||
	return strconv.ParseInt(strings.TrimSpace(stdout), 10, 64)
 | 
						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 := NewCommand("rev-list", "--count")
 | 
				
			||||||
	cmd.AddArguments(revision...)
 | 
						cmd.AddArguments(revision...)
 | 
				
			||||||
	if len(relpath) > 0 {
 | 
						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.
 | 
					// CommitsCount returns number of total commits of until given revision.
 | 
				
			||||||
func CommitsCount(repoPath, revision string) (int64, error) {
 | 
					func CommitsCount(repoPath string, revision ...string) (int64, error) {
 | 
				
			||||||
	return commitsCount(repoPath, []string{revision}, []string{})
 | 
						return CommitsCountFiles(repoPath, revision, []string{})
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// CommitsCount returns number of total commits of until current revision.
 | 
					// CommitsCount returns number of total commits of until current revision.
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,6 +4,8 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
package git
 | 
					package git
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import "strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Reference represents a Git ref.
 | 
					// Reference represents a Git ref.
 | 
				
			||||||
type Reference struct {
 | 
					type Reference struct {
 | 
				
			||||||
	Name   string
 | 
						Name   string
 | 
				
			||||||
@@ -16,3 +18,44 @@ type Reference struct {
 | 
				
			|||||||
func (ref *Reference) Commit() (*Commit, error) {
 | 
					func (ref *Reference) Commit() (*Commit, error) {
 | 
				
			||||||
	return ref.repo.getCommit(ref.Object)
 | 
						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 ""
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -49,7 +49,7 @@ const prettyLogFormat = `--pretty=format:%H`
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// GetAllCommitsCount returns count of all commits in repository
 | 
					// GetAllCommitsCount returns count of all commits in repository
 | 
				
			||||||
func (repo *Repository) GetAllCommitsCount() (int64, error) {
 | 
					func (repo *Repository) GetAllCommitsCount() (int64, error) {
 | 
				
			||||||
	return AllCommitsCount(repo.Path)
 | 
						return AllCommitsCount(repo.Path, false)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (repo *Repository) parsePrettyFormatLogToList(logs []byte) (*list.List, error) {
 | 
					func (repo *Repository) parsePrettyFormatLogToList(logs []byte) (*list.List, error) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -318,7 +318,7 @@ func (repo *Repository) FileChangedBetweenCommits(filename, id1, id2 string) (bo
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// FileCommitsCount return the number of files at a revison
 | 
					// FileCommitsCount return the number of files at a revison
 | 
				
			||||||
func (repo *Repository) FileCommitsCount(revision, file string) (int64, error) {
 | 
					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
 | 
					// 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
 | 
					// CommitsCountBetween return numbers of commits between two commits
 | 
				
			||||||
func (repo *Repository) CommitsCountBetween(start, end string) (int64, error) {
 | 
					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") {
 | 
						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.
 | 
							// 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...
 | 
							// 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
 | 
						return count, err
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -17,23 +17,42 @@ import (
 | 
				
			|||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetCommitGraph return a list of commit (GraphItems) from all branches
 | 
					// GetCommitGraph return a list of commit (GraphItems) from all branches
 | 
				
			||||||
func GetCommitGraph(r *git.Repository, page int, maxAllowedColors int) (*Graph, error) {
 | 
					func GetCommitGraph(r *git.Repository, page int, maxAllowedColors int, hidePRRefs bool, branches, files []string) (*Graph, error) {
 | 
				
			||||||
	format := "DATA:%d|%H|%ad|%an|%ae|%h|%s"
 | 
						format := "DATA:%D|%H|%ad|%h|%s"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if page == 0 {
 | 
						if page == 0 {
 | 
				
			||||||
		page = 1
 | 
							page = 1
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	graphCmd := git.NewCommand("log")
 | 
						args := make([]string, 0, 12+len(branches)+len(files))
 | 
				
			||||||
	graphCmd.AddArguments("--graph",
 | 
					
 | 
				
			||||||
		"--date-order",
 | 
						args = append(args, "--graph", "--date-order", "--decorate=full")
 | 
				
			||||||
		"--all",
 | 
					
 | 
				
			||||||
 | 
						if hidePRRefs {
 | 
				
			||||||
 | 
							args = append(args, "--exclude=refs/pull/*")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if len(branches) == 0 {
 | 
				
			||||||
 | 
							args = append(args, "--all")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						args = append(args,
 | 
				
			||||||
		"-C",
 | 
							"-C",
 | 
				
			||||||
		"-M",
 | 
							"-M",
 | 
				
			||||||
		fmt.Sprintf("-n %d", setting.UI.GraphMaxCommitNum*page),
 | 
							fmt.Sprintf("-n %d", setting.UI.GraphMaxCommitNum*page),
 | 
				
			||||||
		"--date=iso",
 | 
							"--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()
 | 
						graph := NewGraph()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	stderr := new(strings.Builder)
 | 
						stderr := new(strings.Builder)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,6 +7,10 @@ package gitgraph
 | 
				
			|||||||
import (
 | 
					import (
 | 
				
			||||||
	"bytes"
 | 
						"bytes"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/models"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/git"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/log"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// NewGraph creates a basic graph
 | 
					// NewGraph creates a basic graph
 | 
				
			||||||
@@ -77,6 +81,48 @@ func (graph *Graph) AddCommit(row, column int, flowID int64, data []byte) error
 | 
				
			|||||||
	return nil
 | 
						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
 | 
					// NewFlow creates a new flow
 | 
				
			||||||
func NewFlow(flowID int64, color, row, column int) *Flow {
 | 
					func NewFlow(flowID int64, color, row, column int) *Flow {
 | 
				
			||||||
	return &Flow{
 | 
						return &Flow{
 | 
				
			||||||
@@ -142,40 +188,58 @@ var RelationCommit = &Commit{
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// NewCommit creates a new commit from a provided line
 | 
					// NewCommit creates a new commit from a provided line
 | 
				
			||||||
func NewCommit(row, column int, line []byte) (*Commit, error) {
 | 
					func NewCommit(row, column int, line []byte) (*Commit, error) {
 | 
				
			||||||
	data := bytes.SplitN(line, []byte("|"), 7)
 | 
						data := bytes.SplitN(line, []byte("|"), 5)
 | 
				
			||||||
	if len(data) < 7 {
 | 
						if len(data) < 5 {
 | 
				
			||||||
		return nil, fmt.Errorf("malformed data section on line %d with commit: %s", row, string(line))
 | 
							return nil, fmt.Errorf("malformed data section on line %d with commit: %s", row, string(line))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return &Commit{
 | 
						return &Commit{
 | 
				
			||||||
		Row:    row,
 | 
							Row:    row,
 | 
				
			||||||
		Column: column,
 | 
							Column: column,
 | 
				
			||||||
		// 0 matches git log --pretty=format:%d => ref names, like the --decorate option of git-log(1)
 | 
							// 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
 | 
							// 1 matches git log --pretty=format:%H => commit hash
 | 
				
			||||||
		Rev: string(data[1]),
 | 
							Rev: string(data[1]),
 | 
				
			||||||
		// 2 matches git log --pretty=format:%ad => author date (format respects --date= option)
 | 
							// 2 matches git log --pretty=format:%ad => author date (format respects --date= option)
 | 
				
			||||||
		Date: string(data[2]),
 | 
							Date: string(data[2]),
 | 
				
			||||||
		// 3 matches git log --pretty=format:%an => author name
 | 
							// 3 matches git log --pretty=format:%h => abbreviated commit hash
 | 
				
			||||||
		Author: string(data[3]),
 | 
							ShortRev: string(data[3]),
 | 
				
			||||||
		// 4 matches git log --pretty=format:%ae => author email
 | 
							// 4 matches git log --pretty=format:%s => subject
 | 
				
			||||||
		AuthorEmail: string(data[4]),
 | 
							Subject: 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]),
 | 
					 | 
				
			||||||
	}, nil
 | 
						}, 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
 | 
					// Commit represents a commit at co-ordinate X, Y with the data
 | 
				
			||||||
type Commit struct {
 | 
					type Commit struct {
 | 
				
			||||||
 | 
						Commit       *git.Commit
 | 
				
			||||||
 | 
						User         *models.User
 | 
				
			||||||
 | 
						Verification *models.CommitVerification
 | 
				
			||||||
 | 
						Status       *models.CommitStatus
 | 
				
			||||||
	Flow         int64
 | 
						Flow         int64
 | 
				
			||||||
	Row          int
 | 
						Row          int
 | 
				
			||||||
	Column       int
 | 
						Column       int
 | 
				
			||||||
	Branch      string
 | 
						Refs         []git.Reference
 | 
				
			||||||
	Rev          string
 | 
						Rev          string
 | 
				
			||||||
	Date         string
 | 
						Date         string
 | 
				
			||||||
	Author      string
 | 
					 | 
				
			||||||
	AuthorEmail string
 | 
					 | 
				
			||||||
	ShortRev     string
 | 
						ShortRev     string
 | 
				
			||||||
	Subject      string
 | 
						Subject      string
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -22,7 +22,7 @@ func BenchmarkGetCommitGraph(b *testing.B) {
 | 
				
			|||||||
	defer currentRepo.Close()
 | 
						defer currentRepo.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for i := 0; i < b.N; i++ {
 | 
						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 {
 | 
							if err != nil {
 | 
				
			||||||
			b.Error("Could get commit graph")
 | 
								b.Error("Could get commit graph")
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
@@ -34,7 +34,7 @@ func BenchmarkGetCommitGraph(b *testing.B) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func BenchmarkParseCommitString(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 := &Parser{}
 | 
				
			||||||
	parser.Reset()
 | 
						parser.Reset()
 | 
				
			||||||
@@ -44,7 +44,7 @@ func BenchmarkParseCommitString(b *testing.B) {
 | 
				
			|||||||
		if err := parser.AddLineToGraph(graph, 0, []byte(testString)); err != nil {
 | 
							if err := parser.AddLineToGraph(graph, 0, []byte(testString)); err != nil {
 | 
				
			||||||
			b.Error("could not parse teststring")
 | 
								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")
 | 
								b.Error("Did not get expected data")
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -244,7 +244,7 @@ func TestParseGlyphs(t *testing.T) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestCommitStringParsing(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 {
 | 
						tests := []struct {
 | 
				
			||||||
		shouldPass    bool
 | 
							shouldPass    bool
 | 
				
			||||||
		testName      string
 | 
							testName      string
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,6 +16,7 @@ import (
 | 
				
			|||||||
	"mime"
 | 
						"mime"
 | 
				
			||||||
	"net/url"
 | 
						"net/url"
 | 
				
			||||||
	"path/filepath"
 | 
						"path/filepath"
 | 
				
			||||||
 | 
						"reflect"
 | 
				
			||||||
	"regexp"
 | 
						"regexp"
 | 
				
			||||||
	"runtime"
 | 
						"runtime"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
@@ -310,6 +311,26 @@ func NewFuncMap() []template.FuncMap {
 | 
				
			|||||||
				"EventSourceUpdateTime": int(setting.UI.Notification.EventSourceUpdateTime / time.Millisecond),
 | 
									"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 {
 | 
							"contain": func(s []int64, id int64) bool {
 | 
				
			||||||
			for i := 0; i < len(s); i++ {
 | 
								for i := 0; i < len(s); i++ {
 | 
				
			||||||
				if s[i] == id {
 | 
									if s[i] == id {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -822,6 +822,8 @@ audio_not_supported_in_browser = Your browser does not support the HTML5 'audio'
 | 
				
			|||||||
stored_lfs = Stored with Git LFS
 | 
					stored_lfs = Stored with Git LFS
 | 
				
			||||||
symbolic_link = Symbolic link
 | 
					symbolic_link = Symbolic link
 | 
				
			||||||
commit_graph = Commit Graph
 | 
					commit_graph = Commit Graph
 | 
				
			||||||
 | 
					commit_graph.select = Select branches
 | 
				
			||||||
 | 
					commit_graph.hide_pr_refs = Hide Pull Requests
 | 
				
			||||||
commit_graph.monochrome = Mono
 | 
					commit_graph.monochrome = Mono
 | 
				
			||||||
commit_graph.color = Color
 | 
					commit_graph.color = Color
 | 
				
			||||||
blame = Blame
 | 
					blame = Blame
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -23,6 +23,7 @@ import (
 | 
				
			|||||||
const (
 | 
					const (
 | 
				
			||||||
	tplCommits    base.TplName = "repo/commits"
 | 
						tplCommits    base.TplName = "repo/commits"
 | 
				
			||||||
	tplGraph      base.TplName = "repo/graph"
 | 
						tplGraph      base.TplName = "repo/graph"
 | 
				
			||||||
 | 
						tplGraphDiv   base.TplName = "repo/graph/div"
 | 
				
			||||||
	tplCommitPage base.TplName = "repo/commit_page"
 | 
						tplCommitPage base.TplName = "repo/commit_page"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -88,6 +89,7 @@ func Commits(ctx *context.Context) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// Graph render commit graph - show commits from all branches.
 | 
					// Graph render commit graph - show commits from all branches.
 | 
				
			||||||
func Graph(ctx *context.Context) {
 | 
					func Graph(ctx *context.Context) {
 | 
				
			||||||
 | 
						ctx.Data["Title"] = ctx.Tr("repo.commit_graph")
 | 
				
			||||||
	ctx.Data["PageIsCommits"] = true
 | 
						ctx.Data["PageIsCommits"] = true
 | 
				
			||||||
	ctx.Data["PageIsViewCode"] = true
 | 
						ctx.Data["PageIsViewCode"] = true
 | 
				
			||||||
	mode := strings.ToLower(ctx.QueryTrim("mode"))
 | 
						mode := strings.ToLower(ctx.QueryTrim("mode"))
 | 
				
			||||||
@@ -95,6 +97,18 @@ func Graph(ctx *context.Context) {
 | 
				
			|||||||
		mode = "color"
 | 
							mode = "color"
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	ctx.Data["Mode"] = mode
 | 
						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()
 | 
						commitsCount, err := ctx.Repo.GetCommitsCount()
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
@@ -102,28 +116,60 @@ func Graph(ctx *context.Context) {
 | 
				
			|||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	allCommitsCount, err := ctx.Repo.GitRepo.GetAllCommitsCount()
 | 
						graphCommitsCount, err := ctx.Repo.GetCommitGraphsCount(hidePRRefs, realBranches, files)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		ctx.ServerError("GetAllCommitsCount", err)
 | 
							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
 | 
								return
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	page := ctx.QueryInt("page")
 | 
						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 {
 | 
						if err != nil {
 | 
				
			||||||
		ctx.ServerError("GetCommitGraph", err)
 | 
							ctx.ServerError("GetCommitGraph", err)
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := graph.LoadAndProcessCommits(ctx.Repo.Repository, ctx.Repo.GitRepo); err != nil {
 | 
				
			||||||
 | 
							ctx.ServerError("LoadAndProcessCommits", err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	ctx.Data["Graph"] = graph
 | 
						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["Username"] = ctx.Repo.Owner.Name
 | 
				
			||||||
	ctx.Data["Reponame"] = ctx.Repo.Repository.Name
 | 
						ctx.Data["Reponame"] = ctx.Repo.Repository.Name
 | 
				
			||||||
	ctx.Data["CommitCount"] = commitsCount
 | 
						ctx.Data["CommitCount"] = commitsCount
 | 
				
			||||||
	ctx.Data["Branch"] = ctx.Repo.BranchName
 | 
						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, "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
 | 
						ctx.Data["Page"] = paginator
 | 
				
			||||||
 | 
						if ctx.QueryBool("div-only") {
 | 
				
			||||||
 | 
							ctx.HTML(200, tplGraphDiv)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	ctx.HTML(200, tplGraph)
 | 
						ctx.HTML(200, tplGraph)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,60 +3,61 @@
 | 
				
			|||||||
	{{template "repo/header" .}}
 | 
						{{template "repo/header" .}}
 | 
				
			||||||
	<div class="ui container">
 | 
						<div class="ui container">
 | 
				
			||||||
		<div id="git-graph-container" class="ui segment{{if eq .Mode "monochrome"}} monochrome{{end}}">
 | 
							<div id="git-graph-container" class="ui segment{{if eq .Mode "monochrome"}} monochrome{{end}}">
 | 
				
			||||||
			<h2 class="ui header dividing">{{.i18n.Tr "repo.commit_graph"}}
 | 
								<h2 class="ui header dividing">
 | 
				
			||||||
				<div class="ui right">
 | 
									{{.i18n.Tr "repo.commit_graph"}}
 | 
				
			||||||
				<div class="ui icon buttons tiny color-buttons">
 | 
									<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>
 | 
										<div class="ui multiple selection search dropdown" id="flow-select-refs-dropdown">
 | 
				
			||||||
						<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>
 | 
											<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>
 | 
												</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>
 | 
									</div>
 | 
				
			||||||
			</h2>
 | 
								</h2>
 | 
				
			||||||
			<div class="ui dividing"></div>
 | 
								<div class="ui dividing"></div>
 | 
				
			||||||
			<div id="rel-container">
 | 
								<div class="ui segment loading hide" id="loading-indicator"></div>
 | 
				
			||||||
				<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">
 | 
								{{ template "repo/graph/svgcontainer" .}}
 | 
				
			||||||
					{{range $flowid, $flow := .Graph.Flows}}
 | 
								{{ template "repo/graph/commits" .}}
 | 
				
			||||||
						<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>
 | 
							</div>
 | 
				
			||||||
	</div>
 | 
						</div>
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
 | 
					<div id="pagination">
 | 
				
			||||||
	{{template "base/paginate" .}}
 | 
						{{template "base/paginate" .}}
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
{{template "base/footer" .}}
 | 
					{{template "base/footer" .}}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										80
									
								
								templates/repo/graph/commits.tmpl
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								templates/repo/graph/commits.tmpl
									
									
									
									
									
										Normal file
									
								
							@@ -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>
 | 
				
			||||||
							
								
								
									
										7
									
								
								templates/repo/graph/div.tmpl
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								templates/repo/graph/div.tmpl
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,7 @@
 | 
				
			|||||||
 | 
					<div>
 | 
				
			||||||
 | 
						{{template "repo/graph/svgcontainer" .}}
 | 
				
			||||||
 | 
						{{template "repo/graph/commits" .}}
 | 
				
			||||||
 | 
						<div id="pagination">
 | 
				
			||||||
 | 
							{{template "base/paginate" .}}
 | 
				
			||||||
 | 
						</div>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
							
								
								
									
										24
									
								
								templates/repo/graph/svgcontainer.tmpl
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								templates/repo/graph/svgcontainer.tmpl
									
									
									
									
									
										Normal file
									
								
							@@ -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>
 | 
				
			||||||
@@ -46,6 +46,57 @@ export default async function initGitGraph() {
 | 
				
			|||||||
      window.history.replaceState({}, '', window.location.pathname);
 | 
					      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) => {
 | 
					  $('#git-graph-container').on('mouseenter', '#rev-list li', (e) => {
 | 
				
			||||||
    const flow = $(e.currentTarget).data('flow');
 | 
					    const flow = $(e.currentTarget).data('flow');
 | 
				
			||||||
    if (flow === 0) return;
 | 
					    if (flow === 0) return;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1391,6 +1391,10 @@ table th[data-sortt-desc] {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.dropdown .ui.label {
 | 
				
			||||||
 | 
					  margin-left: 0 !important;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.ui.dropdown .menu .item {
 | 
					.ui.dropdown .menu .item {
 | 
				
			||||||
  border-radius: 0;
 | 
					  border-radius: 0;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1462,6 +1462,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  #commits-table td.sha .sha.label,
 | 
					  #commits-table td.sha .sha.label,
 | 
				
			||||||
  #repo-files-table .sha.label,
 | 
					  #repo-files-table .sha.label,
 | 
				
			||||||
 | 
					  #rev-list .sha.label,
 | 
				
			||||||
  .timeline-item.commits-list .singular-commit .sha.label {
 | 
					  .timeline-item.commits-list .singular-commit .sha.label {
 | 
				
			||||||
    border: 1px solid #bbbbbb;
 | 
					    border: 1px solid #bbbbbb;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,8 +1,21 @@
 | 
				
			|||||||
#git-graph-container {
 | 
					#git-graph-container {
 | 
				
			||||||
  float: left;
 | 
					  float: left;
 | 
				
			||||||
  display: block;
 | 
					  display: block;
 | 
				
			||||||
  overflow-x: auto;
 | 
					  overflow-x: scroll;
 | 
				
			||||||
  width: 100%;
 | 
					  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 {
 | 
					  .color-buttons {
 | 
				
			||||||
    margin-right: 0;
 | 
					    margin-right: 0;
 | 
				
			||||||
@@ -12,11 +25,49 @@
 | 
				
			|||||||
    padding-bottom: 10px;
 | 
					    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 {
 | 
					  li {
 | 
				
			||||||
    list-style-type: none;
 | 
					    list-style-type: none;
 | 
				
			||||||
    height: 20px;
 | 
					    height: 24px;
 | 
				
			||||||
    line-height: 20px;
 | 
					    line-height: 24px;
 | 
				
			||||||
    white-space: nowrap;
 | 
					    white-space: nowrap;
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    align-items: center;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    .node-relation {
 | 
					    .node-relation {
 | 
				
			||||||
      font-family: "Bitstream Vera Sans Mono", "Courier", monospace;
 | 
					      font-family: "Bitstream Vera Sans Mono", "Courier", monospace;
 | 
				
			||||||
@@ -31,10 +82,6 @@
 | 
				
			|||||||
      font-size: 80%;
 | 
					      font-size: 80%;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    a {
 | 
					 | 
				
			||||||
      color: #000000;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    a:hover {
 | 
					    a:hover {
 | 
				
			||||||
      text-decoration: underline;
 | 
					      text-decoration: underline;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -59,16 +106,39 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  #rev-list {
 | 
					  #rev-list {
 | 
				
			||||||
    margin: 0;
 | 
					    margin: 0;
 | 
				
			||||||
    padding: 0 5px;
 | 
					    padding: 0;
 | 
				
			||||||
    min-width: 95%;
 | 
					    width: 100%;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    li.highlight,
 | 
					    li.highlight.hover {
 | 
				
			||||||
    li.hover {
 | 
					 | 
				
			||||||
      background-color: rgba(0, 0, 0, .05);
 | 
					      background-color: rgba(0, 0, 0, .05);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    li.highlight.hover {
 | 
					    .tags a.button {
 | 
				
			||||||
      background-color: rgba(0, 0, 0, .1);
 | 
					      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;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1360,10 +1360,6 @@ td.blob-hunk {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
input {
 | 
					 | 
				
			||||||
  background: #2e323e;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.settings .key.list .item:not(:first-child) {
 | 
					.settings .key.list .item:not(:first-child) {
 | 
				
			||||||
  border-top: 1px solid var(--color-secondary);
 | 
					  border-top: 1px solid var(--color-secondary);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -1608,6 +1604,12 @@ a.blob-excerpt:hover {
 | 
				
			|||||||
  color: #dbdbdb;
 | 
					  color: #dbdbdb;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.ui.active.label {
 | 
				
			||||||
 | 
					  background: #393d4a;
 | 
				
			||||||
 | 
					  border-color: #393d4a;
 | 
				
			||||||
 | 
					  color: #dbdbdb;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
a.ui.label:hover,
 | 
					a.ui.label:hover,
 | 
				
			||||||
a.ui.labels .label:hover {
 | 
					a.ui.labels .label:hover {
 | 
				
			||||||
  background-color: #505667 !important;
 | 
					  background-color: #505667 !important;
 | 
				
			||||||
@@ -1617,6 +1619,7 @@ a.ui.labels .label:hover {
 | 
				
			|||||||
.sha.label,
 | 
					.sha.label,
 | 
				
			||||||
.repository #repo-files-table .sha.label,
 | 
					.repository #repo-files-table .sha.label,
 | 
				
			||||||
.repository #commits-table td.sha .sha.label,
 | 
					.repository #commits-table td.sha .sha.label,
 | 
				
			||||||
 | 
					#rev-list .sha.label,
 | 
				
			||||||
.repository .timeline-item.commits-list .singular-commit .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 {
 | 
					.repository.view.issue .comment-list .timeline-item.commits-list .singular-commit .shabox .sha.label {
 | 
				
			||||||
  border-color: #505667;
 | 
					  border-color: #505667;
 | 
				
			||||||
@@ -1624,6 +1627,7 @@ a.ui.labels .label:hover {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
.sha.label.isSigned .detail.icon,
 | 
					.sha.label.isSigned .detail.icon,
 | 
				
			||||||
.repository #commits-table td.sha .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 #repo-files-table .sha.label.isSigned .detail.icon,
 | 
				
			||||||
.repository .timeline-item.commits-list .singular-commit .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 {
 | 
					.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);
 | 
					  color: var(--color-secondary-dark-6);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#git-graph-container li a {
 | 
					 | 
				
			||||||
  color: #c79575;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#git-graph-container li .author {
 | 
					 | 
				
			||||||
  color: #c79575;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.ui.header .sub.header {
 | 
					.ui.header .sub.header {
 | 
				
			||||||
  color: var(--color-secondary-dark-6);
 | 
					  color: var(--color-secondary-dark-6);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -1970,6 +1966,10 @@ a.ui.labels .label:hover {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.ui.loading.segment:before {
 | 
				
			||||||
 | 
					  background: #353945;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.ui.popup {
 | 
					.ui.popup {
 | 
				
			||||||
  background-color: #383c4a;
 | 
					  background-color: #383c4a;
 | 
				
			||||||
  color: var(--color-secondary-dark-6);
 | 
					  color: var(--color-secondary-dark-6);
 | 
				
			||||||
@@ -2053,6 +2053,10 @@ img[src$="/img/matrix.svg"] {
 | 
				
			|||||||
  filter: invert(80%);
 | 
					  filter: invert(80%);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#git-graph-container li .time {
 | 
				
			||||||
 | 
					  color: #6a737d;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#git-graph-container.monochrome #rel-container .flow-group {
 | 
					#git-graph-container.monochrome #rel-container .flow-group {
 | 
				
			||||||
  stroke: dimgrey;
 | 
					  stroke: dimgrey;
 | 
				
			||||||
  fill: 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 {
 | 
					#git-graph-container #rev-list li.highlight.hover {
 | 
				
			||||||
  background-color: rgba(255, 255, 255, .1);
 | 
					  background-color: rgba(255, 255, 255, .1);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user