Applying a fragment requires the content to match the stored counts, so there must be a way to check this. Parsed fragments should always be valid, but manually created or modified fragments may be invalid.
···61 return fmt.Sprintf("@@ -%d,%d +%d,%d @@ %s", f.OldPosition, f.OldLines, f.NewPosition, f.NewLines, f.Comment)
62}
6300000000000000000000000000000000000000000000000000000000000000000064// Line is a line in a text fragment.
65type Line struct {
66 Op LineOp
···61 return fmt.Sprintf("@@ -%d,%d +%d,%d @@ %s", f.OldPosition, f.OldLines, f.NewPosition, f.NewLines, f.Comment)
62}
6364+// Validate checks that the fragment is self-consistent and appliable. Validate
65+// returns an error if and only if the fragment is invalid.
66+func (f *TextFragment) Validate() error {
67+ var (
68+ oldLines, newLines int64
69+ leadingContext, trailingContext int64
70+ contextLines, addedLines, deletedLines int64
71+ )
72+73+ // count the types of lines in the fragment content
74+ for i, line := range f.Lines {
75+ switch line.Op {
76+ case OpContext:
77+ oldLines++
78+ newLines++
79+ contextLines++
80+ if addedLines == 0 && deletedLines == 0 {
81+ leadingContext++
82+ } else {
83+ trailingContext++
84+ }
85+ case OpAdd:
86+ newLines++
87+ addedLines++
88+ trailingContext = 0
89+ case OpDelete:
90+ oldLines++
91+ deletedLines++
92+ trailingContext = 0
93+ default:
94+ return fmt.Errorf("unknown operator %q on line %d", line.Op, i+1)
95+ }
96+ }
97+98+ // check the actual counts against the reported counts
99+ if oldLines != f.OldLines {
100+ return lineCountErr("old", oldLines, f.OldLines)
101+ }
102+ if newLines != f.NewLines {
103+ return lineCountErr("new", newLines, f.NewLines)
104+ }
105+ if leadingContext != f.LeadingContext {
106+ return lineCountErr("leading context", leadingContext, f.LeadingContext)
107+ }
108+ if trailingContext != f.TrailingContext {
109+ return lineCountErr("trailing context", trailingContext, f.TrailingContext)
110+ }
111+ if addedLines != f.LinesAdded {
112+ return lineCountErr("added", addedLines, f.LinesAdded)
113+ }
114+ if deletedLines != f.LinesDeleted {
115+ return lineCountErr("deleted", deletedLines, f.LinesDeleted)
116+ }
117+118+ // if a file is being created, it can only contain additions
119+ if f.OldPosition == 0 && f.OldLines != 0 {
120+ return fmt.Errorf("file creation fragment contains context or deletion lines")
121+ }
122+123+ return nil
124+}
125+126+func lineCountErr(kind string, actual, reported int64) error {
127+ return fmt.Errorf("fragment contains %d %s lines but reports %d", actual, kind, reported)
128+}
129+130// Line is a line in a text fragment.
131type Line struct {
132 Op LineOp