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 }