github.com/stffabi/git-lfs@v2.3.5-0.20180214015214-8eeaa8d88902+incompatible/git/object_scanner.go (about)

     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  }