Imported Upstream version 2.5.1
[scm/test.git] / git / object_scanner.go
1 package git
2
3 import (
4         "bufio"
5         "bytes"
6         "fmt"
7         "io"
8         "io/ioutil"
9         "strconv"
10
11         "github.com/git-lfs/git-lfs/errors"
12         "github.com/rubyist/tracerx"
13 )
14
15 // object represents a generic Git object of any type.
16 type object struct {
17         // Contents reads Git's internal object representation.
18         Contents *io.LimitedReader
19         // Oid is the ID of the object.
20         Oid string
21         // Size is the size in bytes of the object.
22         Size int64
23         // Type is the type of the object being held.
24         Type string
25 }
26
27 // ObjectScanner is a scanner type that scans for Git objects reference-able in
28 // Git's object database by their unique OID.
29 type ObjectScanner struct {
30         // object is the object that the ObjectScanner last scanned, or nil.
31         object *object
32         // err is the error (if any) that the ObjectScanner encountered during
33         // its last scan, or nil.
34         err error
35
36         // from is the buffered source of input to the *ObjectScanner. It
37         // expects input in the form described by
38         // https://git-scm.com/docs/git-cat-file.
39         from *bufio.Reader
40         // to is a writer which accepts the object's OID to be scanned.
41         to io.Writer
42         // closeFn is an optional function that is run before the ObjectScanner
43         // is closed. It is designated to clean up and close any resources held
44         // by the ObjectScanner during runtime.
45         closeFn func() error
46 }
47
48 // NewObjectScanner constructs a new instance of the `*ObjectScanner` type and
49 // returns it. It backs the ObjectScanner with an invocation of the `git
50 // cat-file --batch` command. If any errors were encountered while starting that
51 // command, they will be returned immediately.
52 //
53 // Otherwise, an `*ObjectScanner` is returned with no error.
54 func NewObjectScanner() (*ObjectScanner, error) {
55         cmd := gitNoLFS("cat-file", "--batch")
56         stdout, err := cmd.StdoutPipe()
57         if err != nil {
58                 return nil, errors.Wrap(err, "open stdout")
59         }
60         stdin, err := cmd.StdinPipe()
61         if err != nil {
62                 return nil, errors.Wrap(err, "open stdin")
63         }
64
65         stderr, err := cmd.StderrPipe()
66         if err != nil {
67                 return nil, errors.Wrap(err, "open stderr")
68         }
69
70         closeFn := func() error {
71                 if err := stdin.Close(); err != nil {
72                         return err
73                 }
74
75                 msg, _ := ioutil.ReadAll(stderr)
76                 if err = cmd.Wait(); err != nil {
77                         return errors.Errorf("Error in git cat-file --batch: %v %v", err, string(msg))
78                 }
79
80                 return nil
81         }
82
83         tracerx.Printf("run_command: git cat-file --batch")
84         if err := cmd.Start(); err != nil {
85                 return nil, err
86         }
87
88         return &ObjectScanner{
89                 from: bufio.NewReaderSize(stdout, 16384),
90                 to:   stdin,
91
92                 closeFn: closeFn,
93         }, nil
94 }
95
96 // NewObjectScannerFrom returns a new `*ObjectScanner` populated with data from
97 // the given `io.Reader`, "r". It supplies no close function, and discards any
98 // input given to the Scan() function.
99 func NewObjectScannerFrom(r io.Reader) *ObjectScanner {
100         return &ObjectScanner{
101                 from: bufio.NewReader(r),
102                 to:   ioutil.Discard,
103         }
104 }
105
106 // Scan scans for a particular object given by the "oid" parameter. Once the
107 // scan is complete, the Contents(), Sha1(), Size() and Type() functions may be
108 // called and will return data corresponding to the given OID.
109 //
110 // Scan() returns whether the scan was successful, or in other words, whether or
111 // not the scanner can continue to progress.
112 func (s *ObjectScanner) Scan(oid string) bool {
113         if err := s.reset(); err != nil {
114                 s.err = err
115                 return false
116         }
117
118         obj, err := s.scan(oid)
119         s.object = obj
120
121         if err != nil {
122                 if err != io.EOF {
123                         s.err = err
124                 }
125                 return false
126         }
127         return true
128 }
129
130 // Close closes and frees any resources owned by the *ObjectScanner that it is
131 // called upon. If there were any errors in freeing that (those) resource(s), it
132 // it will be returned, otherwise nil.
133 func (s *ObjectScanner) Close() error {
134         if s == nil {
135                 return nil
136         }
137
138         if s.closeFn != nil {
139                 return s.closeFn()
140         }
141         return nil
142 }
143
144 // Contents returns an io.Reader which reads Git's representation of the object
145 // that was last scanned for.
146 func (s *ObjectScanner) Contents() io.Reader {
147         return s.object.Contents
148 }
149
150 // Sha1 returns the SHA1 object ID of the object that was last scanned for.
151 func (s *ObjectScanner) Sha1() string {
152         return s.object.Oid
153 }
154
155 // Size returns the size in bytes of the object that was last scanned for.
156 func (s *ObjectScanner) Size() int64 {
157         return s.object.Size
158 }
159
160 // Type returns the type of the object that was last scanned for.
161 func (s *ObjectScanner) Type() string {
162         return s.object.Type
163 }
164
165 // Err returns the error (if any) that was encountered during the last Scan()
166 // operation.
167 func (s *ObjectScanner) Err() error { return s.err }
168
169 // reset resets the `*ObjectScanner` to scan again by advancing the reader (if
170 // necessary) and clearing both the object and error fields on the
171 // `*ObjectScanner` instance.
172 func (s *ObjectScanner) reset() error {
173         if s.object != nil {
174                 if s.object.Contents != nil {
175                         remaining := s.object.Contents.N
176                         if _, err := io.CopyN(ioutil.Discard, s.object.Contents, remaining); err != nil {
177                                 return errors.Wrap(err, "unwind contents")
178                         }
179                 }
180
181                 // Consume extra LF inserted by cat-file
182                 if _, err := s.from.ReadByte(); err != nil {
183                         return err
184                 }
185         }
186
187         s.object, s.err = nil, nil
188
189         return nil
190 }
191
192 type missingErr struct {
193         oid string
194 }
195
196 func (m *missingErr) Error() string {
197         return fmt.Sprintf("missing object: %s", m.oid)
198 }
199
200 func IsMissingObject(err error) bool {
201         _, ok := err.(*missingErr)
202         return ok
203 }
204
205 // scan scans for and populates a new Git object given an OID.
206 func (s *ObjectScanner) scan(oid string) (*object, error) {
207         if _, err := fmt.Fprintln(s.to, oid); err != nil {
208                 return nil, err
209         }
210
211         l, err := s.from.ReadBytes('\n')
212         if err != nil {
213                 return nil, err
214         }
215
216         fields := bytes.Fields(l)
217         switch len(fields) {
218         case 2:
219                 if string(fields[1]) == "missing" {
220                         return nil, &missingErr{oid: oid}
221                 }
222                 break
223         case 3:
224                 oid = string(fields[0])
225                 typ := string(fields[1])
226                 size, _ := strconv.Atoi(string(fields[2]))
227                 contents := io.LimitReader(s.from, int64(size))
228
229                 return &object{
230                         Contents: contents.(*io.LimitedReader),
231                         Oid:      oid,
232                         Size:     int64(size),
233                         Type:     typ,
234                 }, nil
235         }
236         return nil, errors.Errorf("invalid line: %q", l)
237 }