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.

lfs.go 3.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  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. //go:build gogit
  5. // +build gogit
  6. package pipeline
  7. import (
  8. "bufio"
  9. "fmt"
  10. "io"
  11. "sort"
  12. "strings"
  13. "sync"
  14. "time"
  15. "code.gitea.io/gitea/modules/git"
  16. gogit "github.com/go-git/go-git/v5"
  17. "github.com/go-git/go-git/v5/plumbing/object"
  18. )
  19. // LFSResult represents commits found using a provided pointer file hash
  20. type LFSResult struct {
  21. Name string
  22. SHA string
  23. Summary string
  24. When time.Time
  25. ParentHashes []git.SHA1
  26. BranchName string
  27. FullCommitName string
  28. }
  29. type lfsResultSlice []*LFSResult
  30. func (a lfsResultSlice) Len() int { return len(a) }
  31. func (a lfsResultSlice) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
  32. func (a lfsResultSlice) Less(i, j int) bool { return a[j].When.After(a[i].When) }
  33. // FindLFSFile finds commits that contain a provided pointer file hash
  34. func FindLFSFile(repo *git.Repository, hash git.SHA1) ([]*LFSResult, error) {
  35. resultsMap := map[string]*LFSResult{}
  36. results := make([]*LFSResult, 0)
  37. basePath := repo.Path
  38. gogitRepo := repo.GoGitRepo()
  39. commitsIter, err := gogitRepo.Log(&gogit.LogOptions{
  40. Order: gogit.LogOrderCommitterTime,
  41. All: true,
  42. })
  43. if err != nil {
  44. return nil, fmt.Errorf("Failed to get GoGit CommitsIter. Error: %w", err)
  45. }
  46. err = commitsIter.ForEach(func(gitCommit *object.Commit) error {
  47. tree, err := gitCommit.Tree()
  48. if err != nil {
  49. return err
  50. }
  51. treeWalker := object.NewTreeWalker(tree, true, nil)
  52. defer treeWalker.Close()
  53. for {
  54. name, entry, err := treeWalker.Next()
  55. if err == io.EOF {
  56. break
  57. }
  58. if entry.Hash == hash {
  59. result := LFSResult{
  60. Name: name,
  61. SHA: gitCommit.Hash.String(),
  62. Summary: strings.Split(strings.TrimSpace(gitCommit.Message), "\n")[0],
  63. When: gitCommit.Author.When,
  64. ParentHashes: gitCommit.ParentHashes,
  65. }
  66. resultsMap[gitCommit.Hash.String()+":"+name] = &result
  67. }
  68. }
  69. return nil
  70. })
  71. if err != nil && err != io.EOF {
  72. return nil, fmt.Errorf("Failure in CommitIter.ForEach: %w", err)
  73. }
  74. for _, result := range resultsMap {
  75. hasParent := false
  76. for _, parentHash := range result.ParentHashes {
  77. if _, hasParent = resultsMap[parentHash.String()+":"+result.Name]; hasParent {
  78. break
  79. }
  80. }
  81. if !hasParent {
  82. results = append(results, result)
  83. }
  84. }
  85. sort.Sort(lfsResultSlice(results))
  86. // Should really use a go-git function here but name-rev is not completed and recapitulating it is not simple
  87. shasToNameReader, shasToNameWriter := io.Pipe()
  88. nameRevStdinReader, nameRevStdinWriter := io.Pipe()
  89. errChan := make(chan error, 1)
  90. wg := sync.WaitGroup{}
  91. wg.Add(3)
  92. go func() {
  93. defer wg.Done()
  94. scanner := bufio.NewScanner(nameRevStdinReader)
  95. i := 0
  96. for scanner.Scan() {
  97. line := scanner.Text()
  98. if len(line) == 0 {
  99. continue
  100. }
  101. result := results[i]
  102. result.FullCommitName = line
  103. result.BranchName = strings.Split(line, "~")[0]
  104. i++
  105. }
  106. }()
  107. go NameRevStdin(shasToNameReader, nameRevStdinWriter, &wg, basePath)
  108. go func() {
  109. defer wg.Done()
  110. defer shasToNameWriter.Close()
  111. for _, result := range results {
  112. i := 0
  113. if i < len(result.SHA) {
  114. n, err := shasToNameWriter.Write([]byte(result.SHA)[i:])
  115. if err != nil {
  116. errChan <- err
  117. break
  118. }
  119. i += n
  120. }
  121. n := 0
  122. for n < 1 {
  123. n, err = shasToNameWriter.Write([]byte{'\n'})
  124. if err != nil {
  125. errChan <- err
  126. break
  127. }
  128. }
  129. }
  130. }()
  131. wg.Wait()
  132. select {
  133. case err, has := <-errChan:
  134. if has {
  135. return nil, fmt.Errorf("Unable to obtain name for LFS files. Error: %w", err)
  136. }
  137. default:
  138. }
  139. return results, nil
  140. }