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