master
  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}