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.0KB

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