From 950676c36030a8796c0a69a8aae606ff1f448b03 Mon Sep 17 00:00:00 2001 From: Antonio Navarro Perez Date: Fri, 16 Dec 2016 19:30:36 +0100 Subject: packfile: delta selection logic (#182) * packfile: delta selection logic - Implemented logic to assign deltas to objects * Requested changes * Improved tests and fix errors --- plumbing/format/packfile/common_test.go | 36 +++++ plumbing/format/packfile/decoder_test.go | 3 - plumbing/format/packfile/delta_selector.go | 169 ++++++++++++++++++++ plumbing/format/packfile/delta_selector_test.go | 199 ++++++++++++++++++++++++ plumbing/format/packfile/delta_test.go | 16 -- plumbing/format/packfile/diff_delta.go | 21 +-- plumbing/format/packfile/encoder.go | 90 +++++------ plumbing/format/packfile/encoder_test.go | 44 ++---- plumbing/format/packfile/object_pack.go | 6 + 9 files changed, 473 insertions(+), 111 deletions(-) create mode 100644 plumbing/format/packfile/common_test.go create mode 100644 plumbing/format/packfile/delta_selector.go create mode 100644 plumbing/format/packfile/delta_selector_test.go (limited to 'plumbing/format/packfile') diff --git a/plumbing/format/packfile/common_test.go b/plumbing/format/packfile/common_test.go new file mode 100644 index 0000000..387c0d1 --- /dev/null +++ b/plumbing/format/packfile/common_test.go @@ -0,0 +1,36 @@ +package packfile + +import ( + "testing" + + "gopkg.in/src-d/go-git.v4/plumbing" + + . "gopkg.in/check.v1" +) + +func Test(t *testing.T) { TestingT(t) } + +func newObject(t plumbing.ObjectType, cont []byte) plumbing.EncodedObject { + o := plumbing.MemoryObject{} + o.SetType(t) + o.SetSize(int64(len(cont))) + o.Write(cont) + + return &o +} + +type piece struct { + val string + times int +} + +func genBytes(elements []piece) []byte { + var result []byte + for _, e := range elements { + for i := 0; i < e.times; i++ { + result = append(result, e.val...) + } + } + + return result +} diff --git a/plumbing/format/packfile/decoder_test.go b/plumbing/format/packfile/decoder_test.go index fdf4c96..eeb1e3d 100644 --- a/plumbing/format/packfile/decoder_test.go +++ b/plumbing/format/packfile/decoder_test.go @@ -2,7 +2,6 @@ package packfile_test import ( "io" - "testing" "gopkg.in/src-d/go-git.v4/fixtures" "gopkg.in/src-d/go-git.v4/plumbing" @@ -16,8 +15,6 @@ import ( . "gopkg.in/check.v1" ) -func Test(t *testing.T) { TestingT(t) } - type ReaderSuite struct { fixtures.Suite } diff --git a/plumbing/format/packfile/delta_selector.go b/plumbing/format/packfile/delta_selector.go new file mode 100644 index 0000000..a73a209 --- /dev/null +++ b/plumbing/format/packfile/delta_selector.go @@ -0,0 +1,169 @@ +package packfile + +import ( + "sort" + + "gopkg.in/src-d/go-git.v4/plumbing" + "gopkg.in/src-d/go-git.v4/plumbing/storer" +) + +const ( + // deltas based on deltas, how many steps we can do. + // 50 is the default value used in JGit + maxDepth = int64(50) +) + +// applyDelta is the set of object types that we should apply deltas +var applyDelta = map[plumbing.ObjectType]bool{ + plumbing.BlobObject: true, + plumbing.TreeObject: true, +} + +type deltaSelector struct { + storer storer.EncodedObjectStorer +} + +func newDeltaSelector(s storer.EncodedObjectStorer) *deltaSelector { + return &deltaSelector{s} +} + +// ObjectsToPack creates a list of ObjectToPack from the hashes provided, +// creating deltas if it's suitable, using an specific internal logic +func (dw *deltaSelector) ObjectsToPack(hashes []plumbing.Hash) ([]*ObjectToPack, error) { + otp, err := dw.objectsToPack(hashes) + if err != nil { + return nil, err + } + + dw.sort(otp) + + if err := dw.walk(otp); err != nil { + return nil, err + } + + return otp, nil +} + +func (dw *deltaSelector) objectsToPack(hashes []plumbing.Hash) ([]*ObjectToPack, error) { + var objectsToPack []*ObjectToPack + for _, h := range hashes { + o, err := dw.storer.EncodedObject(plumbing.AnyObject, h) + if err != nil { + return nil, err + } + + objectsToPack = append(objectsToPack, newObjectToPack(o)) + } + + return objectsToPack, nil +} + +func (dw *deltaSelector) sort(objectsToPack []*ObjectToPack) { + sort.Sort(byTypeAndSize(objectsToPack)) +} + +func (dw *deltaSelector) walk(objectsToPack []*ObjectToPack) error { + for i := 0; i < len(objectsToPack); i++ { + target := objectsToPack[i] + + // We only want to create deltas from specific types + if !applyDelta[target.Original.Type()] { + continue + } + + for j := i - 1; j >= 0; j-- { + base := objectsToPack[j] + // Objects must use only the same type as their delta base. + if base.Original.Type() != target.Original.Type() { + break + } + + if err := dw.tryToDeltify(base, target); err != nil { + return err + } + } + } + + return nil +} + +func (dw *deltaSelector) tryToDeltify(base, target *ObjectToPack) error { + // If the sizes are radically different, this is a bad pairing. + if target.Original.Size() < base.Original.Size()>>4 { + return nil + } + + msz := dw.deltaSizeLimit( + target.Object.Size(), + base.Depth, + target.Depth, + target.IsDelta(), + ) + + // Nearly impossible to fit useful delta. + if msz <= 8 { + return nil + } + + // If we have to insert a lot to make this work, find another. + if base.Original.Size()-target.Object.Size() > msz { + return nil + } + + // Now we can generate the delta using originals + delta, err := GetDelta(base.Original, target.Original) + if err != nil { + return err + } + + // if delta better than target + if delta.Size() < msz { + target.SetDelta(base, delta) + } + + return nil +} + +func (dw *deltaSelector) deltaSizeLimit(targetSize int64, baseDepth int, + targetDepth int, targetDelta bool) int64 { + if !targetDelta { + // Any delta should be no more than 50% of the original size + // (for text files deflate of whole form should shrink 50%). + n := targetSize >> 1 + + // Evenly distribute delta size limits over allowed depth. + // If src is non-delta (depth = 0), delta <= 50% of original. + // If src is almost at limit (9/10), delta <= 10% of original. + return n * (maxDepth - int64(baseDepth)) / maxDepth + } + + // With a delta base chosen any new delta must be "better". + // Retain the distribution described above. + d := int64(targetDepth) + n := targetSize + + // If src is whole (depth=0) and base is near limit (depth=9/10) + // any delta using src can be 10x larger and still be better. + // + // If src is near limit (depth=9/10) and base is whole (depth=0) + // a new delta dependent on src must be 1/10th the size. + return n * (maxDepth - int64(baseDepth)) / (maxDepth - d) +} + +type byTypeAndSize []*ObjectToPack + +func (a byTypeAndSize) Len() int { return len(a) } + +func (a byTypeAndSize) Swap(i, j int) { a[i], a[j] = a[j], a[i] } + +func (a byTypeAndSize) Less(i, j int) bool { + if a[i].Object.Type() < a[j].Object.Type() { + return false + } + + if a[i].Object.Type() > a[j].Object.Type() { + return true + } + + return a[i].Object.Size() > a[j].Object.Size() +} diff --git a/plumbing/format/packfile/delta_selector_test.go b/plumbing/format/packfile/delta_selector_test.go new file mode 100644 index 0000000..9a8833f --- /dev/null +++ b/plumbing/format/packfile/delta_selector_test.go @@ -0,0 +1,199 @@ +package packfile + +import ( + "gopkg.in/src-d/go-git.v4/plumbing" + "gopkg.in/src-d/go-git.v4/storage/memory" + + . "gopkg.in/check.v1" +) + +type DeltaSelectorSuite struct { + ds *deltaSelector + store *memory.Storage + hashes map[string]plumbing.Hash +} + +var _ = Suite(&DeltaSelectorSuite{}) + +func (s *DeltaSelectorSuite) SetUpTest(c *C) { + s.store = memory.NewStorage() + s.createTestObjects() + s.ds = newDeltaSelector(s.store) +} + +func (s *DeltaSelectorSuite) TestSort(c *C) { + var o1 = newObjectToPack(newObject(plumbing.BlobObject, []byte("00000"))) + var o4 = newObjectToPack(newObject(plumbing.BlobObject, []byte("0000"))) + var o6 = newObjectToPack(newObject(plumbing.BlobObject, []byte("00"))) + var o9 = newObjectToPack(newObject(plumbing.BlobObject, []byte("0"))) + var o8 = newObjectToPack(newObject(plumbing.TreeObject, []byte("000"))) + var o2 = newObjectToPack(newObject(plumbing.TreeObject, []byte("00"))) + var o3 = newObjectToPack(newObject(plumbing.TreeObject, []byte("0"))) + var o5 = newObjectToPack(newObject(plumbing.CommitObject, []byte("0000"))) + var o7 = newObjectToPack(newObject(plumbing.CommitObject, []byte("00"))) + + toSort := []*ObjectToPack{o1, o2, o3, o4, o5, o6, o7, o8, o9} + s.ds.sort(toSort) + expected := []*ObjectToPack{o1, o4, o6, o9, o8, o2, o3, o5, o7} + c.Assert(toSort, DeepEquals, expected) +} + +type testObject struct { + id string + object plumbing.EncodedObject +} + +var testObjects []*testObject = []*testObject{{ + id: "base", + object: newObject(plumbing.BlobObject, + genBytes([]piece{{ + times: 1000, + val: "a", + }, { + times: 1000, + val: "b", + }})), +}, { + id: "smallBase", + object: newObject(plumbing.BlobObject, + genBytes([]piece{{ + times: 1, + val: "a", + }, { + times: 1, + val: "b", + }, { + times: 6, + val: "c", + }})), +}, { + id: "smallTarget", + object: newObject(plumbing.BlobObject, + genBytes([]piece{{ + times: 1, + val: "a", + }, { + times: 1, + val: "c", + }})), +}, { + id: "target", + object: newObject(plumbing.BlobObject, + genBytes([]piece{{ + times: 1000, + val: "a", + }, { + times: 1000, + val: "b", + }, { + times: 1000, + val: "c", + }})), +}, { + id: "o1", + object: newObject(plumbing.BlobObject, + genBytes([]piece{{ + times: 1000, + val: "a", + }, { + times: 1000, + val: "b", + }})), +}, { + id: "o2", + object: newObject(plumbing.BlobObject, + genBytes([]piece{{ + times: 1000, + val: "a", + }, { + times: 500, + val: "b", + }})), +}, { + id: "o3", + object: newObject(plumbing.BlobObject, + genBytes([]piece{{ + times: 1000, + val: "a", + }, { + times: 499, + val: "b", + }})), +}, { + id: "bigBase", + object: newObject(plumbing.BlobObject, + genBytes([]piece{{ + times: 1000000, + val: "a", + }})), +}, { + id: "treeType", + object: newObject(plumbing.TreeObject, + []byte("I am a tree!")), +}} + +func (s *DeltaSelectorSuite) createTestObjects() { + s.hashes = make(map[string]plumbing.Hash) + for _, o := range testObjects { + h, err := s.store.SetEncodedObject(o.object) + if err != nil { + panic(err) + } + s.hashes[o.id] = h + } +} + +func (s *DeltaSelectorSuite) TestObjectsToPack(c *C) { + // Different type + hashes := []plumbing.Hash{s.hashes["base"], s.hashes["treeType"]} + otp, err := s.ds.ObjectsToPack(hashes) + c.Assert(err, IsNil) + c.Assert(len(otp), Equals, 2) + c.Assert(otp[0].Object, Equals, s.store.Objects[s.hashes["base"]]) + c.Assert(otp[1].Object, Equals, s.store.Objects[s.hashes["treeType"]]) + + // Size radically different + hashes = []plumbing.Hash{s.hashes["bigBase"], s.hashes["target"]} + otp, err = s.ds.ObjectsToPack(hashes) + c.Assert(err, IsNil) + c.Assert(len(otp), Equals, 2) + c.Assert(otp[0].Object, Equals, s.store.Objects[s.hashes["bigBase"]]) + c.Assert(otp[1].Object, Equals, s.store.Objects[s.hashes["target"]]) + + // Delta Size Limit with no best delta yet + hashes = []plumbing.Hash{s.hashes["smallBase"], s.hashes["smallTarget"]} + otp, err = s.ds.ObjectsToPack(hashes) + c.Assert(err, IsNil) + c.Assert(len(otp), Equals, 2) + c.Assert(otp[0].Object, Equals, s.store.Objects[s.hashes["smallBase"]]) + c.Assert(otp[1].Object, Equals, s.store.Objects[s.hashes["smallTarget"]]) + + // It will create the delta + hashes = []plumbing.Hash{s.hashes["base"], s.hashes["target"]} + otp, err = s.ds.ObjectsToPack(hashes) + c.Assert(err, IsNil) + c.Assert(len(otp), Equals, 2) + c.Assert(otp[0].Object, Equals, s.store.Objects[s.hashes["target"]]) + c.Assert(otp[0].IsDelta(), Equals, false) + c.Assert(otp[1].Original, Equals, s.store.Objects[s.hashes["base"]]) + c.Assert(otp[1].IsDelta(), Equals, true) + c.Assert(otp[1].Depth, Equals, 1) + + // If our base is another delta, the depth will increase by one + hashes = []plumbing.Hash{ + s.hashes["o1"], + s.hashes["o2"], + s.hashes["o3"], + } + otp, err = s.ds.ObjectsToPack(hashes) + c.Assert(err, IsNil) + c.Assert(len(otp), Equals, 3) + c.Assert(otp[0].Object, Equals, s.store.Objects[s.hashes["o1"]]) + c.Assert(otp[0].IsDelta(), Equals, false) + c.Assert(otp[1].Original, Equals, s.store.Objects[s.hashes["o2"]]) + c.Assert(otp[1].IsDelta(), Equals, true) + c.Assert(otp[1].Depth, Equals, 1) + c.Assert(otp[2].Original, Equals, s.store.Objects[s.hashes["o3"]]) + c.Assert(otp[2].IsDelta(), Equals, true) + c.Assert(otp[2].Depth, Equals, 2) +} diff --git a/plumbing/format/packfile/delta_test.go b/plumbing/format/packfile/delta_test.go index 4ef9b70..43253b0 100644 --- a/plumbing/format/packfile/delta_test.go +++ b/plumbing/format/packfile/delta_test.go @@ -12,11 +12,6 @@ type DeltaSuite struct { var _ = Suite(&DeltaSuite{}) -type piece struct { - val string - times int -} - type deltaTest struct { description string base []piece @@ -80,14 +75,3 @@ func (s *DeltaSuite) TestAddDelta(c *C) { c.Assert(result, DeepEquals, targetBuf) } } - -func genBytes(elements []piece) []byte { - var result []byte - for _, e := range elements { - for i := 0; i < e.times; i++ { - result = append(result, e.val...) - } - } - - return result -} diff --git a/plumbing/format/packfile/diff_delta.go b/plumbing/format/packfile/diff_delta.go index bc4fafa..e3607bf 100644 --- a/plumbing/format/packfile/diff_delta.go +++ b/plumbing/format/packfile/diff_delta.go @@ -1,7 +1,6 @@ package packfile import ( - "fmt" "io/ioutil" "gopkg.in/src-d/go-git.v4/plumbing" @@ -15,23 +14,9 @@ const ( maxCopyLen = 0xffff ) -// GetOFSDelta returns an offset delta that knows the way of how to transform +// GetDelta returns an offset delta that knows the way of how to transform // base object to target object -func GetOFSDelta(base, target plumbing.EncodedObject) (plumbing.EncodedObject, error) { - return getDelta(base, target, plumbing.OFSDeltaObject) -} - -// GetRefDelta returns a reference delta that knows the way of how to transform -// base object to target object -func GetRefDelta(base, target plumbing.EncodedObject) (plumbing.EncodedObject, error) { - return getDelta(base, target, plumbing.REFDeltaObject) -} - -func getDelta(base, target plumbing.EncodedObject, t plumbing.ObjectType) (plumbing.EncodedObject, error) { - if t != plumbing.OFSDeltaObject && t != plumbing.REFDeltaObject { - return nil, fmt.Errorf("Type not supported: %v", t) - } - +func GetDelta(base, target plumbing.EncodedObject) (plumbing.EncodedObject, error) { br, err := base.Reader() if err != nil { return nil, err @@ -59,7 +44,7 @@ func getDelta(base, target plumbing.EncodedObject, t plumbing.ObjectType) (plumb } delta.SetSize(int64(len(db))) - delta.SetType(t) + delta.SetType(plumbing.OFSDeltaObject) return delta, nil } diff --git a/plumbing/format/packfile/encoder.go b/plumbing/format/packfile/encoder.go index 847e9e1..a11ba61 100644 --- a/plumbing/format/packfile/encoder.go +++ b/plumbing/format/packfile/encoder.go @@ -14,16 +14,17 @@ import ( // Encoder gets the data from the storage and write it into the writer in PACK // format type Encoder struct { - storage storer.EncodedObjectStorer - w *offsetWriter - zw *zlib.Writer - hasher plumbing.Hasher - offsets map[plumbing.Hash]int64 + selector *deltaSelector + w *offsetWriter + zw *zlib.Writer + hasher plumbing.Hasher + offsets map[plumbing.Hash]int64 + useRefDeltas bool } // NewEncoder creates a new packfile encoder using a specific Writer and // ObjectStorer -func NewEncoder(w io.Writer, s storer.EncodedObjectStorer) *Encoder { +func NewEncoder(w io.Writer, s storer.EncodedObjectStorer, useRefDeltas bool) *Encoder { h := plumbing.Hasher{ Hash: sha1.New(), } @@ -31,29 +32,26 @@ func NewEncoder(w io.Writer, s storer.EncodedObjectStorer) *Encoder { ow := newOffsetWriter(mw) zw := zlib.NewWriter(mw) return &Encoder{ - storage: s, - w: ow, - zw: zw, - hasher: h, - offsets: make(map[plumbing.Hash]int64), + selector: newDeltaSelector(s), + w: ow, + zw: zw, + hasher: h, + offsets: make(map[plumbing.Hash]int64), + useRefDeltas: useRefDeltas, } } // Encode creates a packfile containing all the objects referenced in hashes // and writes it to the writer in the Encoder. func (e *Encoder) Encode(hashes []plumbing.Hash) (plumbing.Hash, error) { - var objects []*ObjectToPack - for _, h := range hashes { - o, err := e.storage.EncodedObject(plumbing.AnyObject, h) - if err != nil { - return plumbing.ZeroHash, err - } - // TODO delta selection logic - objects = append(objects, newObjectToPack(o)) + objects, err := e.selector.ObjectsToPack(hashes) + if err != nil { + return plumbing.ZeroHash, err } return e.encode(objects) } + func (e *Encoder) encode(objects []*ObjectToPack) (plumbing.Hash, error) { if err := e.head(len(objects)); err != nil { return plumbing.ZeroHash, err @@ -67,6 +65,7 @@ func (e *Encoder) encode(objects []*ObjectToPack) (plumbing.Hash, error) { return e.footer() } + func (e *Encoder) head(numEntries int) error { return binary.Write( e.w, @@ -79,17 +78,19 @@ func (e *Encoder) head(numEntries int) error { func (e *Encoder) entry(o *ObjectToPack) error { offset := e.w.Offset() - if err := e.entryHead(o.Object.Type(), o.Object.Size()); err != nil { - return err + if o.IsDelta() { + if err := e.writeDeltaHeader(o, offset); err != nil { + return err + } + } else { + if err := e.entryHead(o.Object.Type(), o.Object.Size()); err != nil { + return err + } } // Save the position using the original hash, maybe a delta will need it e.offsets[o.Original.Hash()] = offset - if err := e.writeDeltaHeaderIfAny(o, offset); err != nil { - return err - } - e.zw.Reset(e.w) or, err := o.Object.Reader() if err != nil { @@ -103,33 +104,34 @@ func (e *Encoder) entry(o *ObjectToPack) error { return e.zw.Close() } -func (e *Encoder) writeDeltaHeaderIfAny(o *ObjectToPack, offset int64) error { - if o.IsDelta() { - switch o.Object.Type() { - case plumbing.OFSDeltaObject: - if err := e.writeOfsDeltaHeader(offset, o.Base.Original.Hash()); err != nil { - return err - } - case plumbing.REFDeltaObject: - if err := e.writeRefDeltaHeader(o.Base.Original.Hash()); err != nil { - return err - } - } +func (e *Encoder) writeDeltaHeader(o *ObjectToPack, offset int64) error { + // Write offset deltas by default + t := plumbing.OFSDeltaObject + if e.useRefDeltas { + t = plumbing.REFDeltaObject } - return nil + if err := e.entryHead(t, o.Object.Size()); err != nil { + return err + } + + if e.useRefDeltas { + return e.writeRefDeltaHeader(o.Base.Original.Hash()) + } else { + return e.writeOfsDeltaHeader(offset, o.Base.Original.Hash()) + } } -func (e *Encoder) writeRefDeltaHeader(source plumbing.Hash) error { - return binary.Write(e.w, source) +func (e *Encoder) writeRefDeltaHeader(base plumbing.Hash) error { + return binary.Write(e.w, base) } -func (e *Encoder) writeOfsDeltaHeader(deltaOffset int64, source plumbing.Hash) error { - // because it is an offset delta, we need the source +func (e *Encoder) writeOfsDeltaHeader(deltaOffset int64, base plumbing.Hash) error { + // because it is an offset delta, we need the base // object position - offset, ok := e.offsets[source] + offset, ok := e.offsets[base] if !ok { - return fmt.Errorf("delta source not found. Hash: %v", source) + return fmt.Errorf("delta base not found. Hash: %v", base) } return binary.WriteVariableWidthInt(e.w, deltaOffset-offset) diff --git a/plumbing/format/packfile/encoder_test.go b/plumbing/format/packfile/encoder_test.go index 1a94d16..dde8570 100644 --- a/plumbing/format/packfile/encoder_test.go +++ b/plumbing/format/packfile/encoder_test.go @@ -22,7 +22,7 @@ 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) + s.enc = NewEncoder(s.buf, s.store, false) } func (s *EncoderSuite) TestCorrectPackHeader(c *C) { @@ -149,26 +149,30 @@ func (s *EncoderSuite) TestDecodeEncodeDecode(c *C) { } func (s *EncoderSuite) TestDecodeEncodeWithDeltaDecodeREF(c *C) { - s.simpleDeltaTest(c, plumbing.REFDeltaObject) + s.enc = NewEncoder(s.buf, s.store, true) + s.simpleDeltaTest(c) } func (s *EncoderSuite) TestDecodeEncodeWithDeltaDecodeOFS(c *C) { - s.simpleDeltaTest(c, plumbing.OFSDeltaObject) + s.enc = NewEncoder(s.buf, s.store, false) + s.simpleDeltaTest(c) } func (s *EncoderSuite) TestDecodeEncodeWithDeltasDecodeREF(c *C) { - s.deltaOverDeltaTest(c, plumbing.REFDeltaObject) + s.enc = NewEncoder(s.buf, s.store, true) + s.deltaOverDeltaTest(c) } func (s *EncoderSuite) TestDecodeEncodeWithDeltasDecodeOFS(c *C) { - s.deltaOverDeltaTest(c, plumbing.OFSDeltaObject) + s.enc = NewEncoder(s.buf, s.store, false) + s.deltaOverDeltaTest(c) } -func (s *EncoderSuite) simpleDeltaTest(c *C, t plumbing.ObjectType) { +func (s *EncoderSuite) simpleDeltaTest(c *C) { srcObject := newObject(plumbing.BlobObject, []byte("0")) targetObject := newObject(plumbing.BlobObject, []byte("01")) - deltaObject, err := delta(srcObject, targetObject, t) + deltaObject, err := GetDelta(srcObject, targetObject) c.Assert(err, IsNil) srcToPack := newObjectToPack(srcObject) @@ -196,16 +200,16 @@ func (s *EncoderSuite) simpleDeltaTest(c *C, t plumbing.ObjectType) { c.Assert(decTarget, DeepEquals, targetObject) } -func (s *EncoderSuite) deltaOverDeltaTest(c *C, t plumbing.ObjectType) { +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 := delta(srcObject, targetObject, t) + deltaObject, err := GetDelta(srcObject, targetObject) c.Assert(err, IsNil) c.Assert(deltaObject.Hash(), Not(Equals), plumbing.ZeroHash) - otherDeltaObject, err := delta(targetObject, otherTargetObject, t) + otherDeltaObject, err := GetDelta(targetObject, otherTargetObject) c.Assert(err, IsNil) c.Assert(otherDeltaObject.Hash(), Not(Equals), plumbing.ZeroHash) @@ -238,23 +242,3 @@ func (s *EncoderSuite) deltaOverDeltaTest(c *C, t plumbing.ObjectType) { c.Assert(err, IsNil) c.Assert(decOtherTarget, DeepEquals, otherTargetObject) } - -func delta(base, target plumbing.EncodedObject, t plumbing.ObjectType) (plumbing.EncodedObject, error) { - switch t { - case plumbing.OFSDeltaObject: - return GetOFSDelta(base, target) - case plumbing.REFDeltaObject: - return GetRefDelta(base, target) - default: - panic("delta type not found") - } -} - -func newObject(t plumbing.ObjectType, cont []byte) plumbing.EncodedObject { - o := plumbing.MemoryObject{} - o.SetType(t) - o.SetSize(int64(len(cont))) - o.Write(cont) - - return &o -} diff --git a/plumbing/format/packfile/object_pack.go b/plumbing/format/packfile/object_pack.go index dfe9bb2..a3e99c0 100644 --- a/plumbing/format/packfile/object_pack.go +++ b/plumbing/format/packfile/object_pack.go @@ -46,3 +46,9 @@ func (o *ObjectToPack) IsDelta() bool { return false } + +func (o *ObjectToPack) SetDelta(base *ObjectToPack, delta plumbing.EncodedObject) { + o.Object = delta + o.Base = base + o.Depth = base.Depth + 1 +} -- cgit