2020-08-06 11:04:08 +03:00
// Copyright 2020 The Gitea Authors. All rights reserved.
2022-11-27 21:20:29 +03:00
// SPDX-License-Identifier: MIT
2020-08-06 11:04:08 +03:00
package gitgraph
import (
"bytes"
"fmt"
2021-04-17 12:27:25 +03:00
"strings"
2020-11-08 20:21:54 +03:00
2021-12-10 11:14:24 +03:00
asymkey_model "code.gitea.io/gitea/models/asymkey"
2021-09-24 14:32:56 +03:00
"code.gitea.io/gitea/models/db"
2022-06-12 18:51:54 +03:00
git_model "code.gitea.io/gitea/models/git"
2021-12-10 04:27:50 +03:00
repo_model "code.gitea.io/gitea/models/repo"
2021-11-24 12:49:20 +03:00
user_model "code.gitea.io/gitea/models/user"
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
2021-12-10 04:27:50 +03:00
func ( graph * Graph ) LoadAndProcessCommits ( repository * repo_model . Repository , gitRepo * git . Repository ) error {
2020-11-08 20:21:54 +03:00
var err error
var ok bool
2021-11-24 12:49:20 +03:00
emails := map [ string ] * user_model . User { }
2020-11-08 20:21:54 +03:00
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 {
2021-11-24 12:49:20 +03:00
c . User , _ = user_model . GetUserByEmail ( email )
2020-11-08 20:21:54 +03:00
emails [ email ] = c . User
}
}
2021-12-10 11:14:24 +03:00
c . Verification = asymkey_model . ParseCommitWithSignature ( c . Commit )
2020-11-08 20:21:54 +03:00
2021-12-10 11:14:24 +03:00
_ = asymkey_model . CalculateTrustStatus ( c . Verification , repository . GetTrustModel ( ) , func ( user * user_model . User ) ( bool , error ) {
2022-06-12 18:51:54 +03:00
return repo_model . IsOwnerMemberCollaborator ( repository , user . ID )
2021-12-10 11:14:24 +03:00
} , & keyMap )
2020-11-08 20:21:54 +03:00
2022-06-12 18:51:54 +03:00
statuses , _ , err := git_model . GetLatestCommitStatus ( db . DefaultContext , 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 {
2022-06-12 18:51:54 +03:00
c . Status = git_model . CalcCommitStatus ( statuses )
2020-11-08 20:21:54 +03:00
}
}
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: " )
2021-10-17 22:47:12 +03:00
} else {
2021-04-17 12:27:25 +03:00
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
2021-11-24 12:49:20 +03:00
User * user_model . User
2021-12-10 11:14:24 +03:00
Verification * asymkey_model . CommitVerification
2022-06-12 18:51:54 +03:00
Status * git_model . CommitStatus
2020-11-08 20:21:54 +03:00
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
}