fork of go-gitdiff with jj support
1package gitdiff
2
3import (
4 "errors"
5 "fmt"
6 "io"
7 "sort"
8)
9
10// Conflict indicates an apply failed due to a conflict between the patch and
11// the source content.
12//
13// Users can test if an error was caused by a conflict by using errors.Is with
14// an empty Conflict:
15//
16// if errors.Is(err, &Conflict{}) {
17// // handle conflict
18// }
19//
20type Conflict struct {
21 msg string
22}
23
24func (c *Conflict) Error() string {
25 return "conflict: " + c.msg
26}
27
28// Is implements error matching for Conflict. Passing an empty instance of
29// Conflict always returns true.
30func (c *Conflict) Is(other error) bool {
31 if other, ok := other.(*Conflict); ok {
32 return other.msg == "" || other.msg == c.msg
33 }
34 return false
35}
36
37// ApplyError wraps an error that occurs during patch application with
38// additional location information, if it is available.
39type ApplyError struct {
40 // Line is the one-indexed line number in the source data
41 Line int64
42 // Fragment is the one-indexed fragment number in the file
43 Fragment int
44 // FragmentLine is the one-indexed line number in the fragment
45 FragmentLine int
46
47 err error
48}
49
50// Unwrap returns the wrapped error.
51func (e *ApplyError) Unwrap() error {
52 return e.err
53}
54
55func (e *ApplyError) Error() string {
56 return fmt.Sprintf("%v", e.err)
57}
58
59type lineNum int
60type fragNum int
61type fragLineNum int
62
63// applyError creates a new *ApplyError wrapping err or augments the information
64// in err with args if it is already an *ApplyError. Returns nil if err is nil.
65func applyError(err error, args ...interface{}) error {
66 if err == nil {
67 return nil
68 }
69
70 e, ok := err.(*ApplyError)
71 if !ok {
72 if err == io.EOF {
73 err = io.ErrUnexpectedEOF
74 }
75 e = &ApplyError{err: err}
76 }
77 for _, arg := range args {
78 switch v := arg.(type) {
79 case lineNum:
80 e.Line = int64(v) + 1
81 case fragNum:
82 e.Fragment = int(v) + 1
83 case fragLineNum:
84 e.FragmentLine = int(v) + 1
85 }
86 }
87 return e
88}
89
90var (
91 errApplyInProgress = errors.New("gitdiff: incompatible apply in progress")
92)
93
94const (
95 applyInitial = iota
96 applyText
97 applyBinary
98 applyFile
99)
100
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.
103func 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.
110//
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.
123type 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.
132func 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.
140func (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}
147 }
148 }
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.
155func (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
168 switch {
169 case f.BinaryFragment != nil:
170 return a.ApplyBinaryFragment(dst, f.BinaryFragment)
171
172 case len(f.TextFragments) > 0:
173 frags := make([]*TextFragment, len(f.TextFragments))
174 copy(frags, f.TextFragments)
175
176 sort.Slice(frags, func(i, j int) bool {
177 return frags[i].OldPosition < frags[j].OldPosition
178 })
179
180 // TODO(bkeyes): consider merging overlapping fragments
181 // right now, the application fails if fragments overlap, but it should be
182 // possible to precompute the result of applying them in order
183
184 for i, frag := range frags {
185 if err := a.ApplyTextFragment(dst, frag); err != nil {
186 return applyError(err, fragNum(i))
187 }
188 }
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.
199func (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 switch {
235 case err == nil:
236 case err == io.EOF && n == len(preimage): // last line of frag has no newline character
237 default:
238 return applyError(err, lineNum(start+int64(n)))
239 }
240
241 // copy leading data before the fragment starts
242 for i, line := range preimage[:fragStart-start] {
243 if _, err := dst.Write(line); err != nil {
244 a.nextLine = start + int64(i)
245 return applyError(err, lineNum(a.nextLine))
246 }
247 }
248 preimage = preimage[fragStart-start:]
249
250 // apply the changes in the fragment
251 used := int64(0)
252 for i, line := range f.Lines {
253 if err := applyTextLine(dst, line, preimage, used); err != nil {
254 a.nextLine = fragStart + used
255 return applyError(err, lineNum(a.nextLine), fragLineNum(i))
256 }
257 if line.Old() {
258 used++
259 }
260 }
261 a.nextLine = fragStart + used
262 return nil
263}
264
265func applyTextLine(dst io.Writer, line Line, preimage [][]byte, i int64) (err error) {
266 if line.Old() && string(preimage[i]) != line.Line {
267 return &Conflict{"fragment line does not match src line"}
268 }
269 if line.New() {
270 _, err = io.WriteString(dst, line.Line)
271 }
272 return err
273}
274
275// Flush writes any data following the last applied fragment to dst.
276func (a *Applier) Flush(dst io.Writer) (err error) {
277 switch a.applyType {
278 case applyInitial:
279 _, err = copyFrom(dst, a.src, 0)
280 case applyText:
281 _, err = copyLinesFrom(dst, a.lineSrc, a.nextLine)
282 case applyBinary:
283 // nothing to flush, binary apply "consumes" full source
284 }
285 return err
286}
287
288// ApplyBinaryFragment applies the changes in the fragment f and writes the
289// result to dst. At most one binary fragment can be applied before a call to
290// Reset.
291func (a *Applier) ApplyBinaryFragment(dst io.Writer, f *BinaryFragment) error {
292 if a.applyType != applyInitial {
293 return applyError(errApplyInProgress)
294 }
295 defer func() { a.applyType = applyBinary }()
296
297 if f == nil {
298 return applyError(errors.New("nil fragment"))
299 }
300
301 switch f.Method {
302 case BinaryPatchLiteral:
303 if _, err := dst.Write(f.Data); err != nil {
304 return applyError(err)
305 }
306 case BinaryPatchDelta:
307 if err := applyBinaryDeltaFragment(dst, a.src, f.Data); err != nil {
308 return applyError(err)
309 }
310 default:
311 return applyError(fmt.Errorf("unsupported binary patch method: %v", f.Method))
312 }
313 return nil
314}
315
316func applyBinaryDeltaFragment(dst io.Writer, src io.ReaderAt, frag []byte) error {
317 srcSize, delta := readBinaryDeltaSize(frag)
318 if err := checkBinarySrcSize(src, srcSize); err != nil {
319 return err
320 }
321
322 dstSize, delta := readBinaryDeltaSize(delta)
323
324 for len(delta) > 0 {
325 op := delta[0]
326 if op == 0 {
327 return errors.New("invalid delta opcode 0")
328 }
329
330 var n int64
331 var err error
332 switch op & 0x80 {
333 case 0x80:
334 n, delta, err = applyBinaryDeltaCopy(dst, op, delta[1:], src)
335 case 0x00:
336 n, delta, err = applyBinaryDeltaAdd(dst, op, delta[1:])
337 }
338 if err != nil {
339 return err
340 }
341 dstSize -= n
342 }
343
344 if dstSize != 0 {
345 return errors.New("corrupt binary delta: insufficient or extra data")
346 }
347 return nil
348}
349
350// readBinaryDeltaSize reads a variable length size from a delta-encoded binary
351// fragment, returing the size and the unused data. Data is encoded as:
352//
353// [[1xxxxxxx]...] [0xxxxxxx]
354//
355// in little-endian order, with 7 bits of the value per byte.
356func readBinaryDeltaSize(d []byte) (size int64, rest []byte) {
357 shift := uint(0)
358 for i, b := range d {
359 size |= int64(b&0x7F) << shift
360 shift += 7
361 if b <= 0x7F {
362 return size, d[i+1:]
363 }
364 }
365 return size, nil
366}
367
368// applyBinaryDeltaAdd applies an add opcode in a delta-encoded binary
369// fragment, returning the amount of data written and the usused part of the
370// fragment. An add operation takes the form:
371//
372// [0xxxxxx][[data1]...]
373//
374// where the lower seven bits of the opcode is the number of data bytes
375// following the opcode. See also pack-format.txt in the Git source.
376func applyBinaryDeltaAdd(w io.Writer, op byte, delta []byte) (n int64, rest []byte, err error) {
377 size := int(op)
378 if len(delta) < size {
379 return 0, delta, errors.New("corrupt binary delta: incomplete add")
380 }
381 _, err = w.Write(delta[:size])
382 return int64(size), delta[size:], err
383}
384
385// applyBinaryDeltaCopy applies a copy opcode in a delta-encoded binary
386// fragment, returing the amount of data written and the unused part of the
387// fragment. A copy operation takes the form:
388//
389// [1xxxxxxx][offset1][offset2][offset3][offset4][size1][size2][size3]
390//
391// where the lower seven bits of the opcode determine which non-zero offset and
392// size bytes are present in little-endian order: if bit 0 is set, offset1 is
393// present, etc. If no offset or size bytes are present, offset is 0 and size
394// is 0x10000. See also pack-format.txt in the Git source.
395func applyBinaryDeltaCopy(w io.Writer, op byte, delta []byte, src io.ReaderAt) (n int64, rest []byte, err error) {
396 const defaultSize = 0x10000
397
398 unpack := func(start, bits uint) (v int64) {
399 for i := uint(0); i < bits; i++ {
400 mask := byte(1 << (i + start))
401 if op&mask > 0 {
402 if len(delta) == 0 {
403 err = errors.New("corrupt binary delta: incomplete copy")
404 return
405 }
406 v |= int64(delta[0]) << (8 * i)
407 delta = delta[1:]
408 }
409 }
410 return
411 }
412
413 offset := unpack(0, 4)
414 size := unpack(4, 3)
415 if err != nil {
416 return 0, delta, err
417 }
418 if size == 0 {
419 size = defaultSize
420 }
421
422 // TODO(bkeyes): consider pooling these buffers
423 b := make([]byte, size)
424 if _, err := src.ReadAt(b, offset); err != nil {
425 return 0, delta, err
426 }
427
428 _, err = w.Write(b)
429 return size, delta, err
430}
431
432func checkBinarySrcSize(r io.ReaderAt, size int64) error {
433 ok, err := isLen(r, size)
434 if err != nil {
435 return err
436 }
437 if !ok {
438 return &Conflict{"fragment src size does not match actual src size"}
439 }
440 return nil
441}