fork of go-git with some jj specific features

plumbing: packfile, Refactor Parser and Scanner logic The changes focus on increasing thread-safety, simplifying the code base, enabling support for sha256 and improving general time and space complexities.

Raw allocations for a packfile parse dropped on average 39%:

│ /tmp/before │ /tmp/after │
│ allocs/op │ allocs/op vs base │
Parse/https://github.com/git-fixtures/root-references.git-16 1.987k ± 0% 1.191k ± 0% -40.06% (p=0.000 n=10)
Parse/https://github.com/git-fixtures/basic.git-16 1034.0 ± 0% 613.0 ± 0% -40.72% (p=0.000 n=10)
Parse/https://github.com/git-fixtures/basic.git#01-16 941.0 ± 0% 576.0 ± 0% -38.79% (p=0.000 n=10)
Parse/https://github.com/git-fixtures/basic.git#02-16 880.0 ± 0% 547.0 ± 0% -37.84% (p=0.000 n=10)
Parse/https://github.com/src-d/go-git.git-16 124.06k ± 0% 67.12k ± 0% -45.90% (p=0.000 n=10)
Parse/https://github.com/git-fixtures/tags.git-16 212.0 ± 0% 155.0 ± 0% -26.89% (p=0.000 n=10)
Parse/https://github.com/spinnaker/spinnaker.git-16 195.6k ± 0% 106.8k ± 0% -45.41% (p=0.000 n=10)
Parse/https://github.com/jamesob/desk.git-16 22.52k ± 0% 12.20k ± 0% -45.82% (p=0.000 n=10)
Parse/https://github.com/cpcs499/Final_Pres_P.git-16 65.00 ± 0% 68.00 ± 0% +4.62% (p=0.000 n=10)
Parse/https://github.com/github/gem-builder.git-16 3.237k ± 0% 1.778k ± 0% -45.07% (p=0.000 n=10)
Parse/https://github.com/githubtraining/example-branches.git-16 871.0 ± 0% 529.0 ± 0% -39.27% (p=0.000 n=10)
Parse/https://github.com/rumpkernel/rumprun-xen.git-16 127.86k ± 0% 70.23k ± 0% -45.07% (p=0.000 n=10)
Parse/https://github.com/mcuadros/skeetr.git-16 9.334k ± 0% 5.317k ± 0% -43.04% (p=0.000 n=10)
Parse/https://github.com/dezfowler/LiteMock.git-16 1.892k ± 0% 1.134k ± 0% -40.06% (p=0.000 n=10)
Parse/https://github.com/tyba/storable.git-16 47.22k ± 0% 25.34k ± 0% -46.32% (p=0.000 n=10)
Parse/https://github.com/toqueteos/ts3.git-16 4.246k ± 0% 2.438k ± 0% -42.58% (p=0.000 n=10)

GetByOffset calls no longer create new allocations for cached operations:

│ /tmp/before │ /tmp/after │
│ B/op │ B/op vs base │
GetByOffset/with_storage-16 6.052µ ± 3% 3.887µ ± 2% -35.78% (p=0.000 n=10)
│ B/op │ B/op vs base │
GetByOffset/with_storage-16 384.0 ± 0% 0.0 ± 0% -100.00% (p=0.000 n=10)
│ allocs/op │ allocs/op vs base │
GetByOffset/with_storage-16 4.000 ± 0% 0.000 ± 0% -100.00% (p=0.000 n=10)

The test files modified as part of this commit started replacing the check.v1
test framework with stretchr/testify. That shall continue as other changes
take place.

Breaking Changes
- Removal of the concept of Large Object Threshold.
- Rename packfile.ObjectHeader from Length to Size.
- Both Parser and Scanner APIs have changed. They now rely on the
Functional Options pattern for increased extensibility.

Signed-off-by: Paulo Gomes <pjbgf@linux.com>

