You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

graph_models.go 6.3KB

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