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  }