11 "github.com/git-lfs/git-lfs/errors"
12 "github.com/rubyist/tracerx"
15 // object represents a generic Git object of any type.
17 // Contents reads Git's internal object representation.
18 Contents *io.LimitedReader
19 // Oid is the ID of the object.
21 // Size is the size in bytes of the object.
23 // Type is the type of the object being held.
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.
32 // err is the error (if any) that the ObjectScanner encountered during
33 // its last scan, or nil.
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.
40 // to is a writer which accepts the object's OID to be scanned.
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.
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.
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()
58 return nil, errors.Wrap(err, "open stdout")
60 stdin, err := cmd.StdinPipe()
62 return nil, errors.Wrap(err, "open stdin")
65 stderr, err := cmd.StderrPipe()
67 return nil, errors.Wrap(err, "open stderr")
70 closeFn := func() error {
71 if err := stdin.Close(); err != nil {
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))
83 tracerx.Printf("run_command: git cat-file --batch")
84 if err := cmd.Start(); err != nil {
88 return &ObjectScanner{
89 from: bufio.NewReaderSize(stdout, 16384),
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),
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.
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 {
118 obj, err := s.scan(oid)
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 {
138 if s.closeFn != nil {
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
150 // Sha1 returns the SHA1 object ID of the object that was last scanned for.
151 func (s *ObjectScanner) Sha1() string {
155 // Size returns the size in bytes of the object that was last scanned for.
156 func (s *ObjectScanner) Size() int64 {
160 // Type returns the type of the object that was last scanned for.
161 func (s *ObjectScanner) Type() string {
165 // Err returns the error (if any) that was encountered during the last Scan()
167 func (s *ObjectScanner) Err() error { return s.err }
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 {
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")
181 // Consume extra LF inserted by cat-file
182 if _, err := s.from.ReadByte(); err != nil {
187 s.object, s.err = nil, nil
192 type missingErr struct {
196 func (m *missingErr) Error() string {
197 return fmt.Sprintf("missing object: %s", m.oid)
200 func IsMissingObject(err error) bool {
201 _, ok := err.(*missingErr)
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 {
211 l, err := s.from.ReadBytes('\n')
216 fields := bytes.Fields(l)
219 if string(fields[1]) == "missing" {
220 return nil, &missingErr{oid: oid}
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))
230 Contents: contents.(*io.LimitedReader),
236 return nil, errors.Errorf("invalid line: %q", l)