github.com/hawser/git-hawser@v2.5.2+incompatible/lfs/gitscanner.go (about) 1 package lfs 2 3 import ( 4 "errors" 5 "fmt" 6 "sync" 7 "time" 8 9 "github.com/git-lfs/git-lfs/filepathfilter" 10 "github.com/rubyist/tracerx" 11 ) 12 13 var missingCallbackErr = errors.New("No callback given") 14 15 // IsCallbackMissing returns a boolean indicating whether the error is reporting 16 // that a GitScanner is missing a required GitScannerCallback. 17 func IsCallbackMissing(err error) bool { 18 return err == missingCallbackErr 19 } 20 21 // GitScanner scans objects in a Git repository for LFS pointers. 22 type GitScanner struct { 23 Filter *filepathfilter.Filter 24 FoundPointer GitScannerFoundPointer 25 FoundLockable GitScannerFoundLockable 26 PotentialLockables GitScannerSet 27 remote string 28 skippedRefs []string 29 30 closed bool 31 started time.Time 32 mu sync.Mutex 33 } 34 35 type GitScannerFoundPointer func(*WrappedPointer, error) 36 type GitScannerFoundLockable func(filename string) 37 38 type GitScannerSet interface { 39 Contains(string) bool 40 } 41 42 // NewGitScanner initializes a *GitScanner for a Git repository in the current 43 // working directory. 44 func NewGitScanner(cb GitScannerFoundPointer) *GitScanner { 45 return &GitScanner{started: time.Now(), FoundPointer: cb} 46 } 47 48 // Close stops exits once all processing has stopped, and all resources are 49 // tracked and cleaned up. 50 func (s *GitScanner) Close() { 51 s.mu.Lock() 52 defer s.mu.Unlock() 53 54 if s.closed { 55 return 56 } 57 58 s.closed = true 59 tracerx.PerformanceSince("scan", s.started) 60 } 61 62 // RemoteForPush sets up this *GitScanner to scan for objects to push to the 63 // given remote. Needed for ScanLeftToRemote(). 64 func (s *GitScanner) RemoteForPush(r string) error { 65 s.mu.Lock() 66 defer s.mu.Unlock() 67 68 if len(s.remote) > 0 && s.remote != r { 69 return fmt.Errorf("Trying to set remote to %q, already set to %q", r, s.remote) 70 } 71 72 s.remote = r 73 s.skippedRefs = calcSkippedRefs(r) 74 return nil 75 } 76 77 // ScanLeftToRemote scans through all commits starting at the given ref that the 78 // given remote does not have. See RemoteForPush(). 79 func (s *GitScanner) ScanLeftToRemote(left string, cb GitScannerFoundPointer) error { 80 callback, err := firstGitScannerCallback(cb, s.FoundPointer) 81 if err != nil { 82 return err 83 } 84 85 s.mu.Lock() 86 if len(s.remote) == 0 { 87 s.mu.Unlock() 88 return fmt.Errorf("Unable to scan starting at %q: no remote set.", left) 89 } 90 s.mu.Unlock() 91 92 return scanLeftRightToChan(s, callback, left, "", s.opts(ScanLeftToRemoteMode)) 93 } 94 95 // ScanRefs through all commits reachable by refs contained in "include" and 96 // not reachable by any refs included in "excluded" 97 func (s *GitScanner) ScanRefs(include, exclude []string, cb GitScannerFoundPointer) error { 98 callback, err := firstGitScannerCallback(cb, s.FoundPointer) 99 if err != nil { 100 return err 101 } 102 103 opts := s.opts(ScanRefsMode) 104 opts.SkipDeletedBlobs = false 105 return scanRefsToChan(s, callback, include, exclude, opts) 106 } 107 108 // ScanRefRange scans through all commits from the given left and right refs, 109 // including git objects that have been modified or deleted. 110 func (s *GitScanner) ScanRefRange(left, right string, cb GitScannerFoundPointer) error { 111 callback, err := firstGitScannerCallback(cb, s.FoundPointer) 112 if err != nil { 113 return err 114 } 115 116 opts := s.opts(ScanRefsMode) 117 opts.SkipDeletedBlobs = false 118 return scanLeftRightToChan(s, callback, left, right, opts) 119 } 120 121 // ScanRefWithDeleted scans through all objects in the given ref, including 122 // git objects that have been modified or deleted. 123 func (s *GitScanner) ScanRefWithDeleted(ref string, cb GitScannerFoundPointer) error { 124 return s.ScanRefRange(ref, "", cb) 125 } 126 127 // ScanRef scans through all objects in the current ref, excluding git objects 128 // that have been modified or deleted before the ref. 129 func (s *GitScanner) ScanRef(ref string, cb GitScannerFoundPointer) error { 130 callback, err := firstGitScannerCallback(cb, s.FoundPointer) 131 if err != nil { 132 return err 133 } 134 135 opts := s.opts(ScanRefsMode) 136 opts.SkipDeletedBlobs = true 137 return scanLeftRightToChan(s, callback, ref, "", opts) 138 } 139 140 // ScanAll scans through all objects in the git repository. 141 func (s *GitScanner) ScanAll(cb GitScannerFoundPointer) error { 142 callback, err := firstGitScannerCallback(cb, s.FoundPointer) 143 if err != nil { 144 return err 145 } 146 147 opts := s.opts(ScanAllMode) 148 opts.SkipDeletedBlobs = false 149 return scanLeftRightToChan(s, callback, "", "", opts) 150 } 151 152 // ScanTree takes a ref and returns WrappedPointer objects in the tree at that 153 // ref. Differs from ScanRefs in that multiple files in the tree with the same 154 // content are all reported. 155 func (s *GitScanner) ScanTree(ref string) error { 156 callback, err := firstGitScannerCallback(s.FoundPointer) 157 if err != nil { 158 return err 159 } 160 return runScanTree(callback, ref, s.Filter) 161 } 162 163 // ScanUnpushed scans history for all LFS pointers which have been added but not 164 // pushed to the named remote. remote can be left blank to mean 'any remote'. 165 func (s *GitScanner) ScanUnpushed(remote string, cb GitScannerFoundPointer) error { 166 callback, err := firstGitScannerCallback(cb, s.FoundPointer) 167 if err != nil { 168 return err 169 } 170 return scanUnpushed(callback, remote) 171 } 172 173 // ScanPreviousVersions scans changes reachable from ref (commit) back to since. 174 // Returns channel of pointers for *previous* versions that overlap that time. 175 // Does not include pointers which were still in use at ref (use ScanRefsToChan 176 // for that) 177 func (s *GitScanner) ScanPreviousVersions(ref string, since time.Time, cb GitScannerFoundPointer) error { 178 callback, err := firstGitScannerCallback(cb, s.FoundPointer) 179 if err != nil { 180 return err 181 } 182 return logPreviousSHAs(callback, ref, since) 183 } 184 185 // ScanIndex scans the git index for modified LFS objects. 186 func (s *GitScanner) ScanIndex(ref string, cb GitScannerFoundPointer) error { 187 callback, err := firstGitScannerCallback(cb, s.FoundPointer) 188 if err != nil { 189 return err 190 } 191 return scanIndex(callback, ref, s.Filter) 192 } 193 194 func (s *GitScanner) opts(mode ScanningMode) *ScanRefsOptions { 195 s.mu.Lock() 196 defer s.mu.Unlock() 197 198 opts := newScanRefsOptions() 199 opts.ScanMode = mode 200 opts.RemoteName = s.remote 201 opts.skippedRefs = s.skippedRefs 202 return opts 203 } 204 205 func firstGitScannerCallback(callbacks ...GitScannerFoundPointer) (GitScannerFoundPointer, error) { 206 for _, cb := range callbacks { 207 if cb == nil { 208 continue 209 } 210 return cb, nil 211 } 212 213 return nil, missingCallbackErr 214 } 215 216 type ScanningMode int 217 218 const ( 219 ScanRefsMode = ScanningMode(iota) // 0 - or default scan mode 220 ScanAllMode = ScanningMode(iota) 221 ScanLeftToRemoteMode = ScanningMode(iota) 222 ) 223 224 type ScanRefsOptions struct { 225 ScanMode ScanningMode 226 RemoteName string 227 SkipDeletedBlobs bool 228 skippedRefs []string 229 nameMap map[string]string 230 mutex *sync.Mutex 231 } 232 233 func (o *ScanRefsOptions) GetName(sha string) (string, bool) { 234 o.mutex.Lock() 235 name, ok := o.nameMap[sha] 236 o.mutex.Unlock() 237 return name, ok 238 } 239 240 func (o *ScanRefsOptions) SetName(sha, name string) { 241 o.mutex.Lock() 242 o.nameMap[sha] = name 243 o.mutex.Unlock() 244 } 245 246 func newScanRefsOptions() *ScanRefsOptions { 247 return &ScanRefsOptions{ 248 nameMap: make(map[string]string, 0), 249 mutex: &sync.Mutex{}, 250 } 251 }