123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253 |
- package packfile
-
- import (
- "bytes"
- "errors"
- "io"
-
- "github.com/go-git/go-git/v5/plumbing"
- "github.com/go-git/go-git/v5/utils/ioutil"
- )
-
- // See https://github.com/git/git/blob/49fa3dc76179e04b0833542fa52d0f287a4955ac/delta.h
- // https://github.com/git/git/blob/c2c5f6b1e479f2c38e0e01345350620944e3527f/patch-delta.c,
- // and https://github.com/tarruda/node-git-core/blob/master/src/js/delta.js
- // for details about the delta format.
-
- const deltaSizeMin = 4
-
- // ApplyDelta writes to target the result of applying the modification deltas in delta to base.
- func ApplyDelta(target, base plumbing.EncodedObject, delta []byte) (err error) {
- r, err := base.Reader()
- if err != nil {
- return err
- }
-
- defer ioutil.CheckClose(r, &err)
-
- w, err := target.Writer()
- if err != nil {
- return err
- }
-
- defer ioutil.CheckClose(w, &err)
-
- buf := bufPool.Get().(*bytes.Buffer)
- defer bufPool.Put(buf)
- buf.Reset()
- _, err = buf.ReadFrom(r)
- if err != nil {
- return err
- }
- src := buf.Bytes()
-
- dst := bufPool.Get().(*bytes.Buffer)
- defer bufPool.Put(dst)
- dst.Reset()
- err = patchDelta(dst, src, delta)
- if err != nil {
- return err
- }
-
-
- target.SetSize(int64(dst.Len()))
-
- b := byteSlicePool.Get().([]byte)
- _, err = io.CopyBuffer(w, dst, b)
- byteSlicePool.Put(b)
- return err
- }
-
- var (
- ErrInvalidDelta = errors.New("invalid delta")
- ErrDeltaCmd = errors.New("wrong delta command")
- )
-
- // PatchDelta returns the result of applying the modification deltas in delta to src.
- // An error will be returned if delta is corrupted (ErrDeltaLen) or an action command
- // is not copy from source or copy from delta (ErrDeltaCmd).
- func PatchDelta(src, delta []byte) ([]byte, error) {
- b := &bytes.Buffer{}
- if err := patchDelta(b, src, delta); err != nil {
- return nil, err
- }
- return b.Bytes(), nil
- }
-
- func patchDelta(dst *bytes.Buffer, src, delta []byte) error {
- if len(delta) < deltaSizeMin {
- return ErrInvalidDelta
- }
-
- srcSz, delta := decodeLEB128(delta)
- if srcSz != uint(len(src)) {
- return ErrInvalidDelta
- }
-
- targetSz, delta := decodeLEB128(delta)
- remainingTargetSz := targetSz
-
- var cmd byte
- dst.Grow(int(targetSz))
- for {
- if len(delta) == 0 {
- return ErrInvalidDelta
- }
-
- cmd = delta[0]
- delta = delta[1:]
- if isCopyFromSrc(cmd) {
- var offset, sz uint
- var err error
- offset, delta, err = decodeOffset(cmd, delta)
- if err != nil {
- return err
- }
-
- sz, delta, err = decodeSize(cmd, delta)
- if err != nil {
- return err
- }
-
- if invalidSize(sz, targetSz) ||
- invalidOffsetSize(offset, sz, srcSz) {
- break
- }
- dst.Write(src[offset:offset+sz])
- remainingTargetSz -= sz
- } else if isCopyFromDelta(cmd) {
- sz := uint(cmd) // cmd is the size itself
- if invalidSize(sz, targetSz) {
- return ErrInvalidDelta
- }
-
- if uint(len(delta)) < sz {
- return ErrInvalidDelta
- }
-
- dst.Write(delta[0:sz])
- remainingTargetSz -= sz
- delta = delta[sz:]
- } else {
- return ErrDeltaCmd
- }
-
- if remainingTargetSz <= 0 {
- break
- }
- }
-
- return nil
- }
-
- // Decodes a number encoded as an unsigned LEB128 at the start of some
- // binary data and returns the decoded number and the rest of the
- // stream.
- //
- // This must be called twice on the delta data buffer, first to get the
- // expected source buffer size, and again to get the target buffer size.
- func decodeLEB128(input []byte) (uint, []byte) {
- var num, sz uint
- var b byte
- for {
- b = input[sz]
- num |= (uint(b) & payload) << (sz * 7) // concats 7 bits chunks
- sz++
-
- if uint(b)&continuation == 0 || sz == uint(len(input)) {
- break
- }
- }
-
- return num, input[sz:]
- }
-
- const (
- payload = 0x7f // 0111 1111
- continuation = 0x80 // 1000 0000
- )
-
- func isCopyFromSrc(cmd byte) bool {
- return (cmd & 0x80) != 0
- }
-
- func isCopyFromDelta(cmd byte) bool {
- return (cmd&0x80) == 0 && cmd != 0
- }
-
- func decodeOffset(cmd byte, delta []byte) (uint, []byte, error) {
- var offset uint
- if (cmd & 0x01) != 0 {
- if len(delta) == 0 {
- return 0, nil, ErrInvalidDelta
- }
- offset = uint(delta[0])
- delta = delta[1:]
- }
- if (cmd & 0x02) != 0 {
- if len(delta) == 0 {
- return 0, nil, ErrInvalidDelta
- }
- offset |= uint(delta[0]) << 8
- delta = delta[1:]
- }
- if (cmd & 0x04) != 0 {
- if len(delta) == 0 {
- return 0, nil, ErrInvalidDelta
- }
- offset |= uint(delta[0]) << 16
- delta = delta[1:]
- }
- if (cmd & 0x08) != 0 {
- if len(delta) == 0 {
- return 0, nil, ErrInvalidDelta
- }
- offset |= uint(delta[0]) << 24
- delta = delta[1:]
- }
-
- return offset, delta, nil
- }
-
- func decodeSize(cmd byte, delta []byte) (uint, []byte, error) {
- var sz uint
- if (cmd & 0x10) != 0 {
- if len(delta) == 0 {
- return 0, nil, ErrInvalidDelta
- }
- sz = uint(delta[0])
- delta = delta[1:]
- }
- if (cmd & 0x20) != 0 {
- if len(delta) == 0 {
- return 0, nil, ErrInvalidDelta
- }
- sz |= uint(delta[0]) << 8
- delta = delta[1:]
- }
- if (cmd & 0x40) != 0 {
- if len(delta) == 0 {
- return 0, nil, ErrInvalidDelta
- }
- sz |= uint(delta[0]) << 16
- delta = delta[1:]
- }
- if sz == 0 {
- sz = 0x10000
- }
-
- return sz, delta, nil
- }
-
- func invalidSize(sz, targetSz uint) bool {
- return sz > targetSz
- }
-
- func invalidOffsetSize(offset, sz, srcSz uint) bool {
- return sumOverflows(offset, sz) ||
- offset+sz > srcSz
- }
-
- func sumOverflows(a, b uint) bool {
- return a+b < a
- }
|