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

Commit 41d6f2c

Browse files
authored
Merge pull request #982 from keybase/strib/gh-KBFS-3474-object-sizes
tree: add a Size() method for getting plaintext size
2 parents 8153e04 + 6faf286 commit 41d6f2c

File tree

8 files changed

+168
-0
lines changed

8 files changed

+168
-0
lines changed

plumbing/format/packfile/packfile.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,24 @@ func (p *Packfile) GetByOffset(o int64) (plumbing.EncodedObject, error) {
9090
return p.nextObject()
9191
}
9292

93+
// GetSizeByOffset retrieves the size of the encoded object from the
94+
// packfile with the given offset.
95+
func (p *Packfile) GetSizeByOffset(o int64) (size int64, err error) {
96+
if _, err := p.s.SeekFromStart(o); err != nil {
97+
if err == io.EOF || isInvalid(err) {
98+
return 0, plumbing.ErrObjectNotFound
99+
}
100+
101+
return 0, err
102+
}
103+
104+
h, err := p.nextObjectHeader()
105+
if err != nil {
106+
return 0, err
107+
}
108+
return h.Length, nil
109+
}
110+
93111
func (p *Packfile) nextObjectHeader() (*ObjectHeader, error) {
94112
h, err := p.s.NextObjectHeader()
95113
p.s.pendingObject = nil

plumbing/object/tree.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,17 @@ func (t *Tree) File(path string) (*File, error) {
8787
return NewFile(path, e.Mode, blob), nil
8888
}
8989

90+
// Size returns the plaintext size of an object, without reading it
91+
// into memory.
92+
func (t *Tree) Size(path string) (int64, error) {
93+
e, err := t.FindEntry(path)
94+
if err != nil {
95+
return 0, ErrEntryNotFound
96+
}
97+
98+
return t.s.EncodedObjectSize(e.Hash)
99+
}
100+
90101
// Tree returns the tree identified by the `path` argument.
91102
// The path is interpreted as relative to the tree receiver.
92103
func (t *Tree) Tree(path string) (*Tree, error) {

plumbing/object/tree_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,12 @@ func (s *TreeSuite) TestFileFailsWithExistingTrees(c *C) {
9898
c.Assert(err, Equals, ErrFileNotFound)
9999
}
100100

101+
func (s *TreeSuite) TestSize(c *C) {
102+
size, err := s.Tree.Size("LICENSE")
103+
c.Assert(err, IsNil)
104+
c.Assert(size, Equals, int64(1072))
105+
}
106+
101107
func (s *TreeSuite) TestFiles(c *C) {
102108
var count int
103109
err := s.Tree.Files().ForEach(func(f *File) error {

plumbing/storer/object.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ type EncodedObjectStorer interface {
4040
// HasEncodedObject returns ErrObjNotFound if the object doesn't
4141
// exist. If the object does exist, it returns nil.
4242
HasEncodedObject(plumbing.Hash) error
43+
// EncodedObjectSize returns the plaintext size of the encoded object.
44+
EncodedObjectSize(plumbing.Hash) (int64, error)
4345
}
4446

4547
// DeltaObjectStorer is an EncodedObjectStorer that can return delta

plumbing/storer/object_test.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,16 @@ func (o *MockObjectStorage) HasEncodedObject(h plumbing.Hash) error {
141141
return plumbing.ErrObjectNotFound
142142
}
143143

144+
func (o *MockObjectStorage) EncodedObjectSize(h plumbing.Hash) (
145+
size int64, err error) {
146+
for _, o := range o.db {
147+
if o.Hash() == h {
148+
return o.Size(), nil
149+
}
150+
}
151+
return 0, plumbing.ErrObjectNotFound
152+
}
153+
144154
func (o *MockObjectStorage) EncodedObject(t plumbing.ObjectType, h plumbing.Hash) (plumbing.EncodedObject, error) {
145155
for _, o := range o.db {
146156
if o.Hash() == h {

storage/filesystem/object.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,79 @@ func (s *ObjectStorage) HasEncodedObject(h plumbing.Hash) (err error) {
160160
return nil
161161
}
162162

163+
func (s *ObjectStorage) encodedObjectSizeFromUnpacked(h plumbing.Hash) (
164+
size int64, err error) {
165+
f, err := s.dir.Object(h)
166+
if err != nil {
167+
if os.IsNotExist(err) {
168+
return 0, plumbing.ErrObjectNotFound
169+
}
170+
171+
return 0, err
172+
}
173+
174+
r, err := objfile.NewReader(f)
175+
if err != nil {
176+
return 0, err
177+
}
178+
defer ioutil.CheckClose(r, &err)
179+
180+
_, size, err = r.Header()
181+
return size, err
182+
}
183+
184+
func (s *ObjectStorage) encodedObjectSizeFromPackfile(h plumbing.Hash) (
185+
size int64, err error) {
186+
if err := s.requireIndex(); err != nil {
187+
return 0, err
188+
}
189+
190+
pack, _, offset := s.findObjectInPackfile(h)
191+
if offset == -1 {
192+
return 0, plumbing.ErrObjectNotFound
193+
}
194+
195+
f, err := s.dir.ObjectPack(pack)
196+
if err != nil {
197+
return 0, err
198+
}
199+
defer ioutil.CheckClose(f, &err)
200+
201+
idx := s.index[pack]
202+
hash, err := idx.FindHash(offset)
203+
if err == nil {
204+
obj, ok := s.deltaBaseCache.Get(hash)
205+
if ok {
206+
return obj.Size(), nil
207+
}
208+
} else if err != nil && err != plumbing.ErrObjectNotFound {
209+
return 0, err
210+
}
211+
212+
var p *packfile.Packfile
213+
if s.deltaBaseCache != nil {
214+
p = packfile.NewPackfileWithCache(idx, s.dir.Fs(), f, s.deltaBaseCache)
215+
} else {
216+
p = packfile.NewPackfile(idx, s.dir.Fs(), f)
217+
}
218+
219+
return p.GetSizeByOffset(offset)
220+
}
221+
222+
// EncodedObjectSize returns the plaintext size of the given object,
223+
// without actually reading the full object data from storage.
224+
func (s *ObjectStorage) EncodedObjectSize(h plumbing.Hash) (
225+
size int64, err error) {
226+
size, err = s.encodedObjectSizeFromUnpacked(h)
227+
if err != nil && err != plumbing.ErrObjectNotFound {
228+
return 0, err
229+
} else if err == nil {
230+
return size, nil
231+
}
232+
233+
return s.encodedObjectSizeFromPackfile(h)
234+
}
235+
163236
// EncodedObject returns the object with the given hash, by searching for it in
164237
// the packfile and the git object directories.
165238
func (s *ObjectStorage) EncodedObject(t plumbing.ObjectType, h plumbing.Hash) (plumbing.EncodedObject, error) {

storage/filesystem/object_test.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,44 @@ func (s *FsSuite) TestGetFromPackfileKeepDescriptors(c *C) {
8383
})
8484
}
8585

86+
func (s *FsSuite) TestGetSizeOfObjectFile(c *C) {
87+
fs := fixtures.ByTag(".git").ByTag("unpacked").One().DotGit()
88+
o := NewObjectStorage(dotgit.New(fs), cache.NewObjectLRUDefault())
89+
90+
// Get the size of `tree_walker.go`.
91+
expected := plumbing.NewHash("cbd81c47be12341eb1185b379d1c82675aeded6a")
92+
size, err := o.EncodedObjectSize(expected)
93+
c.Assert(err, IsNil)
94+
c.Assert(size, Equals, int64(2412))
95+
}
96+
97+
func (s *FsSuite) TestGetSizeFromPackfile(c *C) {
98+
fixtures.Basic().ByTag(".git").Test(c, func(f *fixtures.Fixture) {
99+
fs := f.DotGit()
100+
o := NewObjectStorage(dotgit.New(fs), cache.NewObjectLRUDefault())
101+
102+
// Get the size of `binary.jpg`.
103+
expected := plumbing.NewHash("d5c0f4ab811897cadf03aec358ae60d21f91c50d")
104+
size, err := o.EncodedObjectSize(expected)
105+
c.Assert(err, IsNil)
106+
c.Assert(size, Equals, int64(76110))
107+
})
108+
}
109+
110+
func (s *FsSuite) TestGetSizeOfAllObjectFiles(c *C) {
111+
fs := fixtures.ByTag(".git").One().DotGit()
112+
o := NewObjectStorage(dotgit.New(fs), cache.NewObjectLRUDefault())
113+
114+
// Get the size of `tree_walker.go`.
115+
err := o.ForEachObjectHash(func(h plumbing.Hash) error {
116+
size, err := o.EncodedObjectSize(h)
117+
c.Assert(err, IsNil)
118+
c.Assert(size, Not(Equals), int64(0))
119+
return nil
120+
})
121+
c.Assert(err, IsNil)
122+
}
123+
86124
func (s *FsSuite) TestGetFromPackfileMultiplePackfiles(c *C) {
87125
fs := fixtures.ByTag(".git").ByTag("multi-packfile").One().DotGit()
88126
o := NewObjectStorage(dotgit.New(fs), cache.NewObjectLRUDefault())

storage/memory/storage.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,16 @@ func (o *ObjectStorage) HasEncodedObject(h plumbing.Hash) (err error) {
122122
return nil
123123
}
124124

125+
func (o *ObjectStorage) EncodedObjectSize(h plumbing.Hash) (
126+
size int64, err error) {
127+
obj, ok := o.Objects[h]
128+
if !ok {
129+
return 0, plumbing.ErrObjectNotFound
130+
}
131+
132+
return obj.Size(), nil
133+
}
134+
125135
func (o *ObjectStorage) EncodedObject(t plumbing.ObjectType, h plumbing.Hash) (plumbing.EncodedObject, error) {
126136
obj, ok := o.Objects[h]
127137
if !ok || (plumbing.AnyObject != t && obj.Type() != t) {

0 commit comments

Comments
 (0)