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 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 }
271
272 return nil
273}
274
275func 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.
286func (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.
301func (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 default:
321 return applyError(fmt.Errorf("unsupported binary patch method: %v", f.Method))
322 }
323 return nil
324}
325
326func 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.
366func 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.
386func 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.
405func 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
442func checkBinarySrcSize(r io.ReaderAt, size int64) error {
443 ok, err := isLen(r, size)
444 if err != nil {
445 return err
446 }
447 if !ok {
448 return &Conflict{"fragment src size does not match actual src size"}
449 }
450 return nil
451}