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
263 // new position of +0,0 mean a full delete, so check for leftovers
264 if f.NewPosition == 0 && f.NewLines == 0 {
265 var b [1][]byte
266 n, err := a.lineSrc.ReadLinesAt(b[:], a.nextLine)
267 if err != nil && err != io.EOF {
268 return applyError(err, lineNum(a.nextLine))
269 }
270 if n > 0 {
271 return applyError(&Conflict{"src still has content after full delete"}, lineNum(a.nextLine))
272 }
273 }
274
275 return nil
276}
277
278func applyTextLine(dst io.Writer, line Line, preimage [][]byte, i int64) (err error) {
279 if line.Old() && string(preimage[i]) != line.Line {
280 return &Conflict{"fragment line does not match src line"}
281 }
282 if line.New() {
283 _, err = io.WriteString(dst, line.Line)
284 }
285 return err
286}
287
288// Flush writes any data following the last applied fragment to dst.
289func (a *Applier) Flush(dst io.Writer) (err error) {
290 switch a.applyType {
291 case applyInitial:
292 _, err = copyFrom(dst, a.src, 0)
293 case applyText:
294 _, err = copyLinesFrom(dst, a.lineSrc, a.nextLine)
295 case applyBinary:
296 // nothing to flush, binary apply "consumes" full source
297 }
298 return err
299}
300
301// ApplyBinaryFragment applies the changes in the fragment f and writes the
302// result to dst. At most one binary fragment can be applied before a call to
303// Reset.
304func (a *Applier) ApplyBinaryFragment(dst io.Writer, f *BinaryFragment) error {
305 if a.applyType != applyInitial {
306 return applyError(errApplyInProgress)
307 }
308 defer func() { a.applyType = applyBinary }()
309
310 if f == nil {
311 return applyError(errors.New("nil fragment"))
312 }
313
314 switch f.Method {
315 case BinaryPatchLiteral:
316 if _, err := dst.Write(f.Data); err != nil {
317 return applyError(err)
318 }
319 case BinaryPatchDelta:
320 if err := applyBinaryDeltaFragment(dst, a.src, f.Data); err != nil {
321 return applyError(err)
322 }
323 default:
324 return applyError(fmt.Errorf("unsupported binary patch method: %v", f.Method))
325 }
326 return nil
327}
328
329func applyBinaryDeltaFragment(dst io.Writer, src io.ReaderAt, frag []byte) error {
330 srcSize, delta := readBinaryDeltaSize(frag)
331 if err := checkBinarySrcSize(src, srcSize); err != nil {
332 return err
333 }
334
335 dstSize, delta := readBinaryDeltaSize(delta)
336
337 for len(delta) > 0 {
338 op := delta[0]
339 if op == 0 {
340 return errors.New("invalid delta opcode 0")
341 }
342
343 var n int64
344 var err error
345 switch op & 0x80 {
346 case 0x80:
347 n, delta, err = applyBinaryDeltaCopy(dst, op, delta[1:], src)
348 case 0x00:
349 n, delta, err = applyBinaryDeltaAdd(dst, op, delta[1:])
350 }
351 if err != nil {
352 return err
353 }
354 dstSize -= n
355 }
356
357 if dstSize != 0 {
358 return errors.New("corrupt binary delta: insufficient or extra data")
359 }
360 return nil
361}
362
363// readBinaryDeltaSize reads a variable length size from a delta-encoded binary
364// fragment, returing the size and the unused data. Data is encoded as:
365//
366// [[1xxxxxxx]...] [0xxxxxxx]
367//
368// in little-endian order, with 7 bits of the value per byte.
369func readBinaryDeltaSize(d []byte) (size int64, rest []byte) {
370 shift := uint(0)
371 for i, b := range d {
372 size |= int64(b&0x7F) << shift
373 shift += 7
374 if b <= 0x7F {
375 return size, d[i+1:]
376 }
377 }
378 return size, nil
379}
380
381// applyBinaryDeltaAdd applies an add opcode in a delta-encoded binary
382// fragment, returning the amount of data written and the usused part of the
383// fragment. An add operation takes the form:
384//
385// [0xxxxxx][[data1]...]
386//
387// where the lower seven bits of the opcode is the number of data bytes
388// following the opcode. See also pack-format.txt in the Git source.
389func applyBinaryDeltaAdd(w io.Writer, op byte, delta []byte) (n int64, rest []byte, err error) {
390 size := int(op)
391 if len(delta) < size {
392 return 0, delta, errors.New("corrupt binary delta: incomplete add")
393 }
394 _, err = w.Write(delta[:size])
395 return int64(size), delta[size:], err
396}
397
398// applyBinaryDeltaCopy applies a copy opcode in a delta-encoded binary
399// fragment, returing the amount of data written and the unused part of the
400// fragment. A copy operation takes the form:
401//
402// [1xxxxxxx][offset1][offset2][offset3][offset4][size1][size2][size3]
403//
404// where the lower seven bits of the opcode determine which non-zero offset and
405// size bytes are present in little-endian order: if bit 0 is set, offset1 is
406// present, etc. If no offset or size bytes are present, offset is 0 and size
407// is 0x10000. See also pack-format.txt in the Git source.
408func applyBinaryDeltaCopy(w io.Writer, op byte, delta []byte, src io.ReaderAt) (n int64, rest []byte, err error) {
409 const defaultSize = 0x10000
410
411 unpack := func(start, bits uint) (v int64) {
412 for i := uint(0); i < bits; i++ {
413 mask := byte(1 << (i + start))
414 if op&mask > 0 {
415 if len(delta) == 0 {
416 err = errors.New("corrupt binary delta: incomplete copy")
417 return
418 }
419 v |= int64(delta[0]) << (8 * i)
420 delta = delta[1:]
421 }
422 }
423 return
424 }
425
426 offset := unpack(0, 4)
427 size := unpack(4, 3)
428 if err != nil {
429 return 0, delta, err
430 }
431 if size == 0 {
432 size = defaultSize
433 }
434
435 // TODO(bkeyes): consider pooling these buffers
436 b := make([]byte, size)
437 if _, err := src.ReadAt(b, offset); err != nil {
438 return 0, delta, err
439 }
440
441 _, err = w.Write(b)
442 return size, delta, err
443}
444
445func checkBinarySrcSize(r io.ReaderAt, size int64) error {
446 ok, err := isLen(r, size)
447 if err != nil {
448 return err
449 }
450 if !ok {
451 return &Conflict{"fragment src size does not match actual src size"}
452 }
453 return nil
454}