github.com/stffabi/git-lfs@v2.3.5-0.20180214015214-8eeaa8d88902+incompatible/git/odb/object_db.go (about) 1 package odb 2 3 import ( 4 "bytes" 5 "fmt" 6 "io" 7 "io/ioutil" 8 "os" 9 "strings" 10 "sync/atomic" 11 12 "github.com/git-lfs/git-lfs/errors" 13 "github.com/git-lfs/git-lfs/git/odb/pack" 14 ) 15 16 // ObjectDatabase enables the reading and writing of objects against a storage 17 // backend. 18 type ObjectDatabase struct { 19 // s is the storage backend which opens/creates/reads/writes. 20 s storer 21 // packs are the set of packfiles which contain all packed objects 22 // within this repository. 23 packs *pack.Set 24 25 // closed is a uint32 managed by sync/atomic's <X>Uint32 methods. It 26 // yields a value of 0 if the *ObjectDatabase it is stored upon is open, 27 // and a value of 1 if it is closed. 28 closed uint32 29 30 // temp directory, defaults to os.TempDir 31 tmp string 32 } 33 34 // FromFilesystem constructs an *ObjectDatabase instance that is backed by a 35 // directory on the filesystem. Specifically, this should point to: 36 // 37 // /absolute/repo/path/.git/objects 38 func FromFilesystem(root, tmp string) (*ObjectDatabase, error) { 39 packs, err := pack.NewSet(root) 40 if err != nil { 41 return nil, err 42 } 43 44 return &ObjectDatabase{ 45 tmp: tmp, 46 s: newFileStorer(root, tmp), 47 packs: packs, 48 }, nil 49 } 50 51 // Close closes the *ObjectDatabase, freeing any open resources (namely: the 52 // `*git.ObjectScanner instance), and returning any errors encountered in 53 // closing them. 54 // 55 // If Close() has already been called, this function will return an error. 56 func (o *ObjectDatabase) Close() error { 57 if !atomic.CompareAndSwapUint32(&o.closed, 0, 1) { 58 return errors.New("git/odb: *ObjectDatabase already closed") 59 } 60 61 if err := o.packs.Close(); err != nil { 62 return err 63 } 64 return nil 65 } 66 67 // Blob returns a *Blob as identified by the SHA given, or an error if one was 68 // encountered. 69 func (o *ObjectDatabase) Blob(sha []byte) (*Blob, error) { 70 var b Blob 71 72 if err := o.decode(sha, &b); err != nil { 73 return nil, err 74 } 75 return &b, nil 76 } 77 78 // Tree returns a *Tree as identified by the SHA given, or an error if one was 79 // encountered. 80 func (o *ObjectDatabase) Tree(sha []byte) (*Tree, error) { 81 var t Tree 82 if err := o.decode(sha, &t); err != nil { 83 return nil, err 84 } 85 return &t, nil 86 } 87 88 // Commit returns a *Commit as identified by the SHA given, or an error if one 89 // was encountered. 90 func (o *ObjectDatabase) Commit(sha []byte) (*Commit, error) { 91 var c Commit 92 93 if err := o.decode(sha, &c); err != nil { 94 return nil, err 95 } 96 return &c, nil 97 } 98 99 // Tag returns a *Tag as identified by the SHA given, or an error if one was 100 // encountered. 101 func (o *ObjectDatabase) Tag(sha []byte) (*Tag, error) { 102 var t Tag 103 104 if err := o.decode(sha, &t); err != nil { 105 return nil, err 106 } 107 return &t, nil 108 } 109 110 // WriteBlob stores a *Blob on disk and returns the SHA it is uniquely 111 // identified by, or an error if one was encountered. 112 func (o *ObjectDatabase) WriteBlob(b *Blob) ([]byte, error) { 113 buf, err := ioutil.TempFile(o.tmp, "") 114 if err != nil { 115 return nil, err 116 } 117 defer os.Remove(buf.Name()) 118 119 sha, _, err := o.encodeBuffer(b, buf) 120 if err != nil { 121 return nil, err 122 } 123 124 if err = b.Close(); err != nil { 125 return nil, err 126 } 127 128 return sha, nil 129 } 130 131 // WriteTree stores a *Tree on disk and returns the SHA it is uniquely 132 // identified by, or an error if one was encountered. 133 func (o *ObjectDatabase) WriteTree(t *Tree) ([]byte, error) { 134 sha, _, err := o.encode(t) 135 if err != nil { 136 return nil, err 137 } 138 return sha, nil 139 } 140 141 // WriteCommit stores a *Commit on disk and returns the SHA it is uniquely 142 // identified by, or an error if one was encountered. 143 func (o *ObjectDatabase) WriteCommit(c *Commit) ([]byte, error) { 144 sha, _, err := o.encode(c) 145 if err != nil { 146 return nil, err 147 } 148 return sha, nil 149 } 150 151 // WriteTag stores a *Tag on disk and returns the SHA it is uniquely identified 152 // by, or an error if one was encountered. 153 func (o *ObjectDatabase) WriteTag(t *Tag) ([]byte, error) { 154 sha, _, err := o.encode(t) 155 if err != nil { 156 return nil, err 157 } 158 return sha, nil 159 } 160 161 // Root returns the filesystem root that this *ObjectDatabase works within, if 162 // backed by a fileStorer (constructed by FromFilesystem). If so, it returns 163 // the fully-qualified path on a disk and a value of true. 164 // 165 // Otherwise, it returns empty-string and a value of false. 166 func (o *ObjectDatabase) Root() (string, bool) { 167 type rooter interface { 168 Root() string 169 } 170 171 if root, ok := o.s.(rooter); ok { 172 return root.Root(), true 173 } 174 return "", false 175 } 176 177 // encode encodes and saves an object to the storage backend and uses an 178 // in-memory buffer to calculate the object's encoded body. 179 func (d *ObjectDatabase) encode(object Object) (sha []byte, n int64, err error) { 180 return d.encodeBuffer(object, bytes.NewBuffer(nil)) 181 } 182 183 // encodeBuffer encodes and saves an object to the storage backend by using the 184 // given buffer to calculate and store the object's encoded body. 185 func (d *ObjectDatabase) encodeBuffer(object Object, buf io.ReadWriter) (sha []byte, n int64, err error) { 186 cn, err := object.Encode(buf) 187 if err != nil { 188 return nil, 0, err 189 } 190 191 tmp, err := ioutil.TempFile(d.tmp, "") 192 if err != nil { 193 return nil, 0, err 194 } 195 defer os.Remove(tmp.Name()) 196 197 to := NewObjectWriter(tmp) 198 if _, err = to.WriteHeader(object.Type(), int64(cn)); err != nil { 199 return nil, 0, err 200 } 201 202 if seek, ok := buf.(io.Seeker); ok { 203 if _, err = seek.Seek(0, io.SeekStart); err != nil { 204 return nil, 0, err 205 } 206 } 207 208 if _, err = io.Copy(to, buf); err != nil { 209 return nil, 0, err 210 } 211 212 if err = to.Close(); err != nil { 213 return nil, 0, err 214 } 215 216 if _, err := tmp.Seek(0, io.SeekStart); err != nil { 217 return nil, 0, err 218 } 219 return d.save(to.Sha(), tmp) 220 } 221 222 // save writes the given buffer to the location given by the storer "o.s" as 223 // identified by the sha []byte. 224 func (o *ObjectDatabase) save(sha []byte, buf io.Reader) ([]byte, int64, error) { 225 n, err := o.s.Store(sha, buf) 226 227 return sha, n, err 228 } 229 230 // open gives an `*ObjectReader` for the given loose object keyed by the given 231 // "sha" []byte, or an error. 232 func (o *ObjectDatabase) open(sha []byte) (*ObjectReader, error) { 233 f, err := o.s.Open(sha) 234 if err != nil { 235 if !os.IsNotExist(err) { 236 // If there was some other issue beyond not being able 237 // to find the object, return that immediately and don't 238 // try and fallback to the *git.ObjectScanner. 239 return nil, err 240 } 241 242 // Otherwise, if the file simply couldn't be found, attempt to 243 // load its contents from the *git.ObjectScanner by leveraging 244 // `git-cat-file --batch`. 245 if atomic.LoadUint32(&o.closed) == 1 { 246 return nil, errors.New("git/odb: cannot use closed *pack.Set") 247 } 248 249 packed, err := o.packs.Object(sha) 250 if err != nil { 251 return nil, err 252 } 253 254 unpacked, err := packed.Unpack() 255 if err != nil { 256 return nil, err 257 } 258 259 return NewUncompressedObjectReader(io.MultiReader( 260 // Git object header: 261 strings.NewReader(fmt.Sprintf("%s %d\x00", 262 packed.Type(), len(unpacked), 263 )), 264 265 // Git object (uncompressed) contents: 266 bytes.NewReader(unpacked), 267 )) 268 } 269 270 return NewObjectReadCloser(f) 271 } 272 273 // decode decodes an object given by the sha "sha []byte" into the given object 274 // "into", or returns an error if one was encountered. 275 // 276 // Ordinarily, it closes the object's underlying io.ReadCloser (if it implements 277 // the `io.Closer` interface), but skips this if the "into" Object is of type 278 // BlobObjectType. Blob's don't exhaust the buffer completely (they instead 279 // maintain a handle on the blob's contents via an io.LimitedReader) and 280 // therefore cannot be closed until signaled explicitly by git/odb.Blob.Close(). 281 func (o *ObjectDatabase) decode(sha []byte, into Object) error { 282 r, err := o.open(sha) 283 if err != nil { 284 return err 285 } 286 287 typ, size, err := r.Header() 288 if err != nil { 289 return err 290 } else if typ != into.Type() { 291 return &UnexpectedObjectType{Got: typ, Wanted: into.Type()} 292 } 293 294 if _, err = into.Decode(r, size); err != nil { 295 return err 296 } 297 298 if into.Type() == BlobObjectType { 299 return nil 300 } 301 return r.Close() 302 }