+1 -2
common_test.go
··· 74 74 // NewRepositoryWithEmptyWorktree returns a new repository using the .git folder 75 75 // from the fixture but without a empty memfs worktree, the index and the 76 76 // modules are deleted from the .git folder. 77 - func (s *BaseSuite) NewRepositoryWithEmptyWorktree(f *fixtures.Fixture) *Repository { 77 + func NewRepositoryWithEmptyWorktree(f *fixtures.Fixture) *Repository { 78 78 dotgit := f.DotGit() 79 79 err := dotgit.Remove("index") 80 80 if err != nil { ··· 96 96 } 97 97 98 98 return r 99 - 100 99 } 101 100 102 101 func (s *BaseSuite) NewRepositoryFromPackfile(f *fixtures.Fixture) *Repository {
+2 -3
plumbing/format/idxfile/writer_test.go
··· 24 24 scanner := packfile.NewScanner(f.Packfile()) 25 25 26 26 obs := new(idxfile.Writer) 27 - parser, err := packfile.NewParser(scanner, obs) 28 - c.Assert(err, IsNil) 27 + parser := packfile.NewParser(scanner, packfile.WithScannerObservers(obs)) 29 28 30 - _, err = parser.Parse() 29 + _, err := parser.Parse() 31 30 c.Assert(err, IsNil) 32 31 33 32 idx, err := obs.Index()
+8 -7
plumbing/format/packfile/common.go
··· 6 6 7 7 "github.com/go-git/go-git/v5/plumbing/storer" 8 8 "github.com/go-git/go-git/v5/utils/ioutil" 9 + "github.com/go-git/go-git/v5/utils/sync" 9 10 "github.com/go-git/go-git/v5/utils/trace" 10 11 ) 11 12 ··· 35 36 return WritePackfileToObjectStorage(pw, packfile) 36 37 } 37 38 38 - p, err := NewParserWithStorage(NewScanner(packfile), s) 39 - if err != nil { 40 - return err 41 - } 39 + p := NewParser(packfile, WithStorage(s)) 42 40 43 - _, err = p.Parse() 41 + _, err := p.Parse() 44 42 return err 45 43 } 46 44 ··· 56 54 } 57 55 58 56 defer ioutil.CheckClose(w, &err) 57 + var n int64 59 58 60 - var n int64 61 - n, err = io.Copy(w, packfile) 59 + buf := sync.GetByteSlice() 60 + n, err = io.CopyBuffer(w, packfile, *buf) 61 + sync.PutByteSlice(buf) 62 + 62 63 if err == nil && n == 0 { 63 64 return ErrEmptyPackfile 64 65 }
+3 -10
plumbing/format/packfile/common_test.go
··· 6 6 7 7 "github.com/go-git/go-git/v5/plumbing" 8 8 "github.com/go-git/go-git/v5/storage/memory" 9 - 10 - . "gopkg.in/check.v1" 9 + "github.com/stretchr/testify/assert" 11 10 ) 12 11 13 - func Test(t *testing.T) { TestingT(t) } 14 - 15 - type CommonSuite struct{} 16 - 17 - var _ = Suite(&CommonSuite{}) 18 - 19 - func (s *CommonSuite) TestEmptyUpdateObjectStorage(c *C) { 12 + func TestEmptyUpdateObjectStorage(t *testing.T) { 20 13 var buf bytes.Buffer 21 14 sto := memory.NewStorage() 22 15 23 16 err := UpdateObjectStorage(sto, &buf) 24 - c.Assert(err, Equals, ErrEmptyPackfile) 17 + assert.ErrorIs(t, err, ErrEmptyPackfile) 25 18 } 26 19 27 20 func newObject(t plumbing.ObjectType, cont []byte) plumbing.EncodedObject {
+2 -3
plumbing/format/packfile/encoder_advanced_test.go
··· 94 94 c.Assert(err, IsNil) 95 95 96 96 w := new(idxfile.Writer) 97 - parser, err := NewParser(NewScanner(f), w) 98 - c.Assert(err, IsNil) 97 + parser := NewParser(NewScanner(f), WithScannerObservers(w)) 99 98 100 99 _, err = parser.Parse() 101 100 c.Assert(err, IsNil) ··· 105 104 _, err = f.Seek(0, io.SeekStart) 106 105 c.Assert(err, IsNil) 107 106 108 - p := NewPackfile(index, fs, f, 0) 107 + p := NewPackfile(f, WithIdx(index), WithFs(fs)) 109 108 110 109 decodeHash, err := p.ID() 111 110 c.Assert(err, IsNil)
+2 -3
plumbing/format/packfile/encoder_test.go
··· 309 309 scanner := NewScanner(file) 310 310 311 311 w := new(idxfile.Writer) 312 - p, err := NewParser(scanner, w) 313 - c.Assert(err, IsNil) 312 + p := NewParser(scanner, WithScannerObservers(w)) 314 313 315 314 _, err = p.Parse() 316 315 c.Assert(err, IsNil) ··· 318 317 index, err := w.Index() 319 318 c.Assert(err, IsNil) 320 319 321 - return NewPackfile(index, fs, file, 0), func() { 320 + return NewPackfile(file, WithIdx(index), WithFs(fs)), func() { 322 321 c.Assert(file.Close(), IsNil) 323 322 } 324 323 }
+38 -39
plumbing/format/packfile/fsobject.go
··· 7 7 "github.com/go-git/go-git/v5/plumbing" 8 8 "github.com/go-git/go-git/v5/plumbing/cache" 9 9 "github.com/go-git/go-git/v5/plumbing/format/idxfile" 10 - "github.com/go-git/go-git/v5/utils/ioutil" 10 + "github.com/go-git/go-git/v5/utils/sync" 11 11 ) 12 12 13 13 // FSObject is an object from the packfile on the filesystem. 14 14 type FSObject struct { 15 - hash plumbing.Hash 16 - offset int64 17 - size int64 18 - typ plumbing.ObjectType 19 - index idxfile.Index 20 - fs billy.Filesystem 21 - path string 22 - cache cache.Object 23 - largeObjectThreshold int64 15 + hash plumbing.Hash 16 + offset int64 17 + size int64 18 + typ plumbing.ObjectType 19 + index idxfile.Index 20 + fs billy.Filesystem 21 + path string 22 + cache cache.Object 24 23 } 25 24 26 25 // NewFSObject creates a new filesystem object. ··· 33 32 fs billy.Filesystem, 34 33 path string, 35 34 cache cache.Object, 36 - largeObjectThreshold int64, 37 35 ) *FSObject { 38 36 return &FSObject{ 39 - hash: hash, 40 - offset: offset, 41 - size: contentSize, 42 - typ: finalType, 43 - index: index, 44 - fs: fs, 45 - path: path, 46 - cache: cache, 47 - largeObjectThreshold: largeObjectThreshold, 37 + hash: hash, 38 + offset: offset, 39 + size: contentSize, 40 + typ: finalType, 41 + index: index, 42 + fs: fs, 43 + path: path, 44 + cache: cache, 48 45 } 49 46 } 50 47 ··· 65 62 return nil, err 66 63 } 67 64 68 - p := NewPackfileWithCache(o.index, nil, f, o.cache, o.largeObjectThreshold) 69 - if o.largeObjectThreshold > 0 && o.size > o.largeObjectThreshold { 70 - // We have a big object 71 - h, err := p.objectHeaderAtOffset(o.offset) 72 - if err != nil { 73 - return nil, err 74 - } 75 - 76 - r, err := p.getReaderDirect(h) 77 - if err != nil { 78 - _ = f.Close() 79 - return nil, err 80 - } 81 - return ioutil.NewReadCloserWithCloser(r, f.Close), nil 82 - } 83 - r, err := p.getObjectContent(o.offset) 65 + _, err = f.Seek(o.offset, io.SeekStart) 84 66 if err != nil { 85 - _ = f.Close() 86 67 return nil, err 87 68 } 88 69 89 - if err := f.Close(); err != nil { 70 + dict := sync.GetByteSlice() 71 + zr := sync.NewZlibReader(dict) 72 + err = zr.Reset(f) 73 + if err != nil { 90 74 return nil, err 91 75 } 76 + return &zlibReadCloser{zr, dict}, nil 77 + } 92 78 93 - return r, nil 79 + type zlibReadCloser struct { 80 + r sync.ZLibReader 81 + dict *[]byte 82 + } 83 + 84 + // Read reads up to len(p) bytes into p from the data. 85 + func (r *zlibReadCloser) Read(p []byte) (int, error) { 86 + return r.r.Reader.Read(p) 87 + } 88 + 89 + func (r *zlibReadCloser) Close() error { 90 + sync.PutByteSlice(r.dict) 91 + sync.PutZlibReader(r.r) 92 + return nil 94 93 } 95 94 96 95 // SetSize implements the plumbing.EncodedObject interface. This method
+3 -3
plumbing/format/packfile/object_pack.go
··· 7 7 // ObjectToPack is a representation of an object that is going to be into a 8 8 // pack file. 9 9 type ObjectToPack struct { 10 - // The main object to pack, it could be any object, including deltas 10 + // The main object to pack, it could be any object, including deltas. 11 11 Object plumbing.EncodedObject 12 - // Base is the object that a delta is based on (it could be also another delta). 13 - // If the main object is not a delta, Base will be null 12 + // Base is the object that a delta is based on, which could also be another delta. 13 + // Nil when the main object is not a delta. 14 14 Base *ObjectToPack 15 15 // Original is the object that we can generate applying the delta to 16 16 // Base, or the same object as Object in the case of a non-delta
+182 -473
plumbing/format/packfile/packfile.go
··· 1 1 package packfile 2 2 3 3 import ( 4 - "bytes" 5 4 "fmt" 6 5 "io" 7 6 "os" 7 + "sync" 8 8 9 9 billy "github.com/go-git/go-billy/v5" 10 10 "github.com/go-git/go-git/v5/plumbing" 11 11 "github.com/go-git/go-git/v5/plumbing/cache" 12 12 "github.com/go-git/go-git/v5/plumbing/format/idxfile" 13 13 "github.com/go-git/go-git/v5/plumbing/storer" 14 + "github.com/go-git/go-git/v5/utils/binary" 14 15 "github.com/go-git/go-git/v5/utils/ioutil" 15 - "github.com/go-git/go-git/v5/utils/sync" 16 16 ) 17 17 18 18 var ( ··· 24 24 ErrZLib = NewError("zlib reading error") 25 25 ) 26 26 27 - // When reading small objects from packfile it is beneficial to do so at 28 - // once to exploit the buffered I/O. In many cases the objects are so small 29 - // that they were already loaded to memory when the object header was 30 - // loaded from the packfile. Wrapping in FSObject would cause this buffered 31 - // data to be thrown away and then re-read later, with the additional 32 - // seeking causing reloads from disk. Objects smaller than this threshold 33 - // are now always read into memory and stored in cache instead of being 34 - // wrapped in FSObject. 35 - const smallObjectThreshold = 16 * 1024 36 - 37 27 // Packfile allows retrieving information from inside a packfile. 38 28 type Packfile struct { 39 29 idxfile.Index 40 - fs billy.Filesystem 41 - file billy.File 42 - s *Scanner 43 - deltaBaseCache cache.Object 44 - offsetToType map[int64]plumbing.ObjectType 45 - largeObjectThreshold int64 30 + fs billy.Filesystem 31 + file billy.File 32 + scanner *Scanner 33 + 34 + cache cache.Object 35 + 36 + id plumbing.Hash 37 + m sync.Mutex 38 + 39 + once sync.Once 40 + onceErr error 46 41 } 47 42 48 - // NewPackfileWithCache creates a new Packfile with the given object cache. 43 + // NewPackfile returns a packfile representation for the given packfile file 44 + // and packfile idx. 49 45 // If the filesystem is provided, the packfile will return FSObjects, otherwise 50 46 // it will return MemoryObjects. 51 - func NewPackfileWithCache( 52 - index idxfile.Index, 53 - fs billy.Filesystem, 47 + func NewPackfile( 54 48 file billy.File, 55 - cache cache.Object, 56 - largeObjectThreshold int64, 49 + opts ...PackfileOption, 57 50 ) *Packfile { 58 - if index == nil { 59 - index = idxfile.NewMemoryIndex() 51 + p := &Packfile{ 52 + file: file, 60 53 } 61 - 62 - s := NewScanner(file) 63 - return &Packfile{ 64 - index, 65 - fs, 66 - file, 67 - s, 68 - cache, 69 - make(map[int64]plumbing.ObjectType), 70 - largeObjectThreshold, 54 + for _, opt := range opts { 55 + opt(p) 71 56 } 72 - } 73 57 74 - // NewPackfile returns a packfile representation for the given packfile file 75 - // and packfile idx. 76 - // If the filesystem is provided, the packfile will return FSObjects, otherwise 77 - // it will return MemoryObjects. 78 - func NewPackfile(index idxfile.Index, fs billy.Filesystem, file billy.File, largeObjectThreshold int64) *Packfile { 79 - return NewPackfileWithCache(index, fs, file, cache.NewObjectLRUDefault(), largeObjectThreshold) 58 + return p 80 59 } 81 60 82 61 // Get retrieves the encoded object in the packfile with the given hash. 83 62 func (p *Packfile) Get(h plumbing.Hash) (plumbing.EncodedObject, error) { 84 - offset, err := p.FindOffset(h) 85 - if err != nil { 63 + if err := p.init(); err != nil { 86 64 return nil, err 87 65 } 66 + p.m.Lock() 67 + defer p.m.Unlock() 88 68 89 - return p.objectAtOffset(offset, h) 69 + return p.get(h) 90 70 } 91 71 92 72 // GetByOffset retrieves the encoded object from the packfile at the given 93 73 // offset. 94 - func (p *Packfile) GetByOffset(o int64) (plumbing.EncodedObject, error) { 95 - hash, err := p.FindHash(o) 96 - if err != nil { 74 + func (p *Packfile) GetByOffset(offset int64) (plumbing.EncodedObject, error) { 75 + if err := p.init(); err != nil { 97 76 return nil, err 98 77 } 78 + p.m.Lock() 79 + defer p.m.Unlock() 99 80 100 - return p.objectAtOffset(o, hash) 81 + return p.getByOffset(offset) 101 82 } 102 83 103 84 // GetSizeByOffset retrieves the size of the encoded object from the 104 85 // packfile with the given offset. 105 - func (p *Packfile) GetSizeByOffset(o int64) (size int64, err error) { 106 - if _, err := p.s.SeekFromStart(o); err != nil { 107 - if err == io.EOF || isInvalid(err) { 108 - return 0, plumbing.ErrObjectNotFound 109 - } 110 - 86 + func (p *Packfile) GetSizeByOffset(offset int64) (size int64, err error) { 87 + if err := p.init(); err != nil { 111 88 return 0, err 112 89 } 113 90 114 - h, err := p.nextObjectHeader() 91 + d, err := p.GetByOffset(offset) 115 92 if err != nil { 116 93 return 0, err 117 94 } 118 - return p.getObjectSize(h) 119 - } 120 95 121 - func (p *Packfile) objectHeaderAtOffset(offset int64) (*ObjectHeader, error) { 122 - h, err := p.s.SeekObjectHeader(offset) 123 - p.s.pendingObject = nil 124 - return h, err 96 + return d.Size(), nil 125 97 } 126 98 127 - func (p *Packfile) nextObjectHeader() (*ObjectHeader, error) { 128 - h, err := p.s.NextObjectHeader() 129 - p.s.pendingObject = nil 130 - return h, err 99 + // GetAll returns an iterator with all encoded objects in the packfile. 100 + // The iterator returned is not thread-safe, it should be used in the same 101 + // thread as the Packfile instance. 102 + func (p *Packfile) GetAll() (storer.EncodedObjectIter, error) { 103 + return p.GetByType(plumbing.AnyObject) 131 104 } 132 105 133 - func (p *Packfile) getDeltaObjectSize(buf *bytes.Buffer) int64 { 134 - delta := buf.Bytes() 135 - _, delta = decodeLEB128(delta) // skip src size 136 - sz, _ := decodeLEB128(delta) 137 - return int64(sz) 138 - } 139 - 140 - func (p *Packfile) getObjectSize(h *ObjectHeader) (int64, error) { 141 - switch h.Type { 142 - case plumbing.CommitObject, plumbing.TreeObject, plumbing.BlobObject, plumbing.TagObject: 143 - return h.Length, nil 144 - case plumbing.REFDeltaObject, plumbing.OFSDeltaObject: 145 - buf := sync.GetBytesBuffer() 146 - defer sync.PutBytesBuffer(buf) 147 - 148 - if _, _, err := p.s.NextObject(buf); err != nil { 149 - return 0, err 150 - } 151 - 152 - return p.getDeltaObjectSize(buf), nil 153 - default: 154 - return 0, ErrInvalidObject.AddDetails("type %q", h.Type) 106 + // GetByType returns all the objects of the given type. 107 + func (p *Packfile) GetByType(typ plumbing.ObjectType) (storer.EncodedObjectIter, error) { 108 + if err := p.init(); err != nil { 109 + return nil, err 155 110 } 156 - } 157 111 158 - func (p *Packfile) getObjectType(h *ObjectHeader) (typ plumbing.ObjectType, err error) { 159 - switch h.Type { 160 - case plumbing.CommitObject, plumbing.TreeObject, plumbing.BlobObject, plumbing.TagObject: 161 - return h.Type, nil 162 - case plumbing.REFDeltaObject, plumbing.OFSDeltaObject: 163 - var offset int64 164 - if h.Type == plumbing.REFDeltaObject { 165 - offset, err = p.FindOffset(h.Reference) 166 - if err != nil { 167 - return 168 - } 169 - } else { 170 - offset = h.OffsetReference 112 + switch typ { 113 + case plumbing.AnyObject, 114 + plumbing.BlobObject, 115 + plumbing.TreeObject, 116 + plumbing.CommitObject, 117 + plumbing.TagObject: 118 + entries, err := p.EntriesByOffset() 119 + if err != nil { 120 + return nil, err 171 121 } 172 122 173 - if baseType, ok := p.offsetToType[offset]; ok { 174 - typ = baseType 175 - } else { 176 - h, err = p.objectHeaderAtOffset(offset) 177 - if err != nil { 178 - return 179 - } 180 - 181 - typ, err = p.getObjectType(h) 182 - if err != nil { 183 - return 184 - } 185 - } 123 + return &objectIter{ 124 + p: p, 125 + iter: entries, 126 + typ: typ, 127 + }, nil 186 128 default: 187 - err = ErrInvalidObject.AddDetails("type %q", h.Type) 129 + return nil, plumbing.ErrInvalidType 188 130 } 189 - 190 - p.offsetToType[h.Offset] = typ 191 - 192 - return 193 131 } 194 132 195 - func (p *Packfile) objectAtOffset(offset int64, hash plumbing.Hash) (plumbing.EncodedObject, error) { 196 - if obj, ok := p.cacheGet(hash); ok { 197 - return obj, nil 198 - } 199 - 200 - h, err := p.objectHeaderAtOffset(offset) 201 - if err != nil { 202 - if err == io.EOF || isInvalid(err) { 203 - return nil, plumbing.ErrObjectNotFound 204 - } 133 + // Returns the Packfile's inner scanner. 134 + // 135 + // Deprecated: this will be removed in future versions of the packfile package 136 + // to avoid exposing the package internals and to improve its thread-safety. 137 + func (p *Packfile) Scanner() (*Scanner, error) { 138 + if err := p.init(); err != nil { 205 139 return nil, err 206 140 } 207 141 208 - return p.getNextObject(h, hash) 142 + return p.scanner, nil 209 143 } 210 144 211 - func (p *Packfile) getNextObject(h *ObjectHeader, hash plumbing.Hash) (plumbing.EncodedObject, error) { 212 - var err error 213 - 214 - // If we have no filesystem, we will return a MemoryObject instead 215 - // of an FSObject. 216 - if p.fs == nil { 217 - return p.getNextMemoryObject(h) 145 + // ID returns the ID of the packfile, which is the checksum at the end of it. 146 + func (p *Packfile) ID() (plumbing.Hash, error) { 147 + if err := p.init(); err != nil { 148 + return plumbing.ZeroHash, err 218 149 } 219 150 220 - // If the object is small enough then read it completely into memory now since 221 - // it is already read from disk into buffer anyway. For delta objects we want 222 - // to perform the optimization too, but we have to be careful about applying 223 - // small deltas on big objects. 224 - var size int64 225 - if h.Length <= smallObjectThreshold { 226 - if h.Type != plumbing.OFSDeltaObject && h.Type != plumbing.REFDeltaObject { 227 - return p.getNextMemoryObject(h) 228 - } 229 - 230 - // For delta objects we read the delta data and apply the small object 231 - // optimization only if the expanded version of the object still meets 232 - // the small object threshold condition. 233 - buf := sync.GetBytesBuffer() 234 - defer sync.PutBytesBuffer(buf) 235 - 236 - if _, _, err := p.s.NextObject(buf); err != nil { 237 - return nil, err 238 - } 239 - 240 - size = p.getDeltaObjectSize(buf) 241 - if size <= smallObjectThreshold { 242 - var obj = new(plumbing.MemoryObject) 243 - obj.SetSize(size) 244 - if h.Type == plumbing.REFDeltaObject { 245 - err = p.fillREFDeltaObjectContentWithBuffer(obj, h.Reference, buf) 246 - } else { 247 - err = p.fillOFSDeltaObjectContentWithBuffer(obj, h.OffsetReference, buf) 248 - } 249 - return obj, err 250 - } 251 - } else { 252 - size, err = p.getObjectSize(h) 253 - if err != nil { 254 - return nil, err 255 - } 256 - } 151 + return p.id, nil 152 + } 257 153 258 - typ, err := p.getObjectType(h) 259 - if err != nil { 260 - return nil, err 154 + // get is not threat-safe, and should only be called within packfile.go. 155 + func (p *Packfile) get(h plumbing.Hash) (plumbing.EncodedObject, error) { 156 + if obj, ok := p.cache.Get(h); ok { 157 + return obj, nil 261 158 } 262 159 263 - p.offsetToType[h.Offset] = typ 264 - 265 - return NewFSObject( 266 - hash, 267 - typ, 268 - h.Offset, 269 - size, 270 - p.Index, 271 - p.fs, 272 - p.file.Name(), 273 - p.deltaBaseCache, 274 - p.largeObjectThreshold, 275 - ), nil 276 - } 277 - 278 - func (p *Packfile) getObjectContent(offset int64) (io.ReadCloser, error) { 279 - h, err := p.objectHeaderAtOffset(offset) 160 + offset, err := p.Index.FindOffset(h) 280 161 if err != nil { 281 162 return nil, err 282 163 } 283 164 284 - // getObjectContent is called from FSObject, so we have to explicitly 285 - // get memory object here to avoid recursive cycle 286 - obj, err := p.getNextMemoryObject(h) 165 + oh, err := p.headerFromOffset(offset) 287 166 if err != nil { 288 167 return nil, err 289 168 } 290 169 291 - return obj.Reader() 170 + return p.objectFromHeader(oh) 292 171 } 293 172 294 - func asyncReader(p *Packfile) (io.ReadCloser, error) { 295 - reader := ioutil.NewReaderUsingReaderAt(p.file, p.s.r.offset) 296 - zr, err := sync.GetZlibReader(reader) 173 + // getByOffset is not threat-safe, and should only be called within packfile.go. 174 + func (p *Packfile) getByOffset(offset int64) (plumbing.EncodedObject, error) { 175 + h, err := p.FindHash(offset) 297 176 if err != nil { 298 - return nil, fmt.Errorf("zlib reset error: %s", err) 177 + return nil, err 299 178 } 300 179 301 - return ioutil.NewReadCloserWithCloser(zr.Reader, func() error { 302 - sync.PutZlibReader(zr) 303 - return nil 304 - }), nil 305 - 306 - } 307 - 308 - func (p *Packfile) getReaderDirect(h *ObjectHeader) (io.ReadCloser, error) { 309 - switch h.Type { 310 - case plumbing.CommitObject, plumbing.TreeObject, plumbing.BlobObject, plumbing.TagObject: 311 - return asyncReader(p) 312 - case plumbing.REFDeltaObject: 313 - deltaRc, err := asyncReader(p) 314 - if err != nil { 315 - return nil, err 316 - } 317 - r, err := p.readREFDeltaObjectContent(h, deltaRc) 318 - if err != nil { 319 - return nil, err 320 - } 321 - return r, nil 322 - case plumbing.OFSDeltaObject: 323 - deltaRc, err := asyncReader(p) 324 - if err != nil { 325 - return nil, err 326 - } 327 - r, err := p.readOFSDeltaObjectContent(h, deltaRc) 328 - if err != nil { 329 - return nil, err 330 - } 331 - return r, nil 332 - default: 333 - return nil, ErrInvalidObject.AddDetails("type %q", h.Type) 180 + if obj, ok := p.cache.Get(h); ok { 181 + return obj, nil 334 182 } 335 - } 336 183 337 - func (p *Packfile) getNextMemoryObject(h *ObjectHeader) (plumbing.EncodedObject, error) { 338 - var obj = new(plumbing.MemoryObject) 339 - obj.SetSize(h.Length) 340 - obj.SetType(h.Type) 341 - 342 - var err error 343 - switch h.Type { 344 - case plumbing.CommitObject, plumbing.TreeObject, plumbing.BlobObject, plumbing.TagObject: 345 - err = p.fillRegularObjectContent(obj) 346 - case plumbing.REFDeltaObject: 347 - err = p.fillREFDeltaObjectContent(obj, h.Reference) 348 - case plumbing.OFSDeltaObject: 349 - err = p.fillOFSDeltaObjectContent(obj, h.OffsetReference) 350 - default: 351 - err = ErrInvalidObject.AddDetails("type %q", h.Type) 352 - } 353 - 184 + oh, err := p.headerFromOffset(offset) 354 185 if err != nil { 355 186 return nil, err 356 187 } 357 188 358 - p.offsetToType[h.Offset] = obj.Type() 359 - 360 - return obj, nil 189 + return p.objectFromHeader(oh) 361 190 } 362 191 363 - func (p *Packfile) fillRegularObjectContent(obj plumbing.EncodedObject) (err error) { 364 - w, err := obj.Writer() 365 - if err != nil { 366 - return err 367 - } 368 - 369 - defer ioutil.CheckClose(w, &err) 370 - 371 - _, _, err = p.s.NextObject(w) 372 - p.cachePut(obj) 373 - 374 - return err 375 - } 376 - 377 - func (p *Packfile) fillREFDeltaObjectContent(obj plumbing.EncodedObject, ref plumbing.Hash) error { 378 - buf := sync.GetBytesBuffer() 379 - defer sync.PutBytesBuffer(buf) 380 - 381 - _, _, err := p.s.NextObject(buf) 382 - if err != nil { 383 - return err 384 - } 192 + func (p *Packfile) init() error { 193 + p.once.Do(func() { 194 + if p.file == nil { 195 + p.onceErr = fmt.Errorf("file is not set") 196 + return 197 + } 385 198 386 - return p.fillREFDeltaObjectContentWithBuffer(obj, ref, buf) 387 - } 199 + if p.Index == nil { 200 + p.onceErr = fmt.Errorf("index is not set") 201 + return 202 + } 388 203 389 - func (p *Packfile) readREFDeltaObjectContent(h *ObjectHeader, deltaRC io.Reader) (io.ReadCloser, error) { 390 - var err error 204 + p.scanner = NewScanner(p.file) 205 + // Validate packfile signature. 206 + if !p.scanner.Scan() { 207 + p.onceErr = p.scanner.Error() 208 + return 209 + } 391 210 392 - base, ok := p.cacheGet(h.Reference) 393 - if !ok { 394 - base, err = p.Get(h.Reference) 211 + _, err := p.scanner.Seek(-20, io.SeekEnd) 395 212 if err != nil { 396 - return nil, err 213 + p.onceErr = err 214 + return 397 215 } 398 - } 399 216 400 - return ReaderFromDelta(base, deltaRC) 401 - } 402 - 403 - func (p *Packfile) fillREFDeltaObjectContentWithBuffer(obj plumbing.EncodedObject, ref plumbing.Hash, buf *bytes.Buffer) error { 404 - var err error 405 - 406 - base, ok := p.cacheGet(ref) 407 - if !ok { 408 - base, err = p.Get(ref) 217 + id, err := binary.ReadHash(p.scanner) 409 218 if err != nil { 410 - return err 219 + p.onceErr = err 411 220 } 412 - } 413 - 414 - obj.SetType(base.Type()) 415 - err = ApplyDelta(obj, base, buf.Bytes()) 416 - p.cachePut(obj) 221 + p.id = id 417 222 418 - return err 419 - } 223 + if p.cache == nil { 224 + p.cache = cache.NewObjectLRUDefault() 225 + } 226 + }) 420 227 421 - func (p *Packfile) fillOFSDeltaObjectContent(obj plumbing.EncodedObject, offset int64) error { 422 - buf := sync.GetBytesBuffer() 423 - defer sync.PutBytesBuffer(buf) 424 - 425 - _, _, err := p.s.NextObject(buf) 426 - if err != nil { 427 - return err 428 - } 429 - 430 - return p.fillOFSDeltaObjectContentWithBuffer(obj, offset, buf) 228 + return p.onceErr 431 229 } 432 230 433 - func (p *Packfile) readOFSDeltaObjectContent(h *ObjectHeader, deltaRC io.Reader) (io.ReadCloser, error) { 434 - hash, err := p.FindHash(h.OffsetReference) 231 + func (p *Packfile) headerFromOffset(offset int64) (*ObjectHeader, error) { 232 + err := p.scanner.SeekFromStart(offset) 435 233 if err != nil { 436 234 return nil, err 437 235 } 438 236 439 - base, err := p.objectAtOffset(h.OffsetReference, hash) 440 - if err != nil { 441 - return nil, err 237 + if !p.scanner.Scan() { 238 + return nil, plumbing.ErrObjectNotFound 442 239 } 443 240 444 - return ReaderFromDelta(base, deltaRC) 241 + oh := p.scanner.Data().Value().(ObjectHeader) 242 + return &oh, nil 445 243 } 446 244 447 - func (p *Packfile) fillOFSDeltaObjectContentWithBuffer(obj plumbing.EncodedObject, offset int64, buf *bytes.Buffer) error { 448 - hash, err := p.FindHash(offset) 449 - if err != nil { 450 - return err 451 - } 245 + // Close the packfile and its resources. 246 + func (p *Packfile) Close() error { 247 + p.m.Lock() 248 + defer p.m.Unlock() 452 249 453 - base, err := p.objectAtOffset(offset, hash) 454 - if err != nil { 455 - return err 250 + closer, ok := p.file.(io.Closer) 251 + if !ok { 252 + return nil 456 253 } 457 254 458 - obj.SetType(base.Type()) 459 - err = ApplyDelta(obj, base, buf.Bytes()) 460 - p.cachePut(obj) 461 - 462 - return err 255 + return closer.Close() 463 256 } 464 257 465 - func (p *Packfile) cacheGet(h plumbing.Hash) (plumbing.EncodedObject, bool) { 466 - if p.deltaBaseCache == nil { 467 - return nil, false 258 + func (p *Packfile) objectFromHeader(oh *ObjectHeader) (plumbing.EncodedObject, error) { 259 + if oh == nil { 260 + return nil, plumbing.ErrObjectNotFound 468 261 } 469 262 470 - return p.deltaBaseCache.Get(h) 471 - } 263 + // If we have filesystem, and the object is not a delta type, return a FSObject. 264 + // This avoids having to inflate the object more than once. 265 + if !oh.Type.IsDelta() && p.fs != nil { 266 + fs := NewFSObject( 267 + oh.Hash, 268 + oh.Type, 269 + oh.ContentOffset, 270 + oh.Size, 271 + p.Index, 272 + p.fs, 273 + p.file.Name(), 274 + p.cache, 275 + ) 472 276 473 - func (p *Packfile) cachePut(obj plumbing.EncodedObject) { 474 - if p.deltaBaseCache == nil { 475 - return 277 + p.cache.Put(fs) 278 + return fs, nil 476 279 } 477 280 478 - p.deltaBaseCache.Put(obj) 281 + return p.getMemoryObject(oh) 479 282 } 480 283 481 - // GetAll returns an iterator with all encoded objects in the packfile. 482 - // The iterator returned is not thread-safe, it should be used in the same 483 - // thread as the Packfile instance. 484 - func (p *Packfile) GetAll() (storer.EncodedObjectIter, error) { 485 - return p.GetByType(plumbing.AnyObject) 486 - } 284 + func (p *Packfile) getMemoryObject(oh *ObjectHeader) (plumbing.EncodedObject, error) { 285 + var obj = new(plumbing.MemoryObject) 286 + obj.SetSize(oh.Size) 287 + obj.SetType(oh.Type) 487 288 488 - // GetByType returns all the objects of the given type. 489 - func (p *Packfile) GetByType(typ plumbing.ObjectType) (storer.EncodedObjectIter, error) { 490 - switch typ { 491 - case plumbing.AnyObject, 492 - plumbing.BlobObject, 493 - plumbing.TreeObject, 494 - plumbing.CommitObject, 495 - plumbing.TagObject: 496 - entries, err := p.EntriesByOffset() 497 - if err != nil { 498 - return nil, err 499 - } 500 - 501 - return &objectIter{ 502 - // Easiest way to provide an object decoder is just to pass a Packfile 503 - // instance. To not mess with the seeks, it's a new instance with a 504 - // different scanner but the same cache and offset to hash map for 505 - // reusing as much cache as possible. 506 - p: p, 507 - iter: entries, 508 - typ: typ, 509 - }, nil 510 - default: 511 - return nil, plumbing.ErrInvalidType 512 - } 513 - } 514 - 515 - // ID returns the ID of the packfile, which is the checksum at the end of it. 516 - func (p *Packfile) ID() (plumbing.Hash, error) { 517 - prev, err := p.file.Seek(-20, io.SeekEnd) 289 + w, err := obj.Writer() 518 290 if err != nil { 519 - return plumbing.ZeroHash, err 291 + return nil, err 520 292 } 293 + defer ioutil.CheckClose(w, &err) 521 294 522 - var hash plumbing.Hash 523 - if _, err := io.ReadFull(p.file, hash[:]); err != nil { 524 - return plumbing.ZeroHash, err 525 - } 295 + switch oh.Type { 296 + case plumbing.CommitObject, plumbing.TreeObject, plumbing.BlobObject, plumbing.TagObject: 297 + err = p.scanner.inflateContent(oh.ContentOffset, w) 526 298 527 - if _, err := p.file.Seek(prev, io.SeekStart); err != nil { 528 - return plumbing.ZeroHash, err 529 - } 299 + case plumbing.REFDeltaObject, plumbing.OFSDeltaObject: 300 + var parent plumbing.EncodedObject 530 301 531 - return hash, nil 532 - } 533 - 534 - // Scanner returns the packfile's Scanner 535 - func (p *Packfile) Scanner() *Scanner { 536 - return p.s 537 - } 538 - 539 - // Close the packfile and its resources. 540 - func (p *Packfile) Close() error { 541 - closer, ok := p.file.(io.Closer) 542 - if !ok { 543 - return nil 544 - } 545 - 546 - return closer.Close() 547 - } 302 + switch oh.Type { 303 + case plumbing.REFDeltaObject: 304 + var ok bool 305 + parent, ok = p.cache.Get(oh.Reference) 306 + if !ok { 307 + parent, err = p.get(oh.Reference) 308 + } 309 + case plumbing.OFSDeltaObject: 310 + parent, err = p.getByOffset(oh.OffsetReference) 311 + } 548 312 549 - type objectIter struct { 550 - p *Packfile 551 - typ plumbing.ObjectType 552 - iter idxfile.EntryIter 553 - } 554 - 555 - func (i *objectIter) Next() (plumbing.EncodedObject, error) { 556 - for { 557 - e, err := i.iter.Next() 558 313 if err != nil { 559 - return nil, err 314 + return nil, fmt.Errorf("cannot find base object: %w", err) 560 315 } 561 316 562 - if i.typ != plumbing.AnyObject { 563 - if typ, ok := i.p.offsetToType[int64(e.Offset)]; ok { 564 - if typ != i.typ { 565 - continue 566 - } 567 - } else if obj, ok := i.p.cacheGet(e.Hash); ok { 568 - if obj.Type() != i.typ { 569 - i.p.offsetToType[int64(e.Offset)] = obj.Type() 570 - continue 571 - } 572 - return obj, nil 573 - } else { 574 - h, err := i.p.objectHeaderAtOffset(int64(e.Offset)) 575 - if err != nil { 576 - return nil, err 577 - } 578 - 579 - if h.Type == plumbing.REFDeltaObject || h.Type == plumbing.OFSDeltaObject { 580 - typ, err := i.p.getObjectType(h) 581 - if err != nil { 582 - return nil, err 583 - } 584 - if typ != i.typ { 585 - i.p.offsetToType[int64(e.Offset)] = typ 586 - continue 587 - } 588 - // getObjectType will seek in the file so we cannot use getNextObject safely 589 - return i.p.objectAtOffset(int64(e.Offset), e.Hash) 590 - } else { 591 - if h.Type != i.typ { 592 - i.p.offsetToType[int64(e.Offset)] = h.Type 593 - continue 594 - } 595 - return i.p.getNextObject(h, e.Hash) 596 - } 597 - } 317 + err = p.scanner.inflateContent(oh.ContentOffset, &oh.content) 318 + if err != nil { 319 + return nil, fmt.Errorf("test") 598 320 } 599 321 600 - obj, err := i.p.objectAtOffset(int64(e.Offset), e.Hash) 601 - if err != nil { 602 - return nil, err 603 - } 322 + obj.SetType(parent.Type()) 323 + err = ApplyDelta(obj, parent, oh.content.Bytes()) //nolint:ineffassign 604 324 605 - return obj, nil 325 + default: 326 + err = ErrInvalidObject.AddDetails("type %q", oh.Type) 606 327 } 607 - } 608 328 609 - func (i *objectIter) ForEach(f func(plumbing.EncodedObject) error) error { 610 - for { 611 - o, err := i.Next() 612 - if err != nil { 613 - if err == io.EOF { 614 - return nil 615 - } 616 - return err 617 - } 618 - 619 - if err := f(o); err != nil { 620 - return err 621 - } 329 + if err != nil { 330 + return nil, err 622 331 } 623 - } 624 332 625 - func (i *objectIter) Close() { 626 - i.iter.Close() 333 + p.cache.Put(obj) 334 + 335 + return obj, nil 627 336 } 628 337 629 338 // isInvalid checks whether an error is an os.PathError with an os.ErrInvalid
+90
plumbing/format/packfile/packfile_iter.go
··· 1 + package packfile 2 + 3 + import ( 4 + "io" 5 + 6 + "github.com/go-git/go-git/v5/plumbing" 7 + "github.com/go-git/go-git/v5/plumbing/format/idxfile" 8 + ) 9 + 10 + type objectIter struct { 11 + p *Packfile 12 + typ plumbing.ObjectType 13 + iter idxfile.EntryIter 14 + } 15 + 16 + func (i *objectIter) Next() (plumbing.EncodedObject, error) { 17 + if err := i.p.init(); err != nil { 18 + return nil, err 19 + } 20 + 21 + i.p.m.Lock() 22 + defer i.p.m.Unlock() 23 + 24 + return i.next() 25 + } 26 + 27 + func (i *objectIter) next() (plumbing.EncodedObject, error) { 28 + for { 29 + e, err := i.iter.Next() 30 + if err != nil { 31 + return nil, err 32 + } 33 + 34 + oh, err := i.p.headerFromOffset(int64(e.Offset)) 35 + if err != nil { 36 + return nil, err 37 + } 38 + 39 + if i.typ == plumbing.AnyObject { 40 + return i.p.objectFromHeader(oh) 41 + } 42 + 43 + // Current object header type is a delta, get the actual object to 44 + // assess the actual type. 45 + if oh.Type.IsDelta() { 46 + o, err := i.p.objectFromHeader(oh) 47 + if o.Type() == i.typ { 48 + return o, err 49 + } 50 + 51 + continue 52 + } 53 + 54 + if oh.Type == i.typ { 55 + return i.p.objectFromHeader(oh) 56 + } 57 + 58 + continue 59 + } 60 + } 61 + 62 + func (i *objectIter) ForEach(f func(plumbing.EncodedObject) error) error { 63 + if err := i.p.init(); err != nil { 64 + return err 65 + } 66 + 67 + i.p.m.Lock() 68 + defer i.p.m.Unlock() 69 + 70 + for { 71 + o, err := i.next() 72 + if err != nil { 73 + if err == io.EOF { 74 + return nil 75 + } 76 + return err 77 + } 78 + 79 + if err := f(o); err != nil { 80 + return err 81 + } 82 + } 83 + } 84 + 85 + func (i *objectIter) Close() { 86 + i.p.m.Lock() 87 + defer i.p.m.Unlock() 88 + 89 + i.iter.Close() 90 + }
+32
plumbing/format/packfile/packfile_options.go
··· 1 + package packfile 2 + 3 + import ( 4 + billy "github.com/go-git/go-billy/v5" 5 + "github.com/go-git/go-git/v5/plumbing/cache" 6 + "github.com/go-git/go-git/v5/plumbing/format/idxfile" 7 + ) 8 + 9 + type PackfileOption func(*Packfile) 10 + 11 + // WithCache sets the cache to be used throughout Packfile operations. 12 + // Use this to share existing caches with the Packfile. If not used, a 13 + // new cache instance will be created. 14 + func WithCache(cache cache.Object) PackfileOption { 15 + return func(p *Packfile) { 16 + p.cache = cache 17 + } 18 + } 19 + 20 + // WithIdx sets the idxfile for the packfile. 21 + func WithIdx(idx idxfile.Index) PackfileOption { 22 + return func(p *Packfile) { 23 + p.Index = idx 24 + } 25 + } 26 + 27 + // WithFs sets the filesystem to be used. 28 + func WithFs(fs billy.Filesystem) PackfileOption { 29 + return func(p *Packfile) { 30 + p.fs = fs 31 + } 32 + }
+220 -139
plumbing/format/packfile/packfile_test.go
··· 3 3 import ( 4 4 "io" 5 5 "math" 6 + "testing" 6 7 7 - fixtures "github.com/go-git/go-git-fixtures/v4" 8 + fixtures "github.com/go-git/go-git-fixtures/v5" 8 9 "github.com/go-git/go-git/v5/plumbing" 10 + "github.com/go-git/go-git/v5/plumbing/cache" 9 11 "github.com/go-git/go-git/v5/plumbing/format/idxfile" 10 12 "github.com/go-git/go-git/v5/plumbing/format/packfile" 11 - . "gopkg.in/check.v1" 13 + "github.com/stretchr/testify/assert" 14 + "github.com/stretchr/testify/require" 12 15 ) 13 16 14 - type PackfileSuite struct { 15 - fixtures.Suite 16 - p *packfile.Packfile 17 - idx *idxfile.MemoryIndex 18 - f *fixtures.Fixture 19 - } 17 + func TestGet(t *testing.T) { 18 + t.Parallel() 19 + 20 + f := fixtures.Basic().One() 21 + idx := getIndexFromIdxFile(f.Idx()) 20 22 21 - var _ = Suite(&PackfileSuite{}) 23 + p := packfile.NewPackfile(f.Packfile(), 24 + packfile.WithIdx(idx), packfile.WithFs(fixtures.Filesystem), 25 + ) 22 26 23 - func (s *PackfileSuite) TestGet(c *C) { 24 27 for h := range expectedEntries { 25 - obj, err := s.p.Get(h) 26 - c.Assert(err, IsNil) 27 - c.Assert(obj, Not(IsNil)) 28 - c.Assert(obj.Hash(), Equals, h) 28 + obj, err := p.Get(h) 29 + 30 + assert.NoError(t, err) 31 + assert.NotNil(t, obj) 32 + assert.Equal(t, h.String(), obj.Hash().String()) 29 33 } 30 34 31 - _, err := s.p.Get(plumbing.ZeroHash) 32 - c.Assert(err, Equals, plumbing.ErrObjectNotFound) 35 + _, err := p.Get(plumbing.ZeroHash) 36 + assert.ErrorIs(t, err, plumbing.ErrObjectNotFound) 37 + 38 + id, err := p.ID() 39 + assert.NoError(t, err) 40 + assert.Equal(t, f.PackfileHash, id.String()) 33 41 } 34 42 35 - func (s *PackfileSuite) TestGetByOffset(c *C) { 43 + func TestGetByOffset(t *testing.T) { 44 + t.Parallel() 45 + 46 + f := fixtures.Basic().One() 47 + idx := getIndexFromIdxFile(f.Idx()) 48 + 49 + p := packfile.NewPackfile(f.Packfile(), 50 + packfile.WithIdx(idx), packfile.WithFs(fixtures.Filesystem), 51 + ) 52 + 36 53 for h, o := range expectedEntries { 37 - obj, err := s.p.GetByOffset(o) 38 - c.Assert(err, IsNil) 39 - c.Assert(obj, Not(IsNil)) 40 - c.Assert(obj.Hash(), Equals, h) 54 + obj, err := p.GetByOffset(o) 55 + assert.NoError(t, err) 56 + assert.NotNil(t, obj) 57 + assert.Equal(t, h.String(), obj.Hash().String()) 41 58 } 42 59 43 - _, err := s.p.GetByOffset(math.MaxInt64) 44 - c.Assert(err, Equals, plumbing.ErrObjectNotFound) 60 + _, err := p.GetByOffset(math.MaxInt64) 61 + assert.ErrorIs(t, err, plumbing.ErrObjectNotFound) 45 62 } 46 63 47 - func (s *PackfileSuite) TestID(c *C) { 48 - id, err := s.p.ID() 49 - c.Assert(err, IsNil) 50 - c.Assert(id.String(), Equals, s.f.PackfileHash) 51 - } 64 + func TestGetAll(t *testing.T) { 65 + t.Parallel() 66 + 67 + f := fixtures.Basic().One() 68 + idx := getIndexFromIdxFile(f.Idx()) 52 69 53 - func (s *PackfileSuite) TestGetAll(c *C) { 54 - iter, err := s.p.GetAll() 55 - c.Assert(err, IsNil) 70 + p := packfile.NewPackfile(f.Packfile(), 71 + packfile.WithIdx(idx), 72 + packfile.WithFs(fixtures.Filesystem)) 73 + 74 + iter, err := p.GetAll() 75 + assert.NoError(t, err) 56 76 57 77 var objects int 58 78 for { ··· 60 80 if err == io.EOF { 61 81 break 62 82 } 63 - c.Assert(err, IsNil) 83 + assert.NoError(t, err) 64 84 65 85 objects++ 66 - _, ok := expectedEntries[o.Hash()] 67 - c.Assert(ok, Equals, true) 86 + h := o.Hash() 87 + _, ok := expectedEntries[h] 88 + assert.True(t, ok, "%s not found", h) 68 89 } 69 90 70 - c.Assert(objects, Equals, len(expectedEntries)) 71 - } 91 + assert.Len(t, expectedEntries, objects) 72 92 73 - var expectedEntries = map[plumbing.Hash]int64{ 74 - plumbing.NewHash("1669dce138d9b841a518c64b10914d88f5e488ea"): 615, 75 - plumbing.NewHash("32858aad3c383ed1ff0a0f9bdf231d54a00c9e88"): 1524, 76 - plumbing.NewHash("35e85108805c84807bc66a02d91535e1e24b38b9"): 1063, 77 - plumbing.NewHash("49c6bb89b17060d7b4deacb7b338fcc6ea2352a9"): 78882, 78 - plumbing.NewHash("4d081c50e250fa32ea8b1313cf8bb7c2ad7627fd"): 84688, 79 - plumbing.NewHash("586af567d0bb5e771e49bdd9434f5e0fb76d25fa"): 84559, 80 - plumbing.NewHash("5a877e6a906a2743ad6e45d99c1793642aaf8eda"): 84479, 81 - plumbing.NewHash("6ecf0ef2c2dffb796033e5a02219af86ec6584e5"): 186, 82 - plumbing.NewHash("7e59600739c96546163833214c36459e324bad0a"): 84653, 83 - plumbing.NewHash("880cd14280f4b9b6ed3986d6671f907d7cc2a198"): 78050, 84 - plumbing.NewHash("8dcef98b1d52143e1e2dbc458ffe38f925786bf2"): 84741, 85 - plumbing.NewHash("918c48b83bd081e863dbe1b80f8998f058cd8294"): 286, 86 - plumbing.NewHash("9a48f23120e880dfbe41f7c9b7b708e9ee62a492"): 80998, 87 - plumbing.NewHash("9dea2395f5403188298c1dabe8bdafe562c491e3"): 84032, 88 - plumbing.NewHash("a39771a7651f97faf5c72e08224d857fc35133db"): 84430, 89 - plumbing.NewHash("a5b8b09e2f8fcb0bb99d3ccb0958157b40890d69"): 838, 90 - plumbing.NewHash("a8d315b2b1c615d43042c3a62402b8a54288cf5c"): 84375, 91 - plumbing.NewHash("aa9b383c260e1d05fbbf6b30a02914555e20c725"): 84760, 92 - plumbing.NewHash("af2d6a6954d532f8ffb47615169c8fdf9d383a1a"): 449, 93 - plumbing.NewHash("b029517f6300c2da0f4b651b8642506cd6aaf45d"): 1392, 94 - plumbing.NewHash("b8e471f58bcbca63b07bda20e428190409c2db47"): 1230, 95 - plumbing.NewHash("c192bd6a24ea1ab01d78686e417c8bdc7c3d197f"): 1713, 96 - plumbing.NewHash("c2d30fa8ef288618f65f6eed6e168e0d514886f4"): 84725, 97 - plumbing.NewHash("c8f1d8c61f9da76f4cb49fd86322b6e685dba956"): 80725, 98 - plumbing.NewHash("cf4aa3b38974fb7d81f367c0830f7d78d65ab86b"): 84608, 99 - plumbing.NewHash("d3ff53e0564a9f87d8e84b6e28e5060e517008aa"): 1685, 100 - plumbing.NewHash("d5c0f4ab811897cadf03aec358ae60d21f91c50d"): 2351, 101 - plumbing.NewHash("dbd3641b371024f44d0e469a9c8f5457b0660de1"): 84115, 102 - plumbing.NewHash("e8d3ffab552895c19b9fcf7aa264d277cde33881"): 12, 103 - plumbing.NewHash("eba74343e2f15d62adedfd8c883ee0262b5c8021"): 84708, 104 - plumbing.NewHash("fb72698cab7617ac416264415f13224dfd7a165e"): 84671, 93 + iter.Close() 94 + assert.NoError(t, p.Close()) 105 95 } 106 96 107 - func (s *PackfileSuite) SetUpTest(c *C) { 108 - s.f = fixtures.Basic().One() 109 - 110 - s.idx = idxfile.NewMemoryIndex() 111 - c.Assert(idxfile.NewDecoder(s.f.Idx()).Decode(s.idx), IsNil) 112 - 113 - s.p = packfile.NewPackfile(s.idx, fixtures.Filesystem, s.f.Packfile(), 0) 114 - } 97 + func TestDecode(t *testing.T) { 98 + t.Parallel() 115 99 116 - func (s *PackfileSuite) TearDownTest(c *C) { 117 - c.Assert(s.p.Close(), IsNil) 118 - } 100 + packfiles := fixtures.Basic().ByTag("packfile") 101 + assert.Greater(t, len(packfiles), 0) 119 102 120 - func (s *PackfileSuite) TestDecode(c *C) { 121 - fixtures.Basic().ByTag("packfile").Test(c, func(f *fixtures.Fixture) { 103 + for _, f := range packfiles { 104 + f := f 122 105 index := getIndexFromIdxFile(f.Idx()) 123 106 124 - p := packfile.NewPackfile(index, fixtures.Filesystem, f.Packfile(), 0) 125 - defer p.Close() 107 + p := packfile.NewPackfile(f.Packfile(), 108 + packfile.WithIdx(index), packfile.WithFs(fixtures.Filesystem), 109 + ) 126 110 127 111 for _, h := range expectedHashes { 112 + h := h 128 113 obj, err := p.Get(plumbing.NewHash(h)) 129 - c.Assert(err, IsNil) 130 - c.Assert(obj.Hash().String(), Equals, h) 114 + assert.NoError(t, err) 115 + assert.Equal(t, obj.Hash().String(), h) 131 116 } 132 - }) 117 + 118 + err := p.Close() 119 + assert.NoError(t, err) 120 + } 133 121 } 134 122 135 - func (s *PackfileSuite) TestDecodeByTypeRefDelta(c *C) { 123 + func TestDecodeByTypeRefDelta(t *testing.T) { 124 + t.Parallel() 125 + 136 126 f := fixtures.Basic().ByTag("ref-delta").One() 137 127 138 128 index := getIndexFromIdxFile(f.Idx()) 139 129 140 - packfile := packfile.NewPackfile(index, fixtures.Filesystem, f.Packfile(), 0) 141 - defer packfile.Close() 130 + packfile := packfile.NewPackfile(f.Packfile(), 131 + packfile.WithIdx(index), packfile.WithFs(fixtures.Filesystem)) 142 132 143 133 iter, err := packfile.GetByType(plumbing.CommitObject) 144 - c.Assert(err, IsNil) 134 + assert.NoError(t, err) 145 135 146 136 var count int 147 137 for { ··· 151 141 } 152 142 153 143 count++ 154 - c.Assert(err, IsNil) 155 - c.Assert(obj.Type(), Equals, plumbing.CommitObject) 144 + assert.NoError(t, err) 145 + assert.Equal(t, obj.Type(), plumbing.CommitObject) 156 146 } 157 147 158 - c.Assert(count > 0, Equals, true) 148 + err = packfile.Close() 149 + 150 + assert.NoError(t, err) 151 + assert.Greater(t, count, 0) 159 152 } 160 153 161 - func (s *PackfileSuite) TestDecodeByType(c *C) { 162 - ts := []plumbing.ObjectType{ 154 + func TestDecodeByType(t *testing.T) { 155 + t.Parallel() 156 + 157 + types := []plumbing.ObjectType{ 163 158 plumbing.CommitObject, 164 159 plumbing.TagObject, 165 160 plumbing.TreeObject, 166 161 plumbing.BlobObject, 167 162 } 168 163 169 - fixtures.Basic().ByTag("packfile").Test(c, func(f *fixtures.Fixture) { 170 - for _, t := range ts { 164 + for _, f := range fixtures.Basic().ByTag("packfile") { 165 + f := f 166 + for _, typ := range types { 167 + typ := typ 171 168 index := getIndexFromIdxFile(f.Idx()) 172 169 173 - packfile := packfile.NewPackfile(index, fixtures.Filesystem, f.Packfile(), 0) 170 + packfile := packfile.NewPackfile(f.Packfile(), 171 + packfile.WithIdx(index), packfile.WithFs(fixtures.Filesystem), 172 + ) 174 173 defer packfile.Close() 175 174 176 - iter, err := packfile.GetByType(t) 177 - c.Assert(err, IsNil) 175 + iter, err := packfile.GetByType(typ) 176 + assert.NoError(t, err) 178 177 179 - c.Assert(iter.ForEach(func(obj plumbing.EncodedObject) error { 180 - c.Assert(obj.Type(), Equals, t) 178 + err = iter.ForEach(func(obj plumbing.EncodedObject) error { 179 + assert.Equal(t, typ, obj.Type()) 181 180 return nil 182 - }), IsNil) 181 + }) 182 + assert.NoError(t, err) 183 183 } 184 - }) 184 + } 185 185 } 186 186 187 - func (s *PackfileSuite) TestDecodeByTypeConstructor(c *C) { 187 + func TestDecodeByTypeConstructor(t *testing.T) { 188 + t.Parallel() 189 + 188 190 f := fixtures.Basic().ByTag("packfile").One() 189 191 index := getIndexFromIdxFile(f.Idx()) 190 192 191 - packfile := packfile.NewPackfile(index, fixtures.Filesystem, f.Packfile(), 0) 193 + packfile := packfile.NewPackfile(f.Packfile(), 194 + packfile.WithIdx(index), packfile.WithFs(fixtures.Filesystem), 195 + ) 192 196 defer packfile.Close() 193 197 194 198 _, err := packfile.GetByType(plumbing.OFSDeltaObject) 195 - c.Assert(err, Equals, plumbing.ErrInvalidType) 199 + assert.ErrorIs(t, err, plumbing.ErrInvalidType) 196 200 197 201 _, err = packfile.GetByType(plumbing.REFDeltaObject) 198 - c.Assert(err, Equals, plumbing.ErrInvalidType) 202 + assert.ErrorIs(t, err, plumbing.ErrInvalidType) 199 203 200 204 _, err = packfile.GetByType(plumbing.InvalidObject) 201 - c.Assert(err, Equals, plumbing.ErrInvalidType) 205 + assert.ErrorIs(t, err, plumbing.ErrInvalidType) 206 + } 207 + 208 + func getIndexFromIdxFile(r io.ReadCloser) idxfile.Index { 209 + defer r.Close() 210 + 211 + idx := idxfile.NewMemoryIndex() 212 + if err := idxfile.NewDecoder(r).Decode(idx); err != nil { 213 + panic(err) 214 + } 215 + 216 + return idx 217 + } 218 + 219 + func TestSize(t *testing.T) { 220 + t.Parallel() 221 + 222 + f := fixtures.Basic().ByTag("ref-delta").One() 223 + 224 + index := getIndexFromIdxFile(f.Idx()) 225 + 226 + packfile := packfile.NewPackfile(f.Packfile(), 227 + packfile.WithIdx(index), 228 + packfile.WithFs(fixtures.Filesystem), 229 + ) 230 + defer packfile.Close() 231 + 232 + // Get the size of binary.jpg, which is not delta-encoded. 233 + offset, err := packfile.FindOffset(plumbing.NewHash("d5c0f4ab811897cadf03aec358ae60d21f91c50d")) 234 + assert.NoError(t, err) 235 + 236 + size, err := packfile.GetSizeByOffset(offset) 237 + assert.NoError(t, err) 238 + assert.Equal(t, int64(76110), size) 239 + 240 + // Get the size of the root commit, which is delta-encoded. 241 + offset, err = packfile.FindOffset(plumbing.NewHash(f.Head)) 242 + assert.NoError(t, err) 243 + size, err = packfile.GetSizeByOffset(offset) 244 + assert.NoError(t, err) 245 + assert.Equal(t, int64(245), size) 246 + } 247 + 248 + func BenchmarkGetByOffset(b *testing.B) { 249 + f := fixtures.Basic().One() 250 + idx := idxfile.NewMemoryIndex() 251 + 252 + cache := cache.NewObjectLRUDefault() 253 + err := idxfile.NewDecoder(f.Idx()).Decode(idx) 254 + require.NoError(b, err) 255 + 256 + b.Run("with storage", 257 + benchmarkGetByOffset(packfile.NewPackfile(f.Packfile(), 258 + packfile.WithIdx(idx), packfile.WithFs(fixtures.Filesystem), 259 + packfile.WithCache(cache), 260 + ))) 261 + b.Run("without storage", 262 + benchmarkGetByOffset(packfile.NewPackfile(f.Packfile(), 263 + packfile.WithCache(cache), packfile.WithIdx(idx), 264 + ))) 265 + } 266 + 267 + func benchmarkGetByOffset(p *packfile.Packfile) func(b *testing.B) { 268 + return func(b *testing.B) { 269 + for i := 0; i < b.N; i++ { 270 + for h, o := range expectedEntries { 271 + obj, err := p.GetByOffset(o) 272 + if err != nil { 273 + b.Fatal() 274 + } 275 + if h != obj.Hash() { 276 + b.Fatal() 277 + } 278 + } 279 + } 280 + } 202 281 } 203 282 204 283 var expectedHashes = []string{ ··· 235 314 "7e59600739c96546163833214c36459e324bad0a", 236 315 } 237 316 238 - func getIndexFromIdxFile(r io.Reader) idxfile.Index { 239 - idx := idxfile.NewMemoryIndex() 240 - if err := idxfile.NewDecoder(r).Decode(idx); err != nil { 241 - panic(err) 242 - } 243 - 244 - return idx 245 - } 246 - 247 - func (s *PackfileSuite) TestSize(c *C) { 248 - f := fixtures.Basic().ByTag("ref-delta").One() 249 - 250 - index := getIndexFromIdxFile(f.Idx()) 251 - 252 - packfile := packfile.NewPackfile(index, fixtures.Filesystem, f.Packfile(), 0) 253 - defer packfile.Close() 254 - 255 - // Get the size of binary.jpg, which is not delta-encoded. 256 - offset, err := packfile.FindOffset(plumbing.NewHash("d5c0f4ab811897cadf03aec358ae60d21f91c50d")) 257 - c.Assert(err, IsNil) 258 - size, err := packfile.GetSizeByOffset(offset) 259 - c.Assert(err, IsNil) 260 - c.Assert(size, Equals, int64(76110)) 261 - 262 - // Get the size of the root commit, which is delta-encoded. 263 - offset, err = packfile.FindOffset(plumbing.NewHash(f.Head)) 264 - c.Assert(err, IsNil) 265 - size, err = packfile.GetSizeByOffset(offset) 266 - c.Assert(err, IsNil) 267 - c.Assert(size, Equals, int64(245)) 317 + var expectedEntries = map[plumbing.Hash]int64{ 318 + plumbing.NewHash("1669dce138d9b841a518c64b10914d88f5e488ea"): 615, 319 + plumbing.NewHash("32858aad3c383ed1ff0a0f9bdf231d54a00c9e88"): 1524, 320 + plumbing.NewHash("35e85108805c84807bc66a02d91535e1e24b38b9"): 1063, 321 + plumbing.NewHash("49c6bb89b17060d7b4deacb7b338fcc6ea2352a9"): 78882, 322 + plumbing.NewHash("4d081c50e250fa32ea8b1313cf8bb7c2ad7627fd"): 84688, 323 + plumbing.NewHash("586af567d0bb5e771e49bdd9434f5e0fb76d25fa"): 84559, 324 + plumbing.NewHash("5a877e6a906a2743ad6e45d99c1793642aaf8eda"): 84479, 325 + plumbing.NewHash("6ecf0ef2c2dffb796033e5a02219af86ec6584e5"): 186, 326 + plumbing.NewHash("7e59600739c96546163833214c36459e324bad0a"): 84653, 327 + plumbing.NewHash("880cd14280f4b9b6ed3986d6671f907d7cc2a198"): 78050, 328 + plumbing.NewHash("8dcef98b1d52143e1e2dbc458ffe38f925786bf2"): 84741, 329 + plumbing.NewHash("918c48b83bd081e863dbe1b80f8998f058cd8294"): 286, 330 + plumbing.NewHash("9a48f23120e880dfbe41f7c9b7b708e9ee62a492"): 80998, 331 + plumbing.NewHash("9dea2395f5403188298c1dabe8bdafe562c491e3"): 84032, 332 + plumbing.NewHash("a39771a7651f97faf5c72e08224d857fc35133db"): 84430, 333 + plumbing.NewHash("a5b8b09e2f8fcb0bb99d3ccb0958157b40890d69"): 838, 334 + plumbing.NewHash("a8d315b2b1c615d43042c3a62402b8a54288cf5c"): 84375, 335 + plumbing.NewHash("aa9b383c260e1d05fbbf6b30a02914555e20c725"): 84760, 336 + plumbing.NewHash("af2d6a6954d532f8ffb47615169c8fdf9d383a1a"): 449, 337 + plumbing.NewHash("b029517f6300c2da0f4b651b8642506cd6aaf45d"): 1392, 338 + plumbing.NewHash("b8e471f58bcbca63b07bda20e428190409c2db47"): 1230, 339 + plumbing.NewHash("c192bd6a24ea1ab01d78686e417c8bdc7c3d197f"): 1713, 340 + plumbing.NewHash("c2d30fa8ef288618f65f6eed6e168e0d514886f4"): 84725, 341 + plumbing.NewHash("c8f1d8c61f9da76f4cb49fd86322b6e685dba956"): 80725, 342 + plumbing.NewHash("cf4aa3b38974fb7d81f367c0830f7d78d65ab86b"): 84608, 343 + plumbing.NewHash("d3ff53e0564a9f87d8e84b6e28e5060e517008aa"): 1685, 344 + plumbing.NewHash("d5c0f4ab811897cadf03aec358ae60d21f91c50d"): 2351, 345 + plumbing.NewHash("dbd3641b371024f44d0e469a9c8f5457b0660de1"): 84115, 346 + plumbing.NewHash("e8d3ffab552895c19b9fcf7aa264d277cde33881"): 12, 347 + plumbing.NewHash("eba74343e2f15d62adedfd8c883ee0262b5c8021"): 84708, 348 + plumbing.NewHash("fb72698cab7617ac416264415f13224dfd7a165e"): 84671, 268 349 }
+189 -472
plumbing/format/packfile/parser.go
··· 5 5 "errors" 6 6 "fmt" 7 7 "io" 8 + stdsync "sync" 8 9 9 10 "github.com/go-git/go-git/v5/plumbing" 10 - "github.com/go-git/go-git/v5/plumbing/cache" 11 11 "github.com/go-git/go-git/v5/plumbing/storer" 12 12 "github.com/go-git/go-git/v5/utils/ioutil" 13 - "github.com/go-git/go-git/v5/utils/sync" 14 13 ) 15 14 16 15 var ( ··· 26 25 ErrDeltaNotCached = errors.New("delta could not be found in cache") 27 26 ) 28 27 29 - // Observer interface is implemented by index encoders. 30 - type Observer interface { 31 - // OnHeader is called when a new packfile is opened. 32 - OnHeader(count uint32) error 33 - // OnInflatedObjectHeader is called for each object header read. 34 - OnInflatedObjectHeader(t plumbing.ObjectType, objSize int64, pos int64) error 35 - // OnInflatedObjectContent is called for each decoded object. 36 - OnInflatedObjectContent(h plumbing.Hash, pos int64, crc uint32, content []byte) error 37 - // OnFooter is called when decoding is done. 38 - OnFooter(h plumbing.Hash) error 39 - } 40 - 41 28 // Parser decodes a packfile and calls any observer associated to it. Is used 42 29 // to generate indexes. 43 30 type Parser struct { 44 - storage storer.EncodedObjectStorer 45 - scanner *Scanner 46 - count uint32 47 - oi []*objectInfo 48 - oiByHash map[plumbing.Hash]*objectInfo 49 - oiByOffset map[int64]*objectInfo 50 - checksum plumbing.Hash 31 + storage storer.EncodedObjectStorer 32 + cache *parserCache 51 33 52 - cache *cache.BufferLRU 53 - // delta content by offset, only used if source is not seekable 54 - deltas map[int64][]byte 34 + scanner *Scanner 35 + observers []Observer 36 + hasher plumbing.Hasher 55 37 56 - ob []Observer 38 + checksum plumbing.Hash 39 + m stdsync.Mutex 57 40 } 58 41 59 - // NewParser creates a new Parser. The Scanner source must be seekable. 60 - // If it's not, NewParserWithStorage should be used instead. 61 - func NewParser(scanner *Scanner, ob ...Observer) (*Parser, error) { 62 - return NewParserWithStorage(scanner, nil, ob...) 63 - } 64 - 65 - // NewParserWithStorage creates a new Parser. The scanner source must either 66 - // be seekable or a storage must be provided. 67 - func NewParserWithStorage( 68 - scanner *Scanner, 69 - storage storer.EncodedObjectStorer, 70 - ob ...Observer, 71 - ) (*Parser, error) { 72 - if !scanner.IsSeekable && storage == nil { 73 - return nil, ErrNotSeekableSource 42 + // NewParser creates a new Parser. 43 + // When a storage is set, the objects are written to storage as they 44 + // are parsed. 45 + func NewParser(data io.Reader, opts ...ParserOption) *Parser { 46 + p := &Parser{ 47 + hasher: plumbing.NewHasher(plumbing.AnyObject, 0), 74 48 } 75 - 76 - var deltas map[int64][]byte 77 - if !scanner.IsSeekable { 78 - deltas = make(map[int64][]byte) 49 + for _, opt := range opts { 50 + opt(p) 79 51 } 80 52 81 - return &Parser{ 82 - storage: storage, 83 - scanner: scanner, 84 - ob: ob, 85 - count: 0, 86 - cache: cache.NewBufferLRUDefault(), 87 - deltas: deltas, 88 - }, nil 89 - } 53 + p.scanner = NewScanner(data) 90 54 91 - func (p *Parser) forEachObserver(f func(o Observer) error) error { 92 - for _, o := range p.ob { 93 - if err := f(o); err != nil { 94 - return err 95 - } 55 + if p.storage != nil { 56 + p.scanner.storage = p.storage 96 57 } 97 - return nil 98 - } 99 - 100 - func (p *Parser) onHeader(count uint32) error { 101 - return p.forEachObserver(func(o Observer) error { 102 - return o.OnHeader(count) 103 - }) 104 - } 58 + p.cache = newParserCache() 105 59 106 - func (p *Parser) onInflatedObjectHeader( 107 - t plumbing.ObjectType, 108 - objSize int64, 109 - pos int64, 110 - ) error { 111 - return p.forEachObserver(func(o Observer) error { 112 - return o.OnInflatedObjectHeader(t, objSize, pos) 113 - }) 60 + return p 114 61 } 115 62 116 - func (p *Parser) onInflatedObjectContent( 117 - h plumbing.Hash, 118 - pos int64, 119 - crc uint32, 120 - content []byte, 121 - ) error { 122 - return p.forEachObserver(func(o Observer) error { 123 - return o.OnInflatedObjectContent(h, pos, crc, content) 124 - }) 125 - } 63 + func (p *Parser) storeOrCache(oh *ObjectHeader) error { 64 + // Only need to store deltas, as the scanner already stored non-delta 65 + // objects. 66 + if p.storage != nil && oh.diskType.IsDelta() { 67 + w, err := p.storage.RawObjectWriter(oh.Type, oh.Size) 68 + if err != nil { 69 + return err 70 + } 126 71 127 - func (p *Parser) onFooter(h plumbing.Hash) error { 128 - return p.forEachObserver(func(o Observer) error { 129 - return o.OnFooter(h) 130 - }) 131 - } 72 + defer w.Close() 132 73 133 - // Parse start decoding phase of the packfile. 134 - func (p *Parser) Parse() (plumbing.Hash, error) { 135 - if err := p.init(); err != nil { 136 - return plumbing.ZeroHash, err 74 + _, err = io.Copy(w, bytes.NewReader(oh.content.Bytes())) 75 + if err != nil { 76 + return err 77 + } 137 78 } 138 79 139 - if err := p.indexObjects(); err != nil { 140 - return plumbing.ZeroHash, err 80 + if p.cache != nil { 81 + p.cache.Add(oh) 141 82 } 142 83 143 - var err error 144 - p.checksum, err = p.scanner.Checksum() 145 - if err != nil && err != io.EOF { 146 - return plumbing.ZeroHash, err 147 - } 84 + p.onInflatedObjectHeader(oh.Type, oh.Size, oh.Offset) 85 + p.onInflatedObjectContent(oh.Hash, oh.Offset, oh.Crc32, nil) 148 86 149 - if err := p.resolveDeltas(); err != nil { 150 - return plumbing.ZeroHash, err 151 - } 152 - 153 - if err := p.onFooter(p.checksum); err != nil { 154 - return plumbing.ZeroHash, err 155 - } 156 - 157 - return p.checksum, nil 87 + return nil 158 88 } 159 89 160 - func (p *Parser) init() error { 161 - _, c, err := p.scanner.Header() 162 - if err != nil { 163 - return err 164 - } 165 - 166 - if err := p.onHeader(c); err != nil { 167 - return err 90 + func (p *Parser) resetCache(qty int) { 91 + if p.cache != nil { 92 + p.cache.Reset(qty) 168 93 } 169 - 170 - p.count = c 171 - p.oiByHash = make(map[plumbing.Hash]*objectInfo, p.count) 172 - p.oiByOffset = make(map[int64]*objectInfo, p.count) 173 - p.oi = make([]*objectInfo, p.count) 174 - 175 - return nil 176 94 } 177 95 178 - type objectHeaderWriter func(typ plumbing.ObjectType, sz int64) error 96 + // Parse start decoding phase of the packfile. 97 + func (p *Parser) Parse() (plumbing.Hash, error) { 98 + p.m.Lock() 99 + defer p.m.Unlock() 179 100 180 - type lazyObjectWriter interface { 181 - // LazyWriter enables an object to be lazily written. 182 - // It returns: 183 - // - w: a writer to receive the object's content. 184 - // - lwh: a func to write the object header. 185 - // - err: any error from the initial writer creation process. 186 - // 187 - // Note that if the object header is not written BEFORE the writer 188 - // is used, this will result in an invalid object. 189 - LazyWriter() (w io.WriteCloser, lwh objectHeaderWriter, err error) 190 - } 101 + var pendingDeltas []*ObjectHeader 102 + var pendingDeltaREFs []*ObjectHeader 191 103 192 - func (p *Parser) indexObjects() error { 193 - buf := sync.GetBytesBuffer() 194 - defer sync.PutBytesBuffer(buf) 104 + for p.scanner.Scan() { 105 + data := p.scanner.Data() 106 + switch data.Section { 107 + case HeaderSection: 108 + header := data.Value().(Header) 195 109 196 - for i := uint32(0); i < p.count; i++ { 197 - oh, err := p.scanner.NextObjectHeader() 198 - if err != nil { 199 - return err 200 - } 110 + p.resetCache(int(header.ObjectsQty)) 111 + p.onHeader(header.ObjectsQty) 201 112 202 - delta := false 203 - var ota *objectInfo 204 - switch t := oh.Type; t { 205 - case plumbing.OFSDeltaObject: 206 - delta = true 113 + case ObjectSection: 114 + oh := data.Value().(ObjectHeader) 207 115 208 - parent, ok := p.oiByOffset[oh.OffsetReference] 209 - if !ok { 210 - return plumbing.ErrObjectNotFound 211 - } 212 - 213 - ota = newDeltaObject(oh.Offset, oh.Length, t, parent) 214 - parent.Children = append(parent.Children, ota) 215 - case plumbing.REFDeltaObject: 216 - delta = true 217 - parent, ok := p.oiByHash[oh.Reference] 218 - if !ok { 219 - // can't find referenced object in this pack file 220 - // this must be a "thin" pack. 221 - parent = &objectInfo{ //Placeholder parent 222 - SHA1: oh.Reference, 223 - ExternalRef: true, // mark as an external reference that must be resolved 224 - Type: plumbing.AnyObject, 225 - DiskType: plumbing.AnyObject, 116 + if oh.Type.IsDelta() { 117 + if oh.Type == plumbing.OFSDeltaObject { 118 + pendingDeltas = append(pendingDeltas, &oh) 119 + } else if oh.Type == plumbing.REFDeltaObject { 120 + pendingDeltaREFs = append(pendingDeltaREFs, &oh) 226 121 } 227 - p.oiByHash[oh.Reference] = parent 122 + continue 123 + } else { 124 + p.storeOrCache(&oh) 228 125 } 229 - ota = newDeltaObject(oh.Offset, oh.Length, t, parent) 230 - parent.Children = append(parent.Children, ota) 231 126 232 - default: 233 - ota = newBaseObject(oh.Offset, oh.Length, t) 127 + case FooterSection: 128 + p.checksum = data.Value().(plumbing.Hash) 234 129 } 130 + } 235 131 236 - hasher := plumbing.NewHasher(oh.Type, oh.Length) 237 - writers := []io.Writer{hasher} 238 - var obj *plumbing.MemoryObject 132 + if p.scanner.objects == 0 { 133 + return plumbing.ZeroHash, ErrEmptyPackfile 134 + } 239 135 240 - // Lazy writing is only available for non-delta objects. 241 - if p.storage != nil && !delta { 242 - // When a storage is set and supports lazy writing, 243 - // use that instead of creating a memory object. 244 - if low, ok := p.storage.(lazyObjectWriter); ok { 245 - ow, lwh, err := low.LazyWriter() 246 - if err != nil { 247 - return err 248 - } 249 - 250 - if err = lwh(oh.Type, oh.Length); err != nil { 251 - return err 252 - } 253 - 254 - defer ow.Close() 255 - writers = append(writers, ow) 256 - } else { 257 - obj = new(plumbing.MemoryObject) 258 - obj.SetSize(oh.Length) 259 - obj.SetType(oh.Type) 260 - 261 - writers = append(writers, obj) 262 - } 136 + for _, oh := range pendingDeltas { 137 + err := p.processDelta(oh) 138 + if err != nil { 139 + return plumbing.ZeroHash, err 263 140 } 264 - if delta && !p.scanner.IsSeekable { 265 - buf.Reset() 266 - buf.Grow(int(oh.Length)) 267 - writers = append(writers, buf) 268 - } 269 - 270 - mw := io.MultiWriter(writers...) 141 + } 271 142 272 - _, crc, err := p.scanner.NextObject(mw) 143 + for _, oh := range pendingDeltaREFs { 144 + err := p.processDelta(oh) 273 145 if err != nil { 274 - return err 275 - } 276 - 277 - // Non delta objects needs to be added into the storage. This 278 - // is only required when lazy writing is not supported. 279 - if obj != nil { 280 - if _, err := p.storage.SetEncodedObject(obj); err != nil { 281 - return err 282 - } 146 + return plumbing.ZeroHash, err 283 147 } 284 - 285 - ota.Crc32 = crc 286 - ota.Length = oh.Length 287 - 288 - if !delta { 289 - sha1 := hasher.Sum() 290 - 291 - // Move children of placeholder parent into actual parent, in case this 292 - // was a non-external delta reference. 293 - if placeholder, ok := p.oiByHash[sha1]; ok { 294 - ota.Children = placeholder.Children 295 - for _, c := range ota.Children { 296 - c.Parent = ota 297 - } 298 - } 299 - 300 - ota.SHA1 = sha1 301 - p.oiByHash[ota.SHA1] = ota 302 - } 303 - 304 - if delta && !p.scanner.IsSeekable { 305 - data := buf.Bytes() 306 - p.deltas[oh.Offset] = make([]byte, len(data)) 307 - copy(p.deltas[oh.Offset], data) 308 - } 309 - 310 - p.oiByOffset[oh.Offset] = ota 311 - p.oi[i] = ota 312 148 } 313 149 314 - return nil 150 + return p.checksum, p.onFooter(p.checksum) 315 151 } 316 152 317 - func (p *Parser) resolveDeltas() error { 318 - buf := sync.GetBytesBuffer() 319 - defer sync.PutBytesBuffer(buf) 320 - 321 - for _, obj := range p.oi { 322 - buf.Reset() 323 - buf.Grow(int(obj.Length)) 324 - err := p.get(obj, buf) 325 - if err != nil { 326 - return err 153 + func (p *Parser) processDelta(oh *ObjectHeader) error { 154 + switch oh.Type { 155 + case plumbing.OFSDeltaObject: 156 + pa, ok := p.cache.oiByOffset[oh.OffsetReference] 157 + if !ok { 158 + return plumbing.ErrObjectNotFound 327 159 } 160 + oh.parent = pa 328 161 329 - if err := p.onInflatedObjectHeader(obj.Type, obj.Length, obj.Offset); err != nil { 330 - return err 331 - } 332 - 333 - if err := p.onInflatedObjectContent(obj.SHA1, obj.Offset, obj.Crc32, nil); err != nil { 334 - return err 335 - } 336 - 337 - if !obj.IsDelta() && len(obj.Children) > 0 { 338 - // Dealing with an io.ReaderAt object, means we can 339 - // create it once and reuse across all children. 340 - r := bytes.NewReader(buf.Bytes()) 341 - for _, child := range obj.Children { 342 - // Even though we are discarding the output, we still need to read it to 343 - // so that the scanner can advance to the next object, and the SHA1 can be 344 - // calculated. 345 - if err := p.resolveObject(io.Discard, child, r); err != nil { 346 - return err 347 - } 348 - p.resolveExternalRef(child) 162 + case plumbing.REFDeltaObject: 163 + pa, ok := p.cache.oiByHash[oh.Reference] 164 + if !ok { 165 + // can't find referenced object in this pack file 166 + // this must be a "thin" pack. 167 + oh.parent = &ObjectHeader{ //Placeholder parent 168 + Hash: oh.Reference, 169 + externalRef: true, // mark as an external reference that must be resolved 170 + Type: plumbing.AnyObject, 171 + diskType: plumbing.AnyObject, 349 172 } 350 - 351 - // Remove the delta from the cache. 352 - if obj.DiskType.IsDelta() && !p.scanner.IsSeekable { 353 - delete(p.deltas, obj.Offset) 354 - } 173 + } else { 174 + oh.parent = pa 355 175 } 356 - } 176 + p.cache.oiByHash[oh.Reference] = oh.parent 357 177 358 - return nil 359 - } 360 - 361 - func (p *Parser) resolveExternalRef(o *objectInfo) { 362 - if ref, ok := p.oiByHash[o.SHA1]; ok && ref.ExternalRef { 363 - p.oiByHash[o.SHA1] = o 364 - o.Children = ref.Children 365 - for _, c := range o.Children { 366 - c.Parent = o 367 - } 178 + default: 179 + return fmt.Errorf("unsupported delta type: %v", oh.Type) 368 180 } 369 - } 370 181 371 - func (p *Parser) get(o *objectInfo, buf *bytes.Buffer) (err error) { 372 - if !o.ExternalRef { // skip cache check for placeholder parents 373 - b, ok := p.cache.Get(o.Offset) 374 - if ok { 375 - _, err := buf.Write(b) 376 - return err 377 - } 378 - } 379 - 380 - // If it's not on the cache and is not a delta we can try to find it in the 381 - // storage, if there's one. External refs must enter here. 382 - if p.storage != nil && !o.Type.IsDelta() { 383 - var e plumbing.EncodedObject 384 - e, err = p.storage.EncodedObject(plumbing.AnyObject, o.SHA1) 385 - if err != nil { 386 - return err 387 - } 388 - o.Type = e.Type() 389 - 390 - var r io.ReadCloser 391 - r, err = e.Reader() 392 - if err != nil { 393 - return err 394 - } 395 - 396 - defer ioutil.CheckClose(r, &err) 397 - 398 - _, err = buf.ReadFrom(io.LimitReader(r, e.Size())) 182 + parentContents, err := p.parentReader(oh.parent) 183 + if err != nil { 399 184 return err 400 185 } 401 186 402 - if o.ExternalRef { 403 - // we were not able to resolve a ref in a thin pack 404 - return ErrReferenceDeltaNotFound 405 - } 406 - 407 - if o.DiskType.IsDelta() { 408 - b := sync.GetBytesBuffer() 409 - defer sync.PutBytesBuffer(b) 410 - buf.Grow(int(o.Length)) 411 - err := p.get(o.Parent, b) 412 - if err != nil { 413 - return err 414 - } 415 - 416 - err = p.resolveObject(buf, o, bytes.NewReader(b.Bytes())) 417 - if err != nil { 418 - return err 419 - } 187 + var deltaData bytes.Buffer 188 + if oh.content.Len() > 0 { 189 + oh.content.WriteTo(&deltaData) 420 190 } else { 421 - err := p.readData(buf, o) 191 + deltaData = *bytes.NewBuffer(make([]byte, 0, oh.Size)) 192 + err = p.scanner.inflateContent(oh.ContentOffset, &deltaData) 422 193 if err != nil { 423 194 return err 424 195 } 425 196 } 426 197 427 - // If the scanner is seekable, caching this data into 428 - // memory by offset seems wasteful. 429 - // There is a trade-off to be considered here in terms 430 - // of execution time vs memory consumption. 431 - // 432 - // TODO: improve seekable execution time, so that we can 433 - // skip this cache. 434 - if len(o.Children) > 0 { 435 - data := make([]byte, buf.Len()) 436 - copy(data, buf.Bytes()) 437 - p.cache.Put(o.Offset, data) 198 + w, err := p.cacheWriter(oh) 199 + if err != nil { 200 + return err 438 201 } 439 - return nil 440 - } 202 + 203 + defer w.Close() 441 204 442 - // resolveObject resolves an object from base, using information 443 - // provided by o. 444 - // 445 - // This call has the side-effect of changing field values 446 - // from the object info o: 447 - // - Type: OFSDeltaObject may become the target type (e.g. Blob). 448 - // - Size: The size may be update with the target size. 449 - // - Hash: Zero hashes will be calculated as part of the object 450 - // resolution. Hence why this process can't be avoided even when w 451 - // is an io.Discard. 452 - // 453 - // base must be an io.ReaderAt, which is a requirement from 454 - // patchDeltaStream. The main reason being that reversing an 455 - // delta object may lead to going backs and forths within base, 456 - // which is not supported by io.Reader. 457 - func (p *Parser) resolveObject( 458 - w io.Writer, 459 - o *objectInfo, 460 - base io.ReaderAt, 461 - ) error { 462 - if !o.DiskType.IsDelta() { 463 - return nil 464 - } 465 - buf := sync.GetBytesBuffer() 466 - defer sync.PutBytesBuffer(buf) 467 - err := p.readData(buf, o) 205 + err = applyPatchBaseHeader(oh, parentContents, &deltaData, w, nil) 468 206 if err != nil { 469 207 return err 470 208 } 471 209 472 - writers := []io.Writer{w} 473 - var obj *plumbing.MemoryObject 474 - var lwh objectHeaderWriter 210 + p.storeOrCache(oh) 475 211 476 - if p.storage != nil { 477 - if low, ok := p.storage.(lazyObjectWriter); ok { 478 - ow, wh, err := low.LazyWriter() 479 - if err != nil { 480 - return err 481 - } 482 - lwh = wh 212 + return nil 213 + } 483 214 484 - defer ow.Close() 485 - writers = append(writers, ow) 486 - } else { 487 - obj = new(plumbing.MemoryObject) 488 - ow, err := obj.Writer() 489 - if err != nil { 490 - return err 491 - } 215 + func (p *Parser) parentReader(parent *ObjectHeader) (io.ReaderAt, error) { 216 + // If parent is a Delta object, the inflated object must come 217 + // from either cache or storage, else we would need to inflate 218 + // it to then inflate the current object, which could go on 219 + // indefinitely. 492 220 493 - writers = append(writers, ow) 221 + if p.storage != nil && parent.Hash != plumbing.ZeroHash { 222 + obj, err := p.storage.EncodedObject(parent.Type, parent.Hash) 223 + if err == nil { 224 + r, err := obj.Reader() 225 + if err == nil { 226 + parentData := bytes.NewBuffer(make([]byte, 0, parent.Size)) 227 + 228 + _, err = io.Copy(parentData, r) 229 + if err == nil { 230 + return bytes.NewReader(parentData.Bytes()), nil 231 + } 232 + } 494 233 } 495 234 } 496 235 497 - mw := io.MultiWriter(writers...) 498 - 499 - err = applyPatchBase(o, base, buf, mw, lwh) 500 - if err != nil { 501 - return err 236 + if p.cache != nil && parent.content.Len() > 0 { 237 + return bytes.NewReader(parent.content.Bytes()), nil 502 238 } 503 239 504 - if obj != nil { 505 - obj.SetType(o.Type) 506 - obj.SetSize(o.Size()) // Size here is correct as it was populated by applyPatchBase. 507 - if _, err := p.storage.SetEncodedObject(obj); err != nil { 508 - return err 509 - } 240 + // If the parent is not an external ref and we don't have the 241 + // content offset, we won't be able to inflate via seeking through 242 + // the packfile. 243 + if parent.externalRef && parent.ContentOffset == 0 { 244 + return nil, plumbing.ErrObjectNotFound 510 245 } 511 - return err 512 - } 513 246 514 - func (p *Parser) readData(w io.Writer, o *objectInfo) error { 515 - if !p.scanner.IsSeekable && o.DiskType.IsDelta() { 516 - data, ok := p.deltas[o.Offset] 517 - if !ok { 518 - return ErrDeltaNotCached 519 - } 520 - _, err := w.Write(data) 521 - return err 247 + // Not a seeker data source, so avoid seeking the content. 248 + if p.scanner.seeker == nil { 249 + return nil, plumbing.ErrObjectNotFound 522 250 } 523 251 524 - if _, err := p.scanner.SeekObjectHeader(o.Offset); err != nil { 525 - return err 252 + parentData := bytes.NewBuffer(make([]byte, 0, parent.Size)) 253 + err := p.scanner.inflateContent(parent.ContentOffset, parentData) 254 + if err != nil { 255 + return nil, ErrReferenceDeltaNotFound 526 256 } 257 + return bytes.NewReader(parentData.Bytes()), nil 258 + } 527 259 528 - if _, _, err := p.scanner.NextObject(w); err != nil { 529 - return err 530 - } 531 - return nil 260 + func (p *Parser) cacheWriter(oh *ObjectHeader) (io.WriteCloser, error) { 261 + return ioutil.NewWriteCloser(&oh.content, nil), nil 532 262 } 533 263 534 - // applyPatchBase applies the patch to target. 535 - // 536 - // Note that ota will be updated based on the description in resolveObject. 537 - func applyPatchBase(ota *objectInfo, base io.ReaderAt, delta io.Reader, target io.Writer, wh objectHeaderWriter) error { 264 + func applyPatchBaseHeader(ota *ObjectHeader, base io.ReaderAt, delta io.Reader, target io.Writer, wh objectHeaderWriter) error { 538 265 if target == nil { 539 266 return fmt.Errorf("cannot apply patch against nil target") 540 267 } 541 268 542 269 typ := ota.Type 543 - if ota.SHA1 == plumbing.ZeroHash { 544 - typ = ota.Parent.Type 270 + if ota.Hash == plumbing.ZeroHash { 271 + typ = ota.parent.Type 545 272 } 546 273 547 274 sz, h, err := patchDeltaWriter(target, base, delta, typ, wh) ··· 549 276 return err 550 277 } 551 278 552 - if ota.SHA1 == plumbing.ZeroHash { 279 + if ota.Hash == plumbing.ZeroHash { 553 280 ota.Type = typ 554 - ota.Length = int64(sz) 555 - ota.SHA1 = h 281 + ota.Size = int64(sz) 282 + ota.Hash = h 556 283 } 557 284 558 285 return nil 559 286 } 560 287 561 - func getSHA1(t plumbing.ObjectType, data []byte) (plumbing.Hash, error) { 562 - hasher := plumbing.NewHasher(t, int64(len(data))) 563 - if _, err := hasher.Write(data); err != nil { 564 - return plumbing.ZeroHash, err 288 + func (p *Parser) forEachObserver(f func(o Observer) error) error { 289 + for _, o := range p.observers { 290 + if err := f(o); err != nil { 291 + return err 292 + } 565 293 } 566 - 567 - return hasher.Sum(), nil 568 - } 569 - 570 - type objectInfo struct { 571 - Offset int64 572 - Length int64 573 - Type plumbing.ObjectType 574 - DiskType plumbing.ObjectType 575 - ExternalRef bool // indicates this is an external reference in a thin pack file 576 - 577 - Crc32 uint32 578 - 579 - Parent *objectInfo 580 - Children []*objectInfo 581 - SHA1 plumbing.Hash 294 + return nil 582 295 } 583 296 584 - func newBaseObject(offset, length int64, t plumbing.ObjectType) *objectInfo { 585 - return newDeltaObject(offset, length, t, nil) 297 + func (p *Parser) onHeader(count uint32) error { 298 + return p.forEachObserver(func(o Observer) error { 299 + return o.OnHeader(count) 300 + }) 586 301 } 587 302 588 - func newDeltaObject( 589 - offset, length int64, 303 + func (p *Parser) onInflatedObjectHeader( 590 304 t plumbing.ObjectType, 591 - parent *objectInfo, 592 - ) *objectInfo { 593 - obj := &objectInfo{ 594 - Offset: offset, 595 - Length: length, 596 - Type: t, 597 - DiskType: t, 598 - Crc32: 0, 599 - Parent: parent, 600 - } 601 - 602 - return obj 305 + objSize int64, 306 + pos int64, 307 + ) error { 308 + return p.forEachObserver(func(o Observer) error { 309 + return o.OnInflatedObjectHeader(t, objSize, pos) 310 + }) 603 311 } 604 312 605 - func (o *objectInfo) IsDelta() bool { 606 - return o.Type.IsDelta() 313 + func (p *Parser) onInflatedObjectContent( 314 + h plumbing.Hash, 315 + pos int64, 316 + crc uint32, 317 + content []byte, 318 + ) error { 319 + return p.forEachObserver(func(o Observer) error { 320 + return o.OnInflatedObjectContent(h, pos, crc, content) 321 + }) 607 322 } 608 323 609 - func (o *objectInfo) Size() int64 { 610 - return o.Length 324 + func (p *Parser) onFooter(h plumbing.Hash) error { 325 + return p.forEachObserver(func(o Observer) error { 326 + return o.OnFooter(h) 327 + }) 611 328 }
+42
plumbing/format/packfile/parser_cache.go
··· 1 + package packfile 2 + 3 + import ( 4 + "slices" 5 + 6 + "github.com/go-git/go-git/v5/plumbing" 7 + "golang.org/x/exp/maps" 8 + ) 9 + 10 + func newParserCache() *parserCache { 11 + c := &parserCache{} 12 + return c 13 + } 14 + 15 + // parserCache defines the cache used within the parser. 16 + // This is not thread safe by itself, and relies on the parser to 17 + // enforce thread-safety. 18 + type parserCache struct { 19 + oi []*ObjectHeader 20 + oiByHash map[plumbing.Hash]*ObjectHeader 21 + oiByOffset map[int64]*ObjectHeader 22 + } 23 + 24 + func (c *parserCache) Add(oh *ObjectHeader) { 25 + c.oiByHash[oh.Hash] = oh 26 + c.oiByOffset[oh.Offset] = oh 27 + c.oi = append(c.oi, oh) 28 + } 29 + 30 + func (c *parserCache) Reset(n int) { 31 + if c.oi == nil { 32 + c.oi = make([]*ObjectHeader, 0, n) 33 + c.oiByHash = make(map[plumbing.Hash]*ObjectHeader, n) 34 + c.oiByOffset = make(map[int64]*ObjectHeader, n) 35 + } else { 36 + c.oi = c.oi[:0] 37 + c.oi = slices.Grow(c.oi, n) 38 + 39 + maps.Clear(c.oiByHash) 40 + maps.Clear(c.oiByOffset) 41 + } 42 + }
+27
plumbing/format/packfile/parser_options.go
··· 1 + package packfile 2 + 3 + import ( 4 + "github.com/go-git/go-git/v5/plumbing/storer" 5 + ) 6 + 7 + type ParserOption func(*Parser) 8 + 9 + // WithStorage sets the storage to be used while parsing a pack file. 10 + func WithStorage(storage storer.EncodedObjectStorer) ParserOption { 11 + return func(p *Parser) { 12 + p.storage = storage 13 + } 14 + } 15 + 16 + // WithScannerObservers sets the observers to be notified during the 17 + // scanning or parsing of a pack file. The scanner is responsible for 18 + // notifying observers around general pack file information, such as 19 + // header and footer. The scanner also notifies object headers for 20 + // non-delta objects. 21 + // 22 + // Delta objects are notified as part of the parser logic. 23 + func WithScannerObservers(ob ...Observer) ParserOption { 24 + return func(p *Parser) { 25 + p.observers = ob 26 + } 27 + }
+143 -163
plumbing/format/packfile/parser_test.go
··· 2 2 3 3 import ( 4 4 "io" 5 - "os" 6 5 "testing" 7 6 7 + billy "github.com/go-git/go-billy/v5" 8 8 "github.com/go-git/go-billy/v5/osfs" 9 - "github.com/go-git/go-billy/v5/util" 10 - fixtures "github.com/go-git/go-git-fixtures/v4" 9 + fixtures "github.com/go-git/go-git-fixtures/v5" 11 10 "github.com/go-git/go-git/v5" 12 11 "github.com/go-git/go-git/v5/plumbing" 13 12 "github.com/go-git/go-git/v5/plumbing/cache" 14 13 "github.com/go-git/go-git/v5/plumbing/format/packfile" 15 14 "github.com/go-git/go-git/v5/plumbing/storer" 16 15 "github.com/go-git/go-git/v5/storage/filesystem" 17 - . "gopkg.in/check.v1" 16 + "github.com/go-git/go-git/v5/storage/memory" 17 + "github.com/stretchr/testify/assert" 18 18 ) 19 19 20 - type ParserSuite struct { 21 - fixtures.Suite 22 - } 20 + func TestParserHashes(t *testing.T) { 21 + tests := []struct { 22 + name string 23 + storage storer.Storer 24 + }{ 25 + { 26 + name: "without storage", 27 + }, 28 + { 29 + name: "with storage", 30 + storage: filesystem.NewStorage(osfs.New(t.TempDir()), cache.NewObjectLRUDefault()), 31 + }, 32 + } 23 33 24 - var _ = Suite(&ParserSuite{}) 34 + for _, tc := range tests { 35 + t.Run(tc.name, func(t *testing.T) { 36 + f := fixtures.Basic().One() 25 37 26 - func (s *ParserSuite) TestParserHashes(c *C) { 27 - f := fixtures.Basic().One() 28 - scanner := packfile.NewScanner(f.Packfile()) 38 + obs := new(testObserver) 39 + parser := packfile.NewParser(f.Packfile(), packfile.WithScannerObservers(obs), 40 + packfile.WithStorage(tc.storage)) 29 41 30 - obs := new(testObserver) 31 - parser, err := packfile.NewParser(scanner, obs) 32 - c.Assert(err, IsNil) 42 + commit := plumbing.CommitObject 43 + blob := plumbing.BlobObject 44 + tree := plumbing.TreeObject 33 45 34 - ch, err := parser.Parse() 35 - c.Assert(err, IsNil) 46 + objs := []observerObject{ 47 + {hash: "e8d3ffab552895c19b9fcf7aa264d277cde33881", otype: commit, size: 254, offset: 12, crc: 0xaa07ba4b}, 48 + {hash: "918c48b83bd081e863dbe1b80f8998f058cd8294", otype: commit, size: 242, offset: 286, crc: 0x12438846}, 49 + {hash: "af2d6a6954d532f8ffb47615169c8fdf9d383a1a", otype: commit, size: 242, offset: 449, crc: 0x2905a38c}, 50 + {hash: "1669dce138d9b841a518c64b10914d88f5e488ea", otype: commit, size: 333, offset: 615, crc: 0xd9429436}, 51 + {hash: "a5b8b09e2f8fcb0bb99d3ccb0958157b40890d69", otype: commit, size: 332, offset: 838, crc: 0xbecfde4e}, 52 + {hash: "35e85108805c84807bc66a02d91535e1e24b38b9", otype: commit, size: 244, offset: 1063, crc: 0x780e4b3e}, 53 + {hash: "b8e471f58bcbca63b07bda20e428190409c2db47", otype: commit, size: 243, offset: 1230, crc: 0xdc18344f}, 54 + {hash: "b029517f6300c2da0f4b651b8642506cd6aaf45d", otype: commit, size: 187, offset: 1392, crc: 0xcf4e4280}, 55 + {hash: "32858aad3c383ed1ff0a0f9bdf231d54a00c9e88", otype: blob, size: 189, offset: 1524, crc: 0x1f08118a}, 56 + {hash: "d3ff53e0564a9f87d8e84b6e28e5060e517008aa", otype: blob, size: 18, offset: 1685, crc: 0xafded7b8}, 57 + {hash: "c192bd6a24ea1ab01d78686e417c8bdc7c3d197f", otype: blob, size: 1072, offset: 1713, crc: 0xcc1428ed}, 58 + {hash: "d5c0f4ab811897cadf03aec358ae60d21f91c50d", otype: blob, size: 76110, offset: 2351, crc: 0x1631d22f}, 59 + {hash: "880cd14280f4b9b6ed3986d6671f907d7cc2a198", otype: blob, size: 2780, offset: 78050, crc: 0xbfff5850}, 60 + {hash: "49c6bb89b17060d7b4deacb7b338fcc6ea2352a9", otype: blob, size: 217848, offset: 78882, crc: 0xd108e1d8}, 61 + {hash: "c8f1d8c61f9da76f4cb49fd86322b6e685dba956", otype: blob, size: 706, offset: 80725, crc: 0x8e97ba25}, 62 + {hash: "9a48f23120e880dfbe41f7c9b7b708e9ee62a492", otype: blob, size: 11488, offset: 80998, crc: 0x7316ff70}, 63 + {hash: "9dea2395f5403188298c1dabe8bdafe562c491e3", otype: blob, size: 78, offset: 84032, crc: 0xdb4fce56}, 64 + {hash: "dbd3641b371024f44d0e469a9c8f5457b0660de1", otype: tree, size: 272, offset: 84115, crc: 0x901cce2c}, 65 + {hash: "a39771a7651f97faf5c72e08224d857fc35133db", otype: tree, size: 38, offset: 84430, crc: 0x847905bf}, 66 + {hash: "5a877e6a906a2743ad6e45d99c1793642aaf8eda", otype: tree, size: 75, offset: 84479, crc: 0x3689459a}, 67 + {hash: "586af567d0bb5e771e49bdd9434f5e0fb76d25fa", otype: tree, size: 38, offset: 84559, crc: 0xe67af94a}, 68 + {hash: "cf4aa3b38974fb7d81f367c0830f7d78d65ab86b", otype: tree, size: 34, offset: 84608, crc: 0xc2314a2e}, 69 + {hash: "7e59600739c96546163833214c36459e324bad0a", otype: blob, size: 9, offset: 84653, crc: 0xcd987848}, 70 + {hash: "6ecf0ef2c2dffb796033e5a02219af86ec6584e5", otype: commit, size: 245, offset: 186, crc: 0xf706df58}, 71 + {hash: "a8d315b2b1c615d43042c3a62402b8a54288cf5c", otype: tree, size: 271, offset: 84375, crc: 0xec4552b0}, 72 + {hash: "fb72698cab7617ac416264415f13224dfd7a165e", otype: tree, size: 238, offset: 84671, crc: 0x8a853a6d}, 73 + {hash: "4d081c50e250fa32ea8b1313cf8bb7c2ad7627fd", otype: tree, size: 179, offset: 84688, crc: 0x70c6518}, 74 + {hash: "eba74343e2f15d62adedfd8c883ee0262b5c8021", otype: tree, size: 148, offset: 84708, crc: 0x4f4108e2}, 75 + {hash: "c2d30fa8ef288618f65f6eed6e168e0d514886f4", otype: tree, size: 110, offset: 84725, crc: 0xd6fe09e9}, 76 + {hash: "8dcef98b1d52143e1e2dbc458ffe38f925786bf2", otype: tree, size: 111, offset: 84741, crc: 0xf07a2804}, 77 + {hash: "aa9b383c260e1d05fbbf6b30a02914555e20c725", otype: tree, size: 73, offset: 84760, crc: 0x1d75d6be}, 78 + } 36 79 37 - checksum := "a3fed42da1e8189a077c0e6846c040dcf73fc9dd" 38 - c.Assert(ch.String(), Equals, checksum) 80 + _, err := parser.Parse() 81 + assert.NoError(t, err) 39 82 40 - c.Assert(obs.checksum, Equals, checksum) 41 - c.Assert(int(obs.count), Equals, int(31)) 42 - 43 - commit := plumbing.CommitObject 44 - blob := plumbing.BlobObject 45 - tree := plumbing.TreeObject 46 - 47 - objs := []observerObject{ 48 - {"e8d3ffab552895c19b9fcf7aa264d277cde33881", commit, 254, 12, 0xaa07ba4b}, 49 - {"6ecf0ef2c2dffb796033e5a02219af86ec6584e5", commit, 245, 186, 0xf706df58}, 50 - {"918c48b83bd081e863dbe1b80f8998f058cd8294", commit, 242, 286, 0x12438846}, 51 - {"af2d6a6954d532f8ffb47615169c8fdf9d383a1a", commit, 242, 449, 0x2905a38c}, 52 - {"1669dce138d9b841a518c64b10914d88f5e488ea", commit, 333, 615, 0xd9429436}, 53 - {"a5b8b09e2f8fcb0bb99d3ccb0958157b40890d69", commit, 332, 838, 0xbecfde4e}, 54 - {"35e85108805c84807bc66a02d91535e1e24b38b9", commit, 244, 1063, 0x780e4b3e}, 55 - {"b8e471f58bcbca63b07bda20e428190409c2db47", commit, 243, 1230, 0xdc18344f}, 56 - {"b029517f6300c2da0f4b651b8642506cd6aaf45d", commit, 187, 1392, 0xcf4e4280}, 57 - {"32858aad3c383ed1ff0a0f9bdf231d54a00c9e88", blob, 189, 1524, 0x1f08118a}, 58 - {"d3ff53e0564a9f87d8e84b6e28e5060e517008aa", blob, 18, 1685, 0xafded7b8}, 59 - {"c192bd6a24ea1ab01d78686e417c8bdc7c3d197f", blob, 1072, 1713, 0xcc1428ed}, 60 - {"d5c0f4ab811897cadf03aec358ae60d21f91c50d", blob, 76110, 2351, 0x1631d22f}, 61 - {"880cd14280f4b9b6ed3986d6671f907d7cc2a198", blob, 2780, 78050, 0xbfff5850}, 62 - {"49c6bb89b17060d7b4deacb7b338fcc6ea2352a9", blob, 217848, 78882, 0xd108e1d8}, 63 - {"c8f1d8c61f9da76f4cb49fd86322b6e685dba956", blob, 706, 80725, 0x8e97ba25}, 64 - {"9a48f23120e880dfbe41f7c9b7b708e9ee62a492", blob, 11488, 80998, 0x7316ff70}, 65 - {"9dea2395f5403188298c1dabe8bdafe562c491e3", blob, 78, 84032, 0xdb4fce56}, 66 - {"dbd3641b371024f44d0e469a9c8f5457b0660de1", tree, 272, 84115, 0x901cce2c}, 67 - {"a8d315b2b1c615d43042c3a62402b8a54288cf5c", tree, 271, 84375, 0xec4552b0}, 68 - {"a39771a7651f97faf5c72e08224d857fc35133db", tree, 38, 84430, 0x847905bf}, 69 - {"5a877e6a906a2743ad6e45d99c1793642aaf8eda", tree, 75, 84479, 0x3689459a}, 70 - {"586af567d0bb5e771e49bdd9434f5e0fb76d25fa", tree, 38, 84559, 0xe67af94a}, 71 - {"cf4aa3b38974fb7d81f367c0830f7d78d65ab86b", tree, 34, 84608, 0xc2314a2e}, 72 - {"7e59600739c96546163833214c36459e324bad0a", blob, 9, 84653, 0xcd987848}, 73 - {"fb72698cab7617ac416264415f13224dfd7a165e", tree, 238, 84671, 0x8a853a6d}, 74 - {"4d081c50e250fa32ea8b1313cf8bb7c2ad7627fd", tree, 179, 84688, 0x70c6518}, 75 - {"eba74343e2f15d62adedfd8c883ee0262b5c8021", tree, 148, 84708, 0x4f4108e2}, 76 - {"c2d30fa8ef288618f65f6eed6e168e0d514886f4", tree, 110, 84725, 0xd6fe09e9}, 77 - {"8dcef98b1d52143e1e2dbc458ffe38f925786bf2", tree, 111, 84741, 0xf07a2804}, 78 - {"aa9b383c260e1d05fbbf6b30a02914555e20c725", tree, 73, 84760, 0x1d75d6be}, 83 + assert.Equal(t, "a3fed42da1e8189a077c0e6846c040dcf73fc9dd", obs.checksum) 84 + assert.Equal(t, objs, obs.objects) 85 + }) 79 86 } 80 - 81 - c.Assert(obs.objects, DeepEquals, objs) 82 87 } 83 88 84 - func (s *ParserSuite) TestThinPack(c *C) { 85 - fs := osfs.New(os.TempDir()) 86 - path, err := util.TempDir(fs, "", "") 87 - c.Assert(err, IsNil) 88 - 89 + func TestThinPack(t *testing.T) { 89 90 // Initialize an empty repository 90 - r, err := git.PlainInit(path, true) 91 - c.Assert(err, IsNil) 91 + r, err := git.PlainInit(t.TempDir(), true) 92 + assert.NoError(t, err) 92 93 93 94 // Try to parse a thin pack without having the required objects in the repo to 94 95 // see if the correct errors are returned 95 96 thinpack := fixtures.ByTag("thinpack").One() 96 - scanner := packfile.NewScanner(thinpack.Packfile()) 97 - parser, err := packfile.NewParserWithStorage(scanner, r.Storer) // ParserWithStorage writes to the storer all parsed objects! 98 - c.Assert(err, IsNil) 97 + parser := packfile.NewParser(thinpack.Packfile(), packfile.WithStorage(r.Storer)) // ParserWithStorage writes to the storer all parsed objects! 98 + assert.NoError(t, err) 99 99 100 100 _, err = parser.Parse() 101 - c.Assert(err, Equals, plumbing.ErrObjectNotFound) 102 - 103 - path, err = util.TempDir(fs, "", "") 104 - c.Assert(err, IsNil) 101 + assert.Equal(t, err, plumbing.ErrObjectNotFound) 105 102 106 103 // start over with a clean repo 107 - r, err = git.PlainInit(path, true) 108 - c.Assert(err, IsNil) 104 + r, err = git.PlainInit(t.TempDir(), true) 105 + assert.NoError(t, err) 109 106 110 107 // Now unpack a base packfile into our empty repo: 111 108 f := fixtures.ByURL("https://github.com/spinnaker/spinnaker.git").One() 112 109 w, err := r.Storer.(storer.PackfileWriter).PackfileWriter() 113 - c.Assert(err, IsNil) 110 + assert.NoError(t, err) 114 111 _, err = io.Copy(w, f.Packfile()) 115 - c.Assert(err, IsNil) 112 + assert.NoError(t, err) 116 113 w.Close() 117 114 118 115 // Check that the test object that will come with our thin pack is *not* in the repo 119 116 _, err = r.Storer.EncodedObject(plumbing.CommitObject, plumbing.NewHash(thinpack.Head)) 120 - c.Assert(err, Equals, plumbing.ErrObjectNotFound) 117 + assert.ErrorIs(t, err, plumbing.ErrObjectNotFound) 121 118 122 119 // Now unpack the thin pack: 123 - scanner = packfile.NewScanner(thinpack.Packfile()) 124 - parser, err = packfile.NewParserWithStorage(scanner, r.Storer) // ParserWithStorage writes to the storer all parsed objects! 125 - c.Assert(err, IsNil) 120 + parser = packfile.NewParser(thinpack.Packfile(), packfile.WithStorage(r.Storer)) // ParserWithStorage writes to the storer all parsed objects! 126 121 127 122 h, err := parser.Parse() 128 - c.Assert(err, IsNil) 129 - c.Assert(h, Equals, plumbing.NewHash("1288734cbe0b95892e663221d94b95de1f5d7be8")) 123 + assert.NoError(t, err) 124 + assert.Equal(t, plumbing.NewHash("1288734cbe0b95892e663221d94b95de1f5d7be8"), h) 130 125 131 126 // Check that our test object is now accessible 132 127 _, err = r.Storer.EncodedObject(plumbing.CommitObject, plumbing.NewHash(thinpack.Head)) 133 - c.Assert(err, IsNil) 128 + assert.NoError(t, err) 129 + } 130 + 131 + func TestResolveExternalRefsInThinPack(t *testing.T) { 132 + extRefsThinPack := fixtures.ByTag("codecommit").One().Packfile() 133 + 134 + parser := packfile.NewParser(extRefsThinPack) 135 + 136 + checksum, err := parser.Parse() 137 + assert.NoError(t, err) 138 + assert.NotEqual(t, plumbing.ZeroHash, checksum) 139 + } 140 + 141 + func TestResolveExternalRefs(t *testing.T) { 142 + extRefsThinPack := fixtures.ByTag("delta-before-base").One().Packfile() 134 143 144 + parser := packfile.NewParser(extRefsThinPack) 145 + 146 + checksum, err := parser.Parse() 147 + assert.NoError(t, err) 148 + assert.NotEqual(t, plumbing.ZeroHash, checksum) 135 149 } 136 150 137 - func (s *ParserSuite) TestResolveExternalRefsInThinPack(c *C) { 138 - extRefsThinPack := fixtures.ByTag("codecommit").One() 151 + func TestMemoryResolveExternalRefs(t *testing.T) { 152 + extRefsThinPack := fixtures.ByTag("delta-before-base").One().Packfile() 153 + 154 + parser := packfile.NewParser(extRefsThinPack, packfile.WithStorage(memory.NewStorage())) 139 155 140 - scanner := packfile.NewScanner(extRefsThinPack.Packfile()) 156 + checksum, err := parser.Parse() 157 + assert.NoError(t, err) 158 + assert.NotEqual(t, plumbing.ZeroHash, checksum) 159 + } 141 160 142 - obs := new(testObserver) 143 - parser, err := packfile.NewParser(scanner, obs) 144 - c.Assert(err, IsNil) 161 + func BenchmarkParseBasic(b *testing.B) { 162 + f := fixtures.Basic().One().Packfile() 163 + scanner := packfile.NewScanner(f) 164 + storage := filesystem.NewStorage(osfs.New(b.TempDir()), cache.NewObjectLRUDefault()) 145 165 146 - _, err = parser.Parse() 147 - c.Assert(err, IsNil) 166 + b.Run("with storage", func(b *testing.B) { 167 + benchmarkParseBasic(b, f, scanner, packfile.WithStorage(storage)) 168 + }) 169 + b.Run("with memory storage", func(b *testing.B) { 170 + benchmarkParseBasic(b, f, scanner, packfile.WithStorage(memory.NewStorage())) 171 + }) 172 + b.Run("without storage", func(b *testing.B) { 173 + benchmarkParseBasic(b, f, scanner) 174 + }) 148 175 } 149 176 150 - func (s *ParserSuite) TestResolveExternalRefs(c *C) { 151 - extRefsThinPack := fixtures.ByTag("delta-before-base").One() 177 + func benchmarkParseBasic(b *testing.B, 178 + f billy.File, scanner *packfile.Scanner, 179 + opts ...packfile.ParserOption) { 180 + for i := 0; i < b.N; i++ { 181 + f.Seek(0, io.SeekStart) 182 + scanner.Reset() 183 + parser := packfile.NewParser(scanner, opts...) 184 + 185 + checksum, err := parser.Parse() 186 + if err != nil { 187 + b.Fatal(err) 188 + } 152 189 153 - scanner := packfile.NewScanner(extRefsThinPack.Packfile()) 190 + if checksum == plumbing.ZeroHash { 191 + b.Fatal("failed to parse") 192 + } 193 + } 194 + } 154 195 155 - obs := new(testObserver) 156 - parser, err := packfile.NewParser(scanner, obs) 157 - c.Assert(err, IsNil) 196 + func BenchmarkParse(b *testing.B) { 197 + for _, f := range fixtures.ByTag("packfile") { 198 + scanner := packfile.NewScanner(f.Packfile()) 158 199 159 - _, err = parser.Parse() 160 - c.Assert(err, IsNil) 200 + b.Run(f.URL, func(b *testing.B) { 201 + benchmarkParseBasic(b, f.Packfile(), scanner) 202 + }) 203 + } 161 204 } 162 205 163 206 type observerObject struct { ··· 226 269 t.pos[pos] = len(t.objects) 227 270 t.objects = append(t.objects, o) 228 271 } 229 - 230 - func BenchmarkParse(b *testing.B) { 231 - defer fixtures.Clean() 232 - 233 - for _, f := range fixtures.ByTag("packfile") { 234 - b.Run(f.URL, func(b *testing.B) { 235 - for i := 0; i < b.N; i++ { 236 - parser, err := packfile.NewParser(packfile.NewScanner(f.Packfile())) 237 - if err != nil { 238 - b.Fatal(err) 239 - } 240 - 241 - _, err = parser.Parse() 242 - if err != nil { 243 - b.Fatal(err) 244 - } 245 - } 246 - }) 247 - } 248 - } 249 - 250 - func BenchmarkParseBasic(b *testing.B) { 251 - defer fixtures.Clean() 252 - 253 - f := fixtures.Basic().One() 254 - for i := 0; i < b.N; i++ { 255 - parser, err := packfile.NewParser(packfile.NewScanner(f.Packfile())) 256 - if err != nil { 257 - b.Fatal(err) 258 - } 259 - 260 - _, err = parser.Parse() 261 - if err != nil { 262 - b.Fatal(err) 263 - } 264 - } 265 - } 266 - 267 - func BenchmarkParser(b *testing.B) { 268 - f := fixtures.Basic().One() 269 - defer fixtures.Clean() 270 - 271 - b.ResetTimer() 272 - for n := 0; n < b.N; n++ { 273 - b.StopTimer() 274 - scanner := packfile.NewScanner(f.Packfile()) 275 - fs := osfs.New(os.TempDir()) 276 - storage := filesystem.NewStorage(fs, cache.NewObjectLRUDefault()) 277 - 278 - parser, err := packfile.NewParserWithStorage(scanner, storage) 279 - if err != nil { 280 - b.Error(err) 281 - } 282 - 283 - b.StartTimer() 284 - _, err = parser.Parse() 285 - 286 - b.StopTimer() 287 - if err != nil { 288 - b.Error(err) 289 - } 290 - } 291 - }
+19
plumbing/format/packfile/parser_types.go
··· 1 + package packfile 2 + 3 + import ( 4 + "github.com/go-git/go-git/v5/plumbing" 5 + ) 6 + 7 + // Observer interface is implemented by index encoders. 8 + type Observer interface { 9 + // OnHeader is called when a new packfile is opened. 10 + OnHeader(count uint32) error 11 + // OnInflatedObjectHeader is called for each object header read. 12 + OnInflatedObjectHeader(t plumbing.ObjectType, objSize int64, pos int64) error 13 + // OnInflatedObjectContent is called for each decoded object. 14 + OnInflatedObjectContent(h plumbing.Hash, pos int64, crc uint32, content []byte) error 15 + // OnFooter is called when decoding is done. 16 + OnFooter(h plumbing.Hash) error 17 + } 18 + 19 + type objectHeaderWriter func(typ plumbing.ObjectType, sz int64) error
+359 -334
plumbing/format/packfile/scanner.go
··· 1 1 package packfile 2 2 3 3 import ( 4 - "bufio" 5 4 "bytes" 5 + "encoding/hex" 6 6 "fmt" 7 7 "hash" 8 8 "hash/crc32" 9 9 "io" 10 + "sync" 10 11 11 12 "github.com/go-git/go-git/v5/plumbing" 13 + gogithash "github.com/go-git/go-git/v5/plumbing/hash" 14 + "github.com/go-git/go-git/v5/plumbing/storer" 12 15 "github.com/go-git/go-git/v5/utils/binary" 13 - "github.com/go-git/go-git/v5/utils/ioutil" 14 - "github.com/go-git/go-git/v5/utils/sync" 16 + gogitsync "github.com/go-git/go-git/v5/utils/sync" 15 17 ) 16 18 17 19 var ( 18 - // ErrEmptyPackfile is returned by ReadHeader when no data is found in the packfile 20 + // ErrEmptyPackfile is returned by ReadHeader when no data is found in the packfile. 19 21 ErrEmptyPackfile = NewError("empty packfile") 20 22 // ErrBadSignature is returned by ReadHeader when the signature in the packfile is incorrect. 21 23 ErrBadSignature = NewError("malformed pack file signature") 24 + // ErrMalformedPackfile is returned when the packfile format is incorrect. 25 + ErrMalformedPackfile = NewError("malformed pack file") 22 26 // ErrUnsupportedVersion is returned by ReadHeader when the packfile version is 23 27 // different than VersionSupported. 24 28 ErrUnsupportedVersion = NewError("unsupported packfile version") 25 - // ErrSeekNotSupported returned if seek is not support 29 + // ErrSeekNotSupported returned if seek is not support. 26 30 ErrSeekNotSupported = NewError("not seek support") 27 31 ) 28 32 29 - // ObjectHeader contains the information related to the object, this information 30 - // is collected from the previous bytes to the content of the object. 31 - type ObjectHeader struct { 32 - Type plumbing.ObjectType 33 - Offset int64 34 - Length int64 35 - Reference plumbing.Hash 36 - OffsetReference int64 37 - } 38 - 33 + // Scanner provides sequential access to the data stored in a Git packfile. 34 + // 35 + // A Git packfile is a compressed binary format that stores multiple Git objects, 36 + // such as commits, trees, delta objects and blobs. These packfiles are used to 37 + // reduce the size of data when transferring or storing Git repositories. 38 + // 39 + // A Git packfile is structured as follows: 40 + // 41 + // +----------------------------------------------------+ 42 + // | PACK File Header | 43 + // +----------------------------------------------------+ 44 + // | "PACK" | Version Number | Number of Objects | 45 + // | (4 bytes) | (4 bytes) | (4 bytes) | 46 + // +----------------------------------------------------+ 47 + // | Object Entry #1 | 48 + // +----------------------------------------------------+ 49 + // | Object Header | Compressed Object Data / Delta | 50 + // | (type + size) | (var-length, zlib compressed) | 51 + // +----------------------------------------------------+ 52 + // | ... | 53 + // +----------------------------------------------------+ 54 + // | PACK File Footer | 55 + // +----------------------------------------------------+ 56 + // | SHA-1 Checksum (20 bytes) | 57 + // +----------------------------------------------------+ 58 + // 59 + // For upstream docs, refer to https://git-scm.com/docs/gitformat-pack. 39 60 type Scanner struct { 40 - r *scannerReader 61 + // version holds the packfile version. 62 + version Version 63 + // objects holds the quantiy of objects within the packfile. 64 + objects uint32 65 + // objIndex is the current index when going through the packfile objects. 66 + objIndex int 67 + // hasher is used to hash non-delta objects. 68 + hasher plumbing.Hasher 69 + // hasher256 is optional and used to hash the non-delta objects using SHA256. 70 + hasher256 *plumbing.Hasher256 71 + // crc is used to generate the CRC-32 checksum of each object's content. 41 72 crc hash.Hash32 73 + // packhash hashes the pack contents so that at the end it is able to 74 + // validate the packfile's footer checksum against the calculated hash. 75 + packhash gogithash.Hash 42 76 43 - // pendingObject is used to detect if an object has been read, or still 44 - // is waiting to be read 45 - pendingObject *ObjectHeader 46 - version, objects uint32 77 + // next holds what state function should be executed on the next 78 + // call to Scan(). 79 + nextFn stateFn 80 + // packData holds the data for the last successful call to Scan(). 81 + packData PackData 82 + // err holds the first error that occurred. 83 + err error 47 84 48 - // lsSeekable says if this scanner can do Seek or not, to have a Scanner 49 - // seekable a r implementing io.Seeker is required 50 - IsSeekable bool 51 - } 85 + m sync.Mutex 52 86 53 - // NewScanner returns a new Scanner based on a reader, if the given reader 54 - // implements io.ReadSeeker the Scanner will be also Seekable 55 - func NewScanner(r io.Reader) *Scanner { 56 - _, ok := r.(io.ReadSeeker) 87 + // storage is optional, and when set is used to store full objects found. 88 + // Note that delta objects are not stored. 89 + storage storer.EncodedObjectStorer 57 90 58 - crc := crc32.NewIEEE() 59 - return &Scanner{ 60 - r: newScannerReader(r, crc), 61 - crc: crc, 62 - IsSeekable: ok, 63 - } 91 + *scannerReader 92 + zr gogitsync.ZLibReader 93 + buf bytes.Buffer 64 94 } 65 95 66 - func (s *Scanner) Reset(r io.Reader) { 67 - _, ok := r.(io.ReadSeeker) 96 + // NewScanner creates a new instance of Scanner. 97 + func NewScanner(rs io.Reader, opts ...ScannerOption) *Scanner { 98 + dict := make([]byte, 16*1024) 99 + crc := crc32.NewIEEE() 100 + packhash := gogithash.New(gogithash.CryptoType) 68 101 69 - s.r.Reset(r) 70 - s.crc.Reset() 71 - s.IsSeekable = ok 72 - s.pendingObject = nil 73 - s.version = 0 74 - s.objects = 0 75 - } 102 + r := &Scanner{ 103 + scannerReader: newScannerReader(rs, io.MultiWriter(crc, packhash)), 104 + zr: gogitsync.NewZlibReader(&dict), 105 + objIndex: -1, 106 + hasher: plumbing.NewHasher(plumbing.AnyObject, 0), 107 + crc: crc, 108 + packhash: packhash, 109 + nextFn: packHeaderSignature, 110 + } 76 111 77 - // Header reads the whole packfile header (signature, version and object count). 78 - // It returns the version and the object count and performs checks on the 79 - // validity of the signature and the version fields. 80 - func (s *Scanner) Header() (version, objects uint32, err error) { 81 - if s.version != 0 { 82 - return s.version, s.objects, nil 112 + for _, opt := range opts { 113 + opt(r) 83 114 } 84 115 85 - sig, err := s.readSignature() 86 - if err != nil { 87 - if err == io.EOF { 88 - err = ErrEmptyPackfile 89 - } 116 + return r 117 + } 90 118 91 - return 92 - } 119 + // Scan scans a Packfile sequently. Each call will navigate from a section 120 + // to the next, until the entire file is read. 121 + // 122 + // The section data can be accessed via calls to Data(). Example: 123 + // 124 + // for scanner.Scan() { 125 + // v := scanner.Data().Value() 126 + // 127 + // switch scanner.Data().Section { 128 + // case HeaderSection: 129 + // header := v.(Header) 130 + // fmt.Println("[Header] Objects Qty:", header.ObjectsQty) 131 + // case ObjectSection: 132 + // oh := v.(ObjectHeader) 133 + // fmt.Println("[Object] Object Type:", oh.Type) 134 + // case FooterSection: 135 + // checksum := v.(plumbing.Hash) 136 + // fmt.Println("[Footer] Checksum:", checksum) 137 + // } 138 + // } 139 + func (r *Scanner) Scan() bool { 140 + r.m.Lock() 141 + defer r.m.Unlock() 93 142 94 - if !s.isValidSignature(sig) { 95 - err = ErrBadSignature 96 - return 143 + if r.err != nil || r.nextFn == nil { 144 + return false 97 145 } 98 146 99 - version, err = s.readVersion() 100 - s.version = version 101 - if err != nil { 102 - return 147 + if err := scan(r); err != nil { 148 + r.err = err 149 + return false 103 150 } 104 151 105 - if !s.isSupportedVersion(version) { 106 - err = ErrUnsupportedVersion.AddDetails("%d", version) 107 - return 108 - } 109 - 110 - objects, err = s.readCount() 111 - s.objects = objects 112 - return 152 + return true 113 153 } 114 154 115 - // readSignature reads a returns the signature field in the packfile. 116 - func (s *Scanner) readSignature() ([]byte, error) { 117 - var sig = make([]byte, 4) 118 - if _, err := io.ReadFull(s.r, sig); err != nil { 119 - return []byte{}, err 120 - } 155 + // Reset resets the current scanner, enabling it to be used to scan the 156 + // same Packfile again. 157 + func (r *Scanner) Reset() { 158 + r.scannerReader.Flush() 159 + r.scannerReader.Seek(0, io.SeekStart) 160 + r.packhash.Reset() 121 161 122 - return sig, nil 162 + r.objIndex = -1 163 + r.version = 0 164 + r.objects = 0 165 + r.packData = PackData{} 166 + r.err = nil 167 + r.nextFn = packHeaderSignature 123 168 } 124 169 125 - // isValidSignature returns if sig is a valid packfile signature. 126 - func (s *Scanner) isValidSignature(sig []byte) bool { 127 - return bytes.Equal(sig, signature) 170 + // Data returns the pack data based on the last call to Scan(). 171 + func (r *Scanner) Data() PackData { 172 + return r.packData 128 173 } 129 174 130 - // readVersion reads and returns the version field of a packfile. 131 - func (s *Scanner) readVersion() (uint32, error) { 132 - return binary.ReadUint32(s.r) 175 + // Data returns the first error that occurred on the last call to Scan(). 176 + // Once an error occurs, calls to Scan() becomes a no-op. 177 + func (r *Scanner) Error() error { 178 + return r.err 133 179 } 134 180 135 - // isSupportedVersion returns whether version v is supported by the parser. 136 - // The current supported version is VersionSupported, defined above. 137 - func (s *Scanner) isSupportedVersion(v uint32) bool { 138 - return v == VersionSupported 139 - } 181 + func (r *Scanner) SeekFromStart(offset int64) error { 182 + r.Reset() 140 183 141 - // readCount reads and returns the count of objects field of a packfile. 142 - func (s *Scanner) readCount() (uint32, error) { 143 - return binary.ReadUint32(s.r) 144 - } 145 - 146 - // SeekObjectHeader seeks to specified offset and returns the ObjectHeader 147 - // for the next object in the reader 148 - func (s *Scanner) SeekObjectHeader(offset int64) (*ObjectHeader, error) { 149 - // if seeking we assume that you are not interested in the header 150 - if s.version == 0 { 151 - s.version = VersionSupported 184 + if !r.Scan() { 185 + return fmt.Errorf("failed to reset and read header") 152 186 } 153 187 154 - if _, err := s.r.Seek(offset, io.SeekStart); err != nil { 155 - return nil, err 156 - } 188 + _, err := r.scannerReader.Seek(offset, io.SeekStart) 189 + return err 190 + } 157 191 158 - h, err := s.nextObjectHeader() 159 - if err != nil { 160 - return nil, err 192 + func (s *Scanner) WriteObject(oh *ObjectHeader, writer io.Writer) error { 193 + if oh.content.Len() > 0 { 194 + _, err := io.Copy(writer, bytes.NewReader(oh.content.Bytes())) 195 + return err 161 196 } 162 197 163 - h.Offset = offset 164 - return h, nil 165 - } 166 - 167 - // NextObjectHeader returns the ObjectHeader for the next object in the reader 168 - func (s *Scanner) NextObjectHeader() (*ObjectHeader, error) { 169 - if err := s.doPending(); err != nil { 170 - return nil, err 198 + // If the oh is not an external ref and we don't have the 199 + // content offset, we won't be able to inflate via seeking through 200 + // the packfile. 201 + if oh.externalRef && oh.ContentOffset == 0 { 202 + return plumbing.ErrObjectNotFound 171 203 } 172 204 173 - offset, err := s.r.Seek(0, io.SeekCurrent) 174 - if err != nil { 175 - return nil, err 205 + // Not a seeker data source. 206 + if s.seeker == nil { 207 + return plumbing.ErrObjectNotFound 176 208 } 177 209 178 - h, err := s.nextObjectHeader() 210 + err := s.inflateContent(oh.ContentOffset, writer) 179 211 if err != nil { 180 - return nil, err 212 + return ErrReferenceDeltaNotFound 181 213 } 182 214 183 - h.Offset = offset 184 - return h, nil 215 + return nil 185 216 } 186 217 187 - // nextObjectHeader returns the ObjectHeader for the next object in the reader 188 - // without the Offset field 189 - func (s *Scanner) nextObjectHeader() (*ObjectHeader, error) { 190 - s.r.Flush() 191 - s.crc.Reset() 192 - 193 - h := &ObjectHeader{} 194 - s.pendingObject = h 195 - 196 - var err error 197 - h.Offset, err = s.r.Seek(0, io.SeekCurrent) 218 + func (s *Scanner) inflateContent(contentOffset int64, writer io.Writer) error { 219 + _, err := s.scannerReader.Seek(contentOffset, io.SeekStart) 198 220 if err != nil { 199 - return nil, err 221 + return err 200 222 } 201 223 202 - h.Type, h.Length, err = s.readObjectTypeAndLength() 224 + err = s.zr.Reset(s.scannerReader) 203 225 if err != nil { 204 - return nil, err 226 + return fmt.Errorf("zlib reset error: %s", err) 205 227 } 206 228 207 - switch h.Type { 208 - case plumbing.OFSDeltaObject: 209 - no, err := binary.ReadVariableWidthInt(s.r) 210 - if err != nil { 211 - return nil, err 212 - } 213 - 214 - h.OffsetReference = h.Offset - no 215 - case plumbing.REFDeltaObject: 216 - var err error 217 - h.Reference, err = binary.ReadHash(s.r) 218 - if err != nil { 219 - return nil, err 220 - } 229 + _, err = io.Copy(writer, s.zr.Reader) 230 + if err != nil { 231 + return err 221 232 } 222 233 223 - return h, nil 234 + return nil 224 235 } 225 236 226 - func (s *Scanner) doPending() error { 227 - if s.version == 0 { 228 - var err error 229 - s.version, s.objects, err = s.Header() 237 + // scan goes through the next stateFn. 238 + // 239 + // State functions are chained by returning a non-nil value for stateFn. 240 + // In such cases, the returned stateFn will be called immediately after 241 + // the current func. 242 + func scan(r *Scanner) error { 243 + var err error 244 + for state := r.nextFn; state != nil; { 245 + state, err = state(r) 230 246 if err != nil { 231 247 return err 232 248 } 233 249 } 234 - 235 - return s.discardObjectIfNeeded() 250 + return nil 236 251 } 237 252 238 - func (s *Scanner) discardObjectIfNeeded() error { 239 - if s.pendingObject == nil { 240 - return nil 241 - } 253 + // stateFn defines each individual state within the state machine that 254 + // represents a packfile. 255 + type stateFn func(*Scanner) (stateFn, error) 242 256 243 - h := s.pendingObject 244 - n, _, err := s.NextObject(io.Discard) 257 + // packHeaderSignature validates the packfile's header signature and 258 + // returns [ErrBadSignature] if the value provided is invalid. 259 + // 260 + // This is always the first state of a packfile and starts the chain 261 + // that handles the entire packfile header. 262 + func packHeaderSignature(r *Scanner) (stateFn, error) { 263 + start := make([]byte, 4) 264 + _, err := r.Read(start) 245 265 if err != nil { 246 - return err 266 + return nil, fmt.Errorf("%w: %w", ErrBadSignature, err) 247 267 } 248 268 249 - if n != h.Length { 250 - return fmt.Errorf( 251 - "error discarding object, discarded %d, expected %d", 252 - n, h.Length, 253 - ) 269 + if bytes.Equal(start, signature) { 270 + return packVersion, nil 254 271 } 255 272 256 - return nil 273 + return nil, ErrBadSignature 257 274 } 258 275 259 - // ReadObjectTypeAndLength reads and returns the object type and the 260 - // length field from an object entry in a packfile. 261 - func (s *Scanner) readObjectTypeAndLength() (plumbing.ObjectType, int64, error) { 262 - t, c, err := s.readType() 276 + // packVersion parses the packfile version. It returns [ErrMalformedPackfile] 277 + // when the version cannot be parsed. If a valid version is parsed, but it is 278 + // not currently supported, it returns [ErrUnsupportedVersion] instead. 279 + func packVersion(r *Scanner) (stateFn, error) { 280 + version, err := binary.ReadUint32(r.scannerReader) 263 281 if err != nil { 264 - return t, 0, err 282 + return nil, fmt.Errorf("%w: cannot read version", ErrMalformedPackfile) 265 283 } 266 284 267 - l, err := s.readLength(c) 285 + v := Version(version) 286 + if !v.Supported() { 287 + return nil, ErrUnsupportedVersion 288 + } 268 289 269 - return t, l, err 290 + r.version = v 291 + return packObjectsQty, nil 270 292 } 271 293 272 - func (s *Scanner) readType() (plumbing.ObjectType, byte, error) { 273 - var c byte 274 - var err error 275 - if c, err = s.r.ReadByte(); err != nil { 276 - return plumbing.ObjectType(0), 0, err 294 + // packObjectsQty parses the quantity of objects that the packfile contains. 295 + // If the value cannot be parsed, [ErrMalformedPackfile] is returned. 296 + // 297 + // This state ends the packfile header chain. 298 + func packObjectsQty(r *Scanner) (stateFn, error) { 299 + qty, err := binary.ReadUint32(r.scannerReader) 300 + if err != nil { 301 + return nil, fmt.Errorf("%w: cannot read number of objects", ErrMalformedPackfile) 302 + } 303 + if qty == 0 { 304 + return packFooter, nil 277 305 } 278 306 279 - typ := parseType(c) 307 + r.objects = qty 308 + r.packData = PackData{ 309 + Section: HeaderSection, 310 + header: Header{Version: r.version, ObjectsQty: r.objects}, 311 + } 312 + r.nextFn = objectEntry 280 313 281 - return typ, c, nil 282 - } 283 - 284 - func parseType(b byte) plumbing.ObjectType { 285 - return plumbing.ObjectType((b & maskType) >> firstLengthBits) 314 + return nil, nil 286 315 } 287 316 288 - // the length is codified in the last 4 bits of the first byte and in 289 - // the last 7 bits of subsequent bytes. Last byte has a 0 MSB. 290 - func (s *Scanner) readLength(first byte) (int64, error) { 291 - length := int64(first & maskFirstLength) 292 - 293 - c := first 294 - shift := firstLengthBits 295 - var err error 296 - for c&maskContinue > 0 { 297 - if c, err = s.r.ReadByte(); err != nil { 298 - return 0, err 299 - } 300 - 301 - length += int64(c&maskLength) << shift 302 - shift += lengthBits 317 + // objectEntry handles the object entries within a packfile. This is generally 318 + // split between object headers and their contents. 319 + // 320 + // The object header contains the object type and size. If the type cannot be parsed, 321 + // [ErrMalformedPackfile] is returned. 322 + // 323 + // When SHA256 is enabled, the scanner will also calculate the SHA256 for each object. 324 + func objectEntry(r *Scanner) (stateFn, error) { 325 + if r.objIndex+1 >= int(r.objects) { 326 + return packFooter, nil 303 327 } 328 + r.objIndex++ 304 329 305 - return length, nil 306 - } 330 + offset := r.scannerReader.offset 307 331 308 - // NextObject writes the content of the next object into the reader, returns 309 - // the number of bytes written, the CRC32 of the content and an error, if any 310 - func (s *Scanner) NextObject(w io.Writer) (written int64, crc32 uint32, err error) { 311 - s.pendingObject = nil 312 - written, err = s.copyObject(w) 332 + r.scannerReader.Flush() 333 + r.crc.Reset() 313 334 314 - s.r.Flush() 315 - crc32 = s.crc.Sum32() 316 - s.crc.Reset() 317 - 318 - return 319 - } 320 - 321 - // ReadObject returns a reader for the object content and an error 322 - func (s *Scanner) ReadObject() (io.ReadCloser, error) { 323 - s.pendingObject = nil 324 - zr, err := sync.GetZlibReader(s.r) 325 - 335 + b := []byte{0} 336 + _, err := r.Read(b) 326 337 if err != nil { 327 - return nil, fmt.Errorf("zlib reset error: %s", err) 338 + return nil, err 328 339 } 329 340 330 - return ioutil.NewReadCloserWithCloser(zr.Reader, func() error { 331 - sync.PutZlibReader(zr) 332 - return nil 333 - }), nil 334 - } 341 + typ := parseType(b[0]) 342 + if !typ.Valid() { 343 + return nil, fmt.Errorf("%w: invalid object type: %v", ErrMalformedPackfile, b[0]) 344 + } 335 345 336 - // ReadRegularObject reads and write a non-deltified object 337 - // from it zlib stream in an object entry in the packfile. 338 - func (s *Scanner) copyObject(w io.Writer) (n int64, err error) { 339 - zr, err := sync.GetZlibReader(s.r) 340 - defer sync.PutZlibReader(zr) 341 - 346 + size, err := readVariableLengthSize(b[0], r) 342 347 if err != nil { 343 - return 0, fmt.Errorf("zlib reset error: %s", err) 348 + return nil, err 344 349 } 345 350 346 - defer ioutil.CheckClose(zr.Reader, &err) 347 - buf := sync.GetByteSlice() 348 - n, err = io.CopyBuffer(w, zr.Reader, *buf) 349 - sync.PutByteSlice(buf) 350 - return 351 - } 352 - 353 - // SeekFromStart sets a new offset from start, returns the old position before 354 - // the change. 355 - func (s *Scanner) SeekFromStart(offset int64) (previous int64, err error) { 356 - // if seeking we assume that you are not interested in the header 357 - if s.version == 0 { 358 - s.version = VersionSupported 351 + oh := ObjectHeader{ 352 + Offset: offset, 353 + Type: typ, 354 + diskType: typ, 355 + Size: int64(size), 359 356 } 360 357 361 - previous, err = s.r.Seek(0, io.SeekCurrent) 362 - if err != nil { 363 - return -1, err 358 + switch oh.Type { 359 + case plumbing.OFSDeltaObject, plumbing.REFDeltaObject: 360 + // For delta objects, we need to skip the base reference 361 + if oh.Type == plumbing.OFSDeltaObject { 362 + no, err := binary.ReadVariableWidthInt(r.scannerReader) 363 + if err != nil { 364 + return nil, err 365 + } 366 + oh.OffsetReference = oh.Offset - no 367 + } else { 368 + ref, err := binary.ReadHash(r.scannerReader) 369 + if err != nil { 370 + return nil, err 371 + } 372 + oh.Reference = ref 373 + } 364 374 } 365 375 366 - _, err = s.r.Seek(offset, io.SeekStart) 367 - return previous, err 368 - } 369 - 370 - // Checksum returns the checksum of the packfile 371 - func (s *Scanner) Checksum() (plumbing.Hash, error) { 372 - err := s.discardObjectIfNeeded() 376 + oh.ContentOffset = r.scannerReader.offset 377 + err = r.zr.Reset(r.scannerReader) 373 378 if err != nil { 374 - return plumbing.ZeroHash, err 379 + return nil, fmt.Errorf("zlib reset error: %s", err) 375 380 } 376 381 377 - return binary.ReadHash(s.r) 378 - } 382 + if !oh.Type.IsDelta() { 383 + r.hasher.Reset(oh.Type, oh.Size) 379 384 380 - // Close reads the reader until io.EOF 381 - func (s *Scanner) Close() error { 382 - buf := sync.GetByteSlice() 383 - _, err := io.CopyBuffer(io.Discard, s.r, *buf) 384 - sync.PutByteSlice(buf) 385 + var mw io.Writer = r.hasher 386 + if r.storage != nil { 387 + w, err := r.storage.RawObjectWriter(oh.Type, oh.Size) 388 + if err != nil { 389 + return nil, err 390 + } 385 391 386 - return err 387 - } 392 + defer w.Close() 393 + mw = io.MultiWriter(r.hasher, w) 394 + } 388 395 389 - // Flush is a no-op (deprecated) 390 - func (s *Scanner) Flush() error { 391 - return nil 392 - } 396 + if r.hasher256 != nil { 397 + r.hasher256.Reset(oh.Type, oh.Size) 398 + mw = io.MultiWriter(mw, r.hasher256) 399 + } 393 400 394 - // scannerReader has the following characteristics: 395 - // - Provides an io.SeekReader impl for bufio.Reader, when the underlying 396 - // reader supports it. 397 - // - Keeps track of the current read position, for when the underlying reader 398 - // isn't an io.SeekReader, but we still want to know the current offset. 399 - // - Writes to the hash writer what it reads, with the aid of a smaller buffer. 400 - // The buffer helps avoid a performance penalty for performing small writes 401 - // to the crc32 hash writer. 402 - type scannerReader struct { 403 - reader io.Reader 404 - crc io.Writer 405 - rbuf *bufio.Reader 406 - wbuf *bufio.Writer 407 - offset int64 408 - } 401 + // For non delta objects, simply calculate the hash of each object. 402 + _, err = io.CopyBuffer(mw, r.zr.Reader, r.buf.Bytes()) 403 + if err != nil { 404 + return nil, err 405 + } 409 406 410 - func newScannerReader(r io.Reader, h io.Writer) *scannerReader { 411 - sr := &scannerReader{ 412 - rbuf: bufio.NewReader(nil), 413 - wbuf: bufio.NewWriterSize(nil, 64), 414 - crc: h, 407 + oh.Hash = r.hasher.Sum() 408 + if r.hasher256 != nil { 409 + h := r.hasher256.Sum() 410 + oh.Hash256 = &h 411 + } 412 + } else { 413 + // If data source is not io.Seeker, keep the content 414 + // in the cache, so that it can be accessed by the Parser. 415 + if r.scannerReader.seeker == nil { 416 + _, err = oh.content.ReadFrom(r.zr.Reader) 417 + if err != nil { 418 + return nil, err 419 + } 420 + } else { 421 + // We don't know the compressed length, so we can't seek to 422 + // the next object, we must discard the data instead. 423 + _, err = io.Copy(io.Discard, r.zr.Reader) 424 + if err != nil { 425 + return nil, err 426 + } 427 + } 415 428 } 416 - sr.Reset(r) 429 + r.scannerReader.Flush() 430 + oh.Crc32 = r.crc.Sum32() 431 + 432 + r.packData.Section = ObjectSection 433 + r.packData.objectHeader = oh 417 434 418 - return sr 435 + return nil, nil 419 436 } 420 437 421 - func (r *scannerReader) Reset(reader io.Reader) { 422 - r.reader = reader 423 - r.rbuf.Reset(r.reader) 424 - r.wbuf.Reset(r.crc) 438 + // packFooter parses the packfile checksum. 439 + // If the checksum cannot be parsed, or it does not match the checksum 440 + // calculated during the scanning process, an [ErrMalformedPackfile] is 441 + // returned. 442 + func packFooter(r *Scanner) (stateFn, error) { 443 + r.scannerReader.Flush() 444 + actual := r.packhash.Sum(nil) 425 445 426 - r.offset = 0 427 - if seeker, ok := r.reader.(io.ReadSeeker); ok { 428 - r.offset, _ = seeker.Seek(0, io.SeekCurrent) 446 + checksum, err := binary.ReadHash(r.scannerReader) 447 + if err != nil { 448 + return nil, fmt.Errorf("cannot read PACK checksum: %w", ErrMalformedPackfile) 429 449 } 430 - } 431 450 432 - func (r *scannerReader) Read(p []byte) (n int, err error) { 433 - n, err = r.rbuf.Read(p) 434 - 435 - r.offset += int64(n) 436 - if _, err := r.wbuf.Write(p[:n]); err != nil { 437 - return n, err 451 + if !bytes.Equal(actual, checksum[:]) { 452 + return nil, fmt.Errorf("checksum mismatch expected %q but found %q: %w", 453 + hex.EncodeToString(actual), checksum, ErrMalformedPackfile) 438 454 } 439 - return 440 - } 441 455 442 - func (r *scannerReader) ReadByte() (b byte, err error) { 443 - b, err = r.rbuf.ReadByte() 444 - if err == nil { 445 - r.offset++ 446 - return b, r.wbuf.WriteByte(b) 447 - } 448 - return 449 - } 456 + r.packData.Section = FooterSection 457 + r.packData.checksum = checksum 458 + r.nextFn = nil 450 459 451 - func (r *scannerReader) Flush() error { 452 - return r.wbuf.Flush() 460 + return nil, nil 453 461 } 454 462 455 - // Seek seeks to a location. If the underlying reader is not an io.ReadSeeker, 456 - // then only whence=io.SeekCurrent is supported, any other operation fails. 457 - func (r *scannerReader) Seek(offset int64, whence int) (int64, error) { 458 - var err error 463 + func readVariableLengthSize(first byte, reader io.ByteReader) (uint64, error) { 464 + // Extract the first part of the size (last 3 bits of the first byte). 465 + size := uint64(first & 0x0F) 459 466 460 - if seeker, ok := r.reader.(io.ReadSeeker); !ok { 461 - if whence != io.SeekCurrent || offset != 0 { 462 - return -1, ErrSeekNotSupported 467 + // | 001xxxx | xxxxxxxx | xxxxxxxx | ... 468 + // 469 + // ^^^ ^^^^^^^^ ^^^^^^^^ 470 + // Type Size Part 1 Size Part 2 471 + // 472 + // Check if more bytes are needed to fully determine the size. 473 + if first&maskContinue != 0 { 474 + shift := uint(4) 475 + 476 + for { 477 + b, err := reader.ReadByte() 478 + if err != nil { 479 + return 0, err 480 + } 481 + 482 + // Add the next 7 bits to the size. 483 + size |= uint64(b&0x7F) << shift 484 + 485 + // Check if the continuation bit is set. 486 + if b&maskContinue == 0 { 487 + break 488 + } 489 + 490 + // Prepare for the next byte. 491 + shift += 7 463 492 } 464 - } else { 465 - if whence == io.SeekCurrent && offset == 0 { 466 - return r.offset, nil 467 - } 468 - 469 - r.offset, err = seeker.Seek(offset, whence) 470 - r.rbuf.Reset(r.reader) 471 493 } 494 + return size, nil 495 + } 472 496 473 - return r.offset, err 497 + func parseType(b byte) plumbing.ObjectType { 498 + return plumbing.ObjectType((b & maskType) >> firstLengthBits) 474 499 }
+13
plumbing/format/packfile/scanner_options.go
··· 1 + package packfile 2 + 3 + import "github.com/go-git/go-git/v5/plumbing" 4 + 5 + type ScannerOption func(*Scanner) 6 + 7 + // WithSHA256 enables the SHA256 hashing while scanning a pack file. 8 + func WithSHA256() ScannerOption { 9 + return func(s *Scanner) { 10 + h := plumbing.NewHasher256(plumbing.AnyObject, 0) 11 + s.hasher256 = &h 12 + } 13 + }
+99
plumbing/format/packfile/scanner_reader.go
··· 1 + package packfile 2 + 3 + import ( 4 + "bufio" 5 + "io" 6 + ) 7 + 8 + // scannerReader has the following characteristics: 9 + // - Provides an io.SeekReader impl for bufio.Reader, when the underlying 10 + // reader supports it. 11 + // - Keeps track of the current read position, for when the underlying reader 12 + // isn't an io.SeekReader, but we still want to know the current offset. 13 + // - Writes to the hash writer what it reads, with the aid of a smaller buffer. 14 + // The buffer helps avoid a performance penalty for performing small writes 15 + // to the crc32 hash writer. 16 + // 17 + // Note that this is passed on to zlib, and it mmust support io.BytesReader, else 18 + // it won't be able to just read the content of the current object, but rather it 19 + // will read the entire packfile. 20 + // 21 + // scannerReader is not thread-safe. 22 + type scannerReader struct { 23 + reader io.Reader 24 + crc io.Writer 25 + rbuf *bufio.Reader 26 + wbuf *bufio.Writer 27 + offset int64 28 + seeker io.Seeker 29 + } 30 + 31 + func newScannerReader(r io.Reader, h io.Writer) *scannerReader { 32 + sr := &scannerReader{ 33 + rbuf: bufio.NewReader(nil), 34 + wbuf: bufio.NewWriterSize(nil, 64), 35 + crc: h, 36 + } 37 + sr.Reset(r) 38 + 39 + return sr 40 + } 41 + 42 + func (r *scannerReader) Reset(reader io.Reader) { 43 + r.reader = reader 44 + r.rbuf.Reset(r.reader) 45 + r.wbuf.Reset(r.crc) 46 + 47 + r.offset = 0 48 + 49 + seeker, ok := r.reader.(io.ReadSeeker) 50 + r.seeker = seeker 51 + 52 + if ok { 53 + r.offset, _ = seeker.Seek(0, io.SeekCurrent) 54 + } 55 + } 56 + 57 + func (r *scannerReader) Read(p []byte) (n int, err error) { 58 + n, err = r.rbuf.Read(p) 59 + 60 + r.offset += int64(n) 61 + if _, err := r.wbuf.Write(p[:n]); err != nil { 62 + return n, err 63 + } 64 + return 65 + } 66 + 67 + func (r *scannerReader) ReadByte() (b byte, err error) { 68 + b, err = r.rbuf.ReadByte() 69 + if err == nil { 70 + r.offset++ 71 + return b, r.wbuf.WriteByte(b) 72 + } 73 + return 74 + } 75 + 76 + func (r *scannerReader) Flush() error { 77 + return r.wbuf.Flush() 78 + } 79 + 80 + // Seek seeks to a location. If the underlying reader is not an io.ReadSeeker, 81 + // then only whence=io.SeekCurrent is supported, any other operation fails. 82 + func (r *scannerReader) Seek(offset int64, whence int) (int64, error) { 83 + var err error 84 + 85 + if r.seeker == nil { 86 + if whence != io.SeekCurrent || offset != 0 { 87 + return -1, ErrSeekNotSupported 88 + } 89 + } 90 + 91 + if whence == io.SeekCurrent && offset == 0 { 92 + return r.offset, nil 93 + } 94 + 95 + r.offset, err = r.seeker.Seek(offset, whence) 96 + r.rbuf.Reset(r.reader) 97 + 98 + return r.offset, err 99 + }
+388 -208
plumbing/format/packfile/scanner_test.go
··· 2 2 3 3 import ( 4 4 "bytes" 5 + "encoding/binary" 5 6 "io" 7 + "reflect" 8 + "runtime" 9 + "testing" 6 10 7 - fixtures "github.com/go-git/go-git-fixtures/v4" 11 + "github.com/go-git/go-billy/v5" 12 + fixtures "github.com/go-git/go-git-fixtures/v5" 8 13 "github.com/go-git/go-git/v5/plumbing" 9 - "github.com/go-git/go-git/v5/plumbing/hash" 10 - 11 - . "gopkg.in/check.v1" 14 + "github.com/stretchr/testify/assert" 12 15 ) 13 16 14 - type ScannerSuite struct { 15 - fixtures.Suite 16 - } 17 + func TestScan(t *testing.T) { 18 + tests := []struct { 19 + name string 20 + packfile billy.File 21 + sha256 bool 22 + want []ObjectHeader 23 + wantCrc []uint32 24 + wantChecksum string 25 + }{ 26 + { 27 + name: "ofs", 28 + packfile: fixtures.Basic().One().Packfile(), 29 + want: expectedHeadersOFS256, 30 + wantCrc: expectedCRCOFS, 31 + wantChecksum: "a3fed42da1e8189a077c0e6846c040dcf73fc9dd", 32 + }, 33 + { 34 + name: "ofs sha256", 35 + packfile: fixtures.Basic().One().Packfile(), 36 + sha256: true, 37 + want: expectedHeadersOFS256, 38 + wantCrc: expectedCRCOFS, 39 + wantChecksum: "a3fed42da1e8189a077c0e6846c040dcf73fc9dd", 40 + }, 41 + { 42 + name: "refs", 43 + packfile: fixtures.Basic().ByTag("ref-delta").One().Packfile(), 44 + want: expectedHeadersREF, 45 + wantCrc: expectedCRCREF, 46 + wantChecksum: "c544593473465e6315ad4182d04d366c4592b829", 47 + }, 48 + } 17 49 18 - var _ = Suite(&ScannerSuite{}) 50 + for _, tc := range tests { 51 + t.Run(tc.name, func(t *testing.T) { 52 + var opts []ScannerOption 19 53 20 - func (s *ScannerSuite) TestHeader(c *C) { 21 - r := fixtures.Basic().One().Packfile() 22 - p := NewScanner(r) 54 + if tc.sha256 { 55 + opts = append(opts, WithSHA256()) 56 + } 23 57 24 - version, objects, err := p.Header() 25 - c.Assert(err, IsNil) 26 - c.Assert(version, Equals, VersionSupported) 27 - c.Assert(objects, Equals, uint32(31)) 28 - } 58 + s := NewScanner(tc.packfile, opts...) 59 + i := 0 29 60 30 - func (s *ScannerSuite) TestNextObjectHeaderWithoutHeader(c *C) { 31 - r := fixtures.Basic().One().Packfile() 32 - p := NewScanner(r) 61 + for s.Scan() { 62 + data := s.Data() 63 + v := data.Value() 33 64 34 - h, err := p.NextObjectHeader() 35 - c.Assert(err, IsNil) 36 - c.Assert(h, DeepEquals, &expectedHeadersOFS[0]) 65 + switch data.Section { 66 + case HeaderSection: 67 + gotHeader := v.(Header) 68 + assert.Equal(t, 0, i, "wrong index") 69 + assert.Equal(t, Version(2), gotHeader.Version) 70 + assert.Equal(t, uint32(len(tc.want)), gotHeader.ObjectsQty) 71 + case ObjectSection: 72 + index := i - 1 37 73 38 - version, objects, err := p.Header() 39 - c.Assert(err, IsNil) 40 - c.Assert(version, Equals, VersionSupported) 41 - c.Assert(objects, Equals, uint32(31)) 42 - } 74 + oh := v.(ObjectHeader) 75 + oo := tc.want[index] 76 + assert.Equal(t, oo.Type, oh.Type, "type mismatch index: %d", index) 77 + assert.Equal(t, oo.Offset, oh.Offset, "offset mismatch index: %d", index) 78 + assert.Equal(t, oo.Size, oh.Size, "size mismatch index: %d", index) 79 + assert.Equal(t, oo.Reference, oh.Reference, "reference mismatch index: %d", index) 80 + assert.Equal(t, oo.OffsetReference, oh.OffsetReference, "offset reference mismatch index: %d", index) 81 + assert.Equal(t, oo.Hash.String(), oh.Hash.String(), "hash mismatch index: %d", index) 82 + if tc.sha256 && !oo.Type.IsDelta() { 83 + assert.Equal(t, oo.Hash256.String(), oh.Hash256.String(), "hash mismatch index: %d", index) 84 + } 85 + assert.Equal(t, tc.wantCrc[index], oh.Crc32, "crc mismatch index: %d", index) 86 + case FooterSection: 87 + checksum := v.(plumbing.Hash) 88 + assert.Equal(t, tc.wantChecksum, checksum.String()) 89 + } 90 + i++ 91 + } 43 92 44 - func (s *ScannerSuite) TestNextObjectHeaderREFDelta(c *C) { 45 - s.testNextObjectHeader(c, "ref-delta", expectedHeadersREF, expectedCRCREF) 46 - } 93 + err := s.Error() 94 + assert.NoError(t, err) 47 95 48 - func (s *ScannerSuite) TestNextObjectHeaderOFSDelta(c *C) { 49 - s.testNextObjectHeader(c, "ofs-delta", expectedHeadersOFS, expectedCRCOFS) 96 + // wanted objects + header + footer 97 + assert.Equal(t, len(tc.want)+2, i) 98 + }) 99 + } 50 100 } 51 101 52 - func (s *ScannerSuite) testNextObjectHeader(c *C, tag string, 53 - expected []ObjectHeader, expectedCRC []uint32) { 102 + func BenchmarkScannerBasic(b *testing.B) { 103 + f := fixtures.Basic().One().Packfile() 104 + scanner := NewScanner(f) 105 + for i := 0; i < b.N; i++ { 106 + scanner.Reset() 54 107 55 - r := fixtures.Basic().ByTag(tag).One().Packfile() 56 - p := NewScanner(r) 108 + for scanner.Scan() { 109 + } 57 110 58 - _, objects, err := p.Header() 59 - c.Assert(err, IsNil) 60 - 61 - for i := 0; i < int(objects); i++ { 62 - h, err := p.NextObjectHeader() 63 - c.Assert(err, IsNil) 64 - c.Assert(*h, DeepEquals, expected[i]) 65 - 66 - buf := bytes.NewBuffer(nil) 67 - n, crcFromScanner, err := p.NextObject(buf) 68 - c.Assert(err, IsNil) 69 - c.Assert(n, Equals, h.Length) 70 - c.Assert(crcFromScanner, Equals, expectedCRC[i]) 111 + err := scanner.Error() 112 + if err != nil { 113 + b.Fatal(err) 114 + } 71 115 } 72 - 73 - n, err := p.Checksum() 74 - c.Assert(err, IsNil) 75 - c.Assert(n, HasLen, hash.Size) 76 116 } 77 117 78 - func (s *ScannerSuite) TestNextObjectHeaderWithOutReadObject(c *C) { 79 - f := fixtures.Basic().ByTag("ref-delta").One() 80 - r := f.Packfile() 81 - p := NewScanner(r) 82 - 83 - _, objects, err := p.Header() 84 - c.Assert(err, IsNil) 85 - 86 - for i := 0; i < int(objects); i++ { 87 - h, _ := p.NextObjectHeader() 88 - c.Assert(err, IsNil) 89 - c.Assert(*h, DeepEquals, expectedHeadersREF[i]) 118 + func TestPackHeaderSignature(t *testing.T) { 119 + tests := []struct { 120 + name string 121 + scanner *Scanner 122 + nextState stateFn 123 + wantErr error 124 + }{ 125 + { 126 + name: "valid signature", 127 + scanner: &Scanner{ 128 + scannerReader: newScannerReader(bytes.NewReader([]byte("PACK")), nil), 129 + }, 130 + nextState: packVersion, 131 + }, 132 + { 133 + name: "invalid signature", 134 + scanner: &Scanner{ 135 + scannerReader: newScannerReader(bytes.NewReader([]byte("FOOBAR")), nil), 136 + }, 137 + wantErr: ErrBadSignature, 138 + }, 139 + { 140 + name: "invalid signature - too small", 141 + scanner: &Scanner{ 142 + scannerReader: newScannerReader(bytes.NewReader([]byte("FOO")), nil), 143 + }, 144 + wantErr: ErrBadSignature, 145 + }, 146 + { 147 + name: "empty packfile: io.EOF", 148 + scanner: &Scanner{ 149 + scannerReader: newScannerReader(bytes.NewReader(nil), nil), 150 + }, 151 + wantErr: io.EOF, 152 + }, 153 + { 154 + name: "empty packfile: ErrBadSignature", 155 + scanner: &Scanner{ 156 + scannerReader: newScannerReader(bytes.NewReader(nil), nil), 157 + }, 158 + wantErr: ErrBadSignature, 159 + }, 90 160 } 91 161 92 - err = p.discardObjectIfNeeded() 93 - c.Assert(err, IsNil) 162 + for _, tc := range tests { 163 + t.Run(tc.name, func(t *testing.T) { 164 + next, err := packHeaderSignature(tc.scanner) 94 165 95 - n, err := p.Checksum() 96 - c.Assert(err, IsNil) 97 - c.Assert(n.String(), Equals, f.PackfileHash) 98 - } 99 - 100 - func (s *ScannerSuite) TestNextObjectHeaderWithOutReadObjectNonSeekable(c *C) { 101 - f := fixtures.Basic().ByTag("ref-delta").One() 102 - r := io.MultiReader(f.Packfile()) 103 - p := NewScanner(r) 166 + if tc.wantErr == nil { 167 + assert.Equal(t, 168 + runtime.FuncForPC(reflect.ValueOf(tc.nextState).Pointer()).Name(), 169 + runtime.FuncForPC(reflect.ValueOf(next).Pointer()).Name()) 104 170 105 - _, objects, err := p.Header() 106 - c.Assert(err, IsNil) 107 - 108 - for i := 0; i < int(objects); i++ { 109 - h, _ := p.NextObjectHeader() 110 - c.Assert(err, IsNil) 111 - c.Assert(*h, DeepEquals, expectedHeadersREF[i]) 171 + assert.NoError(t, err) 172 + } else { 173 + assert.Nil(t, next) 174 + assert.ErrorIs(t, err, tc.wantErr) 175 + } 176 + }) 112 177 } 113 - 114 - err = p.discardObjectIfNeeded() 115 - c.Assert(err, IsNil) 116 - 117 - n, err := p.Checksum() 118 - c.Assert(err, IsNil) 119 - c.Assert(n.String(), Equals, f.PackfileHash) 120 178 } 121 179 122 - func (s *ScannerSuite) TestSeekObjectHeader(c *C) { 123 - r := fixtures.Basic().One().Packfile() 124 - p := NewScanner(r) 180 + func TestPackVersion(t *testing.T) { 181 + tests := []struct { 182 + name string 183 + scanner *Scanner 184 + version Version 185 + nextState stateFn 186 + wantErr error 187 + }{ 188 + { 189 + name: "Version 2", 190 + version: Version(2), 191 + scanner: &Scanner{ 192 + scannerReader: func() *scannerReader { 193 + buf := bytes.NewBuffer(make([]byte, 0, 4)) 194 + binary.Write(buf, binary.BigEndian, uint32(2)) 195 + return newScannerReader(buf, nil) 196 + }(), 197 + }, 198 + nextState: packObjectsQty, 199 + }, 200 + { 201 + name: "Version -1", 202 + scanner: &Scanner{ 203 + scannerReader: func() *scannerReader { 204 + buf := bytes.NewBuffer(make([]byte, 0, 4)) 205 + binary.Write(buf, binary.BigEndian, -1) 206 + return newScannerReader(buf, nil) 207 + }(), 208 + }, 209 + wantErr: ErrMalformedPackfile, 210 + }, 211 + { 212 + name: "Unsupported version", 213 + scanner: &Scanner{ 214 + scannerReader: func() *scannerReader { 215 + buf := bytes.NewBuffer(make([]byte, 0, 4)) 216 + binary.Write(buf, binary.BigEndian, uint32(3)) 217 + return newScannerReader(buf, nil) 218 + }(), 219 + }, 220 + wantErr: ErrUnsupportedVersion, 221 + }, 222 + { 223 + name: "empty packfile: ErrMalformedPackfile", 224 + scanner: &Scanner{ 225 + scannerReader: newScannerReader(bytes.NewReader(nil), nil), 226 + }, 227 + wantErr: ErrMalformedPackfile, 228 + }, 229 + } 125 230 126 - h, err := p.SeekObjectHeader(expectedHeadersOFS[4].Offset) 127 - c.Assert(err, IsNil) 128 - c.Assert(h, DeepEquals, &expectedHeadersOFS[4]) 129 - } 231 + for _, tc := range tests { 232 + t.Run(tc.name, func(t *testing.T) { 233 + next, err := packVersion(tc.scanner) 130 234 131 - func (s *ScannerSuite) TestSeekObjectHeaderNonSeekable(c *C) { 132 - r := io.MultiReader(fixtures.Basic().One().Packfile()) 133 - p := NewScanner(r) 235 + if tc.wantErr == nil { 236 + assert.Equal(t, 237 + runtime.FuncForPC(reflect.ValueOf(tc.nextState).Pointer()).Name(), 238 + runtime.FuncForPC(reflect.ValueOf(next).Pointer()).Name()) 134 239 135 - _, err := p.SeekObjectHeader(expectedHeadersOFS[4].Offset) 136 - c.Assert(err, Equals, ErrSeekNotSupported) 240 + assert.Equal(t, tc.version, tc.scanner.version) 241 + assert.NoError(t, err) 242 + } else { 243 + assert.Nil(t, next) 244 + assert.ErrorIs(t, err, tc.wantErr) 245 + } 246 + }) 247 + } 137 248 } 138 249 139 - func (s *ScannerSuite) TestReaderReset(c *C) { 140 - r := fixtures.Basic().One().Packfile() 141 - p := NewScanner(r) 250 + func TestPackObjectQty(t *testing.T) { 251 + tests := []struct { 252 + name string 253 + scanner *Scanner 254 + objects uint32 255 + nextState stateFn 256 + wantErr error 257 + }{ 258 + { 259 + name: "Zero", 260 + scanner: &Scanner{ 261 + scannerReader: func() *scannerReader { 262 + buf := bytes.NewBuffer(make([]byte, 0, 4)) 263 + binary.Write(buf, binary.BigEndian, uint32(0)) 264 + return newScannerReader(buf, nil) 265 + }(), 266 + }, 267 + nextState: packFooter, // if there are no objects, skip to footer. 268 + }, 269 + { 270 + name: "Valid number", 271 + scanner: &Scanner{ 272 + scannerReader: func() *scannerReader { 273 + buf := bytes.NewBuffer(make([]byte, 0, 4)) 274 + binary.Write(buf, binary.BigEndian, uint32(7)) 275 + return newScannerReader(buf, nil) 276 + }(), 277 + }, 278 + objects: 7, 279 + nextState: nil, 280 + }, 281 + { 282 + name: "less than 2 bytes on source", 283 + scanner: &Scanner{ 284 + scannerReader: func() *scannerReader { 285 + buf := bytes.NewBuffer(make([]byte, 0, 2)) 286 + return newScannerReader(buf, nil) 287 + }(), 288 + }, 289 + wantErr: ErrMalformedPackfile, 290 + }, 291 + { 292 + name: "empty packfile: ErrMalformedPackfile", 293 + scanner: &Scanner{ 294 + scannerReader: newScannerReader(bytes.NewReader(nil), nil), 295 + }, 296 + wantErr: ErrMalformedPackfile, 297 + }, 298 + } 142 299 143 - version, objects, err := p.Header() 144 - c.Assert(err, IsNil) 145 - c.Assert(version, Equals, VersionSupported) 146 - c.Assert(objects, Equals, uint32(31)) 300 + for _, tc := range tests { 301 + t.Run(tc.name, func(t *testing.T) { 302 + next, err := packObjectsQty(tc.scanner) 147 303 148 - h, err := p.SeekObjectHeader(expectedHeadersOFS[0].Offset) 149 - c.Assert(err, IsNil) 150 - c.Assert(h, DeepEquals, &expectedHeadersOFS[0]) 304 + if tc.wantErr == nil { 305 + assert.Equal(t, 306 + runtime.FuncForPC(reflect.ValueOf(tc.nextState).Pointer()).Name(), 307 + runtime.FuncForPC(reflect.ValueOf(next).Pointer()).Name()) 151 308 152 - p.Reset(r) 153 - c.Assert(p.pendingObject, IsNil) 154 - c.Assert(p.version, Equals, uint32(0)) 155 - c.Assert(p.objects, Equals, uint32(0)) 156 - c.Assert(p.r.reader, Equals, r) 157 - c.Assert(p.r.offset > expectedHeadersOFS[0].Offset, Equals, true) 158 - 159 - p.Reset(bytes.NewReader(nil)) 160 - c.Assert(p.r.offset, Equals, int64(0)) 309 + assert.Equal(t, tc.objects, tc.scanner.objects) 310 + assert.NoError(t, err) 311 + } else { 312 + assert.Nil(t, next) 313 + assert.ErrorIs(t, err, tc.wantErr) 314 + } 315 + }) 316 + } 161 317 } 162 318 163 - func (s *ScannerSuite) TestReaderResetSeeks(c *C) { 164 - r := fixtures.Basic().One().Packfile() 165 - 166 - // seekable 167 - p := NewScanner(r) 168 - c.Assert(p.IsSeekable, Equals, true) 169 - h, err := p.SeekObjectHeader(expectedHeadersOFS[0].Offset) 170 - c.Assert(err, IsNil) 171 - c.Assert(h, DeepEquals, &expectedHeadersOFS[0]) 172 - 173 - // reset with seekable 174 - p.Reset(r) 175 - c.Assert(p.IsSeekable, Equals, true) 176 - h, err = p.SeekObjectHeader(expectedHeadersOFS[1].Offset) 177 - c.Assert(err, IsNil) 178 - c.Assert(h, DeepEquals, &expectedHeadersOFS[1]) 179 - 180 - // reset with non-seekable 181 - f := fixtures.Basic().ByTag("ref-delta").One() 182 - p.Reset(io.MultiReader(f.Packfile())) 183 - c.Assert(p.IsSeekable, Equals, false) 184 - 185 - _, err = p.SeekObjectHeader(expectedHeadersOFS[4].Offset) 186 - c.Assert(err, Equals, ErrSeekNotSupported) 319 + func ptr[T any](value T) *T { 320 + return &value 187 321 } 188 322 189 - var expectedHeadersOFS = []ObjectHeader{ 190 - {Type: plumbing.CommitObject, Offset: 12, Length: 254}, 191 - {Type: plumbing.OFSDeltaObject, Offset: 186, Length: 93, OffsetReference: 12}, 192 - {Type: plumbing.CommitObject, Offset: 286, Length: 242}, 193 - {Type: plumbing.CommitObject, Offset: 449, Length: 242}, 194 - {Type: plumbing.CommitObject, Offset: 615, Length: 333}, 195 - {Type: plumbing.CommitObject, Offset: 838, Length: 332}, 196 - {Type: plumbing.CommitObject, Offset: 1063, Length: 244}, 197 - {Type: plumbing.CommitObject, Offset: 1230, Length: 243}, 198 - {Type: plumbing.CommitObject, Offset: 1392, Length: 187}, 199 - {Type: plumbing.BlobObject, Offset: 1524, Length: 189}, 200 - {Type: plumbing.BlobObject, Offset: 1685, Length: 18}, 201 - {Type: plumbing.BlobObject, Offset: 1713, Length: 1072}, 202 - {Type: plumbing.BlobObject, Offset: 2351, Length: 76110}, 203 - {Type: plumbing.BlobObject, Offset: 78050, Length: 2780}, 204 - {Type: plumbing.BlobObject, Offset: 78882, Length: 217848}, 205 - {Type: plumbing.BlobObject, Offset: 80725, Length: 706}, 206 - {Type: plumbing.BlobObject, Offset: 80998, Length: 11488}, 207 - {Type: plumbing.BlobObject, Offset: 84032, Length: 78}, 208 - {Type: plumbing.TreeObject, Offset: 84115, Length: 272}, 209 - {Type: plumbing.OFSDeltaObject, Offset: 84375, Length: 43, OffsetReference: 84115}, 210 - {Type: plumbing.TreeObject, Offset: 84430, Length: 38}, 211 - {Type: plumbing.TreeObject, Offset: 84479, Length: 75}, 212 - {Type: plumbing.TreeObject, Offset: 84559, Length: 38}, 213 - {Type: plumbing.TreeObject, Offset: 84608, Length: 34}, 214 - {Type: plumbing.BlobObject, Offset: 84653, Length: 9}, 215 - {Type: plumbing.OFSDeltaObject, Offset: 84671, Length: 6, OffsetReference: 84375}, 216 - {Type: plumbing.OFSDeltaObject, Offset: 84688, Length: 9, OffsetReference: 84375}, 217 - {Type: plumbing.OFSDeltaObject, Offset: 84708, Length: 6, OffsetReference: 84375}, 218 - {Type: plumbing.OFSDeltaObject, Offset: 84725, Length: 5, OffsetReference: 84115}, 219 - {Type: plumbing.OFSDeltaObject, Offset: 84741, Length: 8, OffsetReference: 84375}, 220 - {Type: plumbing.OFSDeltaObject, Offset: 84760, Length: 4, OffsetReference: 84741}, 323 + var expectedHeadersOFS256 = []ObjectHeader{ 324 + {Type: plumbing.CommitObject, Offset: 12, Size: 254, 325 + Hash: plumbing.NewHash("e8d3ffab552895c19b9fcf7aa264d277cde33881"), 326 + Hash256: ptr(plumbing.NewHash256("751ee7d8e2736460ea9b6f1b88aeb050dad7d7641b0313d27f0bb9bedd1b3726"))}, 327 + {Type: plumbing.OFSDeltaObject, Offset: 186, Size: 93, OffsetReference: 12}, 328 + {Type: plumbing.CommitObject, Offset: 286, Size: 242, 329 + Hash: plumbing.NewHash("918c48b83bd081e863dbe1b80f8998f058cd8294"), 330 + Hash256: ptr(plumbing.NewHash256("a279e860c7074462629fefb6a96e77eecb240eba291791c163581f6afeaa7f12"))}, 331 + {Type: plumbing.CommitObject, Offset: 449, Size: 242, 332 + Hash: plumbing.NewHash("af2d6a6954d532f8ffb47615169c8fdf9d383a1a"), 333 + Hash256: ptr(plumbing.NewHash256("aa68eba21ad1796f88c16e470e0374bf6ed1376495ab3a367cd85698c3df766f"))}, 334 + {Type: plumbing.CommitObject, Offset: 615, Size: 333, 335 + Hash: plumbing.NewHash("1669dce138d9b841a518c64b10914d88f5e488ea"), 336 + Hash256: ptr(plumbing.NewHash256("4d00acb62a3ecb5f3f6871aa29c8ea670fc3d27042842277280c6b3e48a206f1"))}, 337 + {Type: plumbing.CommitObject, Offset: 838, Size: 332, 338 + Hash: plumbing.NewHash("a5b8b09e2f8fcb0bb99d3ccb0958157b40890d69"), 339 + Hash256: ptr(plumbing.NewHash256("627852504dc677ba7ac2ec7717d69b42f787c8d79bac9fe1370b8775d2312e94"))}, 340 + {Type: plumbing.CommitObject, Offset: 1063, Size: 244, 341 + Hash: plumbing.NewHash("35e85108805c84807bc66a02d91535e1e24b38b9"), 342 + Hash256: ptr(plumbing.NewHash256("00f0a27f127cffbb2a1089b772edd3ba7c82a6b69d666048b75d4bdcee24515d"))}, 343 + {Type: plumbing.CommitObject, Offset: 1230, Size: 243, 344 + Hash: plumbing.NewHash("b8e471f58bcbca63b07bda20e428190409c2db47"), 345 + Hash256: ptr(plumbing.NewHash256("ef5441299e83e8707722706fefd89e77290a2a6e84be5202b980128eaa6decc2"))}, 346 + {Type: plumbing.CommitObject, Offset: 1392, Size: 187, 347 + Hash: plumbing.NewHash("b029517f6300c2da0f4b651b8642506cd6aaf45d"), 348 + Hash256: ptr(plumbing.NewHash256("809c0681b603794597ef162c71184b38dda79364a423c6c61d2e514a1d46efff"))}, 349 + {Type: plumbing.BlobObject, Offset: 1524, Size: 189, 350 + Hash: plumbing.NewHash("32858aad3c383ed1ff0a0f9bdf231d54a00c9e88"), 351 + Hash256: ptr(plumbing.NewHash256("40b7c05726c9da78c3d5a705c2a48a120261b36f521302ce06bad41916d000f7"))}, 352 + {Type: plumbing.BlobObject, Offset: 1685, Size: 18, 353 + Hash: plumbing.NewHash("d3ff53e0564a9f87d8e84b6e28e5060e517008aa"), 354 + Hash256: ptr(plumbing.NewHash256("e6ee53c7eb0e33417ee04110b84b304ff2da5c1b856f320b61ad9f2ef56c6e4e"))}, 355 + {Type: plumbing.BlobObject, Offset: 1713, Size: 1072, 356 + Hash: plumbing.NewHash("c192bd6a24ea1ab01d78686e417c8bdc7c3d197f"), 357 + Hash256: ptr(plumbing.NewHash256("789c9f4220d167b66020b46bacddcad0ab5bb12f0f469576aa60bb59d98293dc"))}, 358 + {Type: plumbing.BlobObject, Offset: 2351, Size: 76110, 359 + Hash: plumbing.NewHash("d5c0f4ab811897cadf03aec358ae60d21f91c50d"), 360 + Hash256: ptr(plumbing.NewHash256("665e33431d9b88280d7c1837680fdb66664c4cb4b394c9057cdbd07f3b4acff8"))}, 361 + {Type: plumbing.BlobObject, Offset: 78050, Size: 2780, 362 + Hash: plumbing.NewHash("880cd14280f4b9b6ed3986d6671f907d7cc2a198"), 363 + Hash256: ptr(plumbing.NewHash256("33a5013ed4af64b6e54076c986a4733c2c11ce8ab27ede79f21366e8722ac5ed"))}, 364 + {Type: plumbing.BlobObject, Offset: 78882, Size: 217848, 365 + Hash: plumbing.NewHash("49c6bb89b17060d7b4deacb7b338fcc6ea2352a9"), 366 + Hash256: ptr(plumbing.NewHash256("4c61794e77ff8c7ab7f07404cdb1bc0e989b27530e37a6be6d2ef73639aaff6d"))}, 367 + {Type: plumbing.BlobObject, Offset: 80725, Size: 706, 368 + Hash: plumbing.NewHash("c8f1d8c61f9da76f4cb49fd86322b6e685dba956"), 369 + Hash256: ptr(plumbing.NewHash256("2a246d3eaea67b7c4ac36d96d1dc9dad2a4dc24486c4d67eb7cb73963f522481"))}, 370 + {Type: plumbing.BlobObject, Offset: 80998, Size: 11488, 371 + Hash: plumbing.NewHash("9a48f23120e880dfbe41f7c9b7b708e9ee62a492"), 372 + Hash256: ptr(plumbing.NewHash256("73660d98a4c6c8951f86bb8c4744a0b4837a6dd5f796c314064c1615781c400c"))}, 373 + {Type: plumbing.BlobObject, Offset: 84032, Size: 78, 374 + Hash: plumbing.NewHash("9dea2395f5403188298c1dabe8bdafe562c491e3"), 375 + Hash256: ptr(plumbing.NewHash256("2a7543a59f760f7ca41784bc898057799ae960323733cab1175c21960a750f72"))}, 376 + {Type: plumbing.TreeObject, Offset: 84115, Size: 272, 377 + Hash: plumbing.NewHash("dbd3641b371024f44d0e469a9c8f5457b0660de1"), 378 + Hash256: ptr(plumbing.NewHash256("773b6c73238a74067c97f193c06c1bf38a982e39ded04fdf9c833ebc34cedd3d"))}, 379 + {Type: plumbing.OFSDeltaObject, Offset: 84375, Size: 43, OffsetReference: 84115}, 380 + {Type: plumbing.TreeObject, Offset: 84430, Size: 38, 381 + Hash: plumbing.NewHash("a39771a7651f97faf5c72e08224d857fc35133db"), 382 + Hash256: ptr(plumbing.NewHash256("166e4d7c5b5771422259dda0819ea54e06a6e4f07cf927d9fc95f5c370fff28a"))}, 383 + {Type: plumbing.TreeObject, Offset: 84479, Size: 75, 384 + Hash: plumbing.NewHash("5a877e6a906a2743ad6e45d99c1793642aaf8eda"), 385 + Hash256: ptr(plumbing.NewHash256("393e771684c98451b904457acffac4ca5bd5a736a1b9127cedf7b8fa1b6a9901"))}, 386 + {Type: plumbing.TreeObject, Offset: 84559, Size: 38, 387 + Hash: plumbing.NewHash("586af567d0bb5e771e49bdd9434f5e0fb76d25fa"), 388 + Hash256: ptr(plumbing.NewHash256("3db5b7f8353ebe6e4d4bff0bd2953952e08d73e72040abe4a46d08e7c3593dcc"))}, 389 + {Type: plumbing.TreeObject, Offset: 84608, Size: 34, 390 + Hash: plumbing.NewHash("cf4aa3b38974fb7d81f367c0830f7d78d65ab86b"), 391 + Hash256: ptr(plumbing.NewHash256("e39c8c3d47aa310861634c6cf44e54e847c02f99c34c8cb25246e16f40502a7e"))}, 392 + {Type: plumbing.BlobObject, Offset: 84653, Size: 9, 393 + Hash: plumbing.NewHash("7e59600739c96546163833214c36459e324bad0a"), 394 + Hash256: ptr(plumbing.NewHash256("1f307724f91af43be1570b77aeef69c5010e8136e50bef83c28de2918a08f494"))}, 395 + {Type: plumbing.OFSDeltaObject, Offset: 84671, Size: 6, OffsetReference: 84375}, 396 + {Type: plumbing.OFSDeltaObject, Offset: 84688, Size: 9, OffsetReference: 84375}, 397 + {Type: plumbing.OFSDeltaObject, Offset: 84708, Size: 6, OffsetReference: 84375}, 398 + {Type: plumbing.OFSDeltaObject, Offset: 84725, Size: 5, OffsetReference: 84115}, 399 + {Type: plumbing.OFSDeltaObject, Offset: 84741, Size: 8, OffsetReference: 84375}, 400 + {Type: plumbing.OFSDeltaObject, Offset: 84760, Size: 4, OffsetReference: 84741}, 221 401 } 222 402 223 403 var expectedCRCOFS = []uint32{ ··· 255 435 } 256 436 257 437 var expectedHeadersREF = []ObjectHeader{ 258 - {Type: plumbing.CommitObject, Offset: 12, Length: 254}, 259 - {Type: plumbing.REFDeltaObject, Offset: 186, Length: 93, 438 + {Type: plumbing.CommitObject, Offset: 12, Size: 254, Hash: plumbing.NewHash("e8d3ffab552895c19b9fcf7aa264d277cde33881")}, 439 + {Type: plumbing.REFDeltaObject, Offset: 186, Size: 93, 260 440 Reference: plumbing.NewHash("e8d3ffab552895c19b9fcf7aa264d277cde33881")}, 261 - {Type: plumbing.CommitObject, Offset: 304, Length: 242}, 262 - {Type: plumbing.CommitObject, Offset: 467, Length: 242}, 263 - {Type: plumbing.CommitObject, Offset: 633, Length: 333}, 264 - {Type: plumbing.CommitObject, Offset: 856, Length: 332}, 265 - {Type: plumbing.CommitObject, Offset: 1081, Length: 243}, 266 - {Type: plumbing.CommitObject, Offset: 1243, Length: 244}, 267 - {Type: plumbing.CommitObject, Offset: 1410, Length: 187}, 268 - {Type: plumbing.BlobObject, Offset: 1542, Length: 189}, 269 - {Type: plumbing.BlobObject, Offset: 1703, Length: 18}, 270 - {Type: plumbing.BlobObject, Offset: 1731, Length: 1072}, 271 - {Type: plumbing.BlobObject, Offset: 2369, Length: 76110}, 272 - {Type: plumbing.TreeObject, Offset: 78068, Length: 38}, 273 - {Type: plumbing.BlobObject, Offset: 78117, Length: 2780}, 274 - {Type: plumbing.TreeObject, Offset: 79049, Length: 75}, 275 - {Type: plumbing.BlobObject, Offset: 79129, Length: 217848}, 276 - {Type: plumbing.BlobObject, Offset: 80972, Length: 706}, 277 - {Type: plumbing.TreeObject, Offset: 81265, Length: 38}, 278 - {Type: plumbing.BlobObject, Offset: 81314, Length: 11488}, 279 - {Type: plumbing.TreeObject, Offset: 84752, Length: 34}, 280 - {Type: plumbing.BlobObject, Offset: 84797, Length: 78}, 281 - {Type: plumbing.TreeObject, Offset: 84880, Length: 271}, 282 - {Type: plumbing.REFDeltaObject, Offset: 85141, Length: 6, 441 + {Type: plumbing.CommitObject, Offset: 304, Size: 242, Hash: plumbing.NewHash("918c48b83bd081e863dbe1b80f8998f058cd8294")}, 442 + {Type: plumbing.CommitObject, Offset: 467, Size: 242, Hash: plumbing.NewHash("af2d6a6954d532f8ffb47615169c8fdf9d383a1a")}, 443 + {Type: plumbing.CommitObject, Offset: 633, Size: 333, Hash: plumbing.NewHash("1669dce138d9b841a518c64b10914d88f5e488ea")}, 444 + {Type: plumbing.CommitObject, Offset: 856, Size: 332, Hash: plumbing.NewHash("a5b8b09e2f8fcb0bb99d3ccb0958157b40890d69")}, 445 + {Type: plumbing.CommitObject, Offset: 1081, Size: 243, Hash: plumbing.NewHash("b8e471f58bcbca63b07bda20e428190409c2db47")}, 446 + {Type: plumbing.CommitObject, Offset: 1243, Size: 244, Hash: plumbing.NewHash("35e85108805c84807bc66a02d91535e1e24b38b9")}, 447 + {Type: plumbing.CommitObject, Offset: 1410, Size: 187, Hash: plumbing.NewHash("b029517f6300c2da0f4b651b8642506cd6aaf45d")}, 448 + {Type: plumbing.BlobObject, Offset: 1542, Size: 189, Hash: plumbing.NewHash("32858aad3c383ed1ff0a0f9bdf231d54a00c9e88")}, 449 + {Type: plumbing.BlobObject, Offset: 1703, Size: 18, Hash: plumbing.NewHash("d3ff53e0564a9f87d8e84b6e28e5060e517008aa")}, 450 + {Type: plumbing.BlobObject, Offset: 1731, Size: 1072, Hash: plumbing.NewHash("c192bd6a24ea1ab01d78686e417c8bdc7c3d197f")}, 451 + {Type: plumbing.BlobObject, Offset: 2369, Size: 76110, Hash: plumbing.NewHash("d5c0f4ab811897cadf03aec358ae60d21f91c50d")}, 452 + {Type: plumbing.TreeObject, Offset: 78068, Size: 38, Hash: plumbing.NewHash("a39771a7651f97faf5c72e08224d857fc35133db")}, 453 + {Type: plumbing.BlobObject, Offset: 78117, Size: 2780, Hash: plumbing.NewHash("880cd14280f4b9b6ed3986d6671f907d7cc2a198")}, 454 + {Type: plumbing.TreeObject, Offset: 79049, Size: 75, Hash: plumbing.NewHash("5a877e6a906a2743ad6e45d99c1793642aaf8eda")}, 455 + {Type: plumbing.BlobObject, Offset: 79129, Size: 217848, Hash: plumbing.NewHash("49c6bb89b17060d7b4deacb7b338fcc6ea2352a9")}, 456 + {Type: plumbing.BlobObject, Offset: 80972, Size: 706, Hash: plumbing.NewHash("c8f1d8c61f9da76f4cb49fd86322b6e685dba956")}, 457 + {Type: plumbing.TreeObject, Offset: 81265, Size: 38, Hash: plumbing.NewHash("586af567d0bb5e771e49bdd9434f5e0fb76d25fa")}, 458 + {Type: plumbing.BlobObject, Offset: 81314, Size: 11488, Hash: plumbing.NewHash("9a48f23120e880dfbe41f7c9b7b708e9ee62a492")}, 459 + {Type: plumbing.TreeObject, Offset: 84752, Size: 34, Hash: plumbing.NewHash("cf4aa3b38974fb7d81f367c0830f7d78d65ab86b")}, 460 + {Type: plumbing.BlobObject, Offset: 84797, Size: 78, Hash: plumbing.NewHash("9dea2395f5403188298c1dabe8bdafe562c491e3")}, 461 + {Type: plumbing.TreeObject, Offset: 84880, Size: 271, Hash: plumbing.NewHash("a8d315b2b1c615d43042c3a62402b8a54288cf5c")}, 462 + {Type: plumbing.REFDeltaObject, Offset: 85141, Size: 6, 283 463 Reference: plumbing.NewHash("a8d315b2b1c615d43042c3a62402b8a54288cf5c")}, 284 - {Type: plumbing.REFDeltaObject, Offset: 85176, Length: 37, 464 + {Type: plumbing.REFDeltaObject, Offset: 85176, Size: 37, 285 465 Reference: plumbing.NewHash("fb72698cab7617ac416264415f13224dfd7a165e")}, 286 - {Type: plumbing.BlobObject, Offset: 85244, Length: 9}, 287 - {Type: plumbing.REFDeltaObject, Offset: 85262, Length: 9, 466 + {Type: plumbing.BlobObject, Offset: 85244, Size: 9, Hash: plumbing.NewHash("7e59600739c96546163833214c36459e324bad0a")}, 467 + {Type: plumbing.REFDeltaObject, Offset: 85262, Size: 9, 288 468 Reference: plumbing.NewHash("fb72698cab7617ac416264415f13224dfd7a165e")}, 289 - {Type: plumbing.REFDeltaObject, Offset: 85300, Length: 6, 469 + {Type: plumbing.REFDeltaObject, Offset: 85300, Size: 6, 290 470 Reference: plumbing.NewHash("fb72698cab7617ac416264415f13224dfd7a165e")}, 291 - {Type: plumbing.TreeObject, Offset: 85335, Length: 110}, 292 - {Type: plumbing.REFDeltaObject, Offset: 85448, Length: 8, 471 + {Type: plumbing.TreeObject, Offset: 85335, Size: 110, Hash: plumbing.NewHash("c2d30fa8ef288618f65f6eed6e168e0d514886f4")}, 472 + {Type: plumbing.REFDeltaObject, Offset: 85448, Size: 8, 293 473 Reference: plumbing.NewHash("eba74343e2f15d62adedfd8c883ee0262b5c8021")}, 294 - {Type: plumbing.TreeObject, Offset: 85485, Length: 73}, 474 + {Type: plumbing.TreeObject, Offset: 85485, Size: 73, Hash: plumbing.NewHash("aa9b383c260e1d05fbbf6b30a02914555e20c725")}, 295 475 } 296 476 297 477 var expectedCRCREF = []uint32{
+74
plumbing/format/packfile/types.go
··· 1 + package packfile 2 + 3 + import ( 4 + "bytes" 5 + 6 + "github.com/go-git/go-git/v5/plumbing" 7 + ) 8 + 9 + type Version uint32 10 + 11 + const ( 12 + V2 Version = 2 13 + ) 14 + 15 + func (v Version) Supported() bool { 16 + switch v { 17 + case V2: 18 + return true 19 + default: 20 + return false 21 + } 22 + } 23 + 24 + // ObjectHeader contains the information related to the object, this information 25 + // is collected from the previous bytes to the content of the object. 26 + type ObjectHeader struct { 27 + Type plumbing.ObjectType 28 + Offset int64 29 + ContentOffset int64 30 + Size int64 31 + Reference plumbing.Hash 32 + OffsetReference int64 33 + Crc32 uint32 34 + Hash plumbing.Hash 35 + Hash256 *plumbing.Hash256 36 + 37 + content bytes.Buffer 38 + parent *ObjectHeader 39 + diskType plumbing.ObjectType 40 + externalRef bool 41 + } 42 + 43 + type SectionType int 44 + 45 + const ( 46 + HeaderSection SectionType = iota 47 + ObjectSection 48 + FooterSection 49 + ) 50 + 51 + type Header struct { 52 + Version Version 53 + ObjectsQty uint32 54 + } 55 + 56 + type PackData struct { 57 + Section SectionType 58 + header Header 59 + objectHeader ObjectHeader 60 + checksum plumbing.Hash 61 + } 62 + 63 + func (p PackData) Value() interface{} { 64 + switch p.Section { 65 + case HeaderSection: 66 + return p.header 67 + case ObjectSection: 68 + return p.objectHeader 69 + case FooterSection: 70 + return p.checksum 71 + default: 72 + return nil 73 + } 74 + }
+6 -1
plumbing/hash.go
··· 47 47 48 48 func NewHasher(t ObjectType, size int64) Hasher { 49 49 h := Hasher{hash.New(hash.CryptoType)} 50 + h.Reset(t, size) 51 + return h 52 + } 53 + 54 + func (h Hasher) Reset(t ObjectType, size int64) { 55 + h.Hash.Reset() 50 56 h.Write(t.Bytes()) 51 57 h.Write([]byte(" ")) 52 58 h.Write([]byte(strconv.FormatInt(size, 10))) 53 59 h.Write([]byte{0}) 54 - return h 55 60 } 56 61 57 62 func (h Hasher) Sum() (hash Hash) {
+64
plumbing/hash256.go
··· 1 + package plumbing 2 + 3 + import ( 4 + "crypto" 5 + "encoding/hex" 6 + "strconv" 7 + 8 + "github.com/go-git/go-git/v5/plumbing/hash" 9 + ) 10 + 11 + // NewHash return a new Hash256 from a hexadecimal hash representation. 12 + func NewHash256(s string) Hash256 { 13 + b, _ := hex.DecodeString(s) 14 + 15 + var h Hash256 16 + copy(h[:], b) 17 + 18 + return h 19 + } 20 + 21 + // Hash256 represents SHA256 hashed content. 22 + type Hash256 [32]byte 23 + 24 + // ZeroHash is Hash256 with value zero. 25 + var ZeroHash256 Hash256 26 + 27 + func (h Hash256) IsZero() bool { 28 + var empty Hash256 29 + return h == empty 30 + } 31 + 32 + func (h Hash256) String() string { 33 + return hex.EncodeToString(h[:]) 34 + } 35 + 36 + // ComputeHash compute the hash for a given ObjectType and content. 37 + func ComputeHash256(t ObjectType, content []byte) Hash256 { 38 + h := NewHasher256(t, int64(len(content))) 39 + h.Write(content) 40 + return h.Sum() 41 + } 42 + 43 + type Hasher256 struct { 44 + hash.Hash 45 + } 46 + 47 + func NewHasher256(t ObjectType, size int64) Hasher256 { 48 + h := Hasher256{hash.New(crypto.SHA256)} 49 + h.Reset(t, size) 50 + return h 51 + } 52 + 53 + func (h Hasher256) Reset(t ObjectType, size int64) { 54 + h.Hash.Reset() 55 + h.Write(t.Bytes()) 56 + h.Write([]byte(" ")) 57 + h.Write([]byte(strconv.FormatInt(size, 10))) 58 + h.Write([]byte{0}) 59 + } 60 + 61 + func (h Hasher256) Sum() (hash Hash256) { 62 + copy(hash[:], h.Hash.Sum(nil)) 63 + return 64 + }
+5 -1
plumbing/memory.go
··· 3 3 import ( 4 4 "bytes" 5 5 "io" 6 + "slices" 6 7 ) 7 8 8 9 // MemoryObject on memory Object implementation ··· 36 37 37 38 // SetSize set the object size, a content of the given size should be written 38 39 // afterwards 39 - func (o *MemoryObject) SetSize(s int64) { o.sz = s } 40 + func (o *MemoryObject) SetSize(s int64) { 41 + o.cont = slices.Grow(o.cont, int(s)) 42 + o.sz = s 43 + } 40 44 41 45 // Reader returns an io.ReadCloser used to read the object's content. 42 46 //
+30 -15
plumbing/object/commitgraph/commitnode_walker_test.go
··· 2 2 3 3 import ( 4 4 "strings" 5 + "testing" 5 6 6 7 "github.com/go-git/go-git/v5/plumbing" 8 + "github.com/go-git/go-git/v5/plumbing/cache" 7 9 commitgraph "github.com/go-git/go-git/v5/plumbing/format/commitgraph/v2" 10 + "github.com/go-git/go-git/v5/plumbing/format/packfile" 11 + "github.com/go-git/go-git/v5/storage/filesystem" 12 + "github.com/stretchr/testify/assert" 8 13 9 - fixtures "github.com/go-git/go-git-fixtures/v4" 10 - . "gopkg.in/check.v1" 14 + fixtures "github.com/go-git/go-git-fixtures/v5" 11 15 ) 12 16 13 - func (s *CommitNodeSuite) TestCommitNodeIter(c *C) { 17 + func TestCommitNodeIter(t *testing.T) { 18 + t.Parallel() 19 + 14 20 f := fixtures.ByTag("commit-graph-chain-2").One() 15 21 16 - storer := unpackRepository(f) 22 + storer := newUnpackRepository(f) 17 23 18 24 index, err := commitgraph.OpenChainOrFileIndex(storer.Filesystem()) 19 - c.Assert(err, IsNil) 25 + assert.NoError(t, err) 20 26 21 27 nodeIndex := NewGraphCommitNodeIndex(index, storer) 22 28 23 29 head, err := nodeIndex.Get(plumbing.NewHash("ec6f456c0e8c7058a29611429965aa05c190b54b")) 24 - c.Assert(err, IsNil) 30 + assert.NoError(t, err) 31 + 32 + testTopoOrder(t, head) 33 + testDateOrder(t, head) 34 + testAuthorDateOrder(t, head) 35 + } 25 36 26 - testTopoOrder(c, head) 27 - testDateOrder(c, head) 28 - testAuthorDateOrder(c, head) 37 + func newUnpackRepository(f *fixtures.Fixture) *filesystem.Storage { 38 + storer := filesystem.NewStorage(f.DotGit(), cache.NewObjectLRUDefault()) 39 + p := f.Packfile() 40 + defer p.Close() 41 + packfile.UpdateObjectStorage(storer, p) 42 + return storer 29 43 } 30 44 31 - func testTopoOrder(c *C, head CommitNode) { 45 + func testTopoOrder(t *testing.T, head CommitNode) { 32 46 iter := NewCommitNodeIterTopoOrder( 33 47 head, 34 48 nil, ··· 40 54 commits = append(commits, c.ID().String()) 41 55 return nil 42 56 }) 43 - c.Assert(commits, DeepEquals, strings.Split(`ec6f456c0e8c7058a29611429965aa05c190b54b 57 + 58 + assert.Equal(t, commits, strings.Split(`ec6f456c0e8c7058a29611429965aa05c190b54b 44 59 d82f291cde9987322c8a0c81a325e1ba6159684c 45 60 3048d280d2d5b258d9e582a226ff4bbed34fd5c9 46 61 27aa8cdd2431068606741a589383c02c149ea625 ··· 80 95 5d7303c49ac984a9fec60523f2d5297682e16646`, "\n")) 81 96 } 82 97 83 - func testDateOrder(c *C, head CommitNode) { 98 + func testDateOrder(t *testing.T, head CommitNode) { 84 99 iter := NewCommitNodeIterDateOrder( 85 100 head, 86 101 nil, ··· 93 108 return nil 94 109 }) 95 110 96 - c.Assert(commits, DeepEquals, strings.Split(`ec6f456c0e8c7058a29611429965aa05c190b54b 111 + assert.Equal(t, commits, strings.Split(`ec6f456c0e8c7058a29611429965aa05c190b54b 97 112 3048d280d2d5b258d9e582a226ff4bbed34fd5c9 98 113 d82f291cde9987322c8a0c81a325e1ba6159684c 99 114 27aa8cdd2431068606741a589383c02c149ea625 ··· 133 148 5d7303c49ac984a9fec60523f2d5297682e16646`, "\n")) 134 149 } 135 150 136 - func testAuthorDateOrder(c *C, head CommitNode) { 151 + func testAuthorDateOrder(t *testing.T, head CommitNode) { 137 152 iter := NewCommitNodeIterAuthorDateOrder( 138 153 head, 139 154 nil, ··· 146 161 return nil 147 162 }) 148 163 149 - c.Assert(commits, DeepEquals, strings.Split(`ec6f456c0e8c7058a29611429965aa05c190b54b 164 + assert.Equal(t, commits, strings.Split(`ec6f456c0e8c7058a29611429965aa05c190b54b 150 165 3048d280d2d5b258d9e582a226ff4bbed34fd5c9 151 166 d82f291cde9987322c8a0c81a325e1ba6159684c 152 167 27aa8cdd2431068606741a589383c02c149ea625
+4 -1
plumbing/storer/object.go
··· 15 15 16 16 // EncodedObjectStorer generic storage of objects 17 17 type EncodedObjectStorer interface { 18 + // RawObjectWriter returns a io.WriterCloser to write the object without the 19 + // need of providing a plumbing.EncodedObject. 20 + RawObjectWriter(typ plumbing.ObjectType, sz int64) (w io.WriteCloser, err error) 18 21 // NewEncodedObject returns a new plumbing.EncodedObject, the real type 19 22 // of the object can be a custom implementation or the default one, 20 23 // plumbing.MemoryObject. 21 24 NewEncodedObject() plumbing.EncodedObject 22 25 // SetEncodedObject saves an object into the storage, the object should 23 - // be create with the NewEncodedObject, method, and file if the type is 26 + // be created with the NewEncodedObject, method, and file if the type is 24 27 // not supported. 25 28 SetEncodedObject(plumbing.EncodedObject) (plumbing.Hash, error) 26 29 // EncodedObject gets an object by hash with the given
+5
plumbing/storer/object_test.go
··· 2 2 3 3 import ( 4 4 "fmt" 5 + "io" 5 6 "testing" 6 7 7 8 "github.com/go-git/go-git/v5/plumbing" ··· 123 124 124 125 type MockObjectStorage struct { 125 126 db []plumbing.EncodedObject 127 + } 128 + 129 + func (o *MockObjectStorage) RawObjectWriter(typ plumbing.ObjectType, sz int64) (w io.WriteCloser, err error) { 130 + return nil, nil 126 131 } 127 132 128 133 func (o *MockObjectStorage) NewEncodedObject() plumbing.EncodedObject {
+6 -10
storage/filesystem/dotgit/writers.go
··· 19 19 // this operation is synchronized with the write operations. 20 20 // The packfile is written in a temp file, when Close is called this file 21 21 // is renamed/moved (depends on the Filesystem implementation) to the final 22 - // location, if the PackWriter is not used, nothing is written 22 + // location, if the PackWriter is not used, nothing is written. 23 23 type PackWriter struct { 24 24 Notify func(plumbing.Hash, *idxfile.Writer) 25 25 ··· 56 56 } 57 57 58 58 func (w *PackWriter) buildIndex() { 59 - s := packfile.NewScanner(w.synced) 60 59 w.writer = new(idxfile.Writer) 61 60 var err error 62 - w.parser, err = packfile.NewParser(s, w.writer) 63 - if err != nil { 64 - w.result <- err 65 - return 66 - } 67 61 68 - checksum, err := w.parser.Parse() 62 + w.parser = packfile.NewParser(w.synced, packfile.WithScannerObservers(w.writer)) 63 + 64 + h, err := w.parser.Parse() 69 65 if err != nil { 70 66 w.result <- err 71 67 return 72 68 } 73 69 74 - w.checksum = checksum 75 - w.result <- err 70 + w.checksum = h 71 + w.result <- nil 76 72 } 77 73 78 74 // waitBuildIndex waits until buildIndex function finishes, this can terminate
+58 -52
storage/filesystem/dotgit/writers_test.go
··· 5 5 "io" 6 6 "os" 7 7 "strconv" 8 + "testing" 8 9 9 10 "github.com/go-git/go-billy/v5/osfs" 10 11 "github.com/go-git/go-billy/v5/util" 11 12 "github.com/go-git/go-git/v5/plumbing" 12 13 "github.com/go-git/go-git/v5/plumbing/format/idxfile" 13 14 "github.com/go-git/go-git/v5/plumbing/format/packfile" 15 + "github.com/stretchr/testify/assert" 16 + "github.com/stretchr/testify/require" 14 17 15 - fixtures "github.com/go-git/go-git-fixtures/v4" 16 - . "gopkg.in/check.v1" 18 + fixtures "github.com/go-git/go-git-fixtures/v5" 17 19 ) 18 20 19 - func (s *SuiteDotGit) TestNewObjectPack(c *C) { 21 + func TestNewObjectPack(t *testing.T) { 22 + t.Parallel() 23 + 20 24 f := fixtures.Basic().One() 21 25 22 - fs, clean := s.TemporalFilesystem() 23 - defer clean() 24 - 26 + fs := osfs.New(t.TempDir()) 25 27 dot := New(fs) 26 28 27 29 w, err := dot.NewObjectPack() 28 - c.Assert(err, IsNil) 30 + require.NoError(t, err) 29 31 30 32 _, err = io.Copy(w, f.Packfile()) 31 - c.Assert(err, IsNil) 33 + require.NoError(t, err) 32 34 33 - c.Assert(w.Close(), IsNil) 35 + require.NoError(t, w.Close()) 34 36 35 37 pfPath := fmt.Sprintf("objects/pack/pack-%s.pack", f.PackfileHash) 36 38 idxPath := fmt.Sprintf("objects/pack/pack-%s.idx", f.PackfileHash) 37 39 38 40 stat, err := fs.Stat(pfPath) 39 - c.Assert(err, IsNil) 40 - c.Assert(stat.Size(), Equals, int64(84794)) 41 + require.NoError(t, err) 42 + assert.Equal(t, int64(84794), stat.Size()) 41 43 42 44 stat, err = fs.Stat(idxPath) 43 - c.Assert(err, IsNil) 44 - c.Assert(stat.Size(), Equals, int64(1940)) 45 + require.NoError(t, err) 46 + assert.Equal(t, int64(1940), stat.Size()) 45 47 46 48 pf, err := fs.Open(pfPath) 47 - c.Assert(err, IsNil) 49 + assert.NoError(t, err) 50 + 51 + objFound := false 48 52 pfs := packfile.NewScanner(pf) 49 - _, objects, err := pfs.Header() 50 - c.Assert(err, IsNil) 51 - for i := uint32(0); i < objects; i++ { 52 - _, err := pfs.NextObjectHeader() 53 - if err != nil { 54 - c.Assert(err, IsNil) 55 - break 53 + for pfs.Scan() { 54 + data := pfs.Data() 55 + if data.Section != packfile.ObjectSection { 56 + continue 56 57 } 58 + 59 + objFound = true 60 + assert.NotNil(t, data.Value()) 57 61 } 58 - c.Assert(pfs.Close(), IsNil) 62 + 63 + assert.NoError(t, pf.Close()) 64 + assert.True(t, objFound) 59 65 } 60 66 61 - func (s *SuiteDotGit) TestNewObjectPackUnused(c *C) { 62 - fs, clean := s.TemporalFilesystem() 63 - defer clean() 67 + func TestNewObjectPackUnused(t *testing.T) { 68 + t.Parallel() 64 69 70 + fs := osfs.New(t.TempDir()) 65 71 dot := New(fs) 66 72 67 73 w, err := dot.NewObjectPack() 68 - c.Assert(err, IsNil) 74 + require.NoError(t, err) 69 75 70 - c.Assert(w.Close(), IsNil) 76 + assert.NoError(t, w.Close()) 71 77 72 78 info, err := fs.ReadDir("objects/pack") 73 - c.Assert(err, IsNil) 74 - c.Assert(info, HasLen, 0) 79 + require.NoError(t, err) 80 + assert.Len(t, info, 0) 75 81 76 82 // check clean up of temporary files 77 83 info, err = fs.ReadDir("") 78 - c.Assert(err, IsNil) 84 + require.NoError(t, err) 79 85 for _, fi := range info { 80 - c.Assert(fi.IsDir(), Equals, true) 86 + assert.True(t, fi.IsDir()) 81 87 } 82 88 } 83 89 84 - func (s *SuiteDotGit) TestSyncedReader(c *C) { 90 + func TestSyncedReader(t *testing.T) { 91 + t.Parallel() 92 + 85 93 tmpw, err := util.TempFile(osfs.Default, "", "example") 86 - c.Assert(err, IsNil) 94 + require.NoError(t, err) 87 95 88 96 tmpr, err := osfs.Default.Open(tmpw.Name()) 89 - c.Assert(err, IsNil) 97 + require.NoError(t, err) 90 98 91 99 defer func() { 92 100 tmpw.Close() ··· 99 107 go func() { 100 108 for i := 0; i < 281; i++ { 101 109 _, err := synced.Write([]byte(strconv.Itoa(i) + "\n")) 102 - c.Assert(err, IsNil) 110 + require.NoError(t, err) 103 111 } 104 112 105 113 synced.Close() 106 114 }() 107 115 108 116 o, err := synced.Seek(1002, io.SeekStart) 109 - c.Assert(err, IsNil) 110 - c.Assert(o, Equals, int64(1002)) 117 + require.NoError(t, err) 118 + assert.Equal(t, int64(1002), o) 111 119 112 120 head := make([]byte, 3) 113 121 n, err := io.ReadFull(synced, head) 114 - c.Assert(err, IsNil) 115 - c.Assert(n, Equals, 3) 116 - c.Assert(string(head), Equals, "278") 122 + require.NoError(t, err) 123 + assert.Equal(t, 3, n) 124 + assert.Equal(t, "278", string(head)) 117 125 118 126 o, err = synced.Seek(1010, io.SeekStart) 119 - c.Assert(err, IsNil) 120 - c.Assert(o, Equals, int64(1010)) 127 + require.NoError(t, err) 128 + assert.Equal(t, int64(1010), o) 121 129 122 130 n, err = io.ReadFull(synced, head) 123 - c.Assert(err, IsNil) 124 - c.Assert(n, Equals, 3) 125 - c.Assert(string(head), Equals, "280") 131 + require.NoError(t, err) 132 + assert.Equal(t, 3, n) 133 + assert.Equal(t, "280", string(head)) 126 134 } 127 135 128 - func (s *SuiteDotGit) TestPackWriterUnusedNotify(c *C) { 129 - fs, clean := s.TemporalFilesystem() 130 - defer clean() 131 - 136 + func TestPackWriterUnusedNotify(t *testing.T) { 137 + fs := osfs.New(t.TempDir()) 132 138 w, err := newPackWrite(fs) 133 - c.Assert(err, IsNil) 139 + require.NoError(t, err) 134 140 135 141 w.Notify = func(h plumbing.Hash, idx *idxfile.Writer) { 136 - c.Fatal("unexpected call to PackWriter.Notify") 142 + t.Fatal("unexpected call to PackWriter.Notify") 137 143 } 138 144 139 - c.Assert(w.Close(), IsNil) 145 + assert.NoError(t, w.Close()) 140 146 }
+38 -232
storage/filesystem/object.go
··· 2 2 3 3 import ( 4 4 "bytes" 5 + "fmt" 5 6 "io" 6 7 "os" 7 8 "sync" ··· 15 16 "github.com/go-git/go-git/v5/plumbing/storer" 16 17 "github.com/go-git/go-git/v5/storage/filesystem/dotgit" 17 18 "github.com/go-git/go-git/v5/utils/ioutil" 18 - 19 - "github.com/go-git/go-billy/v5" 20 19 ) 21 20 22 21 type ObjectStorage struct { 23 22 options Options 24 23 25 - // objectCache is an object cache uses to cache delta's bases and also recently 26 - // loaded loose objects 24 + // objectCache is an object cache used to cache delta's bases and also recently 25 + // loaded loose objects. 27 26 objectCache cache.Object 28 27 29 28 dir *dotgit.DotGit ··· 93 92 return err 94 93 } 95 94 95 + func (s *ObjectStorage) RawObjectWriter(typ plumbing.ObjectType, sz int64) (w io.WriteCloser, err error) { 96 + ow, err := s.dir.NewObject() 97 + if err != nil { 98 + return nil, err 99 + } 100 + 101 + err = ow.WriteHeader(typ, sz) 102 + if err != nil { 103 + return nil, err 104 + } 105 + 106 + return ow, nil 107 + } 108 + 96 109 func (s *ObjectStorage) NewEncodedObject() plumbing.EncodedObject { 97 110 return &plumbing.MemoryObject{} 98 111 } ··· 218 231 return nil, err 219 232 } 220 233 221 - var p *packfile.Packfile 222 - if s.objectCache != nil { 223 - p = packfile.NewPackfileWithCache(idx, s.dir.Fs(), f, s.objectCache, s.options.LargeObjectThreshold) 224 - } else { 225 - p = packfile.NewPackfile(idx, s.dir.Fs(), f, s.options.LargeObjectThreshold) 226 - } 227 - 234 + p := packfile.NewPackfile(f, 235 + packfile.WithIdx(idx), 236 + packfile.WithFs(s.dir.Fs()), 237 + packfile.WithCache(s.objectCache), 238 + ) 228 239 return p, s.storePackfileInCache(pack, p) 229 240 } 230 241 ··· 369 380 return nil, err 370 381 } 371 382 372 - if plumbing.AnyObject != t && obj.Type() != t { 383 + if obj == nil || (plumbing.AnyObject != t && obj.Type() != t) { 373 384 return nil, plumbing.ErrObjectNotFound 374 385 } 375 386 ··· 487 498 return s.decodeDeltaObjectAt(p, offset, hash) 488 499 } 489 500 490 - return s.decodeObjectAt(p, offset) 491 - } 492 - 493 - func (s *ObjectStorage) decodeObjectAt( 494 - p *packfile.Packfile, 495 - offset int64, 496 - ) (plumbing.EncodedObject, error) { 497 - hash, err := p.FindHash(offset) 498 - if err == nil { 499 - obj, ok := s.objectCache.Get(hash) 500 - if ok { 501 - return obj, nil 502 - } 503 - } 504 - 505 - if err != nil && err != plumbing.ErrObjectNotFound { 506 - return nil, err 507 - } 508 - 509 501 return p.GetByOffset(offset) 510 502 } 511 503 504 + // TODO: refactor this logic into packfile package. 512 505 func (s *ObjectStorage) decodeDeltaObjectAt( 513 506 p *packfile.Packfile, 514 507 offset int64, 515 508 hash plumbing.Hash, 516 509 ) (plumbing.EncodedObject, error) { 517 - scan := p.Scanner() 518 - header, err := scan.SeekObjectHeader(offset) 510 + scan, err := p.Scanner() 519 511 if err != nil { 520 512 return nil, err 521 513 } 514 + err = scan.SeekFromStart(offset) 515 + if err != nil { 516 + return nil, err 517 + } 518 + 519 + if !scan.Scan() { 520 + return nil, fmt.Errorf("failed to decode delta object") 521 + } 522 + 523 + header := scan.Data().Value().(packfile.ObjectHeader) 522 524 523 525 var ( 524 526 base plumbing.Hash ··· 533 535 return nil, err 534 536 } 535 537 default: 536 - return s.decodeObjectAt(p, offset) 538 + return p.GetByOffset(offset) 537 539 } 538 540 539 541 obj := &plumbing.MemoryObject{} ··· 543 545 return nil, err 544 546 } 545 547 546 - if _, _, err := scan.NextObject(w); err != nil { 548 + if err := scan.WriteObject(&header, w); err != nil { 547 549 return nil, err 548 550 } 549 551 550 - return newDeltaObject(obj, hash, base, header.Length), nil 552 + return newDeltaObject(obj, hash, base, header.Size), nil 551 553 } 552 554 553 555 func (s *ObjectStorage) findObjectInPackfile(h plumbing.Hash) (plumbing.Hash, plumbing.Hash, int64) { ··· 650 652 return newPackfileIter( 651 653 s.dir.Fs(), pack, t, seen, s.index[h], 652 654 s.objectCache, s.options.KeepDescriptors, 653 - s.options.LargeObjectThreshold, 654 655 ) 655 656 }, 656 657 }, nil ··· 676 677 s.dir.Close() 677 678 678 679 return firstError 679 - } 680 - 681 - type lazyPackfilesIter struct { 682 - hashes []plumbing.Hash 683 - open func(h plumbing.Hash) (storer.EncodedObjectIter, error) 684 - cur storer.EncodedObjectIter 685 - } 686 - 687 - func (it *lazyPackfilesIter) Next() (plumbing.EncodedObject, error) { 688 - for { 689 - if it.cur == nil { 690 - if len(it.hashes) == 0 { 691 - return nil, io.EOF 692 - } 693 - h := it.hashes[0] 694 - it.hashes = it.hashes[1:] 695 - 696 - sub, err := it.open(h) 697 - if err == io.EOF { 698 - continue 699 - } else if err != nil { 700 - return nil, err 701 - } 702 - it.cur = sub 703 - } 704 - ob, err := it.cur.Next() 705 - if err == io.EOF { 706 - it.cur.Close() 707 - it.cur = nil 708 - continue 709 - } else if err != nil { 710 - return nil, err 711 - } 712 - return ob, nil 713 - } 714 - } 715 - 716 - func (it *lazyPackfilesIter) ForEach(cb func(plumbing.EncodedObject) error) error { 717 - return storer.ForEachIterator(it, cb) 718 - } 719 - 720 - func (it *lazyPackfilesIter) Close() { 721 - if it.cur != nil { 722 - it.cur.Close() 723 - it.cur = nil 724 - } 725 - it.hashes = nil 726 - } 727 - 728 - type packfileIter struct { 729 - pack billy.File 730 - iter storer.EncodedObjectIter 731 - seen map[plumbing.Hash]struct{} 732 - 733 - // tells whether the pack file should be left open after iteration or not 734 - keepPack bool 735 - } 736 - 737 - // NewPackfileIter returns a new EncodedObjectIter for the provided packfile 738 - // and object type. Packfile and index file will be closed after they're 739 - // used. If keepPack is true the packfile won't be closed after the iteration 740 - // finished. 741 - func NewPackfileIter( 742 - fs billy.Filesystem, 743 - f billy.File, 744 - idxFile billy.File, 745 - t plumbing.ObjectType, 746 - keepPack bool, 747 - largeObjectThreshold int64, 748 - ) (storer.EncodedObjectIter, error) { 749 - idx := idxfile.NewMemoryIndex() 750 - if err := idxfile.NewDecoder(idxFile).Decode(idx); err != nil { 751 - return nil, err 752 - } 753 - 754 - if err := idxFile.Close(); err != nil { 755 - return nil, err 756 - } 757 - 758 - seen := make(map[plumbing.Hash]struct{}) 759 - return newPackfileIter(fs, f, t, seen, idx, nil, keepPack, largeObjectThreshold) 760 - } 761 - 762 - func newPackfileIter( 763 - fs billy.Filesystem, 764 - f billy.File, 765 - t plumbing.ObjectType, 766 - seen map[plumbing.Hash]struct{}, 767 - index idxfile.Index, 768 - cache cache.Object, 769 - keepPack bool, 770 - largeObjectThreshold int64, 771 - ) (storer.EncodedObjectIter, error) { 772 - var p *packfile.Packfile 773 - if cache != nil { 774 - p = packfile.NewPackfileWithCache(index, fs, f, cache, largeObjectThreshold) 775 - } else { 776 - p = packfile.NewPackfile(index, fs, f, largeObjectThreshold) 777 - } 778 - 779 - iter, err := p.GetByType(t) 780 - if err != nil { 781 - return nil, err 782 - } 783 - 784 - return &packfileIter{ 785 - pack: f, 786 - iter: iter, 787 - seen: seen, 788 - keepPack: keepPack, 789 - }, nil 790 - } 791 - 792 - func (iter *packfileIter) Next() (plumbing.EncodedObject, error) { 793 - for { 794 - obj, err := iter.iter.Next() 795 - if err != nil { 796 - return nil, err 797 - } 798 - 799 - if _, ok := iter.seen[obj.Hash()]; ok { 800 - continue 801 - } 802 - 803 - return obj, nil 804 - } 805 - } 806 - 807 - func (iter *packfileIter) ForEach(cb func(plumbing.EncodedObject) error) error { 808 - for { 809 - o, err := iter.Next() 810 - if err != nil { 811 - if err == io.EOF { 812 - iter.Close() 813 - return nil 814 - } 815 - return err 816 - } 817 - 818 - if err := cb(o); err != nil { 819 - return err 820 - } 821 - } 822 - } 823 - 824 - func (iter *packfileIter) Close() { 825 - iter.iter.Close() 826 - if !iter.keepPack { 827 - _ = iter.pack.Close() 828 - } 829 - } 830 - 831 - type objectsIter struct { 832 - s *ObjectStorage 833 - t plumbing.ObjectType 834 - h []plumbing.Hash 835 - } 836 - 837 - func (iter *objectsIter) Next() (plumbing.EncodedObject, error) { 838 - if len(iter.h) == 0 { 839 - return nil, io.EOF 840 - } 841 - 842 - obj, err := iter.s.getFromUnpacked(iter.h[0]) 843 - iter.h = iter.h[1:] 844 - 845 - if err != nil { 846 - return nil, err 847 - } 848 - 849 - if iter.t != plumbing.AnyObject && iter.t != obj.Type() { 850 - return iter.Next() 851 - } 852 - 853 - return obj, err 854 - } 855 - 856 - func (iter *objectsIter) ForEach(cb func(plumbing.EncodedObject) error) error { 857 - for { 858 - o, err := iter.Next() 859 - if err != nil { 860 - if err == io.EOF { 861 - return nil 862 - } 863 - return err 864 - } 865 - 866 - if err := cb(o); err != nil { 867 - return err 868 - } 869 - } 870 - } 871 - 872 - func (iter *objectsIter) Close() { 873 - iter.h = []plumbing.Hash{} 874 680 } 875 681 876 682 func hashListAsMap(l []plumbing.Hash) map[plumbing.Hash]struct{} {
+205
storage/filesystem/object_iter.go
··· 1 + package filesystem 2 + 3 + import ( 4 + "io" 5 + 6 + "github.com/go-git/go-billy/v5" 7 + "github.com/go-git/go-git/v5/plumbing" 8 + "github.com/go-git/go-git/v5/plumbing/cache" 9 + "github.com/go-git/go-git/v5/plumbing/format/idxfile" 10 + "github.com/go-git/go-git/v5/plumbing/format/packfile" 11 + "github.com/go-git/go-git/v5/plumbing/storer" 12 + ) 13 + 14 + type lazyPackfilesIter struct { 15 + hashes []plumbing.Hash 16 + open func(h plumbing.Hash) (storer.EncodedObjectIter, error) 17 + cur storer.EncodedObjectIter 18 + } 19 + 20 + func (it *lazyPackfilesIter) Next() (plumbing.EncodedObject, error) { 21 + for { 22 + if it.cur == nil { 23 + if len(it.hashes) == 0 { 24 + return nil, io.EOF 25 + } 26 + h := it.hashes[0] 27 + it.hashes = it.hashes[1:] 28 + 29 + sub, err := it.open(h) 30 + if err == io.EOF { 31 + continue 32 + } else if err != nil { 33 + return nil, err 34 + } 35 + it.cur = sub 36 + } 37 + ob, err := it.cur.Next() 38 + if err == io.EOF { 39 + it.cur.Close() 40 + it.cur = nil 41 + continue 42 + } else if err != nil { 43 + return nil, err 44 + } 45 + return ob, nil 46 + } 47 + } 48 + 49 + func (it *lazyPackfilesIter) ForEach(cb func(plumbing.EncodedObject) error) error { 50 + return storer.ForEachIterator(it, cb) 51 + } 52 + 53 + func (it *lazyPackfilesIter) Close() { 54 + if it.cur != nil { 55 + it.cur.Close() 56 + it.cur = nil 57 + } 58 + it.hashes = nil 59 + } 60 + 61 + type packfileIter struct { 62 + pack billy.File 63 + iter storer.EncodedObjectIter 64 + seen map[plumbing.Hash]struct{} 65 + 66 + // tells whether the pack file should be left open after iteration or not 67 + keepPack bool 68 + } 69 + 70 + // NewPackfileIter returns a new EncodedObjectIter for the provided packfile 71 + // and object type. Packfile and index file will be closed after they're 72 + // used. If keepPack is true the packfile won't be closed after the iteration 73 + // finished. 74 + func NewPackfileIter( 75 + fs billy.Filesystem, 76 + f billy.File, 77 + idxFile billy.File, 78 + t plumbing.ObjectType, 79 + keepPack bool, 80 + largeObjectThreshold int64, 81 + ) (storer.EncodedObjectIter, error) { 82 + idx := idxfile.NewMemoryIndex() 83 + if err := idxfile.NewDecoder(idxFile).Decode(idx); err != nil { 84 + return nil, err 85 + } 86 + 87 + if err := idxFile.Close(); err != nil { 88 + return nil, err 89 + } 90 + 91 + seen := make(map[plumbing.Hash]struct{}) 92 + return newPackfileIter(fs, f, t, seen, idx, nil, keepPack) 93 + } 94 + 95 + func newPackfileIter( 96 + fs billy.Filesystem, 97 + f billy.File, 98 + t plumbing.ObjectType, 99 + seen map[plumbing.Hash]struct{}, 100 + index idxfile.Index, 101 + cache cache.Object, 102 + keepPack bool, 103 + ) (storer.EncodedObjectIter, error) { 104 + p := packfile.NewPackfile(f, 105 + packfile.WithFs(fs), 106 + packfile.WithCache(cache), 107 + packfile.WithIdx(index), 108 + ) 109 + 110 + iter, err := p.GetByType(t) 111 + if err != nil { 112 + return nil, err 113 + } 114 + 115 + return &packfileIter{ 116 + pack: f, 117 + iter: iter, 118 + seen: seen, 119 + keepPack: keepPack, 120 + }, nil 121 + } 122 + 123 + func (iter *packfileIter) Next() (plumbing.EncodedObject, error) { 124 + for { 125 + obj, err := iter.iter.Next() 126 + if err != nil { 127 + return nil, err 128 + } 129 + 130 + if _, ok := iter.seen[obj.Hash()]; ok { 131 + continue 132 + } 133 + 134 + return obj, nil 135 + } 136 + } 137 + 138 + func (iter *packfileIter) ForEach(cb func(plumbing.EncodedObject) error) error { 139 + for { 140 + o, err := iter.Next() 141 + if err != nil { 142 + if err == io.EOF { 143 + iter.Close() 144 + return nil 145 + } 146 + return err 147 + } 148 + 149 + if err := cb(o); err != nil { 150 + return err 151 + } 152 + } 153 + } 154 + 155 + func (iter *packfileIter) Close() { 156 + iter.iter.Close() 157 + if !iter.keepPack { 158 + _ = iter.pack.Close() 159 + } 160 + } 161 + 162 + type objectsIter struct { 163 + s *ObjectStorage 164 + t plumbing.ObjectType 165 + h []plumbing.Hash 166 + } 167 + 168 + func (iter *objectsIter) Next() (plumbing.EncodedObject, error) { 169 + if len(iter.h) == 0 { 170 + return nil, io.EOF 171 + } 172 + 173 + obj, err := iter.s.getFromUnpacked(iter.h[0]) 174 + iter.h = iter.h[1:] 175 + 176 + if err != nil { 177 + return nil, err 178 + } 179 + 180 + if iter.t != plumbing.AnyObject && iter.t != obj.Type() { 181 + return iter.Next() 182 + } 183 + 184 + return obj, err 185 + } 186 + 187 + func (iter *objectsIter) ForEach(cb func(plumbing.EncodedObject) error) error { 188 + for { 189 + o, err := iter.Next() 190 + if err != nil { 191 + if err == io.EOF { 192 + return nil 193 + } 194 + return err 195 + } 196 + 197 + if err := cb(o); err != nil { 198 + return err 199 + } 200 + } 201 + } 202 + 203 + func (iter *objectsIter) Close() { 204 + iter.h = []plumbing.Hash{} 205 + }
+25 -63
storage/filesystem/storage_test.go
··· 1 - package filesystem 1 + package filesystem_test 2 2 3 3 import ( 4 4 "testing" 5 5 6 6 "github.com/go-git/go-git/v5/plumbing/cache" 7 7 "github.com/go-git/go-git/v5/plumbing/storer" 8 - "github.com/go-git/go-git/v5/storage/test" 8 + "github.com/go-git/go-git/v5/storage/filesystem" 9 + "github.com/stretchr/testify/assert" 9 10 10 - "github.com/go-git/go-billy/v5" 11 11 "github.com/go-git/go-billy/v5/memfs" 12 12 "github.com/go-git/go-billy/v5/osfs" 13 - "github.com/go-git/go-billy/v5/util" 14 - . "gopkg.in/check.v1" 15 13 ) 16 14 17 - func Test(t *testing.T) { TestingT(t) } 18 - 19 - type StorageSuite struct { 20 - test.BaseStorageSuite 21 - dir string 22 - fs billy.Filesystem 23 - } 24 - 25 - var _ = Suite(&StorageSuite{}) 26 - 27 - func (s *StorageSuite) SetUpTest(c *C) { 28 - tmp, err := util.TempDir(osfs.Default, "", "go-git-filestystem-config") 29 - c.Assert(err, IsNil) 30 - 31 - s.dir = tmp 32 - s.fs = osfs.New(s.dir) 33 - storage := NewStorage(s.fs, cache.NewObjectLRUDefault()) 34 - 35 - setUpTest(s, c, storage) 36 - } 37 - 38 - func setUpTest(s *StorageSuite, c *C, storage *Storage) { 39 - // ensure that right interfaces are implemented 40 - var _ storer.EncodedObjectStorer = storage 41 - var _ storer.IndexStorer = storage 42 - var _ storer.ReferenceStorer = storage 43 - var _ storer.ShallowStorer = storage 44 - var _ storer.DeltaObjectStorer = storage 45 - var _ storer.PackfileWriter = storage 46 - 47 - s.BaseStorageSuite = test.NewBaseStorageSuite(storage) 48 - } 49 - 50 - func (s *StorageSuite) TestFilesystem(c *C) { 51 - fs := memfs.New() 52 - storage := NewStorage(fs, cache.NewObjectLRUDefault()) 53 - 54 - c.Assert(storage.Filesystem(), Equals, fs) 55 - } 15 + var ( 16 + fs = memfs.New() 17 + sto = filesystem.NewStorage(fs, cache.NewObjectLRUDefault()) 56 18 57 - func (s *StorageSuite) TestNewStorageShouldNotAddAnyContentsToDir(c *C) { 58 - fis, err := s.fs.ReadDir("/") 59 - c.Assert(err, IsNil) 60 - c.Assert(fis, HasLen, 0) 61 - } 19 + // Ensure interfaces are implemented. 20 + _ storer.EncodedObjectStorer = sto 21 + _ storer.IndexStorer = sto 22 + _ storer.ReferenceStorer = sto 23 + _ storer.ShallowStorer = sto 24 + _ storer.DeltaObjectStorer = sto 25 + _ storer.PackfileWriter = sto 26 + ) 62 27 63 - type StorageExclusiveSuite struct { 64 - StorageSuite 28 + func TestFilesystem(t *testing.T) { 29 + assert.Same(t, fs, sto.Filesystem()) 65 30 } 66 31 67 - var _ = Suite(&StorageExclusiveSuite{}) 68 - 69 - func (s *StorageExclusiveSuite) SetUpTest(c *C) { 70 - tmp, err := util.TempDir(osfs.Default, "", "go-git-filestystem-config") 71 - c.Assert(err, IsNil) 72 - 73 - s.dir = tmp 74 - s.fs = osfs.New(s.dir) 32 + func TestNewStorageShouldNotAddAnyContentsToDir(t *testing.T) { 33 + fs := osfs.New(t.TempDir()) 75 34 76 - storage := NewStorageWithOptions( 77 - s.fs, 35 + sto := filesystem.NewStorageWithOptions( 36 + fs, 78 37 cache.NewObjectLRUDefault(), 79 - Options{ExclusiveAccess: true}) 38 + filesystem.Options{ExclusiveAccess: true}) 39 + assert.NotNil(t, sto) 80 40 81 - setUpTest(&s.StorageSuite, c, storage) 41 + fis, err := fs.ReadDir("/") 42 + assert.NoError(t, err) 43 + assert.Len(t, fis, 0) 82 44 }
+35
storage/memory/storage.go
··· 3 3 4 4 import ( 5 5 "fmt" 6 + "io" 6 7 "time" 7 8 8 9 "github.com/go-git/go-git/v5/config" ··· 10 11 "github.com/go-git/go-git/v5/plumbing/format/index" 11 12 "github.com/go-git/go-git/v5/plumbing/storer" 12 13 "github.com/go-git/go-git/v5/storage" 14 + "github.com/go-git/go-git/v5/utils/ioutil" 13 15 ) 14 16 15 17 var ErrUnsupportedObjectType = fmt.Errorf("unsupported object type") ··· 88 90 Trees map[plumbing.Hash]plumbing.EncodedObject 89 91 Blobs map[plumbing.Hash]plumbing.EncodedObject 90 92 Tags map[plumbing.Hash]plumbing.EncodedObject 93 + } 94 + 95 + type lazyCloser struct { 96 + storage *ObjectStorage 97 + obj plumbing.EncodedObject 98 + closer io.Closer 99 + } 100 + 101 + func (c *lazyCloser) Close() error { 102 + err := c.closer.Close() 103 + if err != nil { 104 + return fmt.Errorf("failed to close memory encoded object: %w", err) 105 + } 106 + 107 + _, err = c.storage.SetEncodedObject(c.obj) 108 + return err 109 + } 110 + 111 + func (o *ObjectStorage) RawObjectWriter(typ plumbing.ObjectType, sz int64) (w io.WriteCloser, err error) { 112 + obj := o.NewEncodedObject() 113 + obj.SetType(typ) 114 + obj.SetSize(sz) 115 + 116 + w, err = obj.Writer() 117 + if err != nil { 118 + return nil, err 119 + } 120 + 121 + wc := ioutil.NewWriteCloser(w, 122 + &lazyCloser{storage: o, obj: obj, closer: w}, 123 + ) 124 + 125 + return wc, nil 91 126 } 92 127 93 128 func (o *ObjectStorage) NewEncodedObject() plumbing.EncodedObject {
-20
storage/memory/storage_test.go
··· 1 - package memory 2 - 3 - import ( 4 - "testing" 5 - 6 - "github.com/go-git/go-git/v5/storage/test" 7 - . "gopkg.in/check.v1" 8 - ) 9 - 10 - func Test(t *testing.T) { TestingT(t) } 11 - 12 - type StorageSuite struct { 13 - test.BaseStorageSuite 14 - } 15 - 16 - var _ = Suite(&StorageSuite{}) 17 - 18 - func (s *StorageSuite) SetUpTest(c *C) { 19 - s.BaseStorageSuite = test.NewBaseStorageSuite(NewStorage()) 20 - }
-529
storage/test/storage_suite.go
··· 1 - package test 2 - 3 - import ( 4 - "encoding/hex" 5 - "errors" 6 - "fmt" 7 - "io" 8 - 9 - "github.com/go-git/go-git/v5/config" 10 - "github.com/go-git/go-git/v5/plumbing" 11 - "github.com/go-git/go-git/v5/plumbing/format/index" 12 - "github.com/go-git/go-git/v5/plumbing/storer" 13 - "github.com/go-git/go-git/v5/storage" 14 - 15 - fixtures "github.com/go-git/go-git-fixtures/v4" 16 - . "gopkg.in/check.v1" 17 - ) 18 - 19 - type Storer interface { 20 - storer.EncodedObjectStorer 21 - storer.ReferenceStorer 22 - storer.ShallowStorer 23 - storer.IndexStorer 24 - config.ConfigStorer 25 - storage.ModuleStorer 26 - } 27 - 28 - type TestObject struct { 29 - Object plumbing.EncodedObject 30 - Hash string 31 - Type plumbing.ObjectType 32 - } 33 - 34 - type BaseStorageSuite struct { 35 - Storer Storer 36 - 37 - validTypes []plumbing.ObjectType 38 - testObjects map[plumbing.ObjectType]TestObject 39 - } 40 - 41 - func NewBaseStorageSuite(s Storer) BaseStorageSuite { 42 - commit := &plumbing.MemoryObject{} 43 - commit.SetType(plumbing.CommitObject) 44 - tree := &plumbing.MemoryObject{} 45 - tree.SetType(plumbing.TreeObject) 46 - blob := &plumbing.MemoryObject{} 47 - blob.SetType(plumbing.BlobObject) 48 - tag := &plumbing.MemoryObject{} 49 - tag.SetType(plumbing.TagObject) 50 - 51 - return BaseStorageSuite{ 52 - Storer: s, 53 - validTypes: []plumbing.ObjectType{ 54 - plumbing.CommitObject, 55 - plumbing.BlobObject, 56 - plumbing.TagObject, 57 - plumbing.TreeObject, 58 - }, 59 - testObjects: map[plumbing.ObjectType]TestObject{ 60 - plumbing.CommitObject: {commit, "dcf5b16e76cce7425d0beaef62d79a7d10fce1f5", plumbing.CommitObject}, 61 - plumbing.TreeObject: {tree, "4b825dc642cb6eb9a060e54bf8d69288fbee4904", plumbing.TreeObject}, 62 - plumbing.BlobObject: {blob, "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391", plumbing.BlobObject}, 63 - plumbing.TagObject: {tag, "d994c6bb648123a17e8f70a966857c546b2a6f94", plumbing.TagObject}, 64 - }} 65 - } 66 - 67 - func (s *BaseStorageSuite) TearDownTest(c *C) { 68 - fixtures.Clean() 69 - } 70 - 71 - func (s *BaseStorageSuite) TestSetEncodedObjectAndEncodedObject(c *C) { 72 - for _, to := range s.testObjects { 73 - comment := Commentf("failed for type %s", to.Type.String()) 74 - 75 - h, err := s.Storer.SetEncodedObject(to.Object) 76 - c.Assert(err, IsNil) 77 - c.Assert(h.String(), Equals, to.Hash, comment) 78 - 79 - o, err := s.Storer.EncodedObject(to.Type, h) 80 - c.Assert(err, IsNil) 81 - c.Assert(objectEquals(o, to.Object), IsNil) 82 - 83 - o, err = s.Storer.EncodedObject(plumbing.AnyObject, h) 84 - c.Assert(err, IsNil) 85 - c.Assert(objectEquals(o, to.Object), IsNil) 86 - 87 - for _, t := range s.validTypes { 88 - if t == to.Type { 89 - continue 90 - } 91 - 92 - o, err = s.Storer.EncodedObject(t, h) 93 - c.Assert(o, IsNil) 94 - c.Assert(err, Equals, plumbing.ErrObjectNotFound) 95 - } 96 - } 97 - } 98 - 99 - func (s *BaseStorageSuite) TestSetEncodedObjectInvalid(c *C) { 100 - o := s.Storer.NewEncodedObject() 101 - o.SetType(plumbing.REFDeltaObject) 102 - 103 - _, err := s.Storer.SetEncodedObject(o) 104 - c.Assert(err, NotNil) 105 - } 106 - 107 - func (s *BaseStorageSuite) TestIterEncodedObjects(c *C) { 108 - for _, o := range s.testObjects { 109 - h, err := s.Storer.SetEncodedObject(o.Object) 110 - c.Assert(err, IsNil) 111 - c.Assert(h, Equals, o.Object.Hash()) 112 - } 113 - 114 - for _, t := range s.validTypes { 115 - comment := Commentf("failed for type %s)", t.String()) 116 - i, err := s.Storer.IterEncodedObjects(t) 117 - c.Assert(err, IsNil, comment) 118 - 119 - o, err := i.Next() 120 - c.Assert(err, IsNil) 121 - c.Assert(objectEquals(o, s.testObjects[t].Object), IsNil) 122 - 123 - o, err = i.Next() 124 - c.Assert(o, IsNil) 125 - c.Assert(err, Equals, io.EOF, comment) 126 - } 127 - 128 - i, err := s.Storer.IterEncodedObjects(plumbing.AnyObject) 129 - c.Assert(err, IsNil) 130 - 131 - foundObjects := []plumbing.EncodedObject{} 132 - i.ForEach(func(o plumbing.EncodedObject) error { 133 - foundObjects = append(foundObjects, o) 134 - return nil 135 - }) 136 - 137 - c.Assert(foundObjects, HasLen, len(s.testObjects)) 138 - for _, to := range s.testObjects { 139 - found := false 140 - for _, o := range foundObjects { 141 - if to.Object.Hash() == o.Hash() { 142 - found = true 143 - break 144 - } 145 - } 146 - c.Assert(found, Equals, true, Commentf("Object of type %s not found", to.Type.String())) 147 - } 148 - } 149 - 150 - func (s *BaseStorageSuite) TestPackfileWriter(c *C) { 151 - pwr, ok := s.Storer.(storer.PackfileWriter) 152 - if !ok { 153 - c.Skip("not a storer.PackWriter") 154 - } 155 - 156 - pw, err := pwr.PackfileWriter() 157 - c.Assert(err, IsNil) 158 - 159 - f := fixtures.Basic().One() 160 - _, err = io.Copy(pw, f.Packfile()) 161 - c.Assert(err, IsNil) 162 - 163 - err = pw.Close() 164 - c.Assert(err, IsNil) 165 - 166 - iter, err := s.Storer.IterEncodedObjects(plumbing.AnyObject) 167 - c.Assert(err, IsNil) 168 - objects := 0 169 - err = iter.ForEach(func(plumbing.EncodedObject) error { 170 - objects++ 171 - return nil 172 - }) 173 - c.Assert(err, IsNil) 174 - c.Assert(objects, Equals, 31) 175 - } 176 - 177 - func (s *BaseStorageSuite) TestObjectStorerTxSetEncodedObjectAndCommit(c *C) { 178 - storer, ok := s.Storer.(storer.Transactioner) 179 - if !ok { 180 - c.Skip("not a plumbing.ObjectStorerTx") 181 - } 182 - 183 - tx := storer.Begin() 184 - for _, o := range s.testObjects { 185 - h, err := tx.SetEncodedObject(o.Object) 186 - c.Assert(err, IsNil) 187 - c.Assert(h.String(), Equals, o.Hash) 188 - } 189 - 190 - iter, err := s.Storer.IterEncodedObjects(plumbing.AnyObject) 191 - c.Assert(err, IsNil) 192 - _, err = iter.Next() 193 - c.Assert(err, Equals, io.EOF) 194 - 195 - err = tx.Commit() 196 - c.Assert(err, IsNil) 197 - 198 - iter, err = s.Storer.IterEncodedObjects(plumbing.AnyObject) 199 - c.Assert(err, IsNil) 200 - 201 - var count int 202 - iter.ForEach(func(o plumbing.EncodedObject) error { 203 - count++ 204 - return nil 205 - }) 206 - 207 - c.Assert(count, Equals, 4) 208 - } 209 - 210 - func (s *BaseStorageSuite) TestObjectStorerTxSetObjectAndGetObject(c *C) { 211 - storer, ok := s.Storer.(storer.Transactioner) 212 - if !ok { 213 - c.Skip("not a plumbing.ObjectStorerTx") 214 - } 215 - 216 - tx := storer.Begin() 217 - for _, expected := range s.testObjects { 218 - h, err := tx.SetEncodedObject(expected.Object) 219 - c.Assert(err, IsNil) 220 - c.Assert(h.String(), Equals, expected.Hash) 221 - 222 - o, err := tx.EncodedObject(expected.Type, plumbing.NewHash(expected.Hash)) 223 - c.Assert(err, IsNil) 224 - c.Assert(o.Hash().String(), DeepEquals, expected.Hash) 225 - } 226 - } 227 - 228 - func (s *BaseStorageSuite) TestObjectStorerTxGetObjectNotFound(c *C) { 229 - storer, ok := s.Storer.(storer.Transactioner) 230 - if !ok { 231 - c.Skip("not a plumbing.ObjectStorerTx") 232 - } 233 - 234 - tx := storer.Begin() 235 - o, err := tx.EncodedObject(plumbing.AnyObject, plumbing.ZeroHash) 236 - c.Assert(o, IsNil) 237 - c.Assert(err, Equals, plumbing.ErrObjectNotFound) 238 - } 239 - 240 - func (s *BaseStorageSuite) TestObjectStorerTxSetObjectAndRollback(c *C) { 241 - storer, ok := s.Storer.(storer.Transactioner) 242 - if !ok { 243 - c.Skip("not a plumbing.ObjectStorerTx") 244 - } 245 - 246 - tx := storer.Begin() 247 - for _, o := range s.testObjects { 248 - h, err := tx.SetEncodedObject(o.Object) 249 - c.Assert(err, IsNil) 250 - c.Assert(h.String(), Equals, o.Hash) 251 - } 252 - 253 - err := tx.Rollback() 254 - c.Assert(err, IsNil) 255 - 256 - iter, err := s.Storer.IterEncodedObjects(plumbing.AnyObject) 257 - c.Assert(err, IsNil) 258 - _, err = iter.Next() 259 - c.Assert(err, Equals, io.EOF) 260 - } 261 - 262 - func (s *BaseStorageSuite) TestSetReferenceAndGetReference(c *C) { 263 - err := s.Storer.SetReference( 264 - plumbing.NewReferenceFromStrings("foo", "bc9968d75e48de59f0870ffb71f5e160bbbdcf52"), 265 - ) 266 - c.Assert(err, IsNil) 267 - 268 - err = s.Storer.SetReference( 269 - plumbing.NewReferenceFromStrings("bar", "482e0eada5de4039e6f216b45b3c9b683b83bfa"), 270 - ) 271 - c.Assert(err, IsNil) 272 - 273 - e, err := s.Storer.Reference(plumbing.ReferenceName("foo")) 274 - c.Assert(err, IsNil) 275 - c.Assert(e.Hash().String(), Equals, "bc9968d75e48de59f0870ffb71f5e160bbbdcf52") 276 - } 277 - 278 - func (s *BaseStorageSuite) TestCheckAndSetReference(c *C) { 279 - err := s.Storer.SetReference( 280 - plumbing.NewReferenceFromStrings("foo", "482e0eada5de4039e6f216b45b3c9b683b83bfa"), 281 - ) 282 - c.Assert(err, IsNil) 283 - 284 - err = s.Storer.CheckAndSetReference( 285 - plumbing.NewReferenceFromStrings("foo", "bc9968d75e48de59f0870ffb71f5e160bbbdcf52"), 286 - plumbing.NewReferenceFromStrings("foo", "482e0eada5de4039e6f216b45b3c9b683b83bfa"), 287 - ) 288 - c.Assert(err, IsNil) 289 - 290 - e, err := s.Storer.Reference(plumbing.ReferenceName("foo")) 291 - c.Assert(err, IsNil) 292 - c.Assert(e.Hash().String(), Equals, "bc9968d75e48de59f0870ffb71f5e160bbbdcf52") 293 - } 294 - 295 - func (s *BaseStorageSuite) TestCheckAndSetReferenceNil(c *C) { 296 - err := s.Storer.SetReference( 297 - plumbing.NewReferenceFromStrings("foo", "482e0eada5de4039e6f216b45b3c9b683b83bfa"), 298 - ) 299 - c.Assert(err, IsNil) 300 - 301 - err = s.Storer.CheckAndSetReference( 302 - plumbing.NewReferenceFromStrings("foo", "bc9968d75e48de59f0870ffb71f5e160bbbdcf52"), 303 - nil, 304 - ) 305 - c.Assert(err, IsNil) 306 - 307 - e, err := s.Storer.Reference(plumbing.ReferenceName("foo")) 308 - c.Assert(err, IsNil) 309 - c.Assert(e.Hash().String(), Equals, "bc9968d75e48de59f0870ffb71f5e160bbbdcf52") 310 - } 311 - 312 - func (s *BaseStorageSuite) TestCheckAndSetReferenceError(c *C) { 313 - err := s.Storer.SetReference( 314 - plumbing.NewReferenceFromStrings("foo", "c3f4688a08fd86f1bf8e055724c84b7a40a09733"), 315 - ) 316 - c.Assert(err, IsNil) 317 - 318 - err = s.Storer.CheckAndSetReference( 319 - plumbing.NewReferenceFromStrings("foo", "bc9968d75e48de59f0870ffb71f5e160bbbdcf52"), 320 - plumbing.NewReferenceFromStrings("foo", "482e0eada5de4039e6f216b45b3c9b683b83bfa"), 321 - ) 322 - c.Assert(err, Equals, storage.ErrReferenceHasChanged) 323 - 324 - e, err := s.Storer.Reference(plumbing.ReferenceName("foo")) 325 - c.Assert(err, IsNil) 326 - c.Assert(e.Hash().String(), Equals, "c3f4688a08fd86f1bf8e055724c84b7a40a09733") 327 - } 328 - 329 - func (s *BaseStorageSuite) TestRemoveReference(c *C) { 330 - err := s.Storer.SetReference( 331 - plumbing.NewReferenceFromStrings("foo", "bc9968d75e48de59f0870ffb71f5e160bbbdcf52"), 332 - ) 333 - c.Assert(err, IsNil) 334 - 335 - err = s.Storer.RemoveReference(plumbing.ReferenceName("foo")) 336 - c.Assert(err, IsNil) 337 - 338 - _, err = s.Storer.Reference(plumbing.ReferenceName("foo")) 339 - c.Assert(err, Equals, plumbing.ErrReferenceNotFound) 340 - } 341 - 342 - func (s *BaseStorageSuite) TestRemoveReferenceNonExistent(c *C) { 343 - err := s.Storer.SetReference( 344 - plumbing.NewReferenceFromStrings("foo", "bc9968d75e48de59f0870ffb71f5e160bbbdcf52"), 345 - ) 346 - c.Assert(err, IsNil) 347 - 348 - err = s.Storer.RemoveReference(plumbing.ReferenceName("nonexistent")) 349 - c.Assert(err, IsNil) 350 - 351 - e, err := s.Storer.Reference(plumbing.ReferenceName("foo")) 352 - c.Assert(err, IsNil) 353 - c.Assert(e.Hash().String(), Equals, "bc9968d75e48de59f0870ffb71f5e160bbbdcf52") 354 - } 355 - 356 - func (s *BaseStorageSuite) TestGetReferenceNotFound(c *C) { 357 - r, err := s.Storer.Reference(plumbing.ReferenceName("bar")) 358 - c.Assert(err, Equals, plumbing.ErrReferenceNotFound) 359 - c.Assert(r, IsNil) 360 - } 361 - 362 - func (s *BaseStorageSuite) TestIterReferences(c *C) { 363 - err := s.Storer.SetReference( 364 - plumbing.NewReferenceFromStrings("refs/foo", "bc9968d75e48de59f0870ffb71f5e160bbbdcf52"), 365 - ) 366 - c.Assert(err, IsNil) 367 - 368 - i, err := s.Storer.IterReferences() 369 - c.Assert(err, IsNil) 370 - 371 - e, err := i.Next() 372 - c.Assert(err, IsNil) 373 - c.Assert(e.Hash().String(), Equals, "bc9968d75e48de59f0870ffb71f5e160bbbdcf52") 374 - 375 - e, err = i.Next() 376 - c.Assert(e, IsNil) 377 - c.Assert(err, Equals, io.EOF) 378 - } 379 - 380 - func (s *BaseStorageSuite) TestSetShallowAndShallow(c *C) { 381 - expected := []plumbing.Hash{ 382 - plumbing.NewHash("b66c08ba28aa1f81eb06a1127aa3936ff77e5e2c"), 383 - plumbing.NewHash("c3f4688a08fd86f1bf8e055724c84b7a40a09733"), 384 - plumbing.NewHash("c78874f116be67ecf54df225a613162b84cc6ebf"), 385 - } 386 - 387 - err := s.Storer.SetShallow(expected) 388 - c.Assert(err, IsNil) 389 - 390 - result, err := s.Storer.Shallow() 391 - c.Assert(err, IsNil) 392 - c.Assert(result, DeepEquals, expected) 393 - } 394 - 395 - func (s *BaseStorageSuite) TestSetConfigAndConfig(c *C) { 396 - expected := config.NewConfig() 397 - expected.Core.IsBare = true 398 - expected.Remotes["foo"] = &config.RemoteConfig{ 399 - Name: "foo", 400 - URLs: []string{"http://foo/bar.git"}, 401 - } 402 - 403 - err := s.Storer.SetConfig(expected) 404 - c.Assert(err, IsNil) 405 - 406 - cfg, err := s.Storer.Config() 407 - c.Assert(err, IsNil) 408 - 409 - c.Assert(cfg.Core.IsBare, DeepEquals, expected.Core.IsBare) 410 - c.Assert(cfg.Remotes, DeepEquals, expected.Remotes) 411 - } 412 - 413 - func (s *BaseStorageSuite) TestIndex(c *C) { 414 - expected := &index.Index{} 415 - expected.Version = 2 416 - 417 - idx, err := s.Storer.Index() 418 - c.Assert(err, IsNil) 419 - c.Assert(idx, DeepEquals, expected) 420 - } 421 - 422 - func (s *BaseStorageSuite) TestSetIndexAndIndex(c *C) { 423 - expected := &index.Index{} 424 - expected.Version = 2 425 - 426 - err := s.Storer.SetIndex(expected) 427 - c.Assert(err, IsNil) 428 - 429 - idx, err := s.Storer.Index() 430 - c.Assert(err, IsNil) 431 - c.Assert(idx, DeepEquals, expected) 432 - } 433 - 434 - func (s *BaseStorageSuite) TestSetConfigInvalid(c *C) { 435 - cfg := config.NewConfig() 436 - cfg.Remotes["foo"] = &config.RemoteConfig{} 437 - 438 - err := s.Storer.SetConfig(cfg) 439 - c.Assert(err, NotNil) 440 - } 441 - 442 - func (s *BaseStorageSuite) TestModule(c *C) { 443 - storer, err := s.Storer.Module("foo") 444 - c.Assert(err, IsNil) 445 - c.Assert(storer, NotNil) 446 - 447 - storer, err = s.Storer.Module("foo") 448 - c.Assert(err, IsNil) 449 - c.Assert(storer, NotNil) 450 - } 451 - 452 - func (s *BaseStorageSuite) TestDeltaObjectStorer(c *C) { 453 - dos, ok := s.Storer.(storer.DeltaObjectStorer) 454 - if !ok { 455 - c.Skip("not an DeltaObjectStorer") 456 - } 457 - 458 - pwr, ok := s.Storer.(storer.PackfileWriter) 459 - if !ok { 460 - c.Skip("not a storer.PackWriter") 461 - } 462 - 463 - pw, err := pwr.PackfileWriter() 464 - c.Assert(err, IsNil) 465 - 466 - f := fixtures.Basic().One() 467 - _, err = io.Copy(pw, f.Packfile()) 468 - c.Assert(err, IsNil) 469 - 470 - err = pw.Close() 471 - c.Assert(err, IsNil) 472 - 473 - h := plumbing.NewHash("32858aad3c383ed1ff0a0f9bdf231d54a00c9e88") 474 - obj, err := dos.DeltaObject(plumbing.AnyObject, h) 475 - c.Assert(err, IsNil) 476 - c.Assert(obj.Type(), Equals, plumbing.BlobObject) 477 - 478 - h = plumbing.NewHash("aa9b383c260e1d05fbbf6b30a02914555e20c725") 479 - obj, err = dos.DeltaObject(plumbing.AnyObject, h) 480 - c.Assert(err, IsNil) 481 - c.Assert(obj.Type(), Equals, plumbing.OFSDeltaObject) 482 - _, ok = obj.(plumbing.DeltaObject) 483 - c.Assert(ok, Equals, true) 484 - } 485 - 486 - func objectEquals(a plumbing.EncodedObject, b plumbing.EncodedObject) error { 487 - ha := a.Hash() 488 - hb := b.Hash() 489 - if ha != hb { 490 - return fmt.Errorf("hashes do not match: %s != %s", 491 - ha.String(), hb.String()) 492 - } 493 - 494 - ra, err := a.Reader() 495 - if err != nil { 496 - return fmt.Errorf("can't get reader on a: %q", err) 497 - } 498 - 499 - rb, err := b.Reader() 500 - if err != nil { 501 - return fmt.Errorf("can't get reader on b: %q", err) 502 - } 503 - 504 - ca, err := io.ReadAll(ra) 505 - if err != nil { 506 - return fmt.Errorf("error reading a: %q", err) 507 - } 508 - 509 - cb, err := io.ReadAll(rb) 510 - if err != nil { 511 - return fmt.Errorf("error reading b: %q", err) 512 - } 513 - 514 - if hex.EncodeToString(ca) != hex.EncodeToString(cb) { 515 - return errors.New("content does not match") 516 - } 517 - 518 - err = rb.Close() 519 - if err != nil { 520 - return fmt.Errorf("can't close reader on b: %q", err) 521 - } 522 - 523 - err = ra.Close() 524 - if err != nil { 525 - return fmt.Errorf("can't close reader on a: %q", err) 526 - } 527 - 528 - return nil 529 - }
+598
storage/tests/storage_test.go
··· 1 + package tests 2 + 3 + import ( 4 + "fmt" 5 + "io" 6 + "testing" 7 + 8 + "github.com/go-git/go-billy/v5/memfs" 9 + "github.com/go-git/go-billy/v5/osfs" 10 + fixtures "github.com/go-git/go-git-fixtures/v5" 11 + "github.com/go-git/go-git/v5/config" 12 + "github.com/go-git/go-git/v5/plumbing" 13 + "github.com/go-git/go-git/v5/plumbing/cache" 14 + "github.com/go-git/go-git/v5/plumbing/format/index" 15 + "github.com/go-git/go-git/v5/plumbing/storer" 16 + "github.com/go-git/go-git/v5/storage" 17 + "github.com/go-git/go-git/v5/storage/filesystem" 18 + "github.com/go-git/go-git/v5/storage/memory" 19 + "github.com/go-git/go-git/v5/storage/transactional" 20 + "github.com/stretchr/testify/assert" 21 + "github.com/stretchr/testify/require" 22 + ) 23 + 24 + type Storer interface { 25 + storer.EncodedObjectStorer 26 + storer.ReferenceStorer 27 + storer.ShallowStorer 28 + storer.IndexStorer 29 + config.ConfigStorer 30 + storage.ModuleStorer 31 + } 32 + 33 + type TestObject struct { 34 + Object plumbing.EncodedObject 35 + Hash string 36 + Type plumbing.ObjectType 37 + } 38 + 39 + func testObjects() map[plumbing.ObjectType]TestObject { 40 + commit := &plumbing.MemoryObject{} 41 + commit.SetType(plumbing.CommitObject) 42 + tree := &plumbing.MemoryObject{} 43 + tree.SetType(plumbing.TreeObject) 44 + blob := &plumbing.MemoryObject{} 45 + blob.SetType(plumbing.BlobObject) 46 + tag := &plumbing.MemoryObject{} 47 + tag.SetType(plumbing.TagObject) 48 + 49 + return map[plumbing.ObjectType]TestObject{ 50 + plumbing.CommitObject: {commit, "dcf5b16e76cce7425d0beaef62d79a7d10fce1f5", plumbing.CommitObject}, 51 + plumbing.TreeObject: {tree, "4b825dc642cb6eb9a060e54bf8d69288fbee4904", plumbing.TreeObject}, 52 + plumbing.BlobObject: {blob, "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391", plumbing.BlobObject}, 53 + plumbing.TagObject: {tag, "d994c6bb648123a17e8f70a966857c546b2a6f94", plumbing.TagObject}, 54 + } 55 + } 56 + 57 + func validTypes() []plumbing.ObjectType { 58 + return []plumbing.ObjectType{ 59 + plumbing.CommitObject, 60 + plumbing.BlobObject, 61 + plumbing.TagObject, 62 + plumbing.TreeObject, 63 + } 64 + } 65 + 66 + var storageFactories = []func(t *testing.T) (Storer, string){ 67 + func(_ *testing.T) (Storer, string) { return memory.NewStorage(), "memory" }, 68 + func(t *testing.T) (Storer, string) { 69 + return filesystem.NewStorage(osfs.New(t.TempDir()), nil), "filesystem" 70 + }, 71 + func(t *testing.T) (Storer, string) { 72 + temporal := filesystem.NewStorage(memfs.New(), cache.NewObjectLRUDefault()) 73 + base := memory.NewStorage() 74 + 75 + return transactional.NewStorage(base, temporal), "transactional" 76 + }, 77 + } 78 + 79 + func forEachStorage(t *testing.T, tc func(sto Storer, t *testing.T)) { 80 + for _, factory := range storageFactories { 81 + sto, name := factory(t) 82 + 83 + t.Run(name, func(t *testing.T) { 84 + tc(sto, t) 85 + }) 86 + } 87 + } 88 + 89 + func TestPackfileWriter(t *testing.T) { 90 + t.Parallel() 91 + 92 + forEachStorage(t, func(sto Storer, t *testing.T) { 93 + pwr, ok := sto.(storer.PackfileWriter) 94 + if !ok { 95 + t.Skip("not a PackfileWriter") 96 + } 97 + 98 + pw, err := pwr.PackfileWriter() 99 + assert.NoError(t, err) 100 + 101 + f := fixtures.Basic().One() 102 + _, err = io.Copy(pw, f.Packfile()) 103 + assert.NoError(t, err) 104 + 105 + err = pw.Close() 106 + assert.NoError(t, err) 107 + 108 + iter, err := sto.IterEncodedObjects(plumbing.AnyObject) 109 + assert.NoError(t, err) 110 + objects := 0 111 + 112 + err = iter.ForEach(func(plumbing.EncodedObject) error { 113 + objects++ 114 + return nil 115 + }) 116 + 117 + assert.NoError(t, err) 118 + assert.Equal(t, 31, objects) 119 + }) 120 + } 121 + 122 + func TestDeltaObjectStorer(t *testing.T) { 123 + t.Parallel() 124 + 125 + forEachStorage(t, func(sto Storer, t *testing.T) { 126 + dos, ok := sto.(storer.DeltaObjectStorer) 127 + if !ok { 128 + t.Skip("not an DeltaObjectStorer") 129 + } 130 + 131 + pwr, ok := sto.(storer.PackfileWriter) 132 + if !ok { 133 + t.Skip("not a storer.PackWriter") 134 + } 135 + 136 + pw, err := pwr.PackfileWriter() 137 + require.NoError(t, err) 138 + 139 + f := fixtures.Basic().One() 140 + _, err = io.Copy(pw, f.Packfile()) 141 + require.NoError(t, err) 142 + 143 + err = pw.Close() 144 + require.NoError(t, err) 145 + 146 + h := plumbing.NewHash("32858aad3c383ed1ff0a0f9bdf231d54a00c9e88") 147 + obj, err := dos.DeltaObject(plumbing.AnyObject, h) 148 + require.NoError(t, err) 149 + assert.Equal(t, plumbing.BlobObject, obj.Type()) 150 + 151 + h = plumbing.NewHash("aa9b383c260e1d05fbbf6b30a02914555e20c725") 152 + obj, err = dos.DeltaObject(plumbing.AnyObject, h) 153 + require.NoError(t, err) 154 + assert.Equal(t, plumbing.OFSDeltaObject.String(), obj.Type().String()) 155 + 156 + _, ok = obj.(plumbing.DeltaObject) 157 + assert.True(t, ok) 158 + }) 159 + } 160 + 161 + func TestSetEncodedObjectAndEncodedObject(t *testing.T) { 162 + t.Parallel() 163 + 164 + forEachStorage(t, func(sto Storer, t *testing.T) { 165 + for _, to := range testObjects() { 166 + comment := fmt.Sprintf("failed for type %s", to.Type.String()) 167 + 168 + h, err := sto.SetEncodedObject(to.Object) 169 + require.NoError(t, err) 170 + require.Equal(t, to.Hash, h.String(), comment) 171 + 172 + o, err := sto.EncodedObject(to.Type, h) 173 + require.NoError(t, err) 174 + assert.Equal(t, to.Object, o) 175 + 176 + o, err = sto.EncodedObject(plumbing.AnyObject, h) 177 + require.NoError(t, err) 178 + assert.Equal(t, to.Object, o) 179 + 180 + for _, typ := range validTypes() { 181 + if typ == to.Type { 182 + continue 183 + } 184 + 185 + o, err = sto.EncodedObject(typ, h) 186 + assert.Nil(t, o) 187 + assert.ErrorIs(t, err, plumbing.ErrObjectNotFound) 188 + } 189 + } 190 + }) 191 + } 192 + 193 + func TestSetEncodedObjectInvalid(t *testing.T) { 194 + t.Parallel() 195 + 196 + forEachStorage(t, func(sto Storer, t *testing.T) { 197 + o := sto.NewEncodedObject() 198 + o.SetType(plumbing.REFDeltaObject) 199 + 200 + _, err := sto.SetEncodedObject(o) 201 + assert.Error(t, err) 202 + }) 203 + } 204 + 205 + func TestIterEncodedObjects(t *testing.T) { 206 + t.Parallel() 207 + 208 + forEachStorage(t, func(sto Storer, t *testing.T) { 209 + objs := testObjects() 210 + for _, o := range objs { 211 + h, err := sto.SetEncodedObject(o.Object) 212 + require.NoError(t, err) 213 + assert.Equal(t, o.Object.Hash(), h) 214 + } 215 + 216 + for _, typ := range validTypes() { 217 + comment := fmt.Sprintf("failed for type %s)", typ.String()) 218 + i, err := sto.IterEncodedObjects(typ) 219 + require.NoError(t, err, comment) 220 + 221 + o, err := i.Next() 222 + require.NoError(t, err) 223 + assert.Equal(t, objs[typ].Object, o) 224 + 225 + o, err = i.Next() 226 + assert.Nil(t, o) 227 + assert.ErrorIs(t, err, io.EOF, comment) 228 + } 229 + 230 + i, err := sto.IterEncodedObjects(plumbing.AnyObject) 231 + require.NoError(t, err) 232 + 233 + foundObjects := []plumbing.EncodedObject{} 234 + i.ForEach(func(o plumbing.EncodedObject) error { 235 + foundObjects = append(foundObjects, o) 236 + return nil 237 + }) 238 + 239 + assert.Len(t, foundObjects, len(testObjects())) 240 + for _, to := range testObjects() { 241 + found := false 242 + for _, o := range foundObjects { 243 + if to.Object.Hash() == o.Hash() { 244 + found = true 245 + break 246 + } 247 + } 248 + assert.True(t, found, "Object of type %s not found", to.Type.String()) 249 + } 250 + }) 251 + } 252 + 253 + func TestObjectStorerTxSetEncodedObjectAndCommit(t *testing.T) { 254 + t.Parallel() 255 + 256 + forEachStorage(t, func(sto Storer, t *testing.T) { 257 + storer, ok := sto.(storer.Transactioner) 258 + if !ok { 259 + t.Skip("not a plumbing.ObjectStorerTx") 260 + } 261 + 262 + tx := storer.Begin() 263 + for _, o := range testObjects() { 264 + h, err := tx.SetEncodedObject(o.Object) 265 + require.NoError(t, err) 266 + assert.Equal(t, o.Hash, h.String()) 267 + } 268 + 269 + iter, err := sto.IterEncodedObjects(plumbing.AnyObject) 270 + require.NoError(t, err) 271 + _, err = iter.Next() 272 + assert.ErrorIs(t, err, io.EOF) 273 + 274 + err = tx.Commit() 275 + require.NoError(t, err) 276 + 277 + iter, err = sto.IterEncodedObjects(plumbing.AnyObject) 278 + require.NoError(t, err) 279 + 280 + var count int 281 + iter.ForEach(func(o plumbing.EncodedObject) error { 282 + count++ 283 + return nil 284 + }) 285 + 286 + assert.Equal(t, 4, count) 287 + }) 288 + } 289 + 290 + func TestObjectStorerTxSetObjectAndGetObject(t *testing.T) { 291 + t.Parallel() 292 + 293 + forEachStorage(t, func(sto Storer, t *testing.T) { 294 + storer, ok := sto.(storer.Transactioner) 295 + if !ok { 296 + t.Skip("not a plumbing.ObjectStorerTx") 297 + } 298 + 299 + tx := storer.Begin() 300 + for _, expected := range testObjects() { 301 + h, err := tx.SetEncodedObject(expected.Object) 302 + require.NoError(t, err) 303 + assert.Equal(t, expected.Hash, h.String()) 304 + 305 + o, err := tx.EncodedObject(expected.Type, plumbing.NewHash(expected.Hash)) 306 + require.NoError(t, err) 307 + assert.Equal(t, expected.Hash, o.Hash().String()) 308 + } 309 + }) 310 + } 311 + 312 + func TestObjectStorerTxGetObjectNotFound(t *testing.T) { 313 + t.Parallel() 314 + 315 + forEachStorage(t, func(sto Storer, t *testing.T) { 316 + storer, ok := sto.(storer.Transactioner) 317 + if !ok { 318 + t.Skip("not a plumbing.ObjectStorerTx") 319 + } 320 + 321 + tx := storer.Begin() 322 + o, err := tx.EncodedObject(plumbing.AnyObject, plumbing.ZeroHash) 323 + assert.Nil(t, o) 324 + assert.ErrorIs(t, err, plumbing.ErrObjectNotFound) 325 + }) 326 + } 327 + 328 + func TestObjectStorerTxSetObjectAndRollback(t *testing.T) { 329 + t.Parallel() 330 + 331 + forEachStorage(t, func(sto Storer, t *testing.T) { 332 + storer, ok := sto.(storer.Transactioner) 333 + if !ok { 334 + t.Skip("not a plumbing.ObjectStorerTx") 335 + } 336 + 337 + tx := storer.Begin() 338 + for _, o := range testObjects() { 339 + h, err := tx.SetEncodedObject(o.Object) 340 + require.NoError(t, err) 341 + assert.Equal(t, o.Hash, h.String()) 342 + } 343 + 344 + err := tx.Rollback() 345 + require.NoError(t, err) 346 + 347 + iter, err := sto.IterEncodedObjects(plumbing.AnyObject) 348 + require.NoError(t, err) 349 + _, err = iter.Next() 350 + assert.ErrorIs(t, err, io.EOF) 351 + }) 352 + } 353 + 354 + func TestSetReferenceAndGetReference(t *testing.T) { 355 + t.Parallel() 356 + 357 + forEachStorage(t, func(sto Storer, t *testing.T) { 358 + err := sto.SetReference( 359 + plumbing.NewReferenceFromStrings("foo", "bc9968d75e48de59f0870ffb71f5e160bbbdcf52"), 360 + ) 361 + require.NoError(t, err) 362 + 363 + err = sto.SetReference( 364 + plumbing.NewReferenceFromStrings("bar", "482e0eada5de4039e6f216b45b3c9b683b83bfa"), 365 + ) 366 + require.NoError(t, err) 367 + 368 + e, err := sto.Reference(plumbing.ReferenceName("foo")) 369 + require.NoError(t, err) 370 + assert.Equal(t, e.Hash().String(), "bc9968d75e48de59f0870ffb71f5e160bbbdcf52") 371 + }) 372 + } 373 + 374 + func TestCheckAndSetReference(t *testing.T) { 375 + t.Parallel() 376 + 377 + forEachStorage(t, func(sto Storer, t *testing.T) { 378 + err := sto.SetReference( 379 + plumbing.NewReferenceFromStrings("foo", "482e0eada5de4039e6f216b45b3c9b683b83bfa"), 380 + ) 381 + require.NoError(t, err) 382 + 383 + err = sto.CheckAndSetReference( 384 + plumbing.NewReferenceFromStrings("foo", "bc9968d75e48de59f0870ffb71f5e160bbbdcf52"), 385 + plumbing.NewReferenceFromStrings("foo", "482e0eada5de4039e6f216b45b3c9b683b83bfa"), 386 + ) 387 + require.NoError(t, err) 388 + 389 + e, err := sto.Reference(plumbing.ReferenceName("foo")) 390 + require.NoError(t, err) 391 + assert.Equal(t, e.Hash().String(), "bc9968d75e48de59f0870ffb71f5e160bbbdcf52") 392 + }) 393 + } 394 + 395 + func TestCheckAndSetReferenceNil(t *testing.T) { 396 + t.Parallel() 397 + 398 + forEachStorage(t, func(sto Storer, t *testing.T) { 399 + err := sto.SetReference( 400 + plumbing.NewReferenceFromStrings("foo", "482e0eada5de4039e6f216b45b3c9b683b83bfa"), 401 + ) 402 + require.NoError(t, err) 403 + 404 + err = sto.CheckAndSetReference( 405 + plumbing.NewReferenceFromStrings("foo", "bc9968d75e48de59f0870ffb71f5e160bbbdcf52"), 406 + nil, 407 + ) 408 + require.NoError(t, err) 409 + 410 + e, err := sto.Reference(plumbing.ReferenceName("foo")) 411 + require.NoError(t, err) 412 + assert.Equal(t, e.Hash().String(), "bc9968d75e48de59f0870ffb71f5e160bbbdcf52") 413 + }) 414 + } 415 + 416 + func TestCheckAndSetReferenceError(t *testing.T) { 417 + t.Parallel() 418 + 419 + forEachStorage(t, func(sto Storer, t *testing.T) { 420 + err := sto.SetReference( 421 + plumbing.NewReferenceFromStrings("foo", "c3f4688a08fd86f1bf8e055724c84b7a40a09733"), 422 + ) 423 + require.NoError(t, err) 424 + 425 + err = sto.CheckAndSetReference( 426 + plumbing.NewReferenceFromStrings("foo", "bc9968d75e48de59f0870ffb71f5e160bbbdcf52"), 427 + plumbing.NewReferenceFromStrings("foo", "482e0eada5de4039e6f216b45b3c9b683b83bfa"), 428 + ) 429 + assert.ErrorIs(t, err, storage.ErrReferenceHasChanged) 430 + 431 + e, err := sto.Reference(plumbing.ReferenceName("foo")) 432 + require.NoError(t, err) 433 + assert.Equal(t, e.Hash().String(), "c3f4688a08fd86f1bf8e055724c84b7a40a09733") 434 + }) 435 + } 436 + 437 + func TestRemoveReference(t *testing.T) { 438 + t.Parallel() 439 + 440 + forEachStorage(t, func(sto Storer, t *testing.T) { 441 + err := sto.SetReference( 442 + plumbing.NewReferenceFromStrings("foo", "bc9968d75e48de59f0870ffb71f5e160bbbdcf52"), 443 + ) 444 + require.NoError(t, err) 445 + 446 + err = sto.RemoveReference(plumbing.ReferenceName("foo")) 447 + require.NoError(t, err) 448 + 449 + _, err = sto.Reference(plumbing.ReferenceName("foo")) 450 + assert.ErrorIs(t, err, plumbing.ErrReferenceNotFound) 451 + }) 452 + } 453 + 454 + func TestRemoveReferenceNonExistent(t *testing.T) { 455 + t.Parallel() 456 + 457 + forEachStorage(t, func(sto Storer, t *testing.T) { 458 + err := sto.SetReference( 459 + plumbing.NewReferenceFromStrings("foo", "bc9968d75e48de59f0870ffb71f5e160bbbdcf52"), 460 + ) 461 + require.NoError(t, err) 462 + 463 + err = sto.RemoveReference(plumbing.ReferenceName("nonexistent")) 464 + require.NoError(t, err) 465 + 466 + e, err := sto.Reference(plumbing.ReferenceName("foo")) 467 + require.NoError(t, err) 468 + assert.Equal(t, "bc9968d75e48de59f0870ffb71f5e160bbbdcf52", e.Hash().String()) 469 + }) 470 + } 471 + 472 + func TestGetReferenceNotFound(t *testing.T) { 473 + t.Parallel() 474 + 475 + forEachStorage(t, func(sto Storer, t *testing.T) { 476 + r, err := sto.Reference(plumbing.ReferenceName("bar")) 477 + assert.ErrorIs(t, err, plumbing.ErrReferenceNotFound) 478 + assert.Nil(t, r) 479 + }) 480 + } 481 + 482 + func TestIterReferences(t *testing.T) { 483 + t.Parallel() 484 + 485 + forEachStorage(t, func(sto Storer, t *testing.T) { 486 + err := sto.SetReference( 487 + plumbing.NewReferenceFromStrings("refs/foo", "bc9968d75e48de59f0870ffb71f5e160bbbdcf52"), 488 + ) 489 + require.NoError(t, err) 490 + 491 + i, err := sto.IterReferences() 492 + require.NoError(t, err) 493 + 494 + e, err := i.Next() 495 + require.NoError(t, err) 496 + assert.Equal(t, e.Hash().String(), "bc9968d75e48de59f0870ffb71f5e160bbbdcf52") 497 + 498 + e, err = i.Next() 499 + assert.Nil(t, e) 500 + assert.ErrorIs(t, err, io.EOF) 501 + }) 502 + } 503 + 504 + func TestSetShallowAndShallow(t *testing.T) { 505 + t.Parallel() 506 + 507 + forEachStorage(t, func(sto Storer, t *testing.T) { 508 + expected := []plumbing.Hash{ 509 + plumbing.NewHash("b66c08ba28aa1f81eb06a1127aa3936ff77e5e2c"), 510 + plumbing.NewHash("c3f4688a08fd86f1bf8e055724c84b7a40a09733"), 511 + plumbing.NewHash("c78874f116be67ecf54df225a613162b84cc6ebf"), 512 + } 513 + 514 + err := sto.SetShallow(expected) 515 + require.NoError(t, err) 516 + 517 + result, err := sto.Shallow() 518 + require.NoError(t, err) 519 + assert.Equal(t, expected, result) 520 + }) 521 + } 522 + 523 + func TestSetConfigAndConfig(t *testing.T) { 524 + t.Parallel() 525 + 526 + forEachStorage(t, func(sto Storer, t *testing.T) { 527 + expected := config.NewConfig() 528 + expected.Core.IsBare = true 529 + expected.Remotes["foo"] = &config.RemoteConfig{ 530 + Name: "foo", 531 + URLs: []string{"http://foo/bar.git"}, 532 + } 533 + 534 + err := sto.SetConfig(expected) 535 + require.NoError(t, err) 536 + 537 + cfg, err := sto.Config() 538 + require.NoError(t, err) 539 + 540 + assert.Equal(t, expected.Core.IsBare, cfg.Core.IsBare) 541 + assert.Equal(t, expected.Remotes, cfg.Remotes) 542 + }) 543 + } 544 + 545 + func TestIndex(t *testing.T) { 546 + t.Parallel() 547 + 548 + forEachStorage(t, func(sto Storer, t *testing.T) { 549 + expected := &index.Index{} 550 + expected.Version = 2 551 + 552 + idx, err := sto.Index() 553 + assert.NoError(t, err) 554 + assert.Equal(t, expected, idx) 555 + }) 556 + } 557 + 558 + func TestSetIndexAndIndex(t *testing.T) { 559 + t.Parallel() 560 + 561 + forEachStorage(t, func(sto Storer, t *testing.T) { 562 + expected := &index.Index{} 563 + expected.Version = 2 564 + 565 + err := sto.SetIndex(expected) 566 + require.NoError(t, err) 567 + 568 + idx, err := sto.Index() 569 + require.NoError(t, err) 570 + assert.Equal(t, expected, idx) 571 + }) 572 + } 573 + 574 + func TestSetConfigInvalid(t *testing.T) { 575 + t.Parallel() 576 + 577 + forEachStorage(t, func(sto Storer, t *testing.T) { 578 + cfg := config.NewConfig() 579 + cfg.Remotes["foo"] = &config.RemoteConfig{} 580 + 581 + err := sto.SetConfig(cfg) 582 + assert.Error(t, err) 583 + }) 584 + } 585 + 586 + func TestModule(t *testing.T) { 587 + t.Parallel() 588 + 589 + forEachStorage(t, func(sto Storer, t *testing.T) { 590 + storer, err := sto.Module("foo") 591 + require.NoError(t, err) 592 + assert.NotNil(t, storer) 593 + 594 + storer, err = sto.Module("foo") 595 + require.NoError(t, err) 596 + assert.NotNil(t, storer) 597 + }) 598 + }
+17 -41
storage/transactional/storage_test.go
··· 10 10 "github.com/go-git/go-git/v5/storage" 11 11 "github.com/go-git/go-git/v5/storage/filesystem" 12 12 "github.com/go-git/go-git/v5/storage/memory" 13 - "github.com/go-git/go-git/v5/storage/test" 14 - . "gopkg.in/check.v1" 13 + "github.com/stretchr/testify/assert" 14 + "github.com/stretchr/testify/require" 15 15 ) 16 16 17 - func Test(t *testing.T) { TestingT(t) } 18 - 19 - type StorageSuite struct { 20 - test.BaseStorageSuite 21 - temporal func() storage.Storer 22 - } 23 - 24 - var _ = Suite(&StorageSuite{ 25 - temporal: func() storage.Storer { 26 - return memory.NewStorage() 27 - }, 28 - }) 29 - 30 - var _ = Suite(&StorageSuite{ 31 - temporal: func() storage.Storer { 32 - fs := memfs.New() 33 - return filesystem.NewStorage(fs, cache.NewObjectLRUDefault()) 34 - }, 35 - }) 36 - 37 - func (s *StorageSuite) SetUpTest(c *C) { 38 - base := memory.NewStorage() 39 - temporal := s.temporal() 40 - 41 - s.BaseStorageSuite = test.NewBaseStorageSuite(NewStorage(base, temporal)) 42 - } 43 - 44 - func (s *StorageSuite) TestCommit(c *C) { 17 + func TestCommit(t *testing.T) { 45 18 base := memory.NewStorage() 46 - temporal := s.temporal() 19 + temporal := filesystem.NewStorage(memfs.New(), cache.NewObjectLRUDefault()) 47 20 st := NewStorage(base, temporal) 48 21 49 22 commit := base.NewEncodedObject() 50 23 commit.SetType(plumbing.CommitObject) 51 24 52 25 _, err := st.SetEncodedObject(commit) 53 - c.Assert(err, IsNil) 26 + require.NoError(t, err) 54 27 55 28 ref := plumbing.NewHashReference("refs/a", commit.Hash()) 56 - c.Assert(st.SetReference(ref), IsNil) 29 + require.NoError(t, st.SetReference(ref)) 57 30 58 31 err = st.Commit() 59 - c.Assert(err, IsNil) 32 + require.NoError(t, err) 60 33 61 34 ref, err = base.Reference(ref.Name()) 62 - c.Assert(err, IsNil) 63 - c.Assert(ref.Hash(), Equals, commit.Hash()) 35 + require.NoError(t, err) 36 + assert.Equal(t, commit.Hash(), ref.Hash()) 64 37 65 38 obj, err := base.EncodedObject(plumbing.AnyObject, commit.Hash()) 66 - c.Assert(err, IsNil) 67 - c.Assert(obj.Hash(), Equals, commit.Hash()) 39 + require.NoError(t, err) 40 + assert.Equal(t, commit.Hash(), obj.Hash()) 68 41 } 69 42 70 - func (s *StorageSuite) TestTransactionalPackfileWriter(c *C) { 43 + func TestTransactionalPackfileWriter(t *testing.T) { 71 44 base := memory.NewStorage() 72 - temporal := s.temporal() 45 + var temporal storage.Storer 46 + 47 + temporal = filesystem.NewStorage(memfs.New(), cache.NewObjectLRUDefault()) 48 + 73 49 st := NewStorage(base, temporal) 74 50 75 51 _, tmpOK := temporal.(storer.PackfileWriter) 76 52 _, ok := st.(storer.PackfileWriter) 77 - c.Assert(ok, Equals, tmpOK) 53 + assert.Equal(t, tmpOK, ok) 78 54 }
+3
utils/ioutil/common.go
··· 89 89 } 90 90 91 91 func (r *writeCloser) Close() error { 92 + if r.closer == nil { 93 + return nil 94 + } 92 95 return r.closer.Close() 93 96 } 94 97
+17 -4
utils/sync/zlib.go
··· 11 11 zlibInitBytes = []byte{0x78, 0x9c, 0x01, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01} 12 12 zlibReader = sync.Pool{ 13 13 New: func() interface{} { 14 - r, _ := zlib.NewReader(bytes.NewReader(zlibInitBytes)) 15 - return ZLibReader{ 16 - Reader: r.(zlibReadCloser), 17 - } 14 + return NewZlibReader(nil) 18 15 }, 19 16 } 20 17 zlibWriter = sync.Pool{ ··· 29 26 zlib.Resetter 30 27 } 31 28 29 + func NewZlibReader(dict *[]byte) ZLibReader { 30 + r, _ := zlib.NewReader(bytes.NewReader(zlibInitBytes)) 31 + return ZLibReader{ 32 + Reader: r.(zlibReadCloser), 33 + dict: dict, 34 + } 35 + } 36 + 32 37 type ZLibReader struct { 33 38 dict *[]byte 34 39 Reader zlibReadCloser 40 + } 41 + 42 + func (z ZLibReader) Reset(r io.Reader) error { 43 + var dict []byte 44 + if z.dict != nil { 45 + dict = *z.dict 46 + } 47 + return z.Reader.Reset(r, dict) 35 48 } 36 49 37 50 // GetZlibReader returns a ZLibReader that is managed by a sync.Pool.
+78 -7
worktree_commit_test.go
··· 8 8 "path/filepath" 9 9 "runtime" 10 10 "strings" 11 + "testing" 11 12 "time" 12 13 14 + fixtures "github.com/go-git/go-git-fixtures/v4" 13 15 "github.com/go-git/go-git/v5/plumbing" 14 16 "github.com/go-git/go-git/v5/plumbing/cache" 15 17 "github.com/go-git/go-git/v5/plumbing/object" 16 18 "github.com/go-git/go-git/v5/plumbing/storer" 17 19 "github.com/go-git/go-git/v5/storage/filesystem" 18 20 "github.com/go-git/go-git/v5/storage/memory" 21 + "github.com/stretchr/testify/assert" 22 + "github.com/stretchr/testify/require" 19 23 20 24 "github.com/ProtonMail/go-crypto/openpgp" 21 25 "github.com/ProtonMail/go-crypto/openpgp/armor" ··· 278 282 c.Assert(amendedHash, Equals, plumbing.ZeroHash) 279 283 } 280 284 281 - func (s *WorktreeSuite) TestAddAndCommitWithSkipStatus(c *C) { 285 + func TestCount(t *testing.T) { 286 + f := fixtures.Basic().One() 287 + r := NewRepositoryWithEmptyWorktree(f) 288 + 289 + iter, err := r.CommitObjects() 290 + require.NoError(t, err) 291 + 292 + count := 0 293 + iter.ForEach(func(c *object.Commit) error { 294 + count++ 295 + return nil 296 + }) 297 + assert.Equal(t, 9, count, "commits mismatch") 298 + 299 + trees, err := r.TreeObjects() 300 + require.NoError(t, err) 301 + 302 + count = 0 303 + trees.ForEach(func(c *object.Tree) error { 304 + count++ 305 + return nil 306 + }) 307 + assert.Equal(t, 12, count, "trees mismatch") 308 + 309 + blobs, err := r.BlobObjects() 310 + require.NoError(t, err) 311 + 312 + count = 0 313 + blobs.ForEach(func(c *object.Blob) error { 314 + count++ 315 + return nil 316 + }) 317 + assert.Equal(t, 10, count, "blobs mismatch") 318 + 319 + objects, err := r.Objects() 320 + require.NoError(t, err) 321 + 322 + count = 0 323 + objects.ForEach(func(c object.Object) error { 324 + count++ 325 + return nil 326 + }) 327 + assert.Equal(t, 31, count, "objects mismatch") 328 + } 329 + 330 + func TestAddAndCommitWithSkipStatus(t *testing.T) { 282 331 expected := plumbing.NewHash("375a3808ffde7f129cdd3c8c252fd0fe37cfd13b") 283 332 333 + f := fixtures.Basic().One() 284 334 fs := memfs.New() 335 + r := NewRepositoryWithEmptyWorktree(f) 285 336 w := &Worktree{ 286 - r: s.Repository, 337 + r: r, 287 338 Filesystem: fs, 288 339 } 289 340 290 341 err := w.Checkout(&CheckoutOptions{}) 291 - c.Assert(err, IsNil) 342 + require.NoError(t, err) 292 343 293 344 util.WriteFile(fs, "LICENSE", []byte("foo"), 0644) 294 345 util.WriteFile(fs, "foo", []byte("foo"), 0644) ··· 297 348 Path: "foo", 298 349 SkipStatus: true, 299 350 }) 300 - c.Assert(err, IsNil) 351 + require.NoError(t, err) 301 352 302 353 hash, err := w.Commit("commit foo only\n", &CommitOptions{ 303 354 Author: defaultSignature(), 304 355 }) 305 356 306 - c.Assert(hash, Equals, expected) 307 - c.Assert(err, IsNil) 357 + assert.Equal(t, expected.String(), hash.String()) 358 + require.NoError(t, err) 308 359 309 - assertStorageStatus(c, s.Repository, 13, 11, 10, expected) 360 + assertStorage(t, r, 13, 11, 10, expected) 361 + } 362 + 363 + func assertStorage( 364 + t *testing.T, r *Repository, 365 + treesCount, blobCount, commitCount int, head plumbing.Hash, 366 + ) { 367 + trees, err := r.Storer.IterEncodedObjects(plumbing.TreeObject) 368 + require.NoError(t, err) 369 + blobs, err := r.Storer.IterEncodedObjects(plumbing.BlobObject) 370 + require.NoError(t, err) 371 + commits, err := r.Storer.IterEncodedObjects(plumbing.CommitObject) 372 + require.NoError(t, err) 373 + 374 + assert.Equal(t, treesCount, lenIterEncodedObjects(trees), "trees count mismatch") 375 + assert.Equal(t, blobCount, lenIterEncodedObjects(blobs), "blobs count mismatch") 376 + assert.Equal(t, commitCount, lenIterEncodedObjects(commits), "commits count mismatch") 377 + 378 + ref, err := r.Head() 379 + require.NoError(t, err) 380 + assert.Equal(t, head.String(), ref.Hash().String()) 310 381 } 311 382 312 383 func (s *WorktreeSuite) TestAddAndCommitWithSkipStatusPathNotModified(c *C) {
+5 -5
worktree_test.go
··· 46 46 47 47 func (s *WorktreeSuite) SetUpTest(c *C) { 48 48 f := fixtures.Basic().One() 49 - s.Repository = s.NewRepositoryWithEmptyWorktree(f) 49 + s.Repository = NewRepositoryWithEmptyWorktree(f) 50 50 } 51 51 52 52 func (s *WorktreeSuite) TestPullCheckout(c *C) { ··· 595 595 596 596 func (s *WorktreeSuite) TestCheckoutSubmodule(c *C) { 597 597 url := "https://github.com/git-fixtures/submodule.git" 598 - r := s.NewRepositoryWithEmptyWorktree(fixtures.ByURL(url).One()) 598 + r := NewRepositoryWithEmptyWorktree(fixtures.ByURL(url).One()) 599 599 600 600 w, err := r.Worktree() 601 601 c.Assert(err, IsNil) ··· 858 858 859 859 func (s *WorktreeSuite) TestCheckoutTag(c *C) { 860 860 f := fixtures.ByTag("tags").One() 861 - r := s.NewRepositoryWithEmptyWorktree(f) 861 + r := NewRepositoryWithEmptyWorktree(f) 862 862 w, err := r.Worktree() 863 863 c.Assert(err, IsNil) 864 864 ··· 895 895 896 896 func (s *WorktreeSuite) TestCheckoutTagHash(c *C) { 897 897 f := fixtures.ByTag("tags").One() 898 - r := s.NewRepositoryWithEmptyWorktree(f) 898 + r := NewRepositoryWithEmptyWorktree(f) 899 899 w, err := r.Worktree() 900 900 c.Assert(err, IsNil) 901 901 ··· 944 944 // checking every commit over the previous commit 945 945 func (s *WorktreeSuite) testCheckoutBisect(c *C, url string) { 946 946 f := fixtures.ByURL(url).One() 947 - r := s.NewRepositoryWithEmptyWorktree(f) 947 + r := NewRepositoryWithEmptyWorktree(f) 948 948 949 949 w, err := r.Worktree() 950 950 c.Assert(err, IsNil)