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