+1
.golangci.yml
+1
.golangci.yml
+1
-1
README.md
+1
-1
README.md
+27
-330
gitdiff/apply.go
+27
-330
gitdiff/apply.go
···
89
89
90
90
var (
91
91
errApplyInProgress = errors.New("gitdiff: incompatible apply in progress")
92
-
)
93
-
94
-
const (
95
-
applyInitial = iota
96
-
applyText
97
-
applyBinary
98
-
applyFile
92
+
errApplierClosed = errors.New("gitdiff: applier is closed")
99
93
)
100
94
101
-
// Apply is a convenience function that creates an Applier for src with default
102
-
// settings and applies the changes in f, writing the result to dst.
103
-
func Apply(dst io.Writer, src io.ReaderAt, f *File) error {
104
-
return NewApplier(src).ApplyFile(dst, f)
105
-
}
106
-
107
-
// Applier applies changes described in fragments to source data. If changes
108
-
// are described in multiple fragments, those fragments must be applied in
109
-
// order, usually by calling ApplyFile.
95
+
// Apply applies the changes in f to src, writing the result to dst. It can
96
+
// apply both text and binary changes.
110
97
//
111
-
// By default, Applier operates in "strict" mode, where fragment content and
112
-
// positions must exactly match those of the source.
113
-
//
114
-
// If an error occurs while applying, methods on Applier return instances of
115
-
// *ApplyError that annotate the wrapped error with additional information
116
-
// when available. If the error is because of a conflict between a fragment and
117
-
// the source, the wrapped error will be a *Conflict.
118
-
//
119
-
// While an Applier can apply both text and binary fragments, only one fragment
120
-
// type can be used without resetting the Applier. The first fragment applied
121
-
// sets the type for the Applier. Mixing fragment types or mixing
122
-
// fragment-level and file-level applies results in an error.
123
-
type Applier struct {
124
-
src io.ReaderAt
125
-
lineSrc LineReaderAt
126
-
nextLine int64
127
-
applyType int
128
-
}
129
-
130
-
// NewApplier creates an Applier that reads data from src. If src is a
131
-
// LineReaderAt, it is used directly to apply text fragments.
132
-
func NewApplier(src io.ReaderAt) *Applier {
133
-
a := new(Applier)
134
-
a.Reset(src)
135
-
return a
136
-
}
137
-
138
-
// Reset resets the input and internal state of the Applier. If src is nil, the
139
-
// existing source is reused.
140
-
func (a *Applier) Reset(src io.ReaderAt) {
141
-
if src != nil {
142
-
a.src = src
143
-
if lineSrc, ok := src.(LineReaderAt); ok {
144
-
a.lineSrc = lineSrc
145
-
} else {
146
-
a.lineSrc = &lineReaderAt{r: src}
98
+
// If an error occurs while applying, Apply returns an *ApplyError that
99
+
// annotates the error with additional information. If the error is because of
100
+
// a conflict with the source, the wrapped error will be a *Conflict.
101
+
func Apply(dst io.Writer, src io.ReaderAt, f *File) error {
102
+
if f.IsBinary {
103
+
if len(f.TextFragments) > 0 {
104
+
return applyError(errors.New("binary file contains text fragments"))
105
+
}
106
+
if f.BinaryFragment == nil {
107
+
return applyError(errors.New("binary file does not contain a binary fragment"))
108
+
}
109
+
} else {
110
+
if f.BinaryFragment != nil {
111
+
return applyError(errors.New("text file contains a binary fragment"))
147
112
}
148
113
}
149
-
a.nextLine = 0
150
-
a.applyType = applyInitial
151
-
}
152
-
153
-
// ApplyFile applies the changes in all of the fragments of f and writes the
154
-
// result to dst.
155
-
func (a *Applier) ApplyFile(dst io.Writer, f *File) error {
156
-
if a.applyType != applyInitial {
157
-
return applyError(errApplyInProgress)
158
-
}
159
-
defer func() { a.applyType = applyFile }()
160
-
161
-
if f.IsBinary && len(f.TextFragments) > 0 {
162
-
return applyError(errors.New("binary file contains text fragments"))
163
-
}
164
-
if !f.IsBinary && f.BinaryFragment != nil {
165
-
return applyError(errors.New("text file contains binary fragment"))
166
-
}
167
114
168
115
switch {
169
116
case f.BinaryFragment != nil:
170
-
return a.ApplyBinaryFragment(dst, f.BinaryFragment)
117
+
applier := NewBinaryApplier(dst, src)
118
+
if err := applier.ApplyFragment(f.BinaryFragment); err != nil {
119
+
return err
120
+
}
121
+
return applier.Close()
171
122
172
123
case len(f.TextFragments) > 0:
173
124
frags := make([]*TextFragment, len(f.TextFragments))
···
181
132
// right now, the application fails if fragments overlap, but it should be
182
133
// possible to precompute the result of applying them in order
183
134
135
+
applier := NewTextApplier(dst, src)
184
136
for i, frag := range frags {
185
-
if err := a.ApplyTextFragment(dst, frag); err != nil {
137
+
if err := applier.ApplyFragment(frag); err != nil {
186
138
return applyError(err, fragNum(i))
187
139
}
188
140
}
189
-
}
190
-
191
-
return applyError(a.Flush(dst))
192
-
}
193
-
194
-
// ApplyTextFragment applies the changes in the fragment f and writes unwritten
195
-
// data before the start of the fragment and the result to dst. If multiple
196
-
// text fragments apply to the same source, ApplyTextFragment must be called in
197
-
// order of increasing start position. As a result, each fragment can be
198
-
// applied at most once before a call to Reset.
199
-
func (a *Applier) ApplyTextFragment(dst io.Writer, f *TextFragment) error {
200
-
if a.applyType != applyInitial && a.applyType != applyText {
201
-
return applyError(errApplyInProgress)
202
-
}
203
-
defer func() { a.applyType = applyText }()
204
-
205
-
// application code assumes fragment fields are consistent
206
-
if err := f.Validate(); err != nil {
207
-
return applyError(err)
208
-
}
209
-
210
-
// lines are 0-indexed, positions are 1-indexed (but new files have position = 0)
211
-
fragStart := f.OldPosition - 1
212
-
if fragStart < 0 {
213
-
fragStart = 0
214
-
}
215
-
fragEnd := fragStart + f.OldLines
216
-
217
-
start := a.nextLine
218
-
if fragStart < start {
219
-
return applyError(&Conflict{"fragment overlaps with an applied fragment"})
220
-
}
221
-
222
-
if f.OldPosition == 0 {
223
-
ok, err := isLen(a.src, 0)
224
-
if err != nil {
225
-
return applyError(err)
226
-
}
227
-
if !ok {
228
-
return applyError(&Conflict{"cannot create new file from non-empty src"})
229
-
}
230
-
}
231
-
232
-
preimage := make([][]byte, fragEnd-start)
233
-
n, err := a.lineSrc.ReadLinesAt(preimage, start)
234
-
if err != nil {
235
-
return applyError(err, lineNum(start+int64(n)))
236
-
}
237
-
238
-
// copy leading data before the fragment starts
239
-
for i, line := range preimage[:fragStart-start] {
240
-
if _, err := dst.Write(line); err != nil {
241
-
a.nextLine = start + int64(i)
242
-
return applyError(err, lineNum(a.nextLine))
243
-
}
244
-
}
245
-
preimage = preimage[fragStart-start:]
246
-
247
-
// apply the changes in the fragment
248
-
used := int64(0)
249
-
for i, line := range f.Lines {
250
-
if err := applyTextLine(dst, line, preimage, used); err != nil {
251
-
a.nextLine = fragStart + used
252
-
return applyError(err, lineNum(a.nextLine), fragLineNum(i))
253
-
}
254
-
if line.Old() {
255
-
used++
256
-
}
257
-
}
258
-
a.nextLine = fragStart + used
259
-
260
-
// new position of +0,0 mean a full delete, so check for leftovers
261
-
if f.NewPosition == 0 && f.NewLines == 0 {
262
-
var b [1][]byte
263
-
n, err := a.lineSrc.ReadLinesAt(b[:], a.nextLine)
264
-
if err != nil && err != io.EOF {
265
-
return applyError(err, lineNum(a.nextLine))
266
-
}
267
-
if n > 0 {
268
-
return applyError(&Conflict{"src still has content after full delete"}, lineNum(a.nextLine))
269
-
}
270
-
}
141
+
return applier.Close()
271
142
272
-
return nil
273
-
}
274
-
275
-
func applyTextLine(dst io.Writer, line Line, preimage [][]byte, i int64) (err error) {
276
-
if line.Old() && string(preimage[i]) != line.Line {
277
-
return &Conflict{"fragment line does not match src line"}
278
-
}
279
-
if line.New() {
280
-
_, err = io.WriteString(dst, line.Line)
281
-
}
282
-
return err
283
-
}
284
-
285
-
// Flush writes any data following the last applied fragment to dst.
286
-
func (a *Applier) Flush(dst io.Writer) (err error) {
287
-
switch a.applyType {
288
-
case applyInitial:
289
-
_, err = copyFrom(dst, a.src, 0)
290
-
case applyText:
291
-
_, err = copyLinesFrom(dst, a.lineSrc, a.nextLine)
292
-
case applyBinary:
293
-
// nothing to flush, binary apply "consumes" full source
294
-
}
295
-
return err
296
-
}
297
-
298
-
// ApplyBinaryFragment applies the changes in the fragment f and writes the
299
-
// result to dst. At most one binary fragment can be applied before a call to
300
-
// Reset.
301
-
func (a *Applier) ApplyBinaryFragment(dst io.Writer, f *BinaryFragment) error {
302
-
if a.applyType != applyInitial {
303
-
return applyError(errApplyInProgress)
304
-
}
305
-
defer func() { a.applyType = applyBinary }()
306
-
307
-
if f == nil {
308
-
return applyError(errors.New("nil fragment"))
309
-
}
310
-
311
-
switch f.Method {
312
-
case BinaryPatchLiteral:
313
-
if _, err := dst.Write(f.Data); err != nil {
314
-
return applyError(err)
315
-
}
316
-
case BinaryPatchDelta:
317
-
if err := applyBinaryDeltaFragment(dst, a.src, f.Data); err != nil {
318
-
return applyError(err)
319
-
}
320
143
default:
321
-
return applyError(fmt.Errorf("unsupported binary patch method: %v", f.Method))
322
-
}
323
-
return nil
324
-
}
325
-
326
-
func applyBinaryDeltaFragment(dst io.Writer, src io.ReaderAt, frag []byte) error {
327
-
srcSize, delta := readBinaryDeltaSize(frag)
328
-
if err := checkBinarySrcSize(src, srcSize); err != nil {
329
-
return err
330
-
}
331
-
332
-
dstSize, delta := readBinaryDeltaSize(delta)
333
-
334
-
for len(delta) > 0 {
335
-
op := delta[0]
336
-
if op == 0 {
337
-
return errors.New("invalid delta opcode 0")
338
-
}
339
-
340
-
var n int64
341
-
var err error
342
-
switch op & 0x80 {
343
-
case 0x80:
344
-
n, delta, err = applyBinaryDeltaCopy(dst, op, delta[1:], src)
345
-
case 0x00:
346
-
n, delta, err = applyBinaryDeltaAdd(dst, op, delta[1:])
347
-
}
348
-
if err != nil {
349
-
return err
350
-
}
351
-
dstSize -= n
352
-
}
353
-
354
-
if dstSize != 0 {
355
-
return errors.New("corrupt binary delta: insufficient or extra data")
356
-
}
357
-
return nil
358
-
}
359
-
360
-
// readBinaryDeltaSize reads a variable length size from a delta-encoded binary
361
-
// fragment, returing the size and the unused data. Data is encoded as:
362
-
//
363
-
// [[1xxxxxxx]...] [0xxxxxxx]
364
-
//
365
-
// in little-endian order, with 7 bits of the value per byte.
366
-
func readBinaryDeltaSize(d []byte) (size int64, rest []byte) {
367
-
shift := uint(0)
368
-
for i, b := range d {
369
-
size |= int64(b&0x7F) << shift
370
-
shift += 7
371
-
if b <= 0x7F {
372
-
return size, d[i+1:]
373
-
}
374
-
}
375
-
return size, nil
376
-
}
377
-
378
-
// applyBinaryDeltaAdd applies an add opcode in a delta-encoded binary
379
-
// fragment, returning the amount of data written and the usused part of the
380
-
// fragment. An add operation takes the form:
381
-
//
382
-
// [0xxxxxx][[data1]...]
383
-
//
384
-
// where the lower seven bits of the opcode is the number of data bytes
385
-
// following the opcode. See also pack-format.txt in the Git source.
386
-
func applyBinaryDeltaAdd(w io.Writer, op byte, delta []byte) (n int64, rest []byte, err error) {
387
-
size := int(op)
388
-
if len(delta) < size {
389
-
return 0, delta, errors.New("corrupt binary delta: incomplete add")
390
-
}
391
-
_, err = w.Write(delta[:size])
392
-
return int64(size), delta[size:], err
393
-
}
394
-
395
-
// applyBinaryDeltaCopy applies a copy opcode in a delta-encoded binary
396
-
// fragment, returing the amount of data written and the unused part of the
397
-
// fragment. A copy operation takes the form:
398
-
//
399
-
// [1xxxxxxx][offset1][offset2][offset3][offset4][size1][size2][size3]
400
-
//
401
-
// where the lower seven bits of the opcode determine which non-zero offset and
402
-
// size bytes are present in little-endian order: if bit 0 is set, offset1 is
403
-
// present, etc. If no offset or size bytes are present, offset is 0 and size
404
-
// is 0x10000. See also pack-format.txt in the Git source.
405
-
func applyBinaryDeltaCopy(w io.Writer, op byte, delta []byte, src io.ReaderAt) (n int64, rest []byte, err error) {
406
-
const defaultSize = 0x10000
407
-
408
-
unpack := func(start, bits uint) (v int64) {
409
-
for i := uint(0); i < bits; i++ {
410
-
mask := byte(1 << (i + start))
411
-
if op&mask > 0 {
412
-
if len(delta) == 0 {
413
-
err = errors.New("corrupt binary delta: incomplete copy")
414
-
return
415
-
}
416
-
v |= int64(delta[0]) << (8 * i)
417
-
delta = delta[1:]
418
-
}
419
-
}
420
-
return
421
-
}
422
-
423
-
offset := unpack(0, 4)
424
-
size := unpack(4, 3)
425
-
if err != nil {
426
-
return 0, delta, err
427
-
}
428
-
if size == 0 {
429
-
size = defaultSize
430
-
}
431
-
432
-
// TODO(bkeyes): consider pooling these buffers
433
-
b := make([]byte, size)
434
-
if _, err := src.ReadAt(b, offset); err != nil {
435
-
return 0, delta, err
436
-
}
437
-
438
-
_, err = w.Write(b)
439
-
return size, delta, err
440
-
}
441
-
442
-
func checkBinarySrcSize(r io.ReaderAt, size int64) error {
443
-
ok, err := isLen(r, size)
444
-
if err != nil {
144
+
// nothing to apply, just copy all the data
145
+
_, err := copyFrom(dst, src, 0)
445
146
return err
446
147
}
447
-
if !ok {
448
-
return &Conflict{"fragment src size does not match actual src size"}
449
-
}
450
-
return nil
451
148
}
+206
gitdiff/apply_binary.go
+206
gitdiff/apply_binary.go
···
1
+
package gitdiff
2
+
3
+
import (
4
+
"errors"
5
+
"fmt"
6
+
"io"
7
+
)
8
+
9
+
// BinaryApplier applies binary changes described in a fragment to source data.
10
+
// The applier must be closed after use.
11
+
type BinaryApplier struct {
12
+
dst io.Writer
13
+
src io.ReaderAt
14
+
15
+
closed bool
16
+
dirty bool
17
+
}
18
+
19
+
// NewBinaryApplier creates an BinaryApplier that reads data from src and
20
+
// writes modified data to dst.
21
+
func NewBinaryApplier(dst io.Writer, src io.ReaderAt) *BinaryApplier {
22
+
a := BinaryApplier{
23
+
dst: dst,
24
+
src: src,
25
+
}
26
+
return &a
27
+
}
28
+
29
+
// ApplyFragment applies the changes in the fragment f and writes the result to
30
+
// dst. ApplyFragment can be called at most once.
31
+
//
32
+
// If an error occurs while applying, ApplyFragment returns an *ApplyError that
33
+
// annotates the error with additional information. If the error is because of
34
+
// a conflict between a fragment and the source, the wrapped error will be a
35
+
// *Conflict.
36
+
func (a *BinaryApplier) ApplyFragment(f *BinaryFragment) error {
37
+
if f == nil {
38
+
return applyError(errors.New("nil fragment"))
39
+
}
40
+
if a.closed {
41
+
return applyError(errApplierClosed)
42
+
}
43
+
if a.dirty {
44
+
return applyError(errApplyInProgress)
45
+
}
46
+
47
+
// mark an apply as in progress, even if it fails before making changes
48
+
a.dirty = true
49
+
50
+
switch f.Method {
51
+
case BinaryPatchLiteral:
52
+
if _, err := a.dst.Write(f.Data); err != nil {
53
+
return applyError(err)
54
+
}
55
+
case BinaryPatchDelta:
56
+
if err := applyBinaryDeltaFragment(a.dst, a.src, f.Data); err != nil {
57
+
return applyError(err)
58
+
}
59
+
default:
60
+
return applyError(fmt.Errorf("unsupported binary patch method: %v", f.Method))
61
+
}
62
+
return nil
63
+
}
64
+
65
+
// Close writes any data following the last applied fragment and prevents
66
+
// future calls to ApplyFragment.
67
+
func (a *BinaryApplier) Close() (err error) {
68
+
if a.closed {
69
+
return nil
70
+
}
71
+
72
+
a.closed = true
73
+
if !a.dirty {
74
+
_, err = copyFrom(a.dst, a.src, 0)
75
+
} else {
76
+
// do nothing, applying a binary fragment copies all data
77
+
}
78
+
return err
79
+
}
80
+
81
+
func applyBinaryDeltaFragment(dst io.Writer, src io.ReaderAt, frag []byte) error {
82
+
srcSize, delta := readBinaryDeltaSize(frag)
83
+
if err := checkBinarySrcSize(src, srcSize); err != nil {
84
+
return err
85
+
}
86
+
87
+
dstSize, delta := readBinaryDeltaSize(delta)
88
+
89
+
for len(delta) > 0 {
90
+
op := delta[0]
91
+
if op == 0 {
92
+
return errors.New("invalid delta opcode 0")
93
+
}
94
+
95
+
var n int64
96
+
var err error
97
+
switch op & 0x80 {
98
+
case 0x80:
99
+
n, delta, err = applyBinaryDeltaCopy(dst, op, delta[1:], src)
100
+
case 0x00:
101
+
n, delta, err = applyBinaryDeltaAdd(dst, op, delta[1:])
102
+
}
103
+
if err != nil {
104
+
return err
105
+
}
106
+
dstSize -= n
107
+
}
108
+
109
+
if dstSize != 0 {
110
+
return errors.New("corrupt binary delta: insufficient or extra data")
111
+
}
112
+
return nil
113
+
}
114
+
115
+
// readBinaryDeltaSize reads a variable length size from a delta-encoded binary
116
+
// fragment, returing the size and the unused data. Data is encoded as:
117
+
//
118
+
// [[1xxxxxxx]...] [0xxxxxxx]
119
+
//
120
+
// in little-endian order, with 7 bits of the value per byte.
121
+
func readBinaryDeltaSize(d []byte) (size int64, rest []byte) {
122
+
shift := uint(0)
123
+
for i, b := range d {
124
+
size |= int64(b&0x7F) << shift
125
+
shift += 7
126
+
if b <= 0x7F {
127
+
return size, d[i+1:]
128
+
}
129
+
}
130
+
return size, nil
131
+
}
132
+
133
+
// applyBinaryDeltaAdd applies an add opcode in a delta-encoded binary
134
+
// fragment, returning the amount of data written and the usused part of the
135
+
// fragment. An add operation takes the form:
136
+
//
137
+
// [0xxxxxx][[data1]...]
138
+
//
139
+
// where the lower seven bits of the opcode is the number of data bytes
140
+
// following the opcode. See also pack-format.txt in the Git source.
141
+
func applyBinaryDeltaAdd(w io.Writer, op byte, delta []byte) (n int64, rest []byte, err error) {
142
+
size := int(op)
143
+
if len(delta) < size {
144
+
return 0, delta, errors.New("corrupt binary delta: incomplete add")
145
+
}
146
+
_, err = w.Write(delta[:size])
147
+
return int64(size), delta[size:], err
148
+
}
149
+
150
+
// applyBinaryDeltaCopy applies a copy opcode in a delta-encoded binary
151
+
// fragment, returing the amount of data written and the unused part of the
152
+
// fragment. A copy operation takes the form:
153
+
//
154
+
// [1xxxxxxx][offset1][offset2][offset3][offset4][size1][size2][size3]
155
+
//
156
+
// where the lower seven bits of the opcode determine which non-zero offset and
157
+
// size bytes are present in little-endian order: if bit 0 is set, offset1 is
158
+
// present, etc. If no offset or size bytes are present, offset is 0 and size
159
+
// is 0x10000. See also pack-format.txt in the Git source.
160
+
func applyBinaryDeltaCopy(w io.Writer, op byte, delta []byte, src io.ReaderAt) (n int64, rest []byte, err error) {
161
+
const defaultSize = 0x10000
162
+
163
+
unpack := func(start, bits uint) (v int64) {
164
+
for i := uint(0); i < bits; i++ {
165
+
mask := byte(1 << (i + start))
166
+
if op&mask > 0 {
167
+
if len(delta) == 0 {
168
+
err = errors.New("corrupt binary delta: incomplete copy")
169
+
return
170
+
}
171
+
v |= int64(delta[0]) << (8 * i)
172
+
delta = delta[1:]
173
+
}
174
+
}
175
+
return
176
+
}
177
+
178
+
offset := unpack(0, 4)
179
+
size := unpack(4, 3)
180
+
if err != nil {
181
+
return 0, delta, err
182
+
}
183
+
if size == 0 {
184
+
size = defaultSize
185
+
}
186
+
187
+
// TODO(bkeyes): consider pooling these buffers
188
+
b := make([]byte, size)
189
+
if _, err := src.ReadAt(b, offset); err != nil {
190
+
return 0, delta, err
191
+
}
192
+
193
+
_, err = w.Write(b)
194
+
return size, delta, err
195
+
}
196
+
197
+
func checkBinarySrcSize(r io.ReaderAt, size int64) error {
198
+
ok, err := isLen(r, size)
199
+
if err != nil {
200
+
return err
201
+
}
202
+
if !ok {
203
+
return &Conflict{"fragment src size does not match actual src size"}
204
+
}
205
+
return nil
206
+
}
+10
-73
gitdiff/apply_test.go
+10
-73
gitdiff/apply_test.go
···
9
9
"testing"
10
10
)
11
11
12
-
func TestApplierInvariants(t *testing.T) {
13
-
binary := &BinaryFragment{
14
-
Method: BinaryPatchLiteral,
15
-
Size: 2,
16
-
Data: []byte("\xbe\xef"),
17
-
}
18
-
19
-
text := &TextFragment{
20
-
NewPosition: 1,
21
-
NewLines: 1,
22
-
LinesAdded: 1,
23
-
Lines: []Line{
24
-
{Op: OpAdd, Line: "new line\n"},
25
-
},
26
-
}
27
-
28
-
file := &File{
29
-
TextFragments: []*TextFragment{text},
30
-
}
31
-
32
-
src := bytes.NewReader(nil)
33
-
dst := ioutil.Discard
34
-
35
-
assertInProgress := func(t *testing.T, kind string, err error) {
36
-
if !errors.Is(err, errApplyInProgress) {
37
-
t.Fatalf("expected in-progress error for %s apply, but got: %v", kind, err)
38
-
}
39
-
}
40
-
41
-
t.Run("binaryFirst", func(t *testing.T) {
42
-
a := NewApplier(src)
43
-
if err := a.ApplyBinaryFragment(dst, binary); err != nil {
44
-
t.Fatalf("unexpected error applying fragment: %v", err)
45
-
}
46
-
assertInProgress(t, "text", a.ApplyTextFragment(dst, text))
47
-
assertInProgress(t, "binary", a.ApplyBinaryFragment(dst, binary))
48
-
assertInProgress(t, "file", a.ApplyFile(dst, file))
49
-
})
50
-
51
-
t.Run("textFirst", func(t *testing.T) {
52
-
a := NewApplier(src)
53
-
if err := a.ApplyTextFragment(dst, text); err != nil {
54
-
t.Fatalf("unexpected error applying fragment: %v", err)
55
-
}
56
-
// additional text fragments are allowed
57
-
if err := a.ApplyTextFragment(dst, text); err != nil {
58
-
t.Fatalf("unexpected error applying second fragment: %v", err)
59
-
}
60
-
assertInProgress(t, "binary", a.ApplyBinaryFragment(dst, binary))
61
-
assertInProgress(t, "file", a.ApplyFile(dst, file))
62
-
})
63
-
64
-
t.Run("fileFirst", func(t *testing.T) {
65
-
a := NewApplier(src)
66
-
if err := a.ApplyFile(dst, file); err != nil {
67
-
t.Fatalf("unexpected error applying file: %v", err)
68
-
}
69
-
assertInProgress(t, "text", a.ApplyTextFragment(dst, text))
70
-
assertInProgress(t, "binary", a.ApplyBinaryFragment(dst, binary))
71
-
assertInProgress(t, "file", a.ApplyFile(dst, file))
72
-
})
73
-
}
74
-
75
12
func TestApplyTextFragment(t *testing.T) {
76
13
tests := map[string]applyTest{
77
14
"createFile": {Files: getApplyFiles("text_fragment_new")},
···
127
64
128
65
for name, test := range tests {
129
66
t.Run(name, func(t *testing.T) {
130
-
test.run(t, func(w io.Writer, applier *Applier, file *File) error {
67
+
test.run(t, func(dst io.Writer, src io.ReaderAt, file *File) error {
131
68
if len(file.TextFragments) != 1 {
132
69
t.Fatalf("patch should contain exactly one fragment, but it has %d", len(file.TextFragments))
133
70
}
134
-
return applier.ApplyTextFragment(w, file.TextFragments[0])
71
+
applier := NewTextApplier(dst, src)
72
+
return applier.ApplyFragment(file.TextFragments[0])
135
73
})
136
74
})
137
75
}
···
176
114
177
115
for name, test := range tests {
178
116
t.Run(name, func(t *testing.T) {
179
-
test.run(t, func(w io.Writer, applier *Applier, file *File) error {
180
-
return applier.ApplyBinaryFragment(w, file.BinaryFragment)
117
+
test.run(t, func(dst io.Writer, src io.ReaderAt, file *File) error {
118
+
applier := NewBinaryApplier(dst, src)
119
+
return applier.ApplyFragment(file.BinaryFragment)
181
120
})
182
121
})
183
122
}
···
216
155
217
156
for name, test := range tests {
218
157
t.Run(name, func(t *testing.T) {
219
-
test.run(t, func(w io.Writer, applier *Applier, file *File) error {
220
-
return applier.ApplyFile(w, file)
158
+
test.run(t, func(dst io.Writer, src io.ReaderAt, file *File) error {
159
+
return Apply(dst, src, file)
221
160
})
222
161
})
223
162
}
···
228
167
Err interface{}
229
168
}
230
169
231
-
func (at applyTest) run(t *testing.T, apply func(io.Writer, *Applier, *File) error) {
170
+
func (at applyTest) run(t *testing.T, apply func(io.Writer, io.ReaderAt, *File) error) {
232
171
src, patch, out := at.Files.Load(t)
233
172
234
173
files, _, err := Parse(bytes.NewReader(patch))
···
239
178
t.Fatalf("patch should contain exactly one file, but it has %d", len(files))
240
179
}
241
180
242
-
applier := NewApplier(bytes.NewReader(src))
243
-
244
181
var dst bytes.Buffer
245
-
err = apply(&dst, applier, files[0])
182
+
err = apply(&dst, bytes.NewReader(src), files[0])
246
183
if at.Err != nil {
247
184
assertError(t, at.Err, err, "applying fragment")
248
185
return
+153
gitdiff/apply_text.go
+153
gitdiff/apply_text.go
···
1
+
package gitdiff
2
+
3
+
import (
4
+
"io"
5
+
)
6
+
7
+
// TextApplier applies changes described in text fragments to source data. If
8
+
// changes are described in multiple fragments, those fragments must be applied
9
+
// in order. The applier must be closed after use.
10
+
//
11
+
// By default, TextApplier operates in "strict" mode, where fragment content
12
+
// and positions must exactly match those of the source.
13
+
//
14
+
type TextApplier struct {
15
+
dst io.Writer
16
+
src io.ReaderAt
17
+
lineSrc LineReaderAt
18
+
nextLine int64
19
+
20
+
closed bool
21
+
dirty bool
22
+
}
23
+
24
+
// NewTextApplier creates a TextApplier that reads data from src and writes
25
+
// modified data to dst. If src implements LineReaderAt, it is used directly.
26
+
func NewTextApplier(dst io.Writer, src io.ReaderAt) *TextApplier {
27
+
a := TextApplier{
28
+
dst: dst,
29
+
src: src,
30
+
}
31
+
32
+
if lineSrc, ok := src.(LineReaderAt); ok {
33
+
a.lineSrc = lineSrc
34
+
} else {
35
+
a.lineSrc = &lineReaderAt{r: src}
36
+
}
37
+
38
+
return &a
39
+
}
40
+
41
+
// ApplyFragment applies the changes in the fragment f, writing unwritten data
42
+
// before the start of the fragment and any changes from the fragment. If
43
+
// multiple text fragments apply to the same content, ApplyFragment must be
44
+
// called in order of increasing start position. As a result, each fragment can
45
+
// be applied at most once.
46
+
//
47
+
// If an error occurs while applying, ApplyFragment returns an *ApplyError that
48
+
// annotates the error with additional information. If the error is because of
49
+
// a conflict between the fragment and the source, the wrapped error will be a
50
+
// *Conflict.
51
+
func (a *TextApplier) ApplyFragment(f *TextFragment) error {
52
+
if a.closed {
53
+
return applyError(errApplierClosed)
54
+
}
55
+
56
+
// mark an apply as in progress, even if it fails before making changes
57
+
a.dirty = true
58
+
59
+
// application code assumes fragment fields are consistent
60
+
if err := f.Validate(); err != nil {
61
+
return applyError(err)
62
+
}
63
+
64
+
// lines are 0-indexed, positions are 1-indexed (but new files have position = 0)
65
+
fragStart := f.OldPosition - 1
66
+
if fragStart < 0 {
67
+
fragStart = 0
68
+
}
69
+
fragEnd := fragStart + f.OldLines
70
+
71
+
start := a.nextLine
72
+
if fragStart < start {
73
+
return applyError(&Conflict{"fragment overlaps with an applied fragment"})
74
+
}
75
+
76
+
if f.OldPosition == 0 {
77
+
ok, err := isLen(a.src, 0)
78
+
if err != nil {
79
+
return applyError(err)
80
+
}
81
+
if !ok {
82
+
return applyError(&Conflict{"cannot create new file from non-empty src"})
83
+
}
84
+
}
85
+
86
+
preimage := make([][]byte, fragEnd-start)
87
+
n, err := a.lineSrc.ReadLinesAt(preimage, start)
88
+
if err != nil {
89
+
return applyError(err, lineNum(start+int64(n)))
90
+
}
91
+
92
+
// copy leading data before the fragment starts
93
+
for i, line := range preimage[:fragStart-start] {
94
+
if _, err := a.dst.Write(line); err != nil {
95
+
a.nextLine = start + int64(i)
96
+
return applyError(err, lineNum(a.nextLine))
97
+
}
98
+
}
99
+
preimage = preimage[fragStart-start:]
100
+
101
+
// apply the changes in the fragment
102
+
used := int64(0)
103
+
for i, line := range f.Lines {
104
+
if err := applyTextLine(a.dst, line, preimage, used); err != nil {
105
+
a.nextLine = fragStart + used
106
+
return applyError(err, lineNum(a.nextLine), fragLineNum(i))
107
+
}
108
+
if line.Old() {
109
+
used++
110
+
}
111
+
}
112
+
a.nextLine = fragStart + used
113
+
114
+
// new position of +0,0 mean a full delete, so check for leftovers
115
+
if f.NewPosition == 0 && f.NewLines == 0 {
116
+
var b [1][]byte
117
+
n, err := a.lineSrc.ReadLinesAt(b[:], a.nextLine)
118
+
if err != nil && err != io.EOF {
119
+
return applyError(err, lineNum(a.nextLine))
120
+
}
121
+
if n > 0 {
122
+
return applyError(&Conflict{"src still has content after full delete"}, lineNum(a.nextLine))
123
+
}
124
+
}
125
+
126
+
return nil
127
+
}
128
+
129
+
func applyTextLine(dst io.Writer, line Line, preimage [][]byte, i int64) (err error) {
130
+
if line.Old() && string(preimage[i]) != line.Line {
131
+
return &Conflict{"fragment line does not match src line"}
132
+
}
133
+
if line.New() {
134
+
_, err = io.WriteString(dst, line.Line)
135
+
}
136
+
return err
137
+
}
138
+
139
+
// Close writes any data following the last applied fragment and prevents
140
+
// future calls to ApplyFragment.
141
+
func (a *TextApplier) Close() (err error) {
142
+
if a.closed {
143
+
return nil
144
+
}
145
+
146
+
a.closed = true
147
+
if !a.dirty {
148
+
_, err = copyFrom(a.dst, a.src, 0)
149
+
} else {
150
+
_, err = copyLinesFrom(a.dst, a.lineSrc, a.nextLine)
151
+
}
152
+
return err
153
+
}