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