code.gitea.io/gitea@v1.19.3/modules/gitgraph/graph_models.go (about)

     1  // Copyright 2020 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package gitgraph
     5  
     6  import (
     7  	"bytes"
     8  	"context"
     9  	"fmt"
    10  	"strings"
    11  
    12  	asymkey_model "code.gitea.io/gitea/models/asymkey"
    13  	"code.gitea.io/gitea/models/db"
    14  	git_model "code.gitea.io/gitea/models/git"
    15  	repo_model "code.gitea.io/gitea/models/repo"
    16  	user_model "code.gitea.io/gitea/models/user"
    17  	"code.gitea.io/gitea/modules/git"
    18  	"code.gitea.io/gitea/modules/log"
    19  )
    20  
    21  // NewGraph creates a basic graph
    22  func NewGraph() *Graph {
    23  	graph := &Graph{}
    24  	graph.relationCommit = &Commit{
    25  		Row:    -1,
    26  		Column: -1,
    27  	}
    28  	graph.Flows = map[int64]*Flow{}
    29  	return graph
    30  }
    31  
    32  // Graph represents a collection of flows
    33  type Graph struct {
    34  	Flows          map[int64]*Flow
    35  	Commits        []*Commit
    36  	MinRow         int
    37  	MinColumn      int
    38  	MaxRow         int
    39  	MaxColumn      int
    40  	relationCommit *Commit
    41  }
    42  
    43  // Width returns the width of the graph
    44  func (graph *Graph) Width() int {
    45  	return graph.MaxColumn - graph.MinColumn + 1
    46  }
    47  
    48  // Height returns the height of the graph
    49  func (graph *Graph) Height() int {
    50  	return graph.MaxRow - graph.MinRow + 1
    51  }
    52  
    53  // AddGlyph adds glyph to flows
    54  func (graph *Graph) AddGlyph(row, column int, flowID int64, color int, glyph byte) {
    55  	flow, ok := graph.Flows[flowID]
    56  	if !ok {
    57  		flow = NewFlow(flowID, color, row, column)
    58  		graph.Flows[flowID] = flow
    59  	}
    60  	flow.AddGlyph(row, column, glyph)
    61  
    62  	if row < graph.MinRow {
    63  		graph.MinRow = row
    64  	}
    65  	if row > graph.MaxRow {
    66  		graph.MaxRow = row
    67  	}
    68  	if column < graph.MinColumn {
    69  		graph.MinColumn = column
    70  	}
    71  	if column > graph.MaxColumn {
    72  		graph.MaxColumn = column
    73  	}
    74  }
    75  
    76  // AddCommit adds a commit at row, column on flowID with the provided data
    77  func (graph *Graph) AddCommit(row, column int, flowID int64, data []byte) error {
    78  	commit, err := NewCommit(row, column, data)
    79  	if err != nil {
    80  		return err
    81  	}
    82  	commit.Flow = flowID
    83  	graph.Commits = append(graph.Commits, commit)
    84  
    85  	graph.Flows[flowID].Commits = append(graph.Flows[flowID].Commits, commit)
    86  	return nil
    87  }
    88  
    89  // LoadAndProcessCommits will load the git.Commits for each commit in the graph,
    90  // the associate the commit with the user author, and check the commit verification
    91  // before finally retrieving the latest status
    92  func (graph *Graph) LoadAndProcessCommits(ctx context.Context, repository *repo_model.Repository, gitRepo *git.Repository) error {
    93  	var err error
    94  	var ok bool
    95  
    96  	emails := map[string]*user_model.User{}
    97  	keyMap := map[string]bool{}
    98  
    99  	for _, c := range graph.Commits {
   100  		if len(c.Rev) == 0 {
   101  			continue
   102  		}
   103  		c.Commit, err = gitRepo.GetCommit(c.Rev)
   104  		if err != nil {
   105  			return fmt.Errorf("GetCommit: %s Error: %w", c.Rev, err)
   106  		}
   107  
   108  		if c.Commit.Author != nil {
   109  			email := c.Commit.Author.Email
   110  			if c.User, ok = emails[email]; !ok {
   111  				c.User, _ = user_model.GetUserByEmail(ctx, email)
   112  				emails[email] = c.User
   113  			}
   114  		}
   115  
   116  		c.Verification = asymkey_model.ParseCommitWithSignature(ctx, c.Commit)
   117  
   118  		_ = asymkey_model.CalculateTrustStatus(c.Verification, repository.GetTrustModel(), func(user *user_model.User) (bool, error) {
   119  			return repo_model.IsOwnerMemberCollaborator(repository, user.ID)
   120  		}, &keyMap)
   121  
   122  		statuses, _, err := git_model.GetLatestCommitStatus(db.DefaultContext, repository.ID, c.Commit.ID.String(), db.ListOptions{})
   123  		if err != nil {
   124  			log.Error("GetLatestCommitStatus: %v", err)
   125  		} else {
   126  			c.Status = git_model.CalcCommitStatus(statuses)
   127  		}
   128  	}
   129  	return nil
   130  }
   131  
   132  // NewFlow creates a new flow
   133  func NewFlow(flowID int64, color, row, column int) *Flow {
   134  	return &Flow{
   135  		ID:          flowID,
   136  		ColorNumber: color,
   137  		MinRow:      row,
   138  		MinColumn:   column,
   139  		MaxRow:      row,
   140  		MaxColumn:   column,
   141  	}
   142  }
   143  
   144  // Flow represents a series of glyphs
   145  type Flow struct {
   146  	ID          int64
   147  	ColorNumber int
   148  	Glyphs      []Glyph
   149  	Commits     []*Commit
   150  	MinRow      int
   151  	MinColumn   int
   152  	MaxRow      int
   153  	MaxColumn   int
   154  }
   155  
   156  // Color16 wraps the color numbers around mod 16
   157  func (flow *Flow) Color16() int {
   158  	return flow.ColorNumber % 16
   159  }
   160  
   161  // AddGlyph adds glyph at row and column
   162  func (flow *Flow) AddGlyph(row, column int, glyph byte) {
   163  	if row < flow.MinRow {
   164  		flow.MinRow = row
   165  	}
   166  	if row > flow.MaxRow {
   167  		flow.MaxRow = row
   168  	}
   169  	if column < flow.MinColumn {
   170  		flow.MinColumn = column
   171  	}
   172  	if column > flow.MaxColumn {
   173  		flow.MaxColumn = column
   174  	}
   175  
   176  	flow.Glyphs = append(flow.Glyphs, Glyph{
   177  		row,
   178  		column,
   179  		glyph,
   180  	})
   181  }
   182  
   183  // Glyph represents a co-ordinate and glyph
   184  type Glyph struct {
   185  	Row    int
   186  	Column int
   187  	Glyph  byte
   188  }
   189  
   190  // RelationCommit represents an empty relation commit
   191  var RelationCommit = &Commit{
   192  	Row: -1,
   193  }
   194  
   195  // NewCommit creates a new commit from a provided line
   196  func NewCommit(row, column int, line []byte) (*Commit, error) {
   197  	data := bytes.SplitN(line, []byte("|"), 5)
   198  	if len(data) < 5 {
   199  		return nil, fmt.Errorf("malformed data section on line %d with commit: %s", row, string(line))
   200  	}
   201  	return &Commit{
   202  		Row:    row,
   203  		Column: column,
   204  		// 0 matches git log --pretty=format:%d => ref names, like the --decorate option of git-log(1)
   205  		Refs: newRefsFromRefNames(data[0]),
   206  		// 1 matches git log --pretty=format:%H => commit hash
   207  		Rev: string(data[1]),
   208  		// 2 matches git log --pretty=format:%ad => author date (format respects --date= option)
   209  		Date: string(data[2]),
   210  		// 3 matches git log --pretty=format:%h => abbreviated commit hash
   211  		ShortRev: string(data[3]),
   212  		// 4 matches git log --pretty=format:%s => subject
   213  		Subject: string(data[4]),
   214  	}, nil
   215  }
   216  
   217  func newRefsFromRefNames(refNames []byte) []git.Reference {
   218  	refBytes := bytes.Split(refNames, []byte{',', ' '})
   219  	refs := make([]git.Reference, 0, len(refBytes))
   220  	for _, refNameBytes := range refBytes {
   221  		if len(refNameBytes) == 0 {
   222  			continue
   223  		}
   224  		refName := string(refNameBytes)
   225  		if strings.HasPrefix(refName, "tag: ") {
   226  			refName = strings.TrimPrefix(refName, "tag: ")
   227  		} else {
   228  			refName = strings.TrimPrefix(refName, "HEAD -> ")
   229  		}
   230  		refs = append(refs, git.Reference{
   231  			Name: refName,
   232  		})
   233  	}
   234  	return refs
   235  }
   236  
   237  // Commit represents a commit at co-ordinate X, Y with the data
   238  type Commit struct {
   239  	Commit       *git.Commit
   240  	User         *user_model.User
   241  	Verification *asymkey_model.CommitVerification
   242  	Status       *git_model.CommitStatus
   243  	Flow         int64
   244  	Row          int
   245  	Column       int
   246  	Refs         []git.Reference
   247  	Rev          string
   248  	Date         string
   249  	ShortRev     string
   250  	Subject      string
   251  }
   252  
   253  // OnlyRelation returns whether this a relation only commit
   254  func (c *Commit) OnlyRelation() bool {
   255  	return c.Row == -1
   256  }