package packfile import ( "fmt" "io/ioutil" "gopkg.in/src-d/go-git.v4/plumbing" ) // See https://github.com/jelmer/dulwich/blob/master/dulwich/pack.py and // https://github.com/tarruda/node-git-core/blob/master/src/js/delta.js // for more info const ( maxCopyLen = 0xffff ) // GetOFSDelta returns an offset delta that knows the way of how to transform // base object to target object func GetOFSDelta(base, target plumbing.Object) (plumbing.Object, 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.Object) (plumbing.Object, error) { return getDelta(base, target, plumbing.REFDeltaObject) } func getDelta(base, target plumbing.Object, t plumbing.ObjectType) (plumbing.Object, error) { if t != plumbing.OFSDeltaObject && t != plumbing.REFDeltaObject { return nil, fmt.Errorf("Type not supported: %v", t) } br, err := base.Reader() if err != nil { return nil, err } tr, err := target.Reader() if err != nil { return nil, err } bb, err := ioutil.ReadAll(br) if err != nil { return nil, err } tb, err := ioutil.ReadAll(tr) if err != nil { return nil, err } db := DiffDelta(bb, tb) delta := &plumbing.MemoryObject{} _, err = delta.Write(db) if err != nil { return nil, err } delta.SetSize(int64(len(db))) delta.SetType(t) return delta, nil } // DiffDelta returns the way of how to transform baseBuf to targetBuf func DiffDelta(baseBuf []byte, targetBuf []byte) []byte { var outBuff []byte outBuff = append(outBuff, deltaEncodeSize(len(baseBuf))...) outBuff = append(outBuff, deltaEncodeSize(len(targetBuf))...) sm := newMatcher(baseBuf, targetBuf) for _, op := range sm.GetOpCodes() { switch { case op.Tag == tagEqual: copyStart := op.I1 copyLen := op.I2 - op.I1 for { if copyLen <= 0 { break } var toCopy int if copyLen < maxCopyLen { toCopy = copyLen } else { toCopy = maxCopyLen } outBuff = append(outBuff, encodeCopyOperation(copyStart, toCopy)...) copyStart += toCopy copyLen -= toCopy } case op.Tag == tagReplace || op.Tag == tagInsert: s := op.J2 - op.J1 o := op.J1 for { if s <= 127 { break } outBuff = append(outBuff, byte(127)) outBuff = append(outBuff, targetBuf[o:o+127]...) s -= 127 o += 127 } outBuff = append(outBuff, byte(s)) outBuff = append(outBuff, targetBuf[o:o+s]...) } } return outBuff } func deltaEncodeSize(size int) []byte { var ret []byte c := size & 0x7f size >>= 7 for { if size == 0 { break } ret = append(ret, byte(c|0x80)) c = size & 0x7f size >>= 7 } ret = append(ret, byte(c)) return ret } func encodeCopyOperation(offset, length int) []byte { code := 0x80 var opcodes []byte if offset&0xff != 0 { opcodes = append(opcodes, byte(offset&0xff)) code |= 0x01 } if offset&0xff00 != 0 { opcodes = append(opcodes, byte((offset&0xff00)>>8)) code |= 0x02 } if offset&0xff0000 != 0 { opcodes = append(opcodes, byte((offset&0xff0000)>>16)) code |= 0x04 } if offset&0xff000000 != 0 { opcodes = append(opcodes, byte((offset&0xff000000)>>24)) code |= 0x08 } if length&0xff != 0 { opcodes = append(opcodes, byte(length&0xff)) code |= 0x10 } if length&0xff00 != 0 { opcodes = append(opcodes, byte((length&0xff00)>>8)) code |= 0x20 } if length&0xff0000 != 0 { opcodes = append(opcodes, byte((length&0xff0000)>>16)) code |= 0x40 } return append([]byte{byte(code)}, opcodes...) }