gitea源码

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. // Copyright 2020 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. //go:build !gogit
  4. package pipeline
  5. import (
  6. "bufio"
  7. "bytes"
  8. "io"
  9. "sort"
  10. "strings"
  11. "sync"
  12. "code.gitea.io/gitea/modules/git"
  13. "code.gitea.io/gitea/modules/git/gitcmd"
  14. )
  15. // FindLFSFile finds commits that contain a provided pointer file hash
  16. func FindLFSFile(repo *git.Repository, objectID git.ObjectID) ([]*LFSResult, error) {
  17. resultsMap := map[string]*LFSResult{}
  18. results := make([]*LFSResult, 0)
  19. basePath := repo.Path
  20. // Use rev-list to provide us with all commits in order
  21. revListReader, revListWriter := io.Pipe()
  22. defer func() {
  23. _ = revListWriter.Close()
  24. _ = revListReader.Close()
  25. }()
  26. go func() {
  27. stderr := strings.Builder{}
  28. err := gitcmd.NewCommand("rev-list", "--all").Run(repo.Ctx, &gitcmd.RunOpts{
  29. Dir: repo.Path,
  30. Stdout: revListWriter,
  31. Stderr: &stderr,
  32. })
  33. if err != nil {
  34. _ = revListWriter.CloseWithError(gitcmd.ConcatenateError(err, (&stderr).String()))
  35. } else {
  36. _ = revListWriter.Close()
  37. }
  38. }()
  39. // Next feed the commits in order into cat-file --batch, followed by their trees and sub trees as necessary.
  40. // so let's create a batch stdin and stdout
  41. batchStdinWriter, batchReader, cancel, err := repo.CatFileBatch(repo.Ctx)
  42. if err != nil {
  43. return nil, err
  44. }
  45. defer cancel()
  46. // We'll use a scanner for the revList because it's simpler than a bufio.Reader
  47. scan := bufio.NewScanner(revListReader)
  48. trees := [][]byte{}
  49. paths := []string{}
  50. fnameBuf := make([]byte, 4096)
  51. modeBuf := make([]byte, 40)
  52. workingShaBuf := make([]byte, objectID.Type().FullLength()/2)
  53. for scan.Scan() {
  54. // Get the next commit ID
  55. commitID := scan.Bytes()
  56. // push the commit to the cat-file --batch process
  57. _, err := batchStdinWriter.Write(commitID)
  58. if err != nil {
  59. return nil, err
  60. }
  61. _, err = batchStdinWriter.Write([]byte{'\n'})
  62. if err != nil {
  63. return nil, err
  64. }
  65. var curCommit *git.Commit
  66. curPath := ""
  67. commitReadingLoop:
  68. for {
  69. _, typ, size, err := git.ReadBatchLine(batchReader)
  70. if err != nil {
  71. return nil, err
  72. }
  73. switch typ {
  74. case "tag":
  75. // This shouldn't happen but if it does well just get the commit and try again
  76. id, err := git.ReadTagObjectID(batchReader, size)
  77. if err != nil {
  78. return nil, err
  79. }
  80. _, err = batchStdinWriter.Write([]byte(id + "\n"))
  81. if err != nil {
  82. return nil, err
  83. }
  84. continue
  85. case "commit":
  86. // Read in the commit to get its tree and in case this is one of the last used commits
  87. curCommit, err = git.CommitFromReader(repo, git.MustIDFromString(string(commitID)), io.LimitReader(batchReader, size))
  88. if err != nil {
  89. return nil, err
  90. }
  91. if _, err := batchReader.Discard(1); err != nil {
  92. return nil, err
  93. }
  94. if _, err := batchStdinWriter.Write([]byte(curCommit.Tree.ID.String() + "\n")); err != nil {
  95. return nil, err
  96. }
  97. curPath = ""
  98. case "tree":
  99. var n int64
  100. for n < size {
  101. mode, fname, binObjectID, count, err := git.ParseCatFileTreeLine(objectID.Type(), batchReader, modeBuf, fnameBuf, workingShaBuf)
  102. if err != nil {
  103. return nil, err
  104. }
  105. n += int64(count)
  106. if bytes.Equal(binObjectID, objectID.RawValue()) {
  107. result := LFSResult{
  108. Name: curPath + string(fname),
  109. SHA: curCommit.ID.String(),
  110. Summary: strings.Split(strings.TrimSpace(curCommit.CommitMessage), "\n")[0],
  111. When: curCommit.Author.When,
  112. ParentHashes: curCommit.Parents,
  113. }
  114. resultsMap[curCommit.ID.String()+":"+curPath+string(fname)] = &result
  115. } else if string(mode) == git.EntryModeTree.String() {
  116. hexObjectID := make([]byte, objectID.Type().FullLength())
  117. git.BinToHex(objectID.Type(), binObjectID, hexObjectID)
  118. trees = append(trees, hexObjectID)
  119. paths = append(paths, curPath+string(fname)+"/")
  120. }
  121. }
  122. if _, err := batchReader.Discard(1); err != nil {
  123. return nil, err
  124. }
  125. if len(trees) > 0 {
  126. _, err := batchStdinWriter.Write(trees[len(trees)-1])
  127. if err != nil {
  128. return nil, err
  129. }
  130. _, err = batchStdinWriter.Write([]byte("\n"))
  131. if err != nil {
  132. return nil, err
  133. }
  134. curPath = paths[len(paths)-1]
  135. trees = trees[:len(trees)-1]
  136. paths = paths[:len(paths)-1]
  137. } else {
  138. break commitReadingLoop
  139. }
  140. default:
  141. if err := git.DiscardFull(batchReader, size+1); err != nil {
  142. return nil, err
  143. }
  144. }
  145. }
  146. }
  147. if err := scan.Err(); err != nil {
  148. return nil, err
  149. }
  150. for _, result := range resultsMap {
  151. hasParent := false
  152. for _, parentID := range result.ParentHashes {
  153. if _, hasParent = resultsMap[parentID.String()+":"+result.Name]; hasParent {
  154. break
  155. }
  156. }
  157. if !hasParent {
  158. results = append(results, result)
  159. }
  160. }
  161. sort.Sort(lfsResultSlice(results))
  162. // Should really use a go-git function here but name-rev is not completed and recapitulating it is not simple
  163. shasToNameReader, shasToNameWriter := io.Pipe()
  164. nameRevStdinReader, nameRevStdinWriter := io.Pipe()
  165. errChan := make(chan error, 1)
  166. wg := sync.WaitGroup{}
  167. wg.Add(3)
  168. go func() {
  169. defer wg.Done()
  170. scanner := bufio.NewScanner(nameRevStdinReader)
  171. i := 0
  172. for scanner.Scan() {
  173. line := scanner.Text()
  174. if len(line) == 0 {
  175. continue
  176. }
  177. result := results[i]
  178. result.FullCommitName = line
  179. result.BranchName = strings.Split(line, "~")[0]
  180. i++
  181. }
  182. }()
  183. go NameRevStdin(repo.Ctx, shasToNameReader, nameRevStdinWriter, &wg, basePath)
  184. go func() {
  185. defer wg.Done()
  186. defer shasToNameWriter.Close()
  187. for _, result := range results {
  188. _, err := shasToNameWriter.Write([]byte(result.SHA))
  189. if err != nil {
  190. errChan <- err
  191. break
  192. }
  193. _, err = shasToNameWriter.Write([]byte{'\n'})
  194. if err != nil {
  195. errChan <- err
  196. break
  197. }
  198. }
  199. }()
  200. wg.Wait()
  201. select {
  202. case err, has := <-errChan:
  203. if has {
  204. return nil, lfsError("unable to obtain name for LFS files", err)
  205. }
  206. default:
  207. }
  208. return results, nil
  209. }