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// Applier applies changes described in fragments to source data. If changes
102// are described in multiple fragments, those fragments must be applied in
103// order, usually by calling ApplyFile.
104//
105// By default, Applier operates in "strict" mode, where fragment content and
106// positions must exactly match those of the source.
107//
108// If an error occurs while applying, methods on Applier return instances of
109// *ApplyError that annotate the wrapped error with additional information
110// when available. If the error is because of a conflict between a fragment and
111// the source, the wrapped error will be a *Conflict.
112//
113// While an Applier can apply both text and binary fragments, only one fragment
114// type can be used without resetting the Applier. The first fragment applied
115// sets the type for the Applier. Mixing fragment types or mixing
116// fragment-level and file-level applies results in an error.
117type Applier struct {
118 src io.ReaderAt
119 lineSrc LineReaderAt
120 nextLine int64
121 applyType int
122}
123
124// NewApplier creates an Applier that reads data from src. If src is a
125// LineReaderAt, it is used directly to apply text fragments.
126func NewApplier(src io.ReaderAt) *Applier {
127 a := new(Applier)
128 a.Reset(src)
129 return a
130}
131
132// Reset resets the input and internal state of the Applier. If src is nil, the
133// existing source is reused.
134func (a *Applier) Reset(src io.ReaderAt) {
135 if src != nil {
136 a.src = src
137 if lineSrc, ok := src.(LineReaderAt); ok {
138 a.lineSrc = lineSrc
139 } else {
140 a.lineSrc = &lineReaderAt{r: src}
141 }
142 }
143 a.nextLine = 0
144 a.applyType = applyInitial
145}
146
147// ApplyFile applies the changes in all of the fragments of f and writes the
148// result to dst.
149func (a *Applier) ApplyFile(dst io.Writer, f *File) error {
150 if a.applyType != applyInitial {
151 return applyError(errApplyInProgress)
152 }
153 defer func() { a.applyType = applyFile }()
154
155 if f.IsBinary && len(f.TextFragments) > 0 {
156 return applyError(errors.New("binary file contains text fragments"))
157 }
158 if !f.IsBinary && f.BinaryFragment != nil {
159 return applyError(errors.New("text file contains binary fragment"))
160 }
161
162 switch {
163 case f.BinaryFragment != nil:
164 return a.ApplyBinaryFragment(dst, f.BinaryFragment)
165
166 case len(f.TextFragments) > 0:
167 frags := make([]*TextFragment, len(f.TextFragments))
168 copy(frags, f.TextFragments)
169
170 sort.Slice(frags, func(i, j int) bool {
171 return frags[i].OldPosition < frags[j].OldPosition
172 })
173
174 // TODO(bkeyes): consider merging overlapping fragments
175 // right now, the application fails if fragments overlap, but it should be
176 // possible to precompute the result of applying them in order
177
178 for i, frag := range frags {
179 if err := a.ApplyTextFragment(dst, frag); err != nil {
180 return applyError(err, fragNum(i))
181 }
182 }
183 }
184
185 return applyError(a.Flush(dst))
186}
187
188// ApplyTextFragment applies the changes in the fragment f and writes unwritten
189// data before the start of the fragment and the result to dst. If multiple
190// text fragments apply to the same source, ApplyTextFragment must be called in
191// order of increasing start position. As a result, each fragment can be
192// applied at most once before a call to Reset.
193func (a *Applier) ApplyTextFragment(dst io.Writer, f *TextFragment) error {
194 if a.applyType != applyInitial && a.applyType != applyText {
195 return applyError(errApplyInProgress)
196 }
197 defer func() { a.applyType = applyText }()
198
199 // application code assumes fragment fields are consistent
200 if err := f.Validate(); err != nil {
201 return applyError(err)
202 }
203
204 // lines are 0-indexed, positions are 1-indexed (but new files have position = 0)
205 fragStart := f.OldPosition - 1
206 if fragStart < 0 {
207 fragStart = 0
208 }
209 fragEnd := fragStart + f.OldLines
210
211 start := a.nextLine
212 if fragStart < start {
213 return applyError(&Conflict{"fragment overlaps with an applied fragment"})
214 }
215
216 if f.OldPosition == 0 {
217 ok, err := isLen(a.src, 0)
218 if err != nil {
219 return applyError(err)
220 }
221 if !ok {
222 return applyError(&Conflict{"cannot create new file from non-empty src"})
223 }
224 }
225
226 preimage := make([][]byte, fragEnd-start)
227 n, err := a.lineSrc.ReadLinesAt(preimage, start)
228 switch {
229 case err == nil:
230 case err == io.EOF && n == len(preimage): // last line of frag has no newline character
231 default:
232 return applyError(err, lineNum(start+int64(n)))
233 }
234
235 // copy leading data before the fragment starts
236 for i, line := range preimage[:fragStart-start] {
237 if _, err := dst.Write(line); err != nil {
238 a.nextLine = start + int64(i)
239 return applyError(err, lineNum(a.nextLine))
240 }
241 }
242 preimage = preimage[fragStart-start:]
243
244 // apply the changes in the fragment
245 used := int64(0)
246 for i, line := range f.Lines {
247 if err := applyTextLine(dst, line, preimage, used); err != nil {
248 a.nextLine = fragStart + used
249 return applyError(err, lineNum(a.nextLine), fragLineNum(i))
250 }
251 if line.Old() {
252 used++
253 }
254 }
255 a.nextLine = fragStart + used
256 return nil
257}
258
259func applyTextLine(dst io.Writer, line Line, preimage [][]byte, i int64) (err error) {
260 if line.Old() && string(preimage[i]) != line.Line {
261 return &Conflict{"fragment line does not match src line"}
262 }
263 if line.New() {
264 _, err = io.WriteString(dst, line.Line)
265 }
266 return err
267}
268
269// Flush writes any data following the last applied fragment to dst.
270func (a *Applier) Flush(dst io.Writer) (err error) {
271 switch a.applyType {
272 case applyInitial:
273 _, err = copyFrom(dst, a.src, 0)
274 case applyText:
275 _, err = copyLinesFrom(dst, a.lineSrc, a.nextLine)
276 case applyBinary:
277 // nothing to flush, binary apply "consumes" full source
278 }
279 return err
280}
281
282// ApplyBinaryFragment applies the changes in the fragment f and writes the
283// result to dst. At most one binary fragment can be applied before a call to
284// Reset.
285func (a *Applier) ApplyBinaryFragment(dst io.Writer, f *BinaryFragment) error {
286 if a.applyType != applyInitial {
287 return applyError(errApplyInProgress)
288 }
289 defer func() { a.applyType = applyBinary }()
290
291 if f == nil {
292 return applyError(errors.New("nil fragment"))
293 }
294
295 switch f.Method {
296 case BinaryPatchLiteral:
297 if _, err := dst.Write(f.Data); err != nil {
298 return applyError(err)
299 }
300 case BinaryPatchDelta:
301 if err := applyBinaryDeltaFragment(dst, a.src, f.Data); err != nil {
302 return applyError(err)
303 }
304 default:
305 return applyError(fmt.Errorf("unsupported binary patch method: %v", f.Method))
306 }
307 return nil
308}
309
310func applyBinaryDeltaFragment(dst io.Writer, src io.ReaderAt, frag []byte) error {
311 srcSize, delta := readBinaryDeltaSize(frag)
312 if err := checkBinarySrcSize(src, srcSize); err != nil {
313 return err
314 }
315
316 dstSize, delta := readBinaryDeltaSize(delta)
317
318 for len(delta) > 0 {
319 op := delta[0]
320 if op == 0 {
321 return errors.New("invalid delta opcode 0")
322 }
323
324 var n int64
325 var err error
326 switch op & 0x80 {
327 case 0x80:
328 n, delta, err = applyBinaryDeltaCopy(dst, op, delta[1:], src)
329 case 0x00:
330 n, delta, err = applyBinaryDeltaAdd(dst, op, delta[1:])
331 }
332 if err != nil {
333 return err
334 }
335 dstSize -= n
336 }
337
338 if dstSize != 0 {
339 return errors.New("corrupt binary delta: insufficient or extra data")
340 }
341 return nil
342}
343
344// readBinaryDeltaSize reads a variable length size from a delta-encoded binary
345// fragment, returing the size and the unused data. Data is encoded as:
346//
347// [[1xxxxxxx]...] [0xxxxxxx]
348//
349// in little-endian order, with 7 bits of the value per byte.
350func readBinaryDeltaSize(d []byte) (size int64, rest []byte) {
351 shift := uint(0)
352 for i, b := range d {
353 size |= int64(b&0x7F) << shift
354 shift += 7
355 if b <= 0x7F {
356 return size, d[i+1:]
357 }
358 }
359 return size, nil
360}
361
362// applyBinaryDeltaAdd applies an add opcode in a delta-encoded binary
363// fragment, returning the amount of data written and the usused part of the
364// fragment. An add operation takes the form:
365//
366// [0xxxxxx][[data1]...]
367//
368// where the lower seven bits of the opcode is the number of data bytes
369// following the opcode. See also pack-format.txt in the Git source.
370func applyBinaryDeltaAdd(w io.Writer, op byte, delta []byte) (n int64, rest []byte, err error) {
371 size := int(op)
372 if len(delta) < size {
373 return 0, delta, errors.New("corrupt binary delta: incomplete add")
374 }
375 _, err = w.Write(delta[:size])
376 return int64(size), delta[size:], err
377}
378
379// applyBinaryDeltaCopy applies a copy opcode in a delta-encoded binary
380// fragment, returing the amount of data written and the unused part of the
381// fragment. A copy operation takes the form:
382//
383// [1xxxxxxx][offset1][offset2][offset3][offset4][size1][size2][size3]
384//
385// where the lower seven bits of the opcode determine which non-zero offset and
386// size bytes are present in little-endian order: if bit 0 is set, offset1 is
387// present, etc. If no offset or size bytes are present, offset is 0 and size
388// is 0x10000. See also pack-format.txt in the Git source.
389func applyBinaryDeltaCopy(w io.Writer, op byte, delta []byte, src io.ReaderAt) (n int64, rest []byte, err error) {
390 const defaultSize = 0x10000
391
392 unpack := func(start, bits uint) (v int64) {
393 for i := uint(0); i < bits; i++ {
394 mask := byte(1 << (i + start))
395 if op&mask > 0 {
396 if len(delta) == 0 {
397 err = errors.New("corrupt binary delta: incomplete copy")
398 return
399 }
400 v |= int64(delta[0]) << (8 * i)
401 delta = delta[1:]
402 }
403 }
404 return
405 }
406
407 offset := unpack(0, 4)
408 size := unpack(4, 3)
409 if err != nil {
410 return 0, delta, err
411 }
412 if size == 0 {
413 size = defaultSize
414 }
415
416 // TODO(bkeyes): consider pooling these buffers
417 b := make([]byte, size)
418 if _, err := src.ReadAt(b, offset); err != nil {
419 return 0, delta, err
420 }
421
422 _, err = w.Write(b)
423 return size, delta, err
424}
425
426func checkBinarySrcSize(r io.ReaderAt, size int64) error {
427 ok, err := isLen(r, size)
428 if err != nil {
429 return err
430 }
431 if !ok {
432 return &Conflict{"fragment src size does not match actual src size"}
433 }
434 return nil
435}