Skip to content

Commit 131a046

Browse files
committed
Use io.ReaderAt to implement binary applies
This better matches the actual contract of the delta patch, which reads fixed size chunks of the source files at arbitrary positions.
1 parent a34334d commit 131a046

File tree

1 file changed

+45
-22
lines changed

1 file changed

+45
-22
lines changed

gitdiff/apply.go

Lines changed: 45 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package gitdiff
22

33
import (
4+
"bytes"
45
"errors"
56
"fmt"
67
"io"
@@ -69,10 +70,7 @@ func applyError(err error, args ...interface{}) error {
6970

7071
e, ok := err.(*ApplyError)
7172
if !ok {
72-
if err == io.EOF {
73-
err = io.ErrUnexpectedEOF
74-
}
75-
e = &ApplyError{err: err}
73+
e = &ApplyError{err: wrapEOF(err)}
7674
}
7775
for _, arg := range args {
7876
switch v := arg.(type) {
@@ -95,10 +93,14 @@ func applyError(err error, args ...interface{}) error {
9593
// Partial data may be written to dst in this case.
9694
func (f *File) ApplyStrict(dst io.Writer, src io.Reader) error {
9795
if f.IsBinary {
96+
data, err := ioutil.ReadAll(src)
97+
if err != nil {
98+
return applyError(err)
99+
}
98100
if f.BinaryFragment != nil {
99-
return f.BinaryFragment.Apply(dst, src)
101+
return f.BinaryFragment.Apply(dst, bytes.NewReader(data))
100102
}
101-
_, err := io.Copy(dst, src)
103+
_, err = dst.Write(data)
102104
return applyError(err)
103105
}
104106

@@ -196,10 +198,7 @@ func copyLines(dst io.Writer, src LineReader, limit int64) (string, int64, error
196198
}
197199
return "", n, &Conflict{"fragment overlaps with an applied fragment"}
198200
case err != nil:
199-
if err == io.EOF {
200-
err = io.ErrUnexpectedEOF
201-
}
202-
return line, n, err
201+
return line, n, wrapEOF(err)
203202
}
204203

205204
if _, err := io.WriteString(dst, line); err != nil {
@@ -213,19 +212,14 @@ func copyLines(dst io.Writer, src LineReader, limit int64) (string, int64, error
213212
//
214213
// Unlike text fragments, binary fragments do not distinguish between strict
215214
// and non-strict application.
216-
func (f *BinaryFragment) Apply(dst io.Writer, src io.Reader) error {
217-
fullSrc, err := ioutil.ReadAll(src)
218-
if err != nil {
219-
return err
220-
}
221-
215+
func (f *BinaryFragment) Apply(dst io.Writer, src io.ReaderAt) error {
222216
switch f.Method {
223217
case BinaryPatchLiteral:
224218
if _, err := dst.Write(f.Data); err != nil {
225219
return applyError(err)
226220
}
227221
case BinaryPatchDelta:
228-
if err := applyBinaryDeltaFragment(dst, fullSrc, f.Data); err != nil {
222+
if err := applyBinaryDeltaFragment(dst, src, f.Data); err != nil {
229223
return applyError(err)
230224
}
231225
default:
@@ -235,10 +229,10 @@ func (f *BinaryFragment) Apply(dst io.Writer, src io.Reader) error {
235229
return nil
236230
}
237231

238-
func applyBinaryDeltaFragment(dst io.Writer, src, frag []byte) error {
232+
func applyBinaryDeltaFragment(dst io.Writer, src io.ReaderAt, frag []byte) error {
239233
srcSize, delta := readBinaryDeltaSize(frag)
240-
if srcSize != int64(len(src)) {
241-
return &Conflict{"fragment src size does not match actual src size"}
234+
if err := checkBinarySrcSize(srcSize, src); err != nil {
235+
return err
242236
}
243237

244238
dstSize, delta := readBinaryDeltaSize(delta)
@@ -314,7 +308,7 @@ func applyBinaryDeltaAdd(w io.Writer, op byte, delta []byte) (n int64, rest []by
314308
// size bytes are present in little-endian order: if bit 0 is set, offset1 is
315309
// present, etc. If no offset or size bytes are present, offset is 0 and size
316310
// is 0x10000. See also pack-format.txt in the Git source.
317-
func applyBinaryDeltaCopy(w io.Writer, op byte, delta, src []byte) (n int64, rest []byte, err error) {
311+
func applyBinaryDeltaCopy(w io.Writer, op byte, delta []byte, src io.ReaderAt) (n int64, rest []byte, err error) {
318312
const defaultSize = 0x10000
319313

320314
unpack := func(start, bits uint) (v int64) {
@@ -341,6 +335,35 @@ func applyBinaryDeltaCopy(w io.Writer, op byte, delta, src []byte) (n int64, res
341335
size = defaultSize
342336
}
343337

344-
_, err = w.Write(src[offset : offset+size])
338+
// TODO(bkeyes): consider pooling these buffers
339+
b := make([]byte, size)
340+
if _, err := src.ReadAt(b, offset); err != nil {
341+
return 0, delta, wrapEOF(err)
342+
}
343+
344+
_, err = w.Write(b)
345345
return size, delta, err
346346
}
347+
348+
func checkBinarySrcSize(size int64, src io.ReaderAt) error {
349+
start := size
350+
if start > 0 {
351+
start--
352+
}
353+
var b [2]byte
354+
n, err := src.ReadAt(b[:], start)
355+
if err == io.EOF && (size == 0 && n == 0) || (size > 0 && n == 1) {
356+
return nil
357+
}
358+
if err != nil && err != io.EOF {
359+
return err
360+
}
361+
return &Conflict{"fragment src size does not match actual src size"}
362+
}
363+
364+
func wrapEOF(err error) error {
365+
if err == io.EOF {
366+
err = io.ErrUnexpectedEOF
367+
}
368+
return err
369+
}

0 commit comments

Comments
 (0)