diff options
author | Máximo Cuadros <mcuadros@gmail.com> | 2016-11-08 23:46:38 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2016-11-08 23:46:38 +0100 |
commit | ac095bb12c4d29722b60ba9f20590fa7cfa6bc7d (patch) | |
tree | 223f36f336ba3414b1e45cac8af6c4744a5d7ef6 /plumbing/storer | |
parent | e523701393598f4fa241dd407af9ff8925507a1a (diff) | |
download | go-git-ac095bb12c4d29722b60ba9f20590fa7cfa6bc7d.tar.gz |
new plumbing package (#118)
* plumbing: now core was renamed to core, and formats and clients moved inside
Diffstat (limited to 'plumbing/storer')
-rw-r--r-- | plumbing/storer/index.go | 9 | ||||
-rw-r--r-- | plumbing/storer/object.go | 241 | ||||
-rw-r--r-- | plumbing/storer/object_test.go | 150 | ||||
-rw-r--r-- | plumbing/storer/reference.go | 109 | ||||
-rw-r--r-- | plumbing/storer/reference_test.go | 67 |
5 files changed, 576 insertions, 0 deletions
diff --git a/plumbing/storer/index.go b/plumbing/storer/index.go new file mode 100644 index 0000000..e087296 --- /dev/null +++ b/plumbing/storer/index.go @@ -0,0 +1,9 @@ +package storer + +import "gopkg.in/src-d/go-git.v4/plumbing/format/index" + +// IndexStorer generic storage of index.Index +type IndexStorer interface { + SetIndex(*index.Index) error + Index() (*index.Index, error) +} diff --git a/plumbing/storer/object.go b/plumbing/storer/object.go new file mode 100644 index 0000000..c7841b6 --- /dev/null +++ b/plumbing/storer/object.go @@ -0,0 +1,241 @@ +package storer + +import ( + "errors" + "io" + + "gopkg.in/src-d/go-git.v4/plumbing" +) + +var ( + //ErrStop is used to stop a ForEach function in an Iter + ErrStop = errors.New("stop iter") +) + +// ObjectStorer generic storage of objects +type ObjectStorer interface { + // NewObject returns a new plumbing.Object, the real type of the object can + // be a custom implementation or the defaul one, plumbing.MemoryObject + NewObject() plumbing.Object + // SetObject save an object into the storage, the object shuld be create + // with the NewObject, method, and file if the type is not supported. + SetObject(plumbing.Object) (plumbing.Hash, error) + // Object get 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. + Object(plumbing.ObjectType, plumbing.Hash) (plumbing.Object, error) + // IterObjects returns a custom ObjectIter over all the object on the + // storage. + // + // Valid plumbing.ObjectType values are CommitObject, BlobObject, TagObject, + IterObjects(plumbing.ObjectType) (ObjectIter, 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 +} + +// PackfileWriter is a optional method for ObjectStorer, it enable direct write +// of packfile to the storage +type PackfileWriter interface { + // PackfileWriter retuns 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) +} + +// ObjectIter is a generic closable interface for iterating over objects. +type ObjectIter interface { + Next() (plumbing.Object, error) + ForEach(func(plumbing.Object) error) error + Close() +} + +// Transaction is an in-progress storage transaction. A transaction must end +// with a call to Commit or Rollback. +type Transaction interface { + SetObject(plumbing.Object) (plumbing.Hash, error) + Object(plumbing.ObjectType, plumbing.Hash) (plumbing.Object, error) + Commit() error + Rollback() error +} + +// ObjectLookupIter implements ObjectIter. 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 ObjectLookupIter must be closed with a call to Close() when it is no +// longer needed. +type ObjectLookupIter struct { + storage ObjectStorer + series []plumbing.Hash + t plumbing.ObjectType + pos int +} + +// NewObjectLookupIter returns an object iterator given an object storage and +// a slice of object hashes. +func NewObjectLookupIter( + storage ObjectStorer, t plumbing.ObjectType, series []plumbing.Hash) *ObjectLookupIter { + return &ObjectLookupIter{ + 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 retreieved successfully error will be nil. +func (iter *ObjectLookupIter) Next() (plumbing.Object, error) { + if iter.pos >= len(iter.series) { + return nil, io.EOF + } + + hash := iter.series[iter.pos] + obj, err := iter.storage.Object(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 happends 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 *ObjectLookupIter) ForEach(cb func(plumbing.Object) error) error { + return ForEachIterator(iter, cb) +} + +// Close releases any resources used by the iterator. +func (iter *ObjectLookupIter) Close() { + iter.pos = len(iter.series) +} + +// ObjectSliceIter implements ObjectIter. It iterates over a series of objects +// stored in a slice and yields each one in turn when Next() is called. +// +// The ObjectSliceIter must be closed with a call to Close() when it is no +// longer needed. +type ObjectSliceIter struct { + series []plumbing.Object + pos int +} + +// NewObjectSliceIter returns an object iterator for the given slice of objects. +func NewObjectSliceIter(series []plumbing.Object) *ObjectSliceIter { + return &ObjectSliceIter{ + 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 retreieved +// successfully error will be nil. +func (iter *ObjectSliceIter) Next() (plumbing.Object, 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 happends 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 *ObjectSliceIter) ForEach(cb func(plumbing.Object) error) error { + return ForEachIterator(iter, cb) +} + +// Close releases any resources used by the iterator. +func (iter *ObjectSliceIter) Close() { + iter.series = []plumbing.Object{} +} + +// MultiObjectIter implements ObjectIter. It iterates over several ObjectIter, +// +// The MultiObjectIter must be closed with a call to Close() when it is no +// longer needed. +type MultiObjectIter struct { + iters []ObjectIter + pos int +} + +// NewMultiObjectIter returns an object iterator for the given slice of objects. +func NewMultiObjectIter(iters []ObjectIter) ObjectIter { + return &MultiObjectIter{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 *MultiObjectIter) Next() (plumbing.Object, 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 happends 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 *MultiObjectIter) ForEach(cb func(plumbing.Object) error) error { + return ForEachIterator(iter, cb) +} + +// Close releases any resources used by the iterator. +func (iter *MultiObjectIter) Close() { + for _, i := range iter.iters { + i.Close() + } +} + +type bareIterator interface { + Next() (plumbing.Object, 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.Object) 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 + } + } +} diff --git a/plumbing/storer/object_test.go b/plumbing/storer/object_test.go new file mode 100644 index 0000000..a0a7755 --- /dev/null +++ b/plumbing/storer/object_test.go @@ -0,0 +1,150 @@ +package storer + +import ( + "fmt" + "testing" + + . "gopkg.in/check.v1" + "gopkg.in/src-d/go-git.v4/plumbing" +) + +func Test(t *testing.T) { TestingT(t) } + +type ObjectSuite struct { + Objects []plumbing.Object + Hash []plumbing.Hash +} + +var _ = Suite(&ObjectSuite{}) + +func (s *ObjectSuite) SetUpSuite(c *C) { + s.Objects = []plumbing.Object{ + s.buildObject([]byte("foo")), + s.buildObject([]byte("bar")), + } + + for _, o := range s.Objects { + s.Hash = append(s.Hash, o.Hash()) + } +} + +func (s *ObjectSuite) TestMultiObjectIterNext(c *C) { + expected := []plumbing.Object{ + &plumbing.MemoryObject{}, + &plumbing.MemoryObject{}, + &plumbing.MemoryObject{}, + &plumbing.MemoryObject{}, + &plumbing.MemoryObject{}, + &plumbing.MemoryObject{}, + } + + iter := NewMultiObjectIter([]ObjectIter{ + NewObjectSliceIter(expected[0:2]), + NewObjectSliceIter(expected[2:4]), + NewObjectSliceIter(expected[4:5]), + }) + + var i int + iter.ForEach(func(o plumbing.Object) error { + c.Assert(o, Equals, expected[i]) + i++ + return nil + }) + + iter.Close() +} + +func (s *ObjectSuite) buildObject(content []byte) plumbing.Object { + o := &plumbing.MemoryObject{} + o.Write(content) + + return o +} + +func (s *ObjectSuite) TestObjectLookupIter(c *C) { + var count int + + storage := &MockObjectStorage{s.Objects} + i := NewObjectLookupIter(storage, plumbing.CommitObject, s.Hash) + err := i.ForEach(func(o plumbing.Object) error { + c.Assert(o, NotNil) + c.Assert(o.Hash().String(), Equals, s.Hash[count].String()) + count++ + return nil + }) + + c.Assert(err, IsNil) + i.Close() +} + +func (s *ObjectSuite) TestObjectSliceIter(c *C) { + var count int + + i := NewObjectSliceIter(s.Objects) + err := i.ForEach(func(o plumbing.Object) error { + c.Assert(o, NotNil) + c.Assert(o.Hash().String(), Equals, s.Hash[count].String()) + count++ + return nil + }) + + c.Assert(count, Equals, 2) + c.Assert(err, IsNil) + c.Assert(i.series, HasLen, 0) +} + +func (s *ObjectSuite) TestObjectSliceIterStop(c *C) { + i := NewObjectSliceIter(s.Objects) + + var count = 0 + err := i.ForEach(func(o plumbing.Object) error { + c.Assert(o, NotNil) + c.Assert(o.Hash().String(), Equals, s.Hash[count].String()) + count++ + return ErrStop + }) + + c.Assert(count, Equals, 1) + c.Assert(err, IsNil) +} + +func (s *ObjectSuite) TestObjectSliceIterError(c *C) { + i := NewObjectSliceIter([]plumbing.Object{ + s.buildObject([]byte("foo")), + }) + + err := i.ForEach(func(plumbing.Object) error { + return fmt.Errorf("a random error") + }) + + c.Assert(err, NotNil) +} + +type MockObjectStorage struct { + db []plumbing.Object +} + +func (o *MockObjectStorage) NewObject() plumbing.Object { + return nil +} + +func (o *MockObjectStorage) SetObject(obj plumbing.Object) (plumbing.Hash, error) { + return plumbing.ZeroHash, nil +} + +func (o *MockObjectStorage) Object(t plumbing.ObjectType, h plumbing.Hash) (plumbing.Object, error) { + for _, o := range o.db { + if o.Hash() == h { + return o, nil + } + } + return nil, plumbing.ErrObjectNotFound +} + +func (o *MockObjectStorage) IterObjects(t plumbing.ObjectType) (ObjectIter, error) { + return nil, nil +} + +func (o *MockObjectStorage) Begin() Transaction { + return nil +} diff --git a/plumbing/storer/reference.go b/plumbing/storer/reference.go new file mode 100644 index 0000000..5e818c6 --- /dev/null +++ b/plumbing/storer/reference.go @@ -0,0 +1,109 @@ +package storer + +import ( + "errors" + "io" + + "gopkg.in/src-d/go-git.v4/plumbing" +) + +const MaxResolveRecursion = 1024 + +// ErrMaxResolveRecursion is returned by ResolveReference is MaxResolveRecursion +// is exceeded +var ErrMaxResolveRecursion = errors.New("max. recursion level reached") + +// ReferenceStorer generic storage of references +type ReferenceStorer interface { + SetReference(*plumbing.Reference) error + Reference(plumbing.ReferenceName) (*plumbing.Reference, error) + IterReferences() (ReferenceIter, error) +} + +// ReferenceIter is a generic closable interface for iterating over references +type ReferenceIter interface { + Next() (*plumbing.Reference, error) + ForEach(func(*plumbing.Reference) error) error + Close() +} + +// ReferenceSliceIter implements ReferenceIter. It iterates over a series of +// references stored in a slice and yields each one in turn when Next() is +// called. +// +// The ReferenceSliceIter must be closed with a call to Close() when it is no +// longer needed. +type ReferenceSliceIter struct { + series []*plumbing.Reference + pos int +} + +// NewReferenceSliceIter returns a reference iterator for the given slice of +// objects. +func NewReferenceSliceIter(series []*plumbing.Reference) *ReferenceSliceIter { + return &ReferenceSliceIter{ + series: series, + } +} + +// Next returns the next reference from the iterator. If the iterator has +// reached the end it will return io.EOF as an error. +func (iter *ReferenceSliceIter) Next() (*plumbing.Reference, error) { + if iter.pos >= len(iter.series) { + return nil, io.EOF + } + + obj := iter.series[iter.pos] + iter.pos++ + return obj, nil +} + +// ForEach call the cb function for each reference contained on this iter until +// an error happends 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 *ReferenceSliceIter) ForEach(cb func(*plumbing.Reference) error) error { + defer iter.Close() + for _, r := range iter.series { + if err := cb(r); err != nil { + if err == ErrStop { + return nil + } + + return nil + } + } + + return nil +} + +// Close releases any resources used by the iterator. +func (iter *ReferenceSliceIter) Close() { + iter.pos = len(iter.series) +} + +// ResolveReference resolve a SymbolicReference to a HashReference +func ResolveReference(s ReferenceStorer, n plumbing.ReferenceName) (*plumbing.Reference, error) { + r, err := s.Reference(n) + if err != nil || r == nil { + return r, err + } + return resolveReference(s, r, 0) +} + +func resolveReference(s ReferenceStorer, r *plumbing.Reference, recursion int) (*plumbing.Reference, error) { + if r.Type() != plumbing.SymbolicReference { + return r, nil + } + + if recursion > MaxResolveRecursion { + return nil, ErrMaxResolveRecursion + } + + t, err := s.Reference(r.Target()) + if err != nil { + return nil, err + } + + recursion++ + return resolveReference(s, t, recursion) +} diff --git a/plumbing/storer/reference_test.go b/plumbing/storer/reference_test.go new file mode 100644 index 0000000..3014df5 --- /dev/null +++ b/plumbing/storer/reference_test.go @@ -0,0 +1,67 @@ +package storer + +import ( + "io" + + . "gopkg.in/check.v1" + "gopkg.in/src-d/go-git.v4/plumbing" +) + +type ReferenceSuite struct{} + +var _ = Suite(&ReferenceSuite{}) + +func (s *ReferenceSuite) TestReferenceSliceIterNext(c *C) { + slice := []*plumbing.Reference{ + plumbing.NewReferenceFromStrings("foo", "foo"), + plumbing.NewReferenceFromStrings("bar", "bar"), + } + + i := NewReferenceSliceIter(slice) + foo, err := i.Next() + c.Assert(err, IsNil) + c.Assert(foo == slice[0], Equals, true) + + bar, err := i.Next() + c.Assert(err, IsNil) + c.Assert(bar == slice[1], Equals, true) + + empty, err := i.Next() + c.Assert(err, Equals, io.EOF) + c.Assert(empty, IsNil) +} + +func (s *ReferenceSuite) TestReferenceSliceIterForEach(c *C) { + slice := []*plumbing.Reference{ + plumbing.NewReferenceFromStrings("foo", "foo"), + plumbing.NewReferenceFromStrings("bar", "bar"), + } + + i := NewReferenceSliceIter(slice) + var count int + i.ForEach(func(r *plumbing.Reference) error { + c.Assert(r == slice[count], Equals, true) + count++ + return nil + }) + + c.Assert(count, Equals, 2) +} + +func (s *ReferenceSuite) TestReferenceSliceIterForEachStop(c *C) { + slice := []*plumbing.Reference{ + plumbing.NewReferenceFromStrings("foo", "foo"), + plumbing.NewReferenceFromStrings("bar", "bar"), + } + + i := NewReferenceSliceIter(slice) + + var count int + i.ForEach(func(r *plumbing.Reference) error { + c.Assert(r == slice[count], Equals, true) + count++ + return ErrStop + }) + + c.Assert(count, Equals, 1) +} |