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  }