Imported Upstream version 2.6.1
[scm/test.git] / lfs / gitscanner_tree.go
1 package lfs
2
3 import (
4         "bufio"
5         "bytes"
6         "fmt"
7         "io"
8         "io/ioutil"
9         "strconv"
10         "strings"
11
12         "github.com/git-lfs/git-lfs/filepathfilter"
13         "github.com/git-lfs/git-lfs/git"
14 )
15
16 // An entry from ls-tree or rev-list including a blob sha and tree path
17 type TreeBlob struct {
18         Sha1     string
19         Filename string
20 }
21
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)
26         if err != nil {
27                 return err
28         }
29
30         pcw, err := catFileBatchTree(treeShas)
31         if err != nil {
32                 return err
33         }
34
35         for p := range pcw.Results {
36                 cb(p, nil)
37         }
38
39         if err := pcw.Wait(); err != nil {
40                 cb(nil, err)
41         }
42         return nil
43 }
44
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()
51         if err != nil {
52                 return nil, err
53         }
54
55         pointers := make(chan *WrappedPointer, chanBufSize)
56         errchan := make(chan error, 10) // Multiple errors possible
57
58         go func() {
59                 hasNext := true
60                 for t := range treeblobs.Results {
61                         hasNext = scanner.Scan(t.Sha1)
62
63                         if p := scanner.Pointer(); p != nil {
64                                 p.Name = t.Filename
65                                 pointers <- p
66                         }
67
68                         if err := scanner.Err(); err != nil {
69                                 errchan <- err
70                         }
71
72                         if !hasNext {
73                                 break
74                         }
75                 }
76
77                 // If the scanner quit early, we may still have treeblobs to
78                 // read, so waiting for it to close will cause a deadlock.
79                 if hasNext {
80                         // Deal with nested error from incoming treeblobs
81                         err := treeblobs.Wait()
82                         if err != nil {
83                                 errchan <- err
84                         }
85                 }
86
87                 if err = scanner.Close(); err != nil {
88                         errchan <- err
89                 }
90
91                 close(pointers)
92                 close(errchan)
93         }()
94
95         return NewPointerChannelWrapper(pointers, errchan), nil
96 }
97
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)
103         if err != nil {
104                 return nil, err
105         }
106
107         cmd.Stdin.Close()
108
109         blobs := make(chan TreeBlob, chanBufSize)
110         errchan := make(chan error, 1)
111
112         go func() {
113                 scanner := newLsTreeScanner(cmd.Stdout)
114                 for scanner.Scan() {
115                         if t := scanner.TreeBlob(); t != nil && filter.Allows(t.Filename) {
116                                 blobs <- *t
117                         }
118                 }
119
120                 stderr, _ := ioutil.ReadAll(cmd.Stderr)
121                 err := cmd.Wait()
122                 if err != nil {
123                         errchan <- fmt.Errorf("Error in git ls-tree: %v %v", err, string(stderr))
124                 }
125                 close(blobs)
126                 close(errchan)
127         }()
128
129         return NewTreeBlobChannelWrapper(blobs, errchan), nil
130 }
131
132 type lsTreeScanner struct {
133         s    *bufio.Scanner
134         tree *TreeBlob
135 }
136
137 func newLsTreeScanner(r io.Reader) *lsTreeScanner {
138         s := bufio.NewScanner(r)
139         s.Split(scanNullLines)
140         return &lsTreeScanner{s: s}
141 }
142
143 func (s *lsTreeScanner) TreeBlob() *TreeBlob {
144         return s.tree
145 }
146
147 func (s *lsTreeScanner) Err() error {
148         return nil
149 }
150
151 func (s *lsTreeScanner) Scan() bool {
152         t, hasNext := s.next()
153         s.tree = t
154         return hasNext
155 }
156
157 func (s *lsTreeScanner) next() (*TreeBlob, bool) {
158         hasNext := s.s.Scan()
159         line := s.s.Text()
160         parts := strings.SplitN(line, "\t", 2)
161         if len(parts) < 2 {
162                 return nil, hasNext
163         }
164
165         attrs := strings.SplitN(parts[0], " ", 4)
166         if len(attrs) < 4 {
167                 return nil, hasNext
168         }
169
170         if attrs[1] != "blob" {
171                 return nil, hasNext
172         }
173
174         sz, err := strconv.ParseInt(strings.TrimSpace(attrs[3]), 10, 64)
175         if err != nil {
176                 return nil, hasNext
177         }
178
179         if sz < blobSizeCutoff {
180                 sha1 := attrs[2]
181                 filename := parts[1]
182                 return &TreeBlob{Sha1: sha1, Filename: filename}, hasNext
183         }
184         return nil, hasNext
185 }
186
187 func scanNullLines(data []byte, atEOF bool) (advance int, token []byte, err error) {
188         if atEOF && len(data) == 0 {
189                 return 0, nil, nil
190         }
191
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
195         }
196
197         // If we're at EOF, we have a final, non-terminated line. Return it.
198         if atEOF {
199                 return len(data), data, nil
200         }
201
202         // Request more data.
203         return 0, nil, nil
204 }