Skip to content

Commit 7bd4e0b

Browse files
committed
Fix EOF handling in TextFragment#ApplyStrict
io.EOF was not properly accounted for when dealing with patches that are missing trailing newline characters.
1 parent 02f7ff4 commit 7bd4e0b

9 files changed

+70
-23
lines changed

gitdiff/apply.go

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@ func applyError(err error, args ...interface{}) error {
5353

5454
e, ok := err.(*ApplyError)
5555
if !ok {
56+
if err == io.EOF {
57+
err = io.ErrUnexpectedEOF
58+
}
5659
e = &ApplyError{err: err}
5760
}
5861
for _, arg := range args {
@@ -115,8 +118,8 @@ func (f *TextFragment) ApplyStrict(dst io.Writer, src LineReader) error {
115118
// line numbers are zero-indexed, positions are one-indexed
116119
limit := f.OldPosition - 1
117120

118-
// an EOF is allowed here: the fragment applies to the last line of the
119-
// source but it does not have a newline character
121+
// io.EOF is acceptable here: the first line of the patch is the last of
122+
// the source and it has no newline character
120123
nextLine, n, err := copyLines(dst, src, limit)
121124
if err != nil && err != io.EOF {
122125
return applyError(err, lineNum(n))
@@ -127,16 +130,16 @@ func (f *TextFragment) ApplyStrict(dst io.Writer, src LineReader) error {
127130
if err := applyTextLine(dst, nextLine, line); err != nil {
128131
return applyError(err, lineNum(n), fragLineNum(i))
129132
}
130-
if fromSrc(line) {
133+
if line.Old() {
131134
used++
132135
}
133136
// advance reader if the next fragment line appears in src and we're behind
134-
if i < len(f.Lines)-1 && fromSrc(f.Lines[i+1]) && int64(n)-limit < used {
137+
if i < len(f.Lines)-1 && f.Lines[i+1].Old() && int64(n)-limit < used {
135138
nextLine, n, err = src.ReadLine()
136-
if err != nil {
137-
if err == io.EOF {
138-
err = io.ErrUnexpectedEOF
139-
}
139+
switch {
140+
case err == io.EOF && f.Lines[i+1].NoEOL():
141+
continue
142+
case err != nil:
140143
return applyError(err, lineNum(n), fragLineNum(i+1)) // report for _next_ line in fragment
141144
}
142145
}
@@ -159,14 +162,10 @@ func applyTextLine(dst io.Writer, src string, line Line) (err error) {
159162
return
160163
}
161164

162-
func fromSrc(line Line) bool {
163-
return line.Op != OpAdd
164-
}
165-
166165
// copyLines copies from src to dst until the line at limit, exclusive. Returns
167-
// the line at limit and the line number. The line number may not equal the
168-
// limit if and only if a non-EOF error occurs. A negative limit means the
169-
// first read should return io.EOF and no data.
166+
// the line at limit and the line number. If the error is nil or io.EOF, the
167+
// line number equals limit. A negative limit checks that the source has no
168+
// more lines to read.
170169
func copyLines(dst io.Writer, src LineReader, limit int64) (string, int, error) {
171170
// TODO(bkeyes): fix int vs int64 for limit and return value
172171
for {

gitdiff/apply_test.go

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,18 @@ func TestTextFragmentApplyStrict(t *testing.T) {
1212
File string
1313
Err bool
1414
}{
15-
"createFile": {File: "new"},
16-
"deleteFile": {File: "delete_all"},
17-
"addStart": {File: "add_start"},
18-
"addMiddle": {File: "add_middle"},
19-
"addEnd": {File: "add_end"},
20-
"changeStart": {File: "change_start"},
21-
"changeMiddle": {File: "change_middle"},
22-
"changeEnd": {File: "change_end"},
15+
"createFile": {File: "new"},
16+
"deleteFile": {File: "delete_all"},
17+
18+
"addStart": {File: "add_start"},
19+
"addMiddle": {File: "add_middle"},
20+
"addEnd": {File: "add_end"},
21+
"addEndNoEOL": {File: "add_end_noeol"},
22+
23+
"changeStart": {File: "change_start"},
24+
"changeMiddle": {File: "change_middle"},
25+
"changeEnd": {File: "change_end"},
26+
"changeSingleNoEOL": {File: "change_single_noeol"},
2327
}
2428

2529
for name, test := range tests {

gitdiff/gitdiff.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,21 @@ func (fl Line) String() string {
137137
return fl.Op.String() + fl.Line
138138
}
139139

140+
// Old returns true if the line appears in the old content of the fragment.
141+
func (fl Line) Old() bool {
142+
return fl.Op != OpAdd
143+
}
144+
145+
// New returns true if the line appears in the new content of the fragment.
146+
func (fl Line) New() bool {
147+
return fl.Op == OpAdd
148+
}
149+
150+
// NoEOL returns true if the line is missing a trailing newline character.
151+
func (fl Line) NoEOL() bool {
152+
return len(fl.Line) == 0 || fl.Line[len(fl.Line)-1] != '\n'
153+
}
154+
140155
// LineOp describes the type of a text fragment line: context, added, or removed.
141156
type LineOp int
142157

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
line 1
2+
line 2
3+
line 3
4+
line 4
5+
line 5
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
diff --git a/gitdiff/testdata/apply/text_fragment_add_end_noeol.src b/gitdiff/testdata/apply/text_fragment_add_end_noeol.src
2+
--- a/gitdiff/testdata/apply/text_fragment_add_end_noeol.src
3+
+++ b/gitdiff/testdata/apply/text_fragment_add_end_noeol.src
4+
@@ -1,3 +1,5 @@
5+
line 1
6+
line 2
7+
-line 3
8+
\ No newline at end of file
9+
+line 3
10+
+line 4
11+
+line 5
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
line 1
2+
line 2
3+
line 3
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
new line a
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
diff --git a/gitdiff/testdata/apply/text_fragment_change_single_noeol.src b/gitdiff/testdata/apply/text_fragment_change_single_noeol.src
2+
--- a/gitdiff/testdata/apply/text_fragment_change_single_noeol.src
3+
+++ b/gitdiff/testdata/apply/text_fragment_change_single_noeol.src
4+
@@ -1 +1 @@
5+
-line 1
6+
\ No newline at end of file
7+
+new line a
8+
\ No newline at end of file
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
line 1

0 commit comments

Comments
 (0)