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.

commit_info.go 6.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. // Copyright 2017 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 git
  5. import (
  6. "github.com/emirpasic/gods/trees/binaryheap"
  7. "gopkg.in/src-d/go-git.v4/plumbing"
  8. "gopkg.in/src-d/go-git.v4/plumbing/object"
  9. )
  10. // GetCommitsInfo gets information of all commits that are corresponding to these entries
  11. func (tes Entries) GetCommitsInfo(commit *Commit, treePath string, cache LastCommitCache) ([][]interface{}, *Commit, error) {
  12. entryPaths := make([]string, len(tes)+1)
  13. // Get the commit for the treePath itself
  14. entryPaths[0] = ""
  15. for i, entry := range tes {
  16. entryPaths[i+1] = entry.Name()
  17. }
  18. c, err := commit.repo.gogitRepo.CommitObject(plumbing.Hash(commit.ID))
  19. if err != nil {
  20. return nil, nil, err
  21. }
  22. revs, err := getLastCommitForPaths(c, treePath, entryPaths)
  23. if err != nil {
  24. return nil, nil, err
  25. }
  26. commit.repo.gogitStorage.Close()
  27. commitsInfo := make([][]interface{}, len(tes))
  28. for i, entry := range tes {
  29. if rev, ok := revs[entry.Name()]; ok {
  30. entryCommit := convertCommit(rev)
  31. if entry.IsSubModule() {
  32. subModuleURL := ""
  33. if subModule, err := commit.GetSubModule(entry.Name()); err != nil {
  34. return nil, nil, err
  35. } else if subModule != nil {
  36. subModuleURL = subModule.URL
  37. }
  38. subModuleFile := NewSubModuleFile(entryCommit, subModuleURL, entry.ID.String())
  39. commitsInfo[i] = []interface{}{entry, subModuleFile}
  40. } else {
  41. commitsInfo[i] = []interface{}{entry, entryCommit}
  42. }
  43. } else {
  44. commitsInfo[i] = []interface{}{entry, nil}
  45. }
  46. }
  47. // Retrieve the commit for the treePath itself (see above). We basically
  48. // get it for free during the tree traversal and it's used for listing
  49. // pages to display information about newest commit for a given path.
  50. var treeCommit *Commit
  51. if rev, ok := revs[""]; ok {
  52. treeCommit = convertCommit(rev)
  53. }
  54. return commitsInfo, treeCommit, nil
  55. }
  56. type commitAndPaths struct {
  57. commit *object.Commit
  58. // Paths that are still on the branch represented by commit
  59. paths []string
  60. // Set of hashes for the paths
  61. hashes map[string]plumbing.Hash
  62. }
  63. func getCommitTree(c *object.Commit, treePath string) (*object.Tree, error) {
  64. tree, err := c.Tree()
  65. if err != nil {
  66. return nil, err
  67. }
  68. // Optimize deep traversals by focusing only on the specific tree
  69. if treePath != "" {
  70. tree, err = tree.Tree(treePath)
  71. if err != nil {
  72. return nil, err
  73. }
  74. }
  75. return tree, nil
  76. }
  77. func getFullPath(treePath, path string) string {
  78. if treePath != "" {
  79. if path != "" {
  80. return treePath + "/" + path
  81. }
  82. return treePath
  83. }
  84. return path
  85. }
  86. func getFileHashes(c *object.Commit, treePath string, paths []string) (map[string]plumbing.Hash, error) {
  87. tree, err := getCommitTree(c, treePath)
  88. if err == object.ErrDirectoryNotFound {
  89. // The whole tree didn't exist, so return empty map
  90. return make(map[string]plumbing.Hash), nil
  91. }
  92. if err != nil {
  93. return nil, err
  94. }
  95. hashes := make(map[string]plumbing.Hash)
  96. for _, path := range paths {
  97. if path != "" {
  98. entry, err := tree.FindEntry(path)
  99. if err == nil {
  100. hashes[path] = entry.Hash
  101. }
  102. } else {
  103. hashes[path] = tree.Hash
  104. }
  105. }
  106. return hashes, nil
  107. }
  108. func getLastCommitForPaths(c *object.Commit, treePath string, paths []string) (map[string]*object.Commit, error) {
  109. // We do a tree traversal with nodes sorted by commit time
  110. seen := make(map[plumbing.Hash]bool)
  111. heap := binaryheap.NewWith(func(a, b interface{}) int {
  112. if a.(*commitAndPaths).commit.Committer.When.Before(b.(*commitAndPaths).commit.Committer.When) {
  113. return 1
  114. }
  115. return -1
  116. })
  117. result := make(map[string]*object.Commit)
  118. initialHashes, err := getFileHashes(c, treePath, paths)
  119. if err != nil {
  120. return nil, err
  121. }
  122. // Start search from the root commit and with full set of paths
  123. heap.Push(&commitAndPaths{c, paths, initialHashes})
  124. for {
  125. cIn, ok := heap.Pop()
  126. if !ok {
  127. break
  128. }
  129. current := cIn.(*commitAndPaths)
  130. currentID := current.commit.ID()
  131. if seen[currentID] {
  132. continue
  133. }
  134. seen[currentID] = true
  135. // Load the parent commits for the one we are currently examining
  136. numParents := current.commit.NumParents()
  137. var parents []*object.Commit
  138. for i := 0; i < numParents; i++ {
  139. parent, err := current.commit.Parent(i)
  140. if err != nil {
  141. break
  142. }
  143. parents = append(parents, parent)
  144. }
  145. // Examine the current commit and set of interesting paths
  146. numOfParentsWithPath := make([]int, len(current.paths))
  147. pathChanged := make([]bool, len(current.paths))
  148. parentHashes := make([]map[string]plumbing.Hash, len(parents))
  149. for j, parent := range parents {
  150. parentHashes[j], err = getFileHashes(parent, treePath, current.paths)
  151. if err != nil {
  152. break
  153. }
  154. for i, path := range current.paths {
  155. if parentHashes[j][path] != plumbing.ZeroHash {
  156. numOfParentsWithPath[i]++
  157. if parentHashes[j][path] != current.hashes[path] {
  158. pathChanged[i] = true
  159. }
  160. }
  161. }
  162. }
  163. var remainingPaths []string
  164. for i, path := range current.paths {
  165. switch numOfParentsWithPath[i] {
  166. case 0:
  167. // The path didn't exist in any parent, so it must have been created by
  168. // this commit. The results could already contain some newer change from
  169. // different path, so don't override that.
  170. if result[path] == nil {
  171. result[path] = current.commit
  172. }
  173. case 1:
  174. // The file is present on exactly one parent, so check if it was changed
  175. // and save the revision if it did.
  176. if pathChanged[i] {
  177. if result[path] == nil {
  178. result[path] = current.commit
  179. }
  180. } else {
  181. remainingPaths = append(remainingPaths, path)
  182. }
  183. default:
  184. // The file is present on more than one of the parent paths, so this is
  185. // a merge. We have to examine all the parent trees to find out where
  186. // the change occurred. pathChanged[i] would tell us that the file was
  187. // changed during the merge, but it wouldn't tell us the relevant commit
  188. // that introduced it.
  189. remainingPaths = append(remainingPaths, path)
  190. }
  191. }
  192. if len(remainingPaths) > 0 {
  193. // Add the parent nodes along with remaining paths to the heap for further
  194. // processing.
  195. for j, parent := range parents {
  196. if seen[parent.ID()] {
  197. continue
  198. }
  199. // Combine remainingPath with paths available on the parent branch
  200. // and make union of them
  201. var remainingPathsForParent []string
  202. for _, path := range remainingPaths {
  203. if parentHashes[j][path] != plumbing.ZeroHash {
  204. remainingPathsForParent = append(remainingPathsForParent, path)
  205. }
  206. }
  207. heap.Push(&commitAndPaths{parent, remainingPathsForParent, parentHashes[j]})
  208. }
  209. }
  210. }
  211. return result, nil
  212. }