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// }
19type Conflict struct {
20 msg string
21}
22
23func (c *Conflict) Error() string {
24 return "conflict: " + c.msg
25}
26
27// Is implements error matching for Conflict. Passing an empty instance of
28// Conflict always returns true.
29func (c *Conflict) Is(other error) bool {
30 if other, ok := other.(*Conflict); ok {
31 return other.msg == "" || other.msg == c.msg
32 }
33 return false
34}
35
36// ApplyError wraps an error that occurs during patch application with
37// additional location information, if it is available.
38type ApplyError struct {
39 // Line is the one-indexed line number in the source data
40 Line int64
41 // Fragment is the one-indexed fragment number in the file
42 Fragment int
43 // FragmentLine is the one-indexed line number in the fragment
44 FragmentLine int
45
46 err error
47}
48
49// Unwrap returns the wrapped error.
50func (e *ApplyError) Unwrap() error {
51 return e.err
52}
53
54func (e *ApplyError) Error() string {
55 return fmt.Sprintf("%v", e.err)
56}
57
58type lineNum int
59type fragNum int
60type fragLineNum int
61
62// applyError creates a new *ApplyError wrapping err or augments the information
63// in err with args if it is already an *ApplyError. Returns nil if err is nil.
64func applyError(err error, args ...interface{}) error {
65 if err == nil {
66 return nil
67 }
68
69 e, ok := err.(*ApplyError)
70 if !ok {
71 if err == io.EOF {
72 err = io.ErrUnexpectedEOF
73 }
74 e = &ApplyError{err: err}
75 }
76 for _, arg := range args {
77 switch v := arg.(type) {
78 case lineNum:
79 e.Line = int64(v) + 1
80 case fragNum:
81 e.Fragment = int(v) + 1
82 case fragLineNum:
83 e.FragmentLine = int(v) + 1
84 }
85 }
86 return e
87}
88
89var (
90 errApplyInProgress = errors.New("gitdiff: incompatible apply in progress")
91 errApplierClosed = errors.New("gitdiff: applier is closed")
92)
93
94// Apply applies the changes in f to src, writing the result to dst. It can
95// apply both text and binary changes.
96//
97// If an error occurs while applying, Apply returns an *ApplyError that
98// annotates the error with additional information. If the error is because of
99// a conflict with the source, the wrapped error will be a *Conflict.
100func Apply(dst io.Writer, src io.ReaderAt, f *File) error {
101 if f.IsBinary {
102 if len(f.TextFragments) > 0 {
103 return applyError(errors.New("binary file contains text fragments"))
104 }
105 if f.BinaryFragment == nil {
106 return applyError(errors.New("binary file does not contain a binary fragment"))
107 }
108 } else {
109 if f.BinaryFragment != nil {
110 return applyError(errors.New("text file contains a binary fragment"))
111 }
112 }
113
114 switch {
115 case f.BinaryFragment != nil:
116 applier := NewBinaryApplier(dst, src)
117 if err := applier.ApplyFragment(f.BinaryFragment); err != nil {
118 return err
119 }
120 return applier.Close()
121
122 case len(f.TextFragments) > 0:
123 frags := make([]*TextFragment, len(f.TextFragments))
124 copy(frags, f.TextFragments)
125
126 sort.Slice(frags, func(i, j int) bool {
127 return frags[i].OldPosition < frags[j].OldPosition
128 })
129
130 // TODO(bkeyes): consider merging overlapping fragments
131 // right now, the application fails if fragments overlap, but it should be
132 // possible to precompute the result of applying them in order
133
134 applier := NewTextApplier(dst, src)
135 for i, frag := range frags {
136 if err := applier.ApplyFragment(frag); err != nil {
137 return applyError(err, fragNum(i))
138 }
139 }
140 return applier.Close()
141
142 default:
143 // nothing to apply, just copy all the data
144 _, err := copyFrom(dst, src, 0)
145 return err
146 }
147}