github.com/psexton/git-lfs@v2.1.1-0.20170517224304-289a18b2bc53+incompatible/lfs/gitscanner_index.go (about)

     1  package lfs
     2  
     3  import (
     4  	"strings"
     5  	"sync"
     6  )
     7  
     8  // ScanIndex returns a slice of WrappedPointer objects for all Git LFS pointers
     9  // it finds in the index.
    10  //
    11  // Ref is the ref at which to scan, which may be "HEAD" if there is at least one
    12  // commit.
    13  func scanIndex(cb GitScannerFoundPointer, ref string) error {
    14  	indexMap := &indexFileMap{
    15  		nameMap:      make(map[string][]*indexFile),
    16  		nameShaPairs: make(map[string]bool),
    17  		mutex:        &sync.Mutex{},
    18  	}
    19  
    20  	revs, err := revListIndex(ref, false, indexMap)
    21  	if err != nil {
    22  		return err
    23  	}
    24  
    25  	cachedRevs, err := revListIndex(ref, true, indexMap)
    26  	if err != nil {
    27  		return err
    28  	}
    29  
    30  	allRevsErr := make(chan error, 5) // can be multiple errors below
    31  	allRevsChan := make(chan string, 1)
    32  	allRevs := NewStringChannelWrapper(allRevsChan, allRevsErr)
    33  	go func() {
    34  		seenRevs := make(map[string]bool, 0)
    35  
    36  		for rev := range revs.Results {
    37  			if !seenRevs[rev] {
    38  				allRevsChan <- rev
    39  				seenRevs[rev] = true
    40  			}
    41  		}
    42  		err := revs.Wait()
    43  		if err != nil {
    44  			allRevsErr <- err
    45  		}
    46  
    47  		for rev := range cachedRevs.Results {
    48  			if !seenRevs[rev] {
    49  				allRevsChan <- rev
    50  				seenRevs[rev] = true
    51  			}
    52  		}
    53  		err = cachedRevs.Wait()
    54  		if err != nil {
    55  			allRevsErr <- err
    56  		}
    57  		close(allRevsChan)
    58  		close(allRevsErr)
    59  	}()
    60  
    61  	smallShas, _, err := catFileBatchCheck(allRevs, nil)
    62  	if err != nil {
    63  		return err
    64  	}
    65  
    66  	ch := make(chan gitscannerResult, chanBufSize)
    67  
    68  	barePointerCh, _, err := catFileBatch(smallShas, nil)
    69  	if err != nil {
    70  		return err
    71  	}
    72  
    73  	go func() {
    74  		for p := range barePointerCh.Results {
    75  			for _, file := range indexMap.FilesFor(p.Sha1) {
    76  				// Append a new *WrappedPointer that combines the data
    77  				// from the index file, and the pointer "p".
    78  				ch <- gitscannerResult{
    79  					Pointer: &WrappedPointer{
    80  						Sha1:    p.Sha1,
    81  						Name:    file.Name,
    82  						SrcName: file.SrcName,
    83  						Status:  file.Status,
    84  						Pointer: p.Pointer,
    85  					},
    86  				}
    87  			}
    88  		}
    89  
    90  		if err := barePointerCh.Wait(); err != nil {
    91  			ch <- gitscannerResult{Err: err}
    92  		}
    93  
    94  		close(ch)
    95  	}()
    96  
    97  	for result := range ch {
    98  		cb(result.Pointer, result.Err)
    99  	}
   100  
   101  	return nil
   102  }
   103  
   104  // revListIndex uses git diff-index to return the list of object sha1s
   105  // for in the indexf. It returns a channel from which sha1 strings can be read.
   106  // The namMap will be filled indexFile pointers mapping sha1s to indexFiles.
   107  func revListIndex(atRef string, cache bool, indexMap *indexFileMap) (*StringChannelWrapper, error) {
   108  	scanner, err := NewDiffIndexScanner(atRef, cache)
   109  	if err != nil {
   110  		return nil, err
   111  	}
   112  
   113  	revs := make(chan string, chanBufSize)
   114  	errs := make(chan error, 1)
   115  
   116  	go func() {
   117  		for scanner.Scan() {
   118  			var name string = scanner.Entry().DstName
   119  			if len(name) == 0 {
   120  				name = scanner.Entry().SrcName
   121  			}
   122  
   123  			var sha string = scanner.Entry().DstSha
   124  			if scanner.Entry().Status == StatusModification {
   125  				sha = scanner.Entry().SrcSha
   126  			}
   127  
   128  			indexMap.Add(sha, &indexFile{
   129  				Name:    name,
   130  				SrcName: scanner.Entry().SrcName,
   131  				Status:  string(scanner.Entry().Status),
   132  			})
   133  
   134  			revs <- sha
   135  		}
   136  
   137  		if err := scanner.Err(); err != nil {
   138  			errs <- err
   139  		}
   140  
   141  		close(revs)
   142  		close(errs)
   143  	}()
   144  
   145  	return NewStringChannelWrapper(revs, errs), nil
   146  }
   147  
   148  // indexFile is used when scanning the index. It stores the name of
   149  // the file, the status of the file in the index, and, in the case of
   150  // a moved or copied file, the original name of the file.
   151  type indexFile struct {
   152  	Name    string
   153  	SrcName string
   154  	Status  string
   155  }
   156  
   157  type indexFileMap struct {
   158  	// mutex guards nameMap and nameShaPairs
   159  	mutex *sync.Mutex
   160  	// nameMap maps SHA1s to a slice of `*indexFile`s
   161  	nameMap map[string][]*indexFile
   162  	// nameShaPairs maps "sha1:name" -> bool
   163  	nameShaPairs map[string]bool
   164  }
   165  
   166  // FilesFor returns all `*indexFile`s that match the given `sha`.
   167  func (m *indexFileMap) FilesFor(sha string) []*indexFile {
   168  	m.mutex.Lock()
   169  	defer m.mutex.Unlock()
   170  
   171  	return m.nameMap[sha]
   172  }
   173  
   174  // Add appends unique index files to the given SHA, "sha". A file is considered
   175  // unique if its combination of SHA and current filename have not yet been seen
   176  // by this instance "m" of *indexFileMap.
   177  func (m *indexFileMap) Add(sha string, index *indexFile) {
   178  	m.mutex.Lock()
   179  	defer m.mutex.Unlock()
   180  
   181  	pairKey := strings.Join([]string{sha, index.Name}, ":")
   182  	if m.nameShaPairs[pairKey] {
   183  		return
   184  	}
   185  
   186  	m.nameMap[sha] = append(m.nameMap[sha], index)
   187  	m.nameShaPairs[pairKey] = true
   188  }