2020-08-06 11:04:08 +03:00
|
|
|
// 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"
|
2021-04-17 12:27:25 +03:00
|
|
|
"strings"
|
2020-11-08 20:21:54 +03:00
|
|
|
|
|
|
|
"code.gitea.io/gitea/models"
|
2021-09-24 14:32:56 +03:00
|
|
|
"code.gitea.io/gitea/models/db"
|
2020-11-08 20:21:54 +03:00
|
|
|
"code.gitea.io/gitea/modules/git"
|
|
|
|
"code.gitea.io/gitea/modules/log"
|
2020-08-06 11:04:08 +03:00
|
|
|
)
|
|
|
|
|
|
|
|
// NewGraph creates a basic graph
|
|
|
|
func NewGraph() *Graph {
|
|
|
|
graph := &Graph{}
|
|
|
|
graph.relationCommit = &Commit{
|
|
|
|
Row: -1,
|
|
|
|
Column: -1,
|
|
|
|
}
|
|
|
|
graph.Flows = map[int64]*Flow{}
|
|
|
|
return graph
|
|
|
|
}
|
|
|
|
|
|
|
|
// Graph represents a collection of flows
|
|
|
|
type Graph struct {
|
|
|
|
Flows map[int64]*Flow
|
|
|
|
Commits []*Commit
|
|
|
|
MinRow int
|
|
|
|
MinColumn int
|
|
|
|
MaxRow int
|
|
|
|
MaxColumn int
|
|
|
|
relationCommit *Commit
|
|
|
|
}
|
|
|
|
|
|
|
|
// Width returns the width of the graph
|
|
|
|
func (graph *Graph) Width() int {
|
|
|
|
return graph.MaxColumn - graph.MinColumn + 1
|
|
|
|
}
|
|
|
|
|
|
|
|
// Height returns the height of the graph
|
|
|
|
func (graph *Graph) Height() int {
|
|
|
|
return graph.MaxRow - graph.MinRow + 1
|
|
|
|
}
|
|
|
|
|
|
|
|
// AddGlyph adds glyph to flows
|
|
|
|
func (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 data
|
|
|
|
func (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
|
|
|
|
}
|
|
|
|
|
2020-11-08 20:21:54 +03:00
|
|
|
// 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)
|
|
|
|
|
2021-09-24 14:32:56 +03:00
|
|
|
statuses, err := models.GetLatestCommitStatus(repository.ID, c.Commit.ID.String(), db.ListOptions{})
|
2020-11-08 20:21:54 +03:00
|
|
|
if err != nil {
|
|
|
|
log.Error("GetLatestCommitStatus: %v", err)
|
|
|
|
} else {
|
|
|
|
c.Status = models.CalcCommitStatus(statuses)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-08-06 11:04:08 +03:00
|
|
|
// NewFlow creates a new flow
|
|
|
|
func 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 glyphs
|
|
|
|
type 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 16
|
|
|
|
func (flow *Flow) Color16() int {
|
|
|
|
return flow.ColorNumber % 16
|
|
|
|
}
|
|
|
|
|
|
|
|
// AddGlyph adds glyph at row and column
|
|
|
|
func (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 glyph
|
|
|
|
type Glyph struct {
|
|
|
|
Row int
|
|
|
|
Column int
|
|
|
|
Glyph byte
|
|
|
|
}
|
|
|
|
|
|
|
|
// RelationCommit represents an empty relation commit
|
|
|
|
var RelationCommit = &Commit{
|
|
|
|
Row: -1,
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewCommit creates a new commit from a provided line
|
|
|
|
func NewCommit(row, column int, line []byte) (*Commit, error) {
|
2020-11-08 20:21:54 +03:00
|
|
|
data := bytes.SplitN(line, []byte("|"), 5)
|
|
|
|
if len(data) < 5 {
|
2020-08-06 11:04:08 +03:00
|
|
|
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)
|
2020-11-08 20:21:54 +03:00
|
|
|
Refs: newRefsFromRefNames(data[0]),
|
2020-08-06 11:04:08 +03:00
|
|
|
// 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]),
|
2020-11-08 20:21:54 +03:00
|
|
|
// 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]),
|
2020-08-06 11:04:08 +03:00
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2020-11-08 20:21:54 +03:00
|
|
|
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)
|
2021-04-17 12:27:25 +03:00
|
|
|
if strings.HasPrefix(refName, "tag: ") {
|
|
|
|
refName = strings.TrimPrefix(refName, "tag: ")
|
|
|
|
} else if strings.HasPrefix(refName, "HEAD -> ") {
|
|
|
|
refName = strings.TrimPrefix(refName, "HEAD -> ")
|
2020-11-08 20:21:54 +03:00
|
|
|
}
|
|
|
|
refs = append(refs, git.Reference{
|
|
|
|
Name: refName,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
return refs
|
|
|
|
}
|
|
|
|
|
2020-08-06 11:04:08 +03:00
|
|
|
// Commit represents a commit at co-ordinate X, Y with the data
|
|
|
|
type Commit struct {
|
2020-11-08 20:21:54 +03:00
|
|
|
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
|
2020-08-06 11:04:08 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// OnlyRelation returns whether this a relation only commit
|
|
|
|
func (c *Commit) OnlyRelation() bool {
|
|
|
|
return c.Row == -1
|
|
|
|
}
|