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

Commit 7853ab6

Browse files
authored
Merge pull request #994 from epiclabs-io/fix-thin-pack
plumbing/format/packfile: Fix broken "thin" packfile support. Fixes #991
2 parents 1e77971 + 38ebf56 commit 7853ab6

File tree

2 files changed

+95
-47
lines changed

2 files changed

+95
-47
lines changed

plumbing/format/packfile/parser.go

+45-47
Original file line numberDiff line numberDiff line change
@@ -38,15 +38,14 @@ type Observer interface {
3838
// Parser decodes a packfile and calls any observer associated to it. Is used
3939
// to generate indexes.
4040
type Parser struct {
41-
storage storer.EncodedObjectStorer
42-
scanner *Scanner
43-
count uint32
44-
oi []*objectInfo
45-
oiByHash map[plumbing.Hash]*objectInfo
46-
oiByOffset map[int64]*objectInfo
47-
hashOffset map[plumbing.Hash]int64
48-
pendingRefDeltas map[plumbing.Hash][]*objectInfo
49-
checksum plumbing.Hash
41+
storage storer.EncodedObjectStorer
42+
scanner *Scanner
43+
count uint32
44+
oi []*objectInfo
45+
oiByHash map[plumbing.Hash]*objectInfo
46+
oiByOffset map[int64]*objectInfo
47+
hashOffset map[plumbing.Hash]int64
48+
checksum plumbing.Hash
5049

5150
cache *cache.BufferLRU
5251
// delta content by offset, only used if source is not seekable
@@ -78,13 +77,12 @@ func NewParserWithStorage(
7877
}
7978

8079
return &Parser{
81-
storage: storage,
82-
scanner: scanner,
83-
ob: ob,
84-
count: 0,
85-
cache: cache.NewBufferLRUDefault(),
86-
pendingRefDeltas: make(map[plumbing.Hash][]*objectInfo),
87-
deltas: deltas,
80+
storage: storage,
81+
scanner: scanner,
82+
ob: ob,
83+
count: 0,
84+
cache: cache.NewBufferLRUDefault(),
85+
deltas: deltas,
8886
}, nil
8987
}
9088

@@ -150,10 +148,6 @@ func (p *Parser) Parse() (plumbing.Hash, error) {
150148
return plumbing.ZeroHash, err
151149
}
152150

153-
if len(p.pendingRefDeltas) > 0 {
154-
return plumbing.ZeroHash, ErrReferenceDeltaNotFound
155-
}
156-
157151
if err := p.onFooter(p.checksum); err != nil {
158152
return plumbing.ZeroHash, err
159153
}
@@ -205,18 +199,21 @@ func (p *Parser) indexObjects() error {
205199
parent.Children = append(parent.Children, ota)
206200
case plumbing.REFDeltaObject:
207201
delta = true
208-
209202
parent, ok := p.oiByHash[oh.Reference]
210-
if ok {
211-
ota = newDeltaObject(oh.Offset, oh.Length, t, parent)
212-
parent.Children = append(parent.Children, ota)
213-
} else {
214-
ota = newBaseObject(oh.Offset, oh.Length, t)
215-
p.pendingRefDeltas[oh.Reference] = append(
216-
p.pendingRefDeltas[oh.Reference],
217-
ota,
218-
)
203+
if !ok {
204+
// can't find referenced object in this pack file
205+
// this must be a "thin" pack.
206+
parent = &objectInfo{ //Placeholder parent
207+
SHA1: oh.Reference,
208+
ExternalRef: true, // mark as an external reference that must be resolved
209+
Type: plumbing.AnyObject,
210+
DiskType: plumbing.AnyObject,
211+
}
212+
p.oiByHash[oh.Reference] = parent
219213
}
214+
ota = newDeltaObject(oh.Offset, oh.Length, t, parent)
215+
parent.Children = append(parent.Children, ota)
216+
220217
default:
221218
ota = newBaseObject(oh.Offset, oh.Length, t)
222219
}
@@ -297,16 +294,20 @@ func (p *Parser) resolveDeltas() error {
297294
return nil
298295
}
299296

300-
func (p *Parser) get(o *objectInfo) ([]byte, error) {
301-
b, ok := p.cache.Get(o.Offset)
297+
func (p *Parser) get(o *objectInfo) (b []byte, err error) {
298+
var ok bool
299+
if !o.ExternalRef { // skip cache check for placeholder parents
300+
b, ok = p.cache.Get(o.Offset)
301+
}
302+
302303
// If it's not on the cache and is not a delta we can try to find it in the
303-
// storage, if there's one.
304+
// storage, if there's one. External refs must enter here.
304305
if !ok && p.storage != nil && !o.Type.IsDelta() {
305-
var err error
306306
e, err := p.storage.EncodedObject(plumbing.AnyObject, o.SHA1)
307307
if err != nil {
308308
return nil, err
309309
}
310+
o.Type = e.Type()
310311

311312
r, err := e.Reader()
312313
if err != nil {
@@ -323,6 +324,11 @@ func (p *Parser) get(o *objectInfo) ([]byte, error) {
323324
return b, nil
324325
}
325326

327+
if o.ExternalRef {
328+
// we were not able to resolve a ref in a thin pack
329+
return nil, ErrReferenceDeltaNotFound
330+
}
331+
326332
var data []byte
327333
if o.DiskType.IsDelta() {
328334
base, err := p.get(o.Parent)
@@ -335,7 +341,6 @@ func (p *Parser) get(o *objectInfo) ([]byte, error) {
335341
return nil, err
336342
}
337343
} else {
338-
var err error
339344
data, err = p.readData(o)
340345
if err != nil {
341346
return nil, err
@@ -367,14 +372,6 @@ func (p *Parser) resolveObject(
367372
return nil, err
368373
}
369374

370-
if pending, ok := p.pendingRefDeltas[o.SHA1]; ok {
371-
for _, po := range pending {
372-
po.Parent = o
373-
o.Children = append(o.Children, po)
374-
}
375-
delete(p.pendingRefDeltas, o.SHA1)
376-
}
377-
378375
if p.storage != nil {
379376
obj := new(plumbing.MemoryObject)
380377
obj.SetSize(o.Size())
@@ -447,10 +444,11 @@ func getSHA1(t plumbing.ObjectType, data []byte) (plumbing.Hash, error) {
447444
}
448445

449446
type objectInfo struct {
450-
Offset int64
451-
Length int64
452-
Type plumbing.ObjectType
453-
DiskType plumbing.ObjectType
447+
Offset int64
448+
Length int64
449+
Type plumbing.ObjectType
450+
DiskType plumbing.ObjectType
451+
ExternalRef bool // indicates this is an external reference in a thin pack file
454452

455453
Crc32 uint32
456454

plumbing/format/packfile/parser_test.go

+50
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
package packfile_test
22

33
import (
4+
"io"
45
"testing"
56

7+
git "gopkg.in/src-d/go-git.v4"
68
"gopkg.in/src-d/go-git.v4/plumbing"
79
"gopkg.in/src-d/go-git.v4/plumbing/format/packfile"
10+
"gopkg.in/src-d/go-git.v4/plumbing/storer"
811

912
. "gopkg.in/check.v1"
1013
"gopkg.in/src-d/go-git-fixtures.v3"
@@ -74,6 +77,53 @@ func (s *ParserSuite) TestParserHashes(c *C) {
7477
c.Assert(obs.objects, DeepEquals, objs)
7578
}
7679

80+
func (s *ParserSuite) TestThinPack(c *C) {
81+
82+
// Initialize an empty repository
83+
fs, err := git.PlainInit(c.MkDir(), true)
84+
c.Assert(err, IsNil)
85+
86+
// Try to parse a thin pack without having the required objects in the repo to
87+
// see if the correct errors are returned
88+
thinpack := fixtures.ByTag("thinpack").One()
89+
scanner := packfile.NewScanner(thinpack.Packfile())
90+
parser, err := packfile.NewParserWithStorage(scanner, fs.Storer) // ParserWithStorage writes to the storer all parsed objects!
91+
c.Assert(err, IsNil)
92+
93+
_, err = parser.Parse()
94+
c.Assert(err, Equals, plumbing.ErrObjectNotFound)
95+
96+
// start over with a clean repo
97+
fs, err = git.PlainInit(c.MkDir(), true)
98+
c.Assert(err, IsNil)
99+
100+
// Now unpack a base packfile into our empty repo:
101+
f := fixtures.ByURL("https://github.com/spinnaker/spinnaker.git").One()
102+
w, err := fs.Storer.(storer.PackfileWriter).PackfileWriter()
103+
c.Assert(err, IsNil)
104+
_, err = io.Copy(w, f.Packfile())
105+
c.Assert(err, IsNil)
106+
w.Close()
107+
108+
// Check that the test object that will come with our thin pack is *not* in the repo
109+
_, err = fs.Storer.EncodedObject(plumbing.CommitObject, thinpack.Head)
110+
c.Assert(err, Equals, plumbing.ErrObjectNotFound)
111+
112+
// Now unpack the thin pack:
113+
scanner = packfile.NewScanner(thinpack.Packfile())
114+
parser, err = packfile.NewParserWithStorage(scanner, fs.Storer) // ParserWithStorage writes to the storer all parsed objects!
115+
c.Assert(err, IsNil)
116+
117+
h, err := parser.Parse()
118+
c.Assert(err, IsNil)
119+
c.Assert(h, Equals, plumbing.NewHash("1288734cbe0b95892e663221d94b95de1f5d7be8"))
120+
121+
// Check that our test object is now accessible
122+
_, err = fs.Storer.EncodedObject(plumbing.CommitObject, thinpack.Head)
123+
c.Assert(err, IsNil)
124+
125+
}
126+
77127
type observerObject struct {
78128
hash string
79129
otype plumbing.ObjectType

0 commit comments

Comments
 (0)