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