Skip to content

Commit a1d2195

Browse files
committed
Implement binary fragment application
Currently untested, but based on code I validated against some generated patches. Tests comming in a future commit.
1 parent 32887c7 commit a1d2195

File tree

1 file changed

+126
-1
lines changed

1 file changed

+126
-1
lines changed

gitdiff/apply.go

Lines changed: 126 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package gitdiff
22

33
import (
4+
"errors"
45
"fmt"
56
"io"
7+
"io/ioutil"
68
)
79

810
// Conflict indicates an apply failed due to a conflict between the patch and
@@ -212,5 +214,128 @@ func copyLines(dst io.Writer, src LineReader, limit int64) (string, int64, error
212214
// Unlike text fragments, binary fragments do not distinguish between strict
213215
// and non-strict application.
214216
func (f *BinaryFragment) Apply(dst io.Writer, src io.Reader) error {
215-
panic("TODO(bkeyes): unimplemented")
217+
fullSrc, err := ioutil.ReadAll(src)
218+
if err != nil {
219+
return err
220+
}
221+
222+
switch f.Method {
223+
case BinaryPatchLiteral:
224+
if _, err := dst.Write(f.Data); err != nil {
225+
return applyError(err)
226+
}
227+
case BinaryPatchDelta:
228+
if err := applyBinaryDeltaFragment(dst, fullSrc, f.Data); err != nil {
229+
return applyError(err)
230+
}
231+
}
232+
return applyError(fmt.Errorf("unsupported binary patch method: %v", f.Method))
233+
}
234+
235+
func applyBinaryDeltaFragment(dst io.Writer, src, frag []byte) error {
236+
srcSize, delta := readBinaryDeltaSize(frag)
237+
if srcSize != int64(len(src)) {
238+
return &Conflict{"fragment src size does not match actual src size"}
239+
}
240+
241+
dstSize, delta := readBinaryDeltaSize(delta)
242+
243+
for len(delta) > 0 {
244+
op := delta[0]
245+
if op == 0 {
246+
return errors.New("invalid delta opcode 0")
247+
}
248+
249+
var n int64
250+
var err error
251+
switch op & 0x80 {
252+
case 0x80:
253+
n, delta, err = applyBinaryDeltaCopy(dst, op, delta[1:], src)
254+
case 0x00:
255+
n, delta, err = applyBinaryDeltaAdd(dst, op, delta[1:])
256+
}
257+
if err != nil {
258+
return err
259+
}
260+
dstSize -= n
261+
}
262+
263+
if dstSize != 0 {
264+
return errors.New("corrupt binary delta: insufficient or extra data")
265+
}
266+
return nil
267+
}
268+
269+
// readBinaryDeltaSize reads a variable length size from a delta-encoded binary
270+
// fragment, returing the size and the unused data. Data is encoded as:
271+
//
272+
// [[1xxxxxxx]...] [0xxxxxxx]
273+
//
274+
// in little-endian order, with 7 bits of the value per byte.
275+
func readBinaryDeltaSize(d []byte) (size int64, rest []byte) {
276+
shift := uint(0)
277+
for i, b := range d {
278+
size |= int64(b&0x7F) << shift
279+
shift += 7
280+
if b <= 0x7F {
281+
return size, d[i+1:]
282+
}
283+
}
284+
return size, nil
285+
}
286+
287+
// applyBinaryDeltaAdd applies an add opcode in a delta-encoded binary
288+
// fragment, returning the amount of data written and the usused part of the
289+
// fragment. An add operation takes the form:
290+
//
291+
// [0xxxxxx][[data1]...]
292+
//
293+
// where the lower seven bits of the opcode is the number of data bytes
294+
// following the opcode. See also pack-format.txt in the Git source.
295+
func applyBinaryDeltaAdd(w io.Writer, op byte, delta []byte) (n int64, rest []byte, err error) {
296+
size := int(op)
297+
if len(delta) < size {
298+
return 0, delta, errors.New("corrupt binary delta: incomplete add")
299+
}
300+
_, err = w.Write(delta[:size])
301+
return int64(size), delta[size:], err
302+
}
303+
304+
// applyBinaryDeltaCopy applies a copy opcode in a delta-encoded binary
305+
// fragment, returing the amount of data written and the unused part of the
306+
// fragment. A copy operation takes the form:
307+
//
308+
// [1xxxxxxx][offset1][offset2][offset3][offset4][size1][size2][size3]
309+
//
310+
// where the lower seven bits of the opcode determine which non-zero offset and
311+
// size bytes are present in little-endian order: if bit 0 is set, offset1 is
312+
// present, etc. If no offset or size bytes are present, offset is 0 and size
313+
// is 0x10000. See also pack-format.txt in the Git source.
314+
func applyBinaryDeltaCopy(w io.Writer, op byte, delta, src []byte) (n int64, rest []byte, err error) {
315+
unpack := func(start, bits uint) (v int64) {
316+
for i := uint(0); i < bits; i++ {
317+
mask := byte(1 << (i + start))
318+
if op&mask > 0 {
319+
if len(delta) == 0 {
320+
err = errors.New("corrupt binary delta: incomplete copy")
321+
return
322+
}
323+
v |= int64(delta[0]) << (8 * i)
324+
delta = delta[1:]
325+
}
326+
}
327+
return
328+
}
329+
330+
offset := unpack(0, 4)
331+
size := unpack(4, 3)
332+
if err != nil {
333+
return 0, delta, err
334+
}
335+
if size == 0 {
336+
size = 0x10000
337+
}
338+
339+
_, err = w.Write(src[offset : offset+size])
340+
return size, delta, err
216341
}

0 commit comments

Comments
 (0)