Skip to content
This repository was archived by the owner on Sep 11, 2020. It is now read-only.

tree: add a Size() method for getting plaintext size #982

Merged
merged 4 commits into from
Oct 16, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions plumbing/format/packfile/packfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,24 @@ func (p *Packfile) GetByOffset(o int64) (plumbing.EncodedObject, error) {
return p.nextObject()
}

// GetSizeByOffset retrieves the size of the encoded object from the
// packfile with the given offset.
func (p *Packfile) GetSizeByOffset(o int64) (size int64, err error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add the documentation to this method?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call, done. Thanks for looking!

if _, err := p.s.SeekFromStart(o); err != nil {
if err == io.EOF || isInvalid(err) {
return 0, plumbing.ErrObjectNotFound
}

return 0, err
}

h, err := p.nextObjectHeader()
if err != nil {
return 0, err
}
return h.Length, nil
}

func (p *Packfile) nextObjectHeader() (*ObjectHeader, error) {
h, err := p.s.NextObjectHeader()
p.s.pendingObject = nil
Expand Down
11 changes: 11 additions & 0 deletions plumbing/object/tree.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,17 @@ func (t *Tree) File(path string) (*File, error) {
return NewFile(path, e.Mode, blob), nil
}

// Size returns the plaintext size of an object, without reading it
// into memory.
func (t *Tree) Size(path string) (int64, error) {
e, err := t.FindEntry(path)
if err != nil {
return 0, ErrEntryNotFound
}

return t.s.EncodedObjectSize(e.Hash)
}

// Tree returns the tree identified by the `path` argument.
// The path is interpreted as relative to the tree receiver.
func (t *Tree) Tree(path string) (*Tree, error) {
Expand Down
6 changes: 6 additions & 0 deletions plumbing/object/tree_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,12 @@ func (s *TreeSuite) TestFileFailsWithExistingTrees(c *C) {
c.Assert(err, Equals, ErrFileNotFound)
}

func (s *TreeSuite) TestSize(c *C) {
size, err := s.Tree.Size("LICENSE")
c.Assert(err, IsNil)
c.Assert(size, Equals, int64(1072))
}

func (s *TreeSuite) TestFiles(c *C) {
var count int
err := s.Tree.Files().ForEach(func(f *File) error {
Expand Down
2 changes: 2 additions & 0 deletions plumbing/storer/object.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ type EncodedObjectStorer interface {
// 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
Expand Down
10 changes: 10 additions & 0 deletions plumbing/storer/object_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,16 @@ func (o *MockObjectStorage) HasEncodedObject(h plumbing.Hash) error {
return plumbing.ErrObjectNotFound
}

func (o *MockObjectStorage) EncodedObjectSize(h plumbing.Hash) (
size int64, err error) {
for _, o := range o.db {
if o.Hash() == h {
return o.Size(), nil
}
}
return 0, plumbing.ErrObjectNotFound
}

func (o *MockObjectStorage) EncodedObject(t plumbing.ObjectType, h plumbing.Hash) (plumbing.EncodedObject, error) {
for _, o := range o.db {
if o.Hash() == h {
Expand Down
73 changes: 73 additions & 0 deletions storage/filesystem/object.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,79 @@ func (s *ObjectStorage) HasEncodedObject(h plumbing.Hash) (err error) {
return nil
}

func (s *ObjectStorage) encodedObjectSizeFromUnpacked(h plumbing.Hash) (
size int64, err error) {
f, err := s.dir.Object(h)
if err != nil {
if os.IsNotExist(err) {
return 0, plumbing.ErrObjectNotFound
}

return 0, err
}

r, err := objfile.NewReader(f)
if err != nil {
return 0, err
}
defer ioutil.CheckClose(r, &err)

_, size, err = r.Header()
return size, err
}

func (s *ObjectStorage) encodedObjectSizeFromPackfile(h plumbing.Hash) (
size int64, err error) {
if err := s.requireIndex(); err != nil {
return 0, err
}

pack, _, offset := s.findObjectInPackfile(h)
if offset == -1 {
return 0, plumbing.ErrObjectNotFound
}

f, err := s.dir.ObjectPack(pack)
if err != nil {
return 0, err
}
defer ioutil.CheckClose(f, &err)

idx := s.index[pack]
hash, err := idx.FindHash(offset)
if err == nil {
obj, ok := s.deltaBaseCache.Get(hash)
if ok {
return obj.Size(), nil
}
} else if err != nil && err != plumbing.ErrObjectNotFound {
return 0, err
}

var p *packfile.Packfile
if s.deltaBaseCache != nil {
p = packfile.NewPackfileWithCache(idx, s.dir.Fs(), f, s.deltaBaseCache)
} else {
p = packfile.NewPackfile(idx, s.dir.Fs(), f)
}

return p.GetSizeByOffset(offset)
}

// EncodedObjectSize returns the plaintext size of the given object,
// without actually reading the full object data from storage.
func (s *ObjectStorage) EncodedObjectSize(h plumbing.Hash) (
size int64, err error) {
size, err = s.encodedObjectSizeFromUnpacked(h)
if err != nil && err != plumbing.ErrObjectNotFound {
return 0, err
} else if err == nil {
return size, nil
}

return s.encodedObjectSizeFromPackfile(h)
}

// EncodedObject returns the object with the given hash, by searching for it in
// the packfile and the git object directories.
func (s *ObjectStorage) EncodedObject(t plumbing.ObjectType, h plumbing.Hash) (plumbing.EncodedObject, error) {
Expand Down
38 changes: 38 additions & 0 deletions storage/filesystem/object_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,44 @@ func (s *FsSuite) TestGetFromPackfileKeepDescriptors(c *C) {
})
}

func (s *FsSuite) TestGetSizeOfObjectFile(c *C) {
fs := fixtures.ByTag(".git").ByTag("unpacked").One().DotGit()
o := NewObjectStorage(dotgit.New(fs), cache.NewObjectLRUDefault())

// Get the size of `tree_walker.go`.
expected := plumbing.NewHash("cbd81c47be12341eb1185b379d1c82675aeded6a")
size, err := o.EncodedObjectSize(expected)
c.Assert(err, IsNil)
c.Assert(size, Equals, int64(2412))
}

func (s *FsSuite) TestGetSizeFromPackfile(c *C) {
fixtures.Basic().ByTag(".git").Test(c, func(f *fixtures.Fixture) {
fs := f.DotGit()
o := NewObjectStorage(dotgit.New(fs), cache.NewObjectLRUDefault())

// Get the size of `binary.jpg`.
expected := plumbing.NewHash("d5c0f4ab811897cadf03aec358ae60d21f91c50d")
size, err := o.EncodedObjectSize(expected)
c.Assert(err, IsNil)
c.Assert(size, Equals, int64(76110))
})
}

func (s *FsSuite) TestGetSizeOfAllObjectFiles(c *C) {
fs := fixtures.ByTag(".git").One().DotGit()
o := NewObjectStorage(dotgit.New(fs), cache.NewObjectLRUDefault())

// Get the size of `tree_walker.go`.
err := o.ForEachObjectHash(func(h plumbing.Hash) error {
size, err := o.EncodedObjectSize(h)
c.Assert(err, IsNil)
c.Assert(size, Not(Equals), int64(0))
return nil
})
c.Assert(err, IsNil)
}

func (s *FsSuite) TestGetFromPackfileMultiplePackfiles(c *C) {
fs := fixtures.ByTag(".git").ByTag("multi-packfile").One().DotGit()
o := NewObjectStorage(dotgit.New(fs), cache.NewObjectLRUDefault())
Expand Down
10 changes: 10 additions & 0 deletions storage/memory/storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,16 @@ func (o *ObjectStorage) HasEncodedObject(h plumbing.Hash) (err error) {
return nil
}

func (o *ObjectStorage) EncodedObjectSize(h plumbing.Hash) (
size int64, err error) {
obj, ok := o.Objects[h]
if !ok {
return 0, plumbing.ErrObjectNotFound
}

return obj.Size(), nil
}

func (o *ObjectStorage) EncodedObject(t plumbing.ObjectType, h plumbing.Hash) (plumbing.EncodedObject, error) {
obj, ok := o.Objects[h]
if !ok || (plumbing.AnyObject != t && obj.Type() != t) {
Expand Down