package storer import ( "errors" "io" "time" "github.com/go-git/go-git/v5/plumbing" ) var ( //ErrStop is used to stop a ForEach function in an Iter ErrStop = errors.New("stop iter") ) // EncodedObjectStorer generic storage of objects type EncodedObjectStorer interface { // NewEncodedObject returns a new plumbing.EncodedObject, the real type // of the object can be a custom implementation or the default one, // plumbing.MemoryObject. NewEncodedObject() plumbing.EncodedObject // SetEncodedObject saves an object into the storage, the object should // be create with the NewEncodedObject, method, and file if the type is // not supported. SetEncodedObject(plumbing.EncodedObject) (plumbing.Hash, error) // EncodedObject gets an object by hash with the given // plumbing.ObjectType. Implementors should return // (nil, plumbing.ErrObjectNotFound) if an object doesn't exist with // both the given hash and object type. // // Valid plumbing.ObjectType values are CommitObject, BlobObject, TagObject, // TreeObject and AnyObject. If plumbing.AnyObject is given, the object must // be looked up regardless of its type. EncodedObject(plumbing.ObjectType, plumbing.Hash) (plumbing.EncodedObject, error) // IterObjects returns a custom EncodedObjectStorer over all the object // on the storage. // // Valid plumbing.ObjectType values are CommitObject, BlobObject, TagObject, IterEncodedObjects(plumbing.ObjectType) (EncodedObjectIter, error) // HasEncodedObject returns ErrObjNotFound if the object doesn't // exist. If the object does exist, it returns nil. HasEncodedObject(plumbing.Hash) error // EncodedObjectSize returns the plaintext size of the encoded object. EncodedObjectSize(plumbing.Hash) (int64, error) } // DeltaObjectStorer is an EncodedObjectStorer that can return delta // objects. type DeltaObjectStorer interface { // DeltaObject is the same as EncodedObject but without resolving deltas. // Deltas will be returned as plumbing.DeltaObject instances. DeltaObject(plumbing.ObjectType, plumbing.Hash) (plumbing.EncodedObject, error) } // Transactioner is a optional method for ObjectStorer, it enable transaction // base write and read operations in the storage type Transactioner interface { // Begin starts a transaction. Begin() Transaction } // LooseObjectStorer is an optional interface for managing "loose" // objects, i.e. those not in packfiles. type LooseObjectStorer interface { // ForEachObjectHash iterates over all the (loose) object hashes // in the repository without necessarily having to read those objects. // Objects only inside pack files may be omitted. // If ErrStop is sent the iteration is stop but no error is returned. ForEachObjectHash(func(plumbing.Hash) error) error // LooseObjectTime looks up the (m)time associated with the // loose object (that is not in a pack file). Some // implementations (e.g. without loose objects) // always return an error. LooseObjectTime(plumbing.Hash) (time.Time, error) // DeleteLooseObject deletes a loose object if it exists. DeleteLooseObject(plumbing.Hash) error } // PackedObjectStorer is an optional interface for managing objects in // packfiles. type PackedObjectStorer interface { // ObjectPacks returns hashes of object packs if the underlying // implementation has pack files. ObjectPacks() ([]plumbing.Hash, error) // DeleteOldObjectPackAndIndex deletes an object pack and the corresponding index file if they exist. // Deletion is only performed if the pack is older than the supplied time (or the time is zero). DeleteOldObjectPackAndIndex(plumbing.Hash, time.Time) error } // PackfileWriter is a optional method for ObjectStorer, it enable direct write // of packfile to the storage type PackfileWriter interface { // PackfileWriter returns a writer for writing a packfile to the storage // // If the Storer not implements PackfileWriter the objects should be written // using the Set method. PackfileWriter() (io.WriteCloser, error) } // EncodedObjectIter is a generic closable interface for iterating over objects. type EncodedObjectIter interface { Next() (plumbing.EncodedObject, error) ForEach(func(plumbing.EncodedObject) error) error Close() } // Transaction is an in-progress storage transaction. A transaction must end // with a call to Commit or Rollback. type Transaction interface { SetEncodedObject(plumbing.EncodedObject) (plumbing.Hash, error) EncodedObject(plumbing.ObjectType, plumbing.Hash) (plumbing.EncodedObject, error) Commit() error Rollback() error } // EncodedObjectLookupIter implements EncodedObjectIter. It iterates over a // series of object hashes and yields their associated objects by retrieving // each one from object storage. The retrievals are lazy and only occur when the // iterator moves forward with a call to Next(). // // The EncodedObjectLookupIter must be closed with a call to Close() when it is // no longer needed. type EncodedObjectLookupIter struct { storage EncodedObjectStorer series []plumbing.Hash t plumbing.ObjectType pos int } // NewEncodedObjectLookupIter returns an object iterator given an object storage // and a slice of object hashes. func NewEncodedObjectLookupIter( storage EncodedObjectStorer, t plumbing.ObjectType, series []plumbing.Hash) *EncodedObjectLookupIter { return &EncodedObjectLookupIter{ storage: storage, series: series, t: t, } } // Next returns the next object from the iterator. If the iterator has reached // the end it will return io.EOF as an error. If the object can't be found in // the object storage, it will return plumbing.ErrObjectNotFound as an error. // If the object is retrieved successfully error will be nil. func (iter *EncodedObjectLookupIter) Next() (plumbing.EncodedObject, error) { if iter.pos >= len(iter.series) { return nil, io.EOF } hash := iter.series[iter.pos] obj, err := iter.storage.EncodedObject(iter.t, hash) if err == nil { iter.pos++ } return obj, err } // ForEach call the cb function for each object contained on this iter until // an error happens or the end of the iter is reached. If ErrStop is sent // the iteration is stop but no error is returned. The iterator is closed. func (iter *EncodedObjectLookupIter) ForEach(cb func(plumbing.EncodedObject) error) error { return ForEachIterator(iter, cb) } // Close releases any resources used by the iterator. func (iter *EncodedObjectLookupIter) Close() { iter.pos = len(iter.series) } // EncodedObjectSliceIter implements EncodedObjectIter. It iterates over a // series of objects stored in a slice and yields each one in turn when Next() // is called. // // The EncodedObjectSliceIter must be closed with a call to Close() when it is // no longer needed. type EncodedObjectSliceIter struct { series []plumbing.EncodedObject } // NewEncodedObjectSliceIter returns an object iterator for the given slice of // objects. func NewEncodedObjectSliceIter(series []plumbing.EncodedObject) *EncodedObjectSliceIter { return &EncodedObjectSliceIter{ series: series, } } // Next returns the next object from the iterator. If the iterator has reached // the end it will return io.EOF as an error. If the object is retrieved // successfully error will be nil. func (iter *EncodedObjectSliceIter) Next() (plumbing.EncodedObject, error) { if len(iter.series) == 0 { return nil, io.EOF } obj := iter.series[0] iter.series = iter.series[1:] return obj, nil } // ForEach call the cb function for each object contained on this iter until // an error happens or the end of the iter is reached. If ErrStop is sent // the iteration is stop but no error is returned. The iterator is closed. func (iter *EncodedObjectSliceIter) ForEach(cb func(plumbing.EncodedObject) error) error { return ForEachIterator(iter, cb) } // Close releases any resources used by the iterator. func (iter *EncodedObjectSliceIter) Close() { iter.series = []plumbing.EncodedObject{} } // MultiEncodedObjectIter implements EncodedObjectIter. It iterates over several // EncodedObjectIter, // // The MultiObjectIter must be closed with a call to Close() when it is no // longer needed. type MultiEncodedObjectIter struct { iters []EncodedObjectIter } // NewMultiEncodedObjectIter returns an object iterator for the given slice of // EncodedObjectIters. func NewMultiEncodedObjectIter(iters []EncodedObjectIter) EncodedObjectIter { return &MultiEncodedObjectIter{iters: iters} } // Next returns the next object from the iterator, if one iterator reach io.EOF // is removed and the next one is used. func (iter *MultiEncodedObjectIter) Next() (plumbing.EncodedObject, error) { if len(iter.iters) == 0 { return nil, io.EOF } obj, err := iter.iters[0].Next() if err == io.EOF { iter.iters[0].Close() iter.iters = iter.iters[1:] return iter.Next() } return obj, err } // ForEach call the cb function for each object contained on this iter until // an error happens or the end of the iter is reached. If ErrStop is sent // the iteration is stop but no error is returned. The iterator is closed. func (iter *MultiEncodedObjectIter) ForEach(cb func(plumbing.EncodedObject) error) error { return ForEachIterator(iter, cb) } // Close releases any resources used by the iterator. func (iter *MultiEncodedObjectIter) Close() { for _, i := range iter.iters { i.Close() } } type bareIterator interface { Next() (plumbing.EncodedObject, error) Close() } // ForEachIterator is a helper function to build iterators without need to // rewrite the same ForEach function each time. func ForEachIterator(iter bareIterator, cb func(plumbing.EncodedObject) error) error { defer iter.Close() for { obj, err := iter.Next() if err != nil { if err == io.EOF { return nil } return err } if err := cb(obj); err != nil { if err == ErrStop { return nil } return err } } }