package packfile
import (
"bytes"
"io"
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/format/idxfile"
"github.com/go-git/go-git/v5/plumbing/hash"
"github.com/go-git/go-git/v5/storage/memory"
"github.com/go-git/go-billy/v5/memfs"
fixtures "github.com/go-git/go-git-fixtures/v4"
. "gopkg.in/check.v1"
)
type EncoderSuite struct {
fixtures.Suite
buf *bytes.Buffer
store *memory.Storage
enc *Encoder
}
var _ = Suite(&EncoderSuite{})
func (s *EncoderSuite) SetUpTest(c *C) {
s.buf = bytes.NewBuffer(nil)
s.store = memory.NewStorage()
s.enc = NewEncoder(s.buf, s.store, false)
}
func (s *EncoderSuite) TestCorrectPackHeader(c *C) {
h, err := s.enc.Encode([]plumbing.Hash{}, 10)
c.Assert(err, IsNil)
hb := [hash.Size]byte(h)
// PACK + VERSION + OBJECTS + HASH
expectedResult := []byte{'P', 'A', 'C', 'K', 0, 0, 0, 2, 0, 0, 0, 0}
expectedResult = append(expectedResult, hb[:]...)
result := s.buf.Bytes()
c.Assert(result, DeepEquals, expectedResult)
}
func (s *EncoderSuite) TestCorrectPackWithOneEmptyObject(c *C) {
o := &plumbing.MemoryObject{}
o.SetType(plumbing.CommitObject)
o.SetSize(0)
_, err := s.store.SetEncodedObject(o)
c.Assert(err, IsNil)
h, err := s.enc.Encode([]plumbing.Hash{o.Hash()}, 10)
c.Assert(err, IsNil)
// PACK + VERSION(2) + OBJECT NUMBER(1)
expectedResult := []byte{'P', 'A', 'C', 'K', 0, 0, 0, 2, 0, 0, 0, 1}
// OBJECT HEADER(TYPE + SIZE)= 0001 0000
expectedResult = append(expectedResult, []byte{16}...)
// Zlib header
expectedResult = append(expectedResult,
[]byte{120, 156, 1, 0, 0, 255, 255, 0, 0, 0, 1}...)
// + HASH
hb := [hash.Size]byte(h)
expectedResult = append(expectedResult, hb[:]...)
result := s.buf.Bytes()
c.Assert(result, DeepEquals, expectedResult)
}
func (s *EncoderSuite) TestMaxObjectSize(c *C) {
o := s.store.NewEncodedObject()
o.SetSize(9223372036854775807)
o.SetType(plumbing.CommitObject)
_, err := s.store.SetEncodedObject(o)
c.Assert(err, IsNil)
hash, err := s.enc.Encode([]plumbing.Hash{o.Hash()}, 10)
c.Assert(err, IsNil)
c.Assert(hash.IsZero(), Not(Equals), true)
}
func (s *EncoderSuite) TestHashNotFound(c *C) {
h, err := s.enc.Encode([]plumbing.Hash{plumbing.NewHash("BAD")}, 10)
c.Assert(h, Equals, plumbing.ZeroHash)
c.Assert(err, NotNil)
c.Assert(err, Equals, plumbing.ErrObjectNotFound)
}
func (s *EncoderSuite) TestDecodeEncodeWithDeltaDecodeREF(c *C) {
s.enc = NewEncoder(s.buf, s.store, true)
s.simpleDeltaTest(c)
}
func (s *EncoderSuite) TestDecodeEncodeWithDeltaDecodeOFS(c *C) {
s.enc = NewEncoder(s.buf, s.store, false)
s.simpleDeltaTest(c)
}
func (s *EncoderSuite) TestDecodeEncodeWithDeltasDecodeREF(c *C) {
s.enc = NewEncoder(s.buf, s.store, true)
s.deltaOverDeltaTest(c)
}
func (s *EncoderSuite) TestDecodeEncodeWithDeltasDecodeOFS(c *C) {
s.enc = NewEncoder(s.buf, s.store, false)
s.deltaOverDeltaTest(c)
}
func (s *EncoderSuite) TestDecodeEncodeWithCycleREF(c *C) {
s.enc = NewEncoder(s.buf, s.store, true)
s.deltaOverDeltaCyclicTest(c)
}
func (s *EncoderSuite) TestDecodeEncodeWithCycleOFS(c *C) {
s.enc = NewEncoder(s.buf, s.store, false)
s.deltaOverDeltaCyclicTest(c)
}
func (s *EncoderSuite) simpleDeltaTest(c *C) {
srcObject := newObject(plumbing.BlobObject, []byte("0"))
targetObject := newObject(plumbing.BlobObject, []byte("01"))
deltaObject, err := GetDelta(srcObject, targetObject)
c.Assert(err, IsNil)
srcToPack := newObjectToPack(srcObject)
encHash, err := s.enc.encode([]*ObjectToPack{
srcToPack,
newDeltaObjectToPack(srcToPack, targetObject, deltaObject),
})
c.Assert(err, IsNil)
p, cleanup := packfileFromReader(c, s.buf)
defer cleanup()
decHash, err := p.ID()
c.Assert(err, IsNil)
c.Assert(encHash, Equals, decHash)
decSrc, err := p.Get(srcObject.Hash())
c.Assert(err, IsNil)
objectsEqual(c, decSrc, srcObject)
decTarget, err := p.Get(targetObject.Hash())
c.Assert(err, IsNil)
objectsEqual(c, decTarget, targetObject)
}
func (s *EncoderSuite) deltaOverDeltaTest(c *C) {
srcObject := newObject(plumbing.BlobObject, []byte("0"))
targetObject := newObject(plumbing.BlobObject, []byte("01"))
otherTargetObject := newObject(plumbing.BlobObject, []byte("011111"))
deltaObject, err := GetDelta(srcObject, targetObject)
c.Assert(err, IsNil)
c.Assert(deltaObject.Hash(), Not(Equals), plumbing.ZeroHash)
otherDeltaObject, err := GetDelta(targetObject, otherTargetObject)
c.Assert(err, IsNil)
c.Assert(otherDeltaObject.Hash(), Not(Equals), plumbing.ZeroHash)
srcToPack := newObjectToPack(srcObject)
targetToPack := newObjectToPack(targetObject)
encHash, err := s.enc.encode([]*ObjectToPack{
targetToPack,
srcToPack,
newDeltaObjectToPack(srcToPack, targetObject, deltaObject),
newDeltaObjectToPack(targetToPack, otherTargetObject, otherDeltaObject),
})
c.Assert(err, IsNil)
p, cleanup := packfileFromReader(c, s.buf)
defer cleanup()
decHash, err := p.ID()
c.Assert(err, IsNil)
c.Assert(encHash, Equals, decHash)
decSrc, err := p.Get(srcObject.Hash())
c.Assert(err, IsNil)
objectsEqual(c, decSrc, srcObject)
decTarget, err := p.Get(targetObject.Hash())
c.Assert(err, IsNil)
objectsEqual(c, decTarget, targetObject)
decOtherTarget, err := p.Get(otherTargetObject.Hash())
c.Assert(err, IsNil)
objectsEqual(c, decOtherTarget, otherTargetObject)
}
func (s *EncoderSuite) deltaOverDeltaCyclicTest(c *C) {
o1 := newObject(plumbing.BlobObject, []byte("0"))
o2 := newObject(plumbing.BlobObject, []byte("01"))
o3 := newObject(plumbing.BlobObject, []byte("011111"))
o4 := newObject(plumbing.BlobObject, []byte("01111100000"))
_, err := s.store.SetEncodedObject(o1)
c.Assert(err, IsNil)
_, err = s.store.SetEncodedObject(o2)
c.Assert(err, IsNil)
_, err = s.store.SetEncodedObject(o3)
c.Assert(err, IsNil)
_, err = s.store.SetEncodedObject(o4)
c.Assert(err, IsNil)
d2, err := GetDelta(o1, o2)
c.Assert(err, IsNil)
d3, err := GetDelta(o4, o3)
c.Assert(err, IsNil)
d4, err := GetDelta(o3, o4)
c.Assert(err, IsNil)
po1 := newObjectToPack(o1)
pd2 := newDeltaObjectToPack(po1, o2, d2)
pd3 := newObjectToPack(o3)
pd4 := newObjectToPack(o4)
pd3.SetDelta(pd4, d3)
pd4.SetDelta(pd3, d4)
// SetOriginal is used by delta selector when generating ObjectToPack.
// It also fills type, hash and size values to be used when Original
// is nil.
po1.SetOriginal(po1.Original)
pd2.SetOriginal(pd2.Original)
pd2.CleanOriginal()
pd3.SetOriginal(pd3.Original)
pd3.CleanOriginal()
pd4.SetOriginal(pd4.Original)
encHash, err := s.enc.encode([]*ObjectToPack{
po1,
pd2,
pd3,
pd4,
})
c.Assert(err, IsNil)
p, cleanup := packfileFromReader(c, s.buf)
defer cleanup()
decHash, err := p.ID()
c.Assert(err, IsNil)
c.Assert(encHash, Equals, decHash)
decSrc, err := p.Get(o1.Hash())
c.Assert(err, IsNil)
objectsEqual(c, decSrc, o1)
decTarget, err := p.Get(o2.Hash())
c.Assert(err, IsNil)
objectsEqual(c, decTarget, o2)
decOtherTarget, err := p.Get(o3.Hash())
c.Assert(err, IsNil)
objectsEqual(c, decOtherTarget, o3)
decAnotherTarget, err := p.Get(o4.Hash())
c.Assert(err, IsNil)
objectsEqual(c, decAnotherTarget, o4)
}
func objectsEqual(c *C, o1, o2 plumbing.EncodedObject) {
c.Assert(o1.Type(), Equals, o2.Type())
c.Assert(o1.Hash(), Equals, o2.Hash())
c.Assert(o1.Size(), Equals, o2.Size())
r1, err := o1.Reader()
c.Assert(err, IsNil)
b1, err := io.ReadAll(r1)
c.Assert(err, IsNil)
r2, err := o2.Reader()
c.Assert(err, IsNil)
b2, err := io.ReadAll(r2)
c.Assert(err, IsNil)
c.Assert(bytes.Compare(b1, b2), Equals, 0)
err = r2.Close()
c.Assert(err, IsNil)
err = r1.Close()
c.Assert(err, IsNil)
}
func packfileFromReader(c *C, buf *bytes.Buffer) (*Packfile, func()) {
fs := memfs.New()
file, err := fs.Create("packfile")
c.Assert(err, IsNil)
_, err = file.Write(buf.Bytes())
c.Assert(err, IsNil)
_, err = file.Seek(0, io.SeekStart)
c.Assert(err, IsNil)
scanner := NewScanner(file)
w := new(idxfile.Writer)
p, err := NewParser(scanner, w)
c.Assert(err, IsNil)
_, err = p.Parse()
c.Assert(err, IsNil)
index, err := w.Index()
c.Assert(err, IsNil)
return NewPackfile(index, fs, file, 0), func() {
c.Assert(file.Close(), IsNil)
}
}