fork of go-gitdiff with jj support
at v0.8.1 3.7 kB view raw
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}