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 }