257 lines · 6.3 KB · Go
RawDownload
// Copyright 2020 The Gitea Authors. All rights reserved.// Use of this source code is governed by a MIT-style// license that can be found in the LICENSE file. package gitgraph import (	"bytes"	"fmt"	"strings" 	"code.gitea.io/gitea/models"	asymkey_model "code.gitea.io/gitea/models/asymkey"	"code.gitea.io/gitea/models/db"	repo_model "code.gitea.io/gitea/models/repo"	user_model "code.gitea.io/gitea/models/user"	"code.gitea.io/gitea/modules/git"	"code.gitea.io/gitea/modules/log") // NewGraph creates a basic graphfunc NewGraph() *Graph {	graph := &Graph{}	graph.relationCommit = &Commit{		Row:    -1,		Column: -1,	}	graph.Flows = map[int64]*Flow{}	return graph} // Graph represents a collection of flowstype Graph struct {	Flows          map[int64]*Flow	Commits        []*Commit	MinRow         int	MinColumn      int	MaxRow         int	MaxColumn      int	relationCommit *Commit} // Width returns the width of the graphfunc (graph *Graph) Width() int {	return graph.MaxColumn - graph.MinColumn + 1} // Height returns the height of the graphfunc (graph *Graph) Height() int {	return graph.MaxRow - graph.MinRow + 1} // AddGlyph adds glyph to flowsfunc (graph *Graph) AddGlyph(row, column int, flowID int64, color int, glyph byte) {	flow, ok := graph.Flows[flowID]	if !ok {		flow = NewFlow(flowID, color, row, column)		graph.Flows[flowID] = flow	}	flow.AddGlyph(row, column, glyph) 	if row < graph.MinRow {		graph.MinRow = row	}	if row > graph.MaxRow {		graph.MaxRow = row	}	if column < graph.MinColumn {		graph.MinColumn = column	}	if column > graph.MaxColumn {		graph.MaxColumn = column	}} // AddCommit adds a commit at row, column on flowID with the provided datafunc (graph *Graph) AddCommit(row, column int, flowID int64, data []byte) error {	commit, err := NewCommit(row, column, data)	if err != nil {		return err	}	commit.Flow = flowID	graph.Commits = append(graph.Commits, commit) 	graph.Flows[flowID].Commits = append(graph.Flows[flowID].Commits, commit)	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 statusfunc (graph *Graph) LoadAndProcessCommits(repository *repo_model.Repository, gitRepo *git.Repository) error {	var err error 	var ok bool 	emails := map[string]*user_model.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, _ = user_model.GetUserByEmail(email)				emails[email] = c.User			}		} 		c.Verification = asymkey_model.ParseCommitWithSignature(c.Commit) 		_ = asymkey_model.CalculateTrustStatus(c.Verification, repository.GetTrustModel(), func(user *user_model.User) (bool, error) {			return models.IsUserRepoAdmin(repository, user)		}, &keyMap) 		statuses, _, err := models.GetLatestCommitStatus(repository.ID, c.Commit.ID.String(), db.ListOptions{})		if err != nil {			log.Error("GetLatestCommitStatus: %v", err)		} else {			c.Status = models.CalcCommitStatus(statuses)		}	}	return nil} // NewFlow creates a new flowfunc NewFlow(flowID int64, color, row, column int) *Flow {	return &Flow{		ID:          flowID,		ColorNumber: color,		MinRow:      row,		MinColumn:   column,		MaxRow:      row,		MaxColumn:   column,	}} // Flow represents a series of glyphstype Flow struct {	ID          int64	ColorNumber int	Glyphs      []Glyph	Commits     []*Commit	MinRow      int	MinColumn   int	MaxRow      int	MaxColumn   int} // Color16 wraps the color numbers around mod 16func (flow *Flow) Color16() int {	return flow.ColorNumber % 16} // AddGlyph adds glyph at row and columnfunc (flow *Flow) AddGlyph(row, column int, glyph byte) {	if row < flow.MinRow {		flow.MinRow = row	}	if row > flow.MaxRow {		flow.MaxRow = row	}	if column < flow.MinColumn {		flow.MinColumn = column	}	if column > flow.MaxColumn {		flow.MaxColumn = column	} 	flow.Glyphs = append(flow.Glyphs, Glyph{		row,		column,		glyph,	})} // Glyph represents a co-ordinate and glyphtype Glyph struct {	Row    int	Column int	Glyph  byte} // RelationCommit represents an empty relation commitvar RelationCommit = &Commit{	Row: -1,} // NewCommit creates a new commit from a provided linefunc NewCommit(row, column int, line []byte) (*Commit, error) {	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)		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:%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 strings.HasPrefix(refName, "tag: ") {			refName = strings.TrimPrefix(refName, "tag: ")		} else {			refName = strings.TrimPrefix(refName, "HEAD -> ")		}		refs = append(refs, git.Reference{			Name: refName,		})	}	return refs} // Commit represents a commit at co-ordinate X, Y with the datatype Commit struct {	Commit       *git.Commit	User         *user_model.User	Verification *asymkey_model.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 commitfunc (c *Commit) OnlyRelation() bool {	return c.Row == -1}