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  }