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 }