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)
AddAlternate(remote string) 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 enables transactional read and write
// operations.
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 an optional method for ObjectStorer, it enables directly writing
// a packfile to 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
}
}
}