+134
-79
gitdiff/apply.go
+134
-79
gitdiff/apply.go
···
1
1
package gitdiff
2
2
3
3
import (
4
-
"bytes"
5
4
"errors"
6
5
"fmt"
7
6
"io"
8
-
"io/ioutil"
9
7
)
10
8
11
9
// Conflict indicates an apply failed due to a conflict between the patch and
···
85
83
return e
86
84
}
87
85
88
-
// ApplyStrict writes data from src to dst, modifying it as described by the
89
-
// fragments in the file. For text files, each fragment, including all context
90
-
// lines, must exactly match src at the expected line number.
86
+
var (
87
+
errApplyInProgress = errors.New("gitdiff: incompatible apply in progress")
88
+
)
89
+
90
+
const (
91
+
applyInitial = iota
92
+
applyText
93
+
applyBinary
94
+
)
95
+
96
+
// Applier applies changes described in fragments to source data. If changes
97
+
// are described in multiple fragments, those fragments must be applied in
98
+
// order, usually by calling ApplyFile.
91
99
//
92
-
// If the apply fails, ApplyStrict returns an *ApplyError wrapping the cause.
93
-
// Partial data may be written to dst in this case.
94
-
func (f *File) ApplyStrict(dst io.Writer, src io.Reader) error {
95
-
// TODO(bkeyes): take an io.ReaderAt and avoid this!
96
-
data, err := ioutil.ReadAll(src)
97
-
if err != nil {
98
-
return applyError(err)
100
+
// By default, Applier operates in "strict" mode, where fragment content and
101
+
// positions must exactly match those of the source.
102
+
//
103
+
// If an error occurs while applying, methods on Applier return instances of
104
+
// *ApplyError that annotate the wrapped error with additional information
105
+
// when available. If the error is because of a conflict between a fragment and
106
+
// the source, the wrapped error will be a *Conflict.
107
+
//
108
+
// While an Applier can apply both text and binary fragments, only one fragment
109
+
// type can be used without resetting the Applier. The first fragment applied
110
+
// sets the type for the Applier. Mixing fragment types or mixing
111
+
// fragment-level and file-level applies results in an error.
112
+
type Applier struct {
113
+
src io.ReaderAt
114
+
lineSrc LineReaderAt
115
+
nextLine int64
116
+
applyType int
117
+
}
118
+
119
+
// NewApplier creates an Applier that reads data from src. If src is a
120
+
// LineReaderAt, it is used directly to apply text fragments.
121
+
func NewApplier(src io.ReaderAt) *Applier {
122
+
a := new(Applier)
123
+
a.Reset(src)
124
+
return a
125
+
}
126
+
127
+
// Reset resets the input and internal state of the Applier. If src is nil, the
128
+
// existing source is reused.
129
+
func (a *Applier) Reset(src io.ReaderAt) {
130
+
if src != nil {
131
+
a.src = src
132
+
if lineSrc, ok := src.(LineReaderAt); ok {
133
+
a.lineSrc = lineSrc
134
+
} else {
135
+
a.lineSrc = &lineReaderAt{r: src}
136
+
}
99
137
}
138
+
a.nextLine = 0
139
+
a.applyType = applyInitial
140
+
}
100
141
101
-
if f.IsBinary {
102
-
if f.BinaryFragment != nil {
103
-
return f.BinaryFragment.Apply(dst, bytes.NewReader(data))
104
-
}
105
-
_, err = dst.Write(data)
106
-
return applyError(err)
142
+
// ApplyFile applies the changes in all of the fragments of f and writes the
143
+
// result to dst.
144
+
func (a *Applier) ApplyFile(dst io.Writer, f *File) error {
145
+
if a.applyType != applyInitial {
146
+
return applyError(errApplyInProgress)
107
147
}
108
148
109
-
// TODO(bkeyes): check for this conflict case
110
-
// &Conflict{"cannot create new file from non-empty src"}
149
+
if f.IsBinary && f.BinaryFragment != nil {
150
+
return a.ApplyBinaryFragment(dst, f.BinaryFragment)
151
+
}
111
152
112
-
lra := NewLineReaderAt(bytes.NewReader(data))
153
+
// TODO(bkeyes): sort fragments by start position
154
+
// TODO(bkeyes): merge overlapping fragments
113
155
114
-
var next int64
115
156
for i, frag := range f.TextFragments {
116
-
next, err = frag.ApplyStrict(dst, lra, next)
117
-
if err != nil {
157
+
if err := a.ApplyTextFragment(dst, frag); err != nil {
118
158
return applyError(err, fragNum(i))
119
159
}
120
160
}
121
161
122
-
// TODO(bkeyes): extract this to a utility
123
-
buf := make([][]byte, 64)
124
-
for {
125
-
n, err := lra.ReadLinesAt(buf, next)
126
-
if err != nil && err != io.EOF {
127
-
return applyError(err, lineNum(next+int64(n)))
128
-
}
162
+
return applyError(a.Flush(dst))
163
+
}
129
164
130
-
for i := 0; i < n; i++ {
131
-
if _, err := dst.Write(buf[n]); err != nil {
132
-
return applyError(err, lineNum(next+int64(n)))
133
-
}
134
-
}
135
-
136
-
next += int64(n)
137
-
if n < len(buf) {
138
-
return nil
139
-
}
165
+
// ApplyTextFragment applies the changes in the fragment f and writes unwritten
166
+
// data before the start of the fragment and the result to dst. If multiple
167
+
// text fragments apply to the same source, ApplyTextFragment must be called in
168
+
// order of increasing start position. As a result, each fragment can be
169
+
// applied at most once before a call to Reset.
170
+
func (a *Applier) ApplyTextFragment(dst io.Writer, f *TextFragment) error {
171
+
switch a.applyType {
172
+
case applyInitial, applyText:
173
+
default:
174
+
return applyError(errApplyInProgress)
140
175
}
141
-
}
176
+
a.applyType = applyText
142
177
143
-
// ApplyStrict copies from src to dst, from line start through then end of the
144
-
// fragment, modifying the data as described by the fragment. The fragment,
145
-
// including all context lines, must exactly match src at the expected line
146
-
// number. ApplyStrict returns the number of the next unprocessed line in src
147
-
// and any error. When the error is not non-nil, partial data may be written.
148
-
func (f *TextFragment) ApplyStrict(dst io.Writer, src LineReaderAt, start int64) (next int64, err error) {
149
178
// application code assumes fragment fields are consistent
150
179
if err := f.Validate(); err != nil {
151
-
return start, applyError(err)
180
+
return applyError(err)
152
181
}
153
182
154
183
// lines are 0-indexed, positions are 1-indexed (but new files have position = 0)
···
158
187
}
159
188
fragEnd := fragStart + f.OldLines
160
189
190
+
start := a.nextLine
161
191
if fragStart < start {
162
-
return start, applyError(&Conflict{"fragment overlaps with an applied fragment"})
192
+
return applyError(&Conflict{"fragment overlaps with an applied fragment"})
193
+
}
194
+
195
+
if f.OldPosition == 0 {
196
+
ok, err := isLen(a.src, 0)
197
+
if err != nil {
198
+
return applyError(err)
199
+
}
200
+
if !ok {
201
+
return applyError(&Conflict{"cannot create new file from non-empty src"})
202
+
}
163
203
}
164
204
165
205
preimage := make([][]byte, fragEnd-start)
166
-
n, err := src.ReadLinesAt(preimage, start)
206
+
n, err := a.lineSrc.ReadLinesAt(preimage, start)
167
207
switch {
168
208
case err == nil:
169
209
case err == io.EOF && n == len(preimage): // last line of frag has no newline character
170
210
default:
171
-
return start, applyError(err, lineNum(start+int64(n)))
211
+
return applyError(err, lineNum(start+int64(n)))
172
212
}
173
213
174
214
// copy leading data before the fragment starts
175
215
for i, line := range preimage[:fragStart-start] {
176
216
if _, err := dst.Write(line); err != nil {
177
-
next = start + int64(i)
178
-
return next, applyError(err, lineNum(next))
217
+
a.nextLine = start + int64(i)
218
+
return applyError(err, lineNum(a.nextLine))
179
219
}
180
220
}
181
221
preimage = preimage[fragStart-start:]
···
184
224
used := int64(0)
185
225
for i, line := range f.Lines {
186
226
if err := applyTextLine(dst, line, preimage, used); err != nil {
187
-
next = fragStart + used
188
-
return next, applyError(err, lineNum(next), fragLineNum(i))
227
+
a.nextLine = fragStart + used
228
+
return applyError(err, lineNum(a.nextLine), fragLineNum(i))
189
229
}
190
230
if line.Old() {
191
231
used++
192
232
}
193
233
}
194
-
return fragStart + used, nil
234
+
a.nextLine = fragStart + used
235
+
return nil
195
236
}
196
237
197
238
func applyTextLine(dst io.Writer, line Line, preimage [][]byte, i int64) (err error) {
···
201
242
if line.New() {
202
243
_, err = io.WriteString(dst, line.Line)
203
244
}
204
-
return
245
+
return err
205
246
}
206
247
207
-
// Apply writes data from src to dst, modifying it as described by the
208
-
// fragment.
209
-
//
210
-
// Unlike text fragments, binary fragments do not distinguish between strict
211
-
// and non-strict application.
212
-
func (f *BinaryFragment) Apply(dst io.Writer, src io.ReaderAt) error {
248
+
// Flush writes any data following the last applied fragment to dst.
249
+
func (a *Applier) Flush(dst io.Writer) (err error) {
250
+
switch a.applyType {
251
+
case applyInitial:
252
+
_, err = copyFrom(dst, a.src, 0)
253
+
case applyText:
254
+
_, err = copyLinesFrom(dst, a.lineSrc, a.nextLine)
255
+
case applyBinary:
256
+
// nothing to flush, binary apply "consumes" full source
257
+
}
258
+
return err
259
+
}
260
+
261
+
// ApplyBinaryFragment applies the changes in the fragment f and writes the
262
+
// result to dst. At most one binary fragment can be applied before a call to
263
+
// Reset.
264
+
func (a *Applier) ApplyBinaryFragment(dst io.Writer, f *BinaryFragment) error {
265
+
if a.applyType != applyInitial {
266
+
return applyError(errApplyInProgress)
267
+
}
268
+
a.applyType = applyText
269
+
270
+
if f == nil {
271
+
return applyError(errors.New("nil fragment"))
272
+
}
273
+
213
274
switch f.Method {
214
275
case BinaryPatchLiteral:
215
276
if _, err := dst.Write(f.Data); err != nil {
216
277
return applyError(err)
217
278
}
218
279
case BinaryPatchDelta:
219
-
if err := applyBinaryDeltaFragment(dst, src, f.Data); err != nil {
280
+
if err := applyBinaryDeltaFragment(dst, a.src, f.Data); err != nil {
220
281
return applyError(err)
221
282
}
222
283
default:
223
284
return applyError(fmt.Errorf("unsupported binary patch method: %v", f.Method))
224
285
}
225
-
226
286
return nil
227
287
}
228
288
229
289
func applyBinaryDeltaFragment(dst io.Writer, src io.ReaderAt, frag []byte) error {
230
290
srcSize, delta := readBinaryDeltaSize(frag)
231
-
if err := checkBinarySrcSize(srcSize, src); err != nil {
291
+
if err := checkBinarySrcSize(src, srcSize); err != nil {
232
292
return err
233
293
}
234
294
···
342
402
return size, delta, err
343
403
}
344
404
345
-
func checkBinarySrcSize(size int64, src io.ReaderAt) error {
346
-
start := size
347
-
if start > 0 {
348
-
start--
405
+
func checkBinarySrcSize(r io.ReaderAt, size int64) error {
406
+
ok, err := isLen(r, size)
407
+
if err != nil {
408
+
return err
349
409
}
350
-
var b [2]byte
351
-
n, err := src.ReadAt(b[:], start)
352
-
if err == io.EOF && (size == 0 && n == 0) || (size > 0 && n == 1) {
353
-
return nil
410
+
if !ok {
411
+
return &Conflict{"fragment src size does not match actual src size"}
354
412
}
355
-
if err != nil && err != io.EOF {
356
-
return err
357
-
}
358
-
return &Conflict{"fragment src size does not match actual src size"}
413
+
return nil
359
414
}
360
415
361
416
func wrapEOF(err error) error {
+11
-15
gitdiff/apply_test.go
+11
-15
gitdiff/apply_test.go
···
57
57
},
58
58
Err: &Conflict{},
59
59
},
60
-
// TODO(bkeyes): this check has moved to the file level (probably)
61
-
// "errorNewFile": {
62
-
// Files: applyFiles{
63
-
// Src: "text_fragment_error.src",
64
-
// Patch: "text_fragment_error_new_file.patch",
65
-
// },
66
-
// Err: &Conflict{},
67
-
// },
60
+
"errorNewFile": {
61
+
Files: applyFiles{
62
+
Src: "text_fragment_error.src",
63
+
Patch: "text_fragment_error_new_file.patch",
64
+
},
65
+
Err: &Conflict{},
66
+
},
68
67
}
69
68
70
69
for name, test := range tests {
···
82
81
t.Fatalf("patch should contain exactly one fragment, but it has %d", len(files[0].TextFragments))
83
82
}
84
83
85
-
frag := files[0].TextFragments[0]
84
+
applier := NewApplier(bytes.NewReader(src))
86
85
87
86
var dst bytes.Buffer
88
-
_, err = frag.ApplyStrict(&dst, NewLineReaderAt(bytes.NewReader(src)), 0)
87
+
err = applier.ApplyTextFragment(&dst, files[0].TextFragments[0])
89
88
if test.Err != nil {
90
89
checkApplyError(t, test.Err, err)
91
90
return
···
153
152
t.Fatalf("patch should contain exactly one file, but it has %d", len(files))
154
153
}
155
154
156
-
frag := files[0].BinaryFragment
157
-
if frag == nil {
158
-
t.Fatalf("patch should contain a binary fragment, but it was nil")
159
-
}
155
+
applier := NewApplier(bytes.NewReader(src))
160
156
161
157
var dst bytes.Buffer
162
-
err = frag.Apply(&dst, bytes.NewReader(src))
158
+
err = applier.ApplyBinaryFragment(&dst, files[0].BinaryFragment)
163
159
if test.Err != nil {
164
160
checkApplyError(t, test.Err, err)
165
161
return
+6
-1
gitdiff/gitdiff.go
+6
-1
gitdiff/gitdiff.go
···
1
1
package gitdiff
2
2
3
3
import (
4
+
"errors"
4
5
"fmt"
5
6
"os"
6
7
)
···
64
65
// Validate checks that the fragment is self-consistent and appliable. Validate
65
66
// returns an error if and only if the fragment is invalid.
66
67
func (f *TextFragment) Validate() error {
68
+
if f == nil {
69
+
return errors.New("nil fragment")
70
+
}
71
+
67
72
var (
68
73
oldLines, newLines int64
69
74
leadingContext, trailingContext int64
···
117
122
118
123
// if a file is being created, it can only contain additions
119
124
if f.OldPosition == 0 && f.OldLines != 0 {
120
-
return fmt.Errorf("file creation fragment contains context or deletion lines")
125
+
return errors.New("file creation fragment contains context or deletion lines")
121
126
}
122
127
123
128
return nil
+79
-5
gitdiff/io.go
+79
-5
gitdiff/io.go
···
24
24
ReadLinesAt(lines [][]byte, offset int64) (n int, err error)
25
25
}
26
26
27
-
// NewLineReaderAt creates a LineReaderAt from an io.ReaderAt.
28
-
func NewLineReaderAt(r io.ReaderAt) LineReaderAt {
29
-
return &lineReaderAt{r: r}
30
-
}
31
-
32
27
type lineReaderAt struct {
33
28
r io.ReaderAt
34
29
index []int64
···
129
124
}
130
125
return
131
126
}
127
+
128
+
func isLen(r io.ReaderAt, n int64) (bool, error) {
129
+
off := n - 1
130
+
if off < 0 {
131
+
off = 0
132
+
}
133
+
134
+
var b [2]byte
135
+
nr, err := r.ReadAt(b[:], off)
136
+
if err == io.EOF {
137
+
return (n == 0 && nr == 0) || (n > 0 && nr == 1), nil
138
+
}
139
+
return false, err
140
+
}
141
+
142
+
// copyFrom writes bytes starting from offset off in src to dst stopping at the
143
+
// end of src or at the first error. copyFrom returns the number of bytes
144
+
// written and any error.
145
+
func copyFrom(dst io.Writer, src io.ReaderAt, off int64) (written int64, err error) {
146
+
buf := make([]byte, 32*1024) // stolen from io.Copy
147
+
for {
148
+
nr, rerr := src.ReadAt(buf, off)
149
+
if nr > 0 {
150
+
nw, werr := dst.Write(buf[0:nr])
151
+
if nw > 0 {
152
+
written += int64(nw)
153
+
}
154
+
if werr != nil {
155
+
err = werr
156
+
break
157
+
}
158
+
if nr != nw {
159
+
err = io.ErrShortWrite
160
+
break
161
+
}
162
+
}
163
+
if rerr != nil {
164
+
if rerr != io.EOF {
165
+
err = rerr
166
+
}
167
+
break
168
+
}
169
+
}
170
+
return written, err
171
+
}
172
+
173
+
// copyLinesFrom writes lines starting from line off in src to dst stopping at
174
+
// the end of src or at the first error. copyLinesFrom returns the number of
175
+
// lines written and any error.
176
+
func copyLinesFrom(dst io.Writer, src LineReaderAt, off int64) (written int64, err error) {
177
+
buf := make([][]byte, 32)
178
+
ReadLoop:
179
+
for {
180
+
nr, rerr := src.ReadLinesAt(buf, off)
181
+
if nr > 0 {
182
+
for _, line := range buf[0:nr] {
183
+
nw, werr := dst.Write(line)
184
+
if nw > 0 {
185
+
written++
186
+
}
187
+
if werr != nil {
188
+
err = werr
189
+
break ReadLoop
190
+
}
191
+
if len(line) != nw {
192
+
err = io.ErrShortWrite
193
+
break ReadLoop
194
+
}
195
+
}
196
+
}
197
+
if rerr != nil {
198
+
if rerr != io.EOF {
199
+
err = rerr
200
+
}
201
+
break
202
+
}
203
+
}
204
+
return written, err
205
+
}