2022-07-07 20:11:50 +00:00
|
|
|
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:
|
|
|
|
//
|
2024-10-16 10:54:40 +00:00
|
|
|
// if errors.Is(err, &Conflict{}) {
|
|
|
|
// // handle conflict
|
|
|
|
// }
|
2022-07-07 20:11:50 +00:00
|
|
|
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")
|
2024-10-16 10:54:40 +00:00
|
|
|
errApplierClosed = errors.New("gitdiff: applier is closed")
|
2022-07-07 20:11:50 +00:00
|
|
|
)
|
|
|
|
|
2024-10-16 10:54:40 +00:00
|
|
|
// Apply applies the changes in f to src, writing the result to dst. It can
|
|
|
|
// apply both text and binary changes.
|
2022-07-07 20:11:50 +00:00
|
|
|
//
|
2024-10-16 10:54:40 +00:00
|
|
|
// 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"))
|
2022-07-07 20:11:50 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
switch {
|
|
|
|
case f.BinaryFragment != nil:
|
2024-10-16 10:54:40 +00:00
|
|
|
applier := NewBinaryApplier(dst, src)
|
|
|
|
if err := applier.ApplyFragment(f.BinaryFragment); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return applier.Close()
|
2022-07-07 20:11:50 +00:00
|
|
|
|
|
|
|
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
|
|
|
|
|
2024-10-16 10:54:40 +00:00
|
|
|
applier := NewTextApplier(dst, src)
|
2022-07-07 20:11:50 +00:00
|
|
|
for i, frag := range frags {
|
2024-10-16 10:54:40 +00:00
|
|
|
if err := applier.ApplyFragment(frag); err != nil {
|
2022-07-07 20:11:50 +00:00
|
|
|
return applyError(err, fragNum(i))
|
|
|
|
}
|
|
|
|
}
|
2024-10-16 10:54:40 +00:00
|
|
|
return applier.Close()
|
2022-07-07 20:11:50 +00:00
|
|
|
|
|
|
|
default:
|
2024-10-16 10:54:40 +00:00
|
|
|
// nothing to apply, just copy all the data
|
|
|
|
_, err := copyFrom(dst, src, 0)
|
2022-07-07 20:11:50 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|