12 "github.com/git-lfs/git-lfs/filepathfilter"
13 "github.com/git-lfs/git-lfs/git"
16 // An entry from ls-tree or rev-list including a blob sha and tree path
17 type TreeBlob struct {
22 func runScanTree(cb GitScannerFoundPointer, ref string, filter *filepathfilter.Filter) error {
23 // We don't use the nameMap approach here since that's imprecise when >1 file
24 // can be using the same content
25 treeShas, err := lsTreeBlobs(ref, filter)
30 pcw, err := catFileBatchTree(treeShas)
35 for p := range pcw.Results {
39 if err := pcw.Wait(); err != nil {
45 // catFileBatchTree uses git cat-file --batch to get the object contents
46 // of a git object, given its sha1. The contents will be decoded into
47 // a Git LFS pointer. treeblobs is a channel over which blob entries
48 // will be sent. It returns a channel from which point.Pointers can be read.
49 func catFileBatchTree(treeblobs *TreeBlobChannelWrapper) (*PointerChannelWrapper, error) {
50 scanner, err := NewPointerScanner()
55 pointers := make(chan *WrappedPointer, chanBufSize)
56 errchan := make(chan error, 10) // Multiple errors possible
60 for t := range treeblobs.Results {
61 hasNext = scanner.Scan(t.Sha1)
63 if p := scanner.Pointer(); p != nil {
68 if err := scanner.Err(); err != nil {
77 // If the scanner quit early, we may still have treeblobs to
78 // read, so waiting for it to close will cause a deadlock.
80 // Deal with nested error from incoming treeblobs
81 err := treeblobs.Wait()
87 if err = scanner.Close(); err != nil {
95 return NewPointerChannelWrapper(pointers, errchan), nil
98 // Use ls-tree at ref to find a list of candidate tree blobs which might be lfs files
99 // The returned channel will be sent these blobs which should be sent to catFileBatchTree
100 // for final check & conversion to Pointer
101 func lsTreeBlobs(ref string, filter *filepathfilter.Filter) (*TreeBlobChannelWrapper, error) {
102 cmd, err := git.LsTree(ref)
109 blobs := make(chan TreeBlob, chanBufSize)
110 errchan := make(chan error, 1)
113 scanner := newLsTreeScanner(cmd.Stdout)
115 if t := scanner.TreeBlob(); t != nil && filter.Allows(t.Filename) {
120 stderr, _ := ioutil.ReadAll(cmd.Stderr)
123 errchan <- fmt.Errorf("Error in git ls-tree: %v %v", err, string(stderr))
129 return NewTreeBlobChannelWrapper(blobs, errchan), nil
132 type lsTreeScanner struct {
137 func newLsTreeScanner(r io.Reader) *lsTreeScanner {
138 s := bufio.NewScanner(r)
139 s.Split(scanNullLines)
140 return &lsTreeScanner{s: s}
143 func (s *lsTreeScanner) TreeBlob() *TreeBlob {
147 func (s *lsTreeScanner) Err() error {
151 func (s *lsTreeScanner) Scan() bool {
152 t, hasNext := s.next()
157 func (s *lsTreeScanner) next() (*TreeBlob, bool) {
158 hasNext := s.s.Scan()
160 parts := strings.SplitN(line, "\t", 2)
165 attrs := strings.SplitN(parts[0], " ", 4)
170 if attrs[1] != "blob" {
174 sz, err := strconv.ParseInt(strings.TrimSpace(attrs[3]), 10, 64)
179 if sz < blobSizeCutoff {
182 return &TreeBlob{Sha1: sha1, Filename: filename}, hasNext
187 func scanNullLines(data []byte, atEOF bool) (advance int, token []byte, err error) {
188 if atEOF && len(data) == 0 {
192 if i := bytes.IndexByte(data, '\000'); i >= 0 {
193 // We have a full null-terminated line.
194 return i + 1, data[0:i], nil
197 // If we're at EOF, we have a final, non-terminated line. Return it.
199 return len(data), data, nil
202 // Request more data.