github.com/psexton/git-lfs@v2.1.1-0.20170517224304-289a18b2bc53+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 scanRefsToChan(s, callback, left, "", s.opts(ScanLeftToRemoteMode)) 93 } 94 95 // ScanRefRange scans through all commits from the given left and right refs, 96 // including git objects that have been modified or deleted. 97 func (s *GitScanner) ScanRefRange(left, right 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, left, right, opts) 106 } 107 108 // ScanRefWithDeleted scans through all objects in the given ref, including 109 // git objects that have been modified or deleted. 110 func (s *GitScanner) ScanRefWithDeleted(ref string, cb GitScannerFoundPointer) error { 111 return s.ScanRefRange(ref, "", cb) 112 } 113 114 // ScanRef scans through all objects in the current ref, excluding git objects 115 // that have been modified or deleted before the ref. 116 func (s *GitScanner) ScanRef(ref string, cb GitScannerFoundPointer) error { 117 callback, err := firstGitScannerCallback(cb, s.FoundPointer) 118 if err != nil { 119 return err 120 } 121 122 opts := s.opts(ScanRefsMode) 123 opts.SkipDeletedBlobs = true 124 return scanRefsToChan(s, callback, ref, "", opts) 125 } 126 127 // ScanAll scans through all objects in the git repository. 128 func (s *GitScanner) ScanAll(cb GitScannerFoundPointer) error { 129 callback, err := firstGitScannerCallback(cb, s.FoundPointer) 130 if err != nil { 131 return err 132 } 133 134 opts := s.opts(ScanAllMode) 135 opts.SkipDeletedBlobs = false 136 return scanRefsToChan(s, callback, "", "", opts) 137 } 138 139 // ScanTree takes a ref and returns WrappedPointer objects in the tree at that 140 // ref. Differs from ScanRefs in that multiple files in the tree with the same 141 // content are all reported. 142 func (s *GitScanner) ScanTree(ref string) error { 143 callback, err := firstGitScannerCallback(s.FoundPointer) 144 if err != nil { 145 return err 146 } 147 return runScanTree(callback, ref, s.Filter) 148 } 149 150 // ScanUnpushed scans history for all LFS pointers which have been added but not 151 // pushed to the named remote. remote can be left blank to mean 'any remote'. 152 func (s *GitScanner) ScanUnpushed(remote string, cb GitScannerFoundPointer) error { 153 callback, err := firstGitScannerCallback(cb, s.FoundPointer) 154 if err != nil { 155 return err 156 } 157 return scanUnpushed(callback, remote) 158 } 159 160 // ScanPreviousVersions scans changes reachable from ref (commit) back to since. 161 // Returns channel of pointers for *previous* versions that overlap that time. 162 // Does not include pointers which were still in use at ref (use ScanRefsToChan 163 // for that) 164 func (s *GitScanner) ScanPreviousVersions(ref string, since time.Time, cb GitScannerFoundPointer) error { 165 callback, err := firstGitScannerCallback(cb, s.FoundPointer) 166 if err != nil { 167 return err 168 } 169 return logPreviousSHAs(callback, ref, since) 170 } 171 172 // ScanIndex scans the git index for modified LFS objects. 173 func (s *GitScanner) ScanIndex(ref string, cb GitScannerFoundPointer) error { 174 callback, err := firstGitScannerCallback(cb, s.FoundPointer) 175 if err != nil { 176 return err 177 } 178 return scanIndex(callback, ref) 179 } 180 181 func (s *GitScanner) opts(mode ScanningMode) *ScanRefsOptions { 182 s.mu.Lock() 183 defer s.mu.Unlock() 184 185 opts := newScanRefsOptions() 186 opts.ScanMode = mode 187 opts.RemoteName = s.remote 188 opts.skippedRefs = s.skippedRefs 189 return opts 190 } 191 192 func firstGitScannerCallback(callbacks ...GitScannerFoundPointer) (GitScannerFoundPointer, error) { 193 for _, cb := range callbacks { 194 if cb == nil { 195 continue 196 } 197 return cb, nil 198 } 199 200 return nil, missingCallbackErr 201 } 202 203 type ScanningMode int 204 205 const ( 206 ScanRefsMode = ScanningMode(iota) // 0 - or default scan mode 207 ScanAllMode = ScanningMode(iota) 208 ScanLeftToRemoteMode = ScanningMode(iota) 209 ) 210 211 type ScanRefsOptions struct { 212 ScanMode ScanningMode 213 RemoteName string 214 SkipDeletedBlobs bool 215 skippedRefs []string 216 nameMap map[string]string 217 mutex *sync.Mutex 218 } 219 220 func (o *ScanRefsOptions) GetName(sha string) (string, bool) { 221 o.mutex.Lock() 222 name, ok := o.nameMap[sha] 223 o.mutex.Unlock() 224 return name, ok 225 } 226 227 func (o *ScanRefsOptions) SetName(sha, name string) { 228 o.mutex.Lock() 229 o.nameMap[sha] = name 230 o.mutex.Unlock() 231 } 232 233 func newScanRefsOptions() *ScanRefsOptions { 234 return &ScanRefsOptions{ 235 nameMap: make(map[string]string, 0), 236 mutex: &sync.Mutex{}, 237 } 238 }