mirror of
https://github.com/bluekeyes/go-gitdiff
synced 2026-06-08 13:18:30 +00:00
18058d1e15
This removes the distinction between "strict" and "fuzzy" application by allowing future methods on Applier that control settings. It also avoids state tracking in the text fragment apply signature by moving it into the Applier type. While in practice, an Applier will be used once and discarded, the capability is provided to reset it for multiple uses.
200 lines
4.8 KiB
Go
200 lines
4.8 KiB
Go
package gitdiff
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
)
|
|
|
|
// File describes changes to a single file. It can be either a text file or a
|
|
// binary file.
|
|
type File struct {
|
|
OldName string
|
|
NewName string
|
|
|
|
IsNew bool
|
|
IsDelete bool
|
|
IsCopy bool
|
|
IsRename bool
|
|
|
|
OldMode os.FileMode
|
|
NewMode os.FileMode
|
|
|
|
OldOIDPrefix string
|
|
NewOIDPrefix string
|
|
Score int
|
|
|
|
// TextFragments contains the fragments describing changes to a text file. It
|
|
// may be empty if the file is empty or if only the mode changes.
|
|
TextFragments []*TextFragment
|
|
|
|
// IsBinary is true if the file is a binary file. If the patch includes
|
|
// binary data, BinaryFragment will be non-nil and describe the changes to
|
|
// the data. If the patch is reversible, ReverseBinaryFragment will also be
|
|
// non-nil and describe the changes needed to restore the original file
|
|
// after applying the changes in BinaryFragment.
|
|
IsBinary bool
|
|
BinaryFragment *BinaryFragment
|
|
ReverseBinaryFragment *BinaryFragment
|
|
}
|
|
|
|
// TextFragment describes changed lines starting at a specific line in a text file.
|
|
type TextFragment struct {
|
|
Comment string
|
|
|
|
OldPosition int64
|
|
OldLines int64
|
|
|
|
NewPosition int64
|
|
NewLines int64
|
|
|
|
LinesAdded int64
|
|
LinesDeleted int64
|
|
|
|
LeadingContext int64
|
|
TrailingContext int64
|
|
|
|
Lines []Line
|
|
}
|
|
|
|
// Header returns the canonical header of this fragment.
|
|
func (f *TextFragment) Header() string {
|
|
return fmt.Sprintf("@@ -%d,%d +%d,%d @@ %s", f.OldPosition, f.OldLines, f.NewPosition, f.NewLines, f.Comment)
|
|
}
|
|
|
|
// Validate checks that the fragment is self-consistent and appliable. Validate
|
|
// returns an error if and only if the fragment is invalid.
|
|
func (f *TextFragment) Validate() error {
|
|
if f == nil {
|
|
return errors.New("nil fragment")
|
|
}
|
|
|
|
var (
|
|
oldLines, newLines int64
|
|
leadingContext, trailingContext int64
|
|
contextLines, addedLines, deletedLines int64
|
|
)
|
|
|
|
// count the types of lines in the fragment content
|
|
for i, line := range f.Lines {
|
|
switch line.Op {
|
|
case OpContext:
|
|
oldLines++
|
|
newLines++
|
|
contextLines++
|
|
if addedLines == 0 && deletedLines == 0 {
|
|
leadingContext++
|
|
} else {
|
|
trailingContext++
|
|
}
|
|
case OpAdd:
|
|
newLines++
|
|
addedLines++
|
|
trailingContext = 0
|
|
case OpDelete:
|
|
oldLines++
|
|
deletedLines++
|
|
trailingContext = 0
|
|
default:
|
|
return fmt.Errorf("unknown operator %q on line %d", line.Op, i+1)
|
|
}
|
|
}
|
|
|
|
// check the actual counts against the reported counts
|
|
if oldLines != f.OldLines {
|
|
return lineCountErr("old", oldLines, f.OldLines)
|
|
}
|
|
if newLines != f.NewLines {
|
|
return lineCountErr("new", newLines, f.NewLines)
|
|
}
|
|
if leadingContext != f.LeadingContext {
|
|
return lineCountErr("leading context", leadingContext, f.LeadingContext)
|
|
}
|
|
if trailingContext != f.TrailingContext {
|
|
return lineCountErr("trailing context", trailingContext, f.TrailingContext)
|
|
}
|
|
if addedLines != f.LinesAdded {
|
|
return lineCountErr("added", addedLines, f.LinesAdded)
|
|
}
|
|
if deletedLines != f.LinesDeleted {
|
|
return lineCountErr("deleted", deletedLines, f.LinesDeleted)
|
|
}
|
|
|
|
// if a file is being created, it can only contain additions
|
|
if f.OldPosition == 0 && f.OldLines != 0 {
|
|
return errors.New("file creation fragment contains context or deletion lines")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func lineCountErr(kind string, actual, reported int64) error {
|
|
return fmt.Errorf("fragment contains %d %s lines but reports %d", actual, kind, reported)
|
|
}
|
|
|
|
// Line is a line in a text fragment.
|
|
type Line struct {
|
|
Op LineOp
|
|
Line string
|
|
}
|
|
|
|
func (fl Line) String() string {
|
|
return fl.Op.String() + fl.Line
|
|
}
|
|
|
|
// Old returns true if the line appears in the old content of the fragment.
|
|
func (fl Line) Old() bool {
|
|
return fl.Op == OpContext || fl.Op == OpDelete
|
|
}
|
|
|
|
// New returns true if the line appears in the new content of the fragment.
|
|
func (fl Line) New() bool {
|
|
return fl.Op == OpContext || fl.Op == OpAdd
|
|
}
|
|
|
|
// NoEOL returns true if the line is missing a trailing newline character.
|
|
func (fl Line) NoEOL() bool {
|
|
return len(fl.Line) == 0 || fl.Line[len(fl.Line)-1] != '\n'
|
|
}
|
|
|
|
// LineOp describes the type of a text fragment line: context, added, or removed.
|
|
type LineOp int
|
|
|
|
const (
|
|
// OpContext indicates a context line
|
|
OpContext LineOp = iota
|
|
// OpDelete indicates a deleted line
|
|
OpDelete
|
|
// OpAdd indicates an added line
|
|
OpAdd
|
|
)
|
|
|
|
func (op LineOp) String() string {
|
|
switch op {
|
|
case OpContext:
|
|
return " "
|
|
case OpDelete:
|
|
return "-"
|
|
case OpAdd:
|
|
return "+"
|
|
}
|
|
return "?"
|
|
}
|
|
|
|
// BinaryFragment describes changes to a binary file.
|
|
type BinaryFragment struct {
|
|
Method BinaryPatchMethod
|
|
Size int64
|
|
Data []byte
|
|
}
|
|
|
|
// BinaryPatchMethod is the method used to create and apply the binary patch.
|
|
type BinaryPatchMethod int
|
|
|
|
const (
|
|
// BinaryPatchDelta indicates the data uses Git's packfile encoding
|
|
BinaryPatchDelta BinaryPatchMethod = iota
|
|
// BinaryPatchLiteral indicates the data is the exact file content
|
|
BinaryPatchLiteral
|
|
)
|