mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-31 21:28:11 +09: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:
		| @@ -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) | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
		Reference in New Issue
	
	Block a user