mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-11-03 17:24:22 +00: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