···11package gitdiff
2233import (
44+ "errors"
45 "fmt"
56 "io"
77+ "io/ioutil"
68)
79810// Conflict indicates an apply failed due to a conflict between the patch and
···212214// Unlike text fragments, binary fragments do not distinguish between strict
213215// and non-strict application.
214216func (f *BinaryFragment) Apply(dst io.Writer, src io.Reader) error {
215215- panic("TODO(bkeyes): unimplemented")
217217+ fullSrc, err := ioutil.ReadAll(src)
218218+ if err != nil {
219219+ return err
220220+ }
221221+222222+ switch f.Method {
223223+ case BinaryPatchLiteral:
224224+ if _, err := dst.Write(f.Data); err != nil {
225225+ return applyError(err)
226226+ }
227227+ case BinaryPatchDelta:
228228+ if err := applyBinaryDeltaFragment(dst, fullSrc, f.Data); err != nil {
229229+ return applyError(err)
230230+ }
231231+ }
232232+ return applyError(fmt.Errorf("unsupported binary patch method: %v", f.Method))
233233+}
234234+235235+func applyBinaryDeltaFragment(dst io.Writer, src, frag []byte) error {
236236+ srcSize, delta := readBinaryDeltaSize(frag)
237237+ if srcSize != int64(len(src)) {
238238+ return &Conflict{"fragment src size does not match actual src size"}
239239+ }
240240+241241+ dstSize, delta := readBinaryDeltaSize(delta)
242242+243243+ for len(delta) > 0 {
244244+ op := delta[0]
245245+ if op == 0 {
246246+ return errors.New("invalid delta opcode 0")
247247+ }
248248+249249+ var n int64
250250+ var err error
251251+ switch op & 0x80 {
252252+ case 0x80:
253253+ n, delta, err = applyBinaryDeltaCopy(dst, op, delta[1:], src)
254254+ case 0x00:
255255+ n, delta, err = applyBinaryDeltaAdd(dst, op, delta[1:])
256256+ }
257257+ if err != nil {
258258+ return err
259259+ }
260260+ dstSize -= n
261261+ }
262262+263263+ if dstSize != 0 {
264264+ return errors.New("corrupt binary delta: insufficient or extra data")
265265+ }
266266+ return nil
267267+}
268268+269269+// readBinaryDeltaSize reads a variable length size from a delta-encoded binary
270270+// fragment, returing the size and the unused data. Data is encoded as:
271271+//
272272+// [[1xxxxxxx]...] [0xxxxxxx]
273273+//
274274+// in little-endian order, with 7 bits of the value per byte.
275275+func readBinaryDeltaSize(d []byte) (size int64, rest []byte) {
276276+ shift := uint(0)
277277+ for i, b := range d {
278278+ size |= int64(b&0x7F) << shift
279279+ shift += 7
280280+ if b <= 0x7F {
281281+ return size, d[i+1:]
282282+ }
283283+ }
284284+ return size, nil
285285+}
286286+287287+// applyBinaryDeltaAdd applies an add opcode in a delta-encoded binary
288288+// fragment, returning the amount of data written and the usused part of the
289289+// fragment. An add operation takes the form:
290290+//
291291+// [0xxxxxx][[data1]...]
292292+//
293293+// where the lower seven bits of the opcode is the number of data bytes
294294+// following the opcode. See also pack-format.txt in the Git source.
295295+func applyBinaryDeltaAdd(w io.Writer, op byte, delta []byte) (n int64, rest []byte, err error) {
296296+ size := int(op)
297297+ if len(delta) < size {
298298+ return 0, delta, errors.New("corrupt binary delta: incomplete add")
299299+ }
300300+ _, err = w.Write(delta[:size])
301301+ return int64(size), delta[size:], err
302302+}
303303+304304+// applyBinaryDeltaCopy applies a copy opcode in a delta-encoded binary
305305+// fragment, returing the amount of data written and the unused part of the
306306+// fragment. A copy operation takes the form:
307307+//
308308+// [1xxxxxxx][offset1][offset2][offset3][offset4][size1][size2][size3]
309309+//
310310+// where the lower seven bits of the opcode determine which non-zero offset and
311311+// size bytes are present in little-endian order: if bit 0 is set, offset1 is
312312+// present, etc. If no offset or size bytes are present, offset is 0 and size
313313+// is 0x10000. See also pack-format.txt in the Git source.
314314+func applyBinaryDeltaCopy(w io.Writer, op byte, delta, src []byte) (n int64, rest []byte, err error) {
315315+ unpack := func(start, bits uint) (v int64) {
316316+ for i := uint(0); i < bits; i++ {
317317+ mask := byte(1 << (i + start))
318318+ if op&mask > 0 {
319319+ if len(delta) == 0 {
320320+ err = errors.New("corrupt binary delta: incomplete copy")
321321+ return
322322+ }
323323+ v |= int64(delta[0]) << (8 * i)
324324+ delta = delta[1:]
325325+ }
326326+ }
327327+ return
328328+ }
329329+330330+ offset := unpack(0, 4)
331331+ size := unpack(4, 3)
332332+ if err != nil {
333333+ return 0, delta, err
334334+ }
335335+ if size == 0 {
336336+ size = 0x10000
337337+ }
338338+339339+ _, err = w.Write(src[offset : offset+size])
340340+ return size, delta, err
216341}