···53535454 e, ok := err.(*ApplyError)
5555 if !ok {
5656+ if err == io.EOF {
5757+ err = io.ErrUnexpectedEOF
5858+ }
5659 e = &ApplyError{err: err}
5760 }
5861 for _, arg := range args {
···115118 // line numbers are zero-indexed, positions are one-indexed
116119 limit := f.OldPosition - 1
117120118118- // an EOF is allowed here: the fragment applies to the last line of the
119119- // source but it does not have a newline character
121121+ // io.EOF is acceptable here: the first line of the patch is the last of
122122+ // 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))
···127130 if err := applyTextLine(dst, nextLine, line); err != nil {
128131 return applyError(err, lineNum(n), fragLineNum(i))
129132 }
130130- if fromSrc(line) {
133133+ if line.Old() {
131134 used++
132135 }
133136 // advance reader if the next fragment line appears in src and we're behind
134134- if i < len(f.Lines)-1 && fromSrc(f.Lines[i+1]) && int64(n)-limit < used {
137137+ if i < len(f.Lines)-1 && f.Lines[i+1].Old() && int64(n)-limit < used {
135138 nextLine, n, err = src.ReadLine()
136136- if err != nil {
137137- if err == io.EOF {
138138- err = io.ErrUnexpectedEOF
139139- }
139139+ switch {
140140+ case err == io.EOF && f.Lines[i+1].NoEOL():
141141+ continue
142142+ case err != nil:
140143 return applyError(err, lineNum(n), fragLineNum(i+1)) // report for _next_ line in fragment
141144 }
142145 }
···159162 return
160163}
161164162162-func fromSrc(line Line) bool {
163163- return line.Op != OpAdd
164164-}
165165-166165// copyLines copies from src to dst until the line at limit, exclusive. Returns
167167-// the line at limit and the line number. The line number may not equal the
168168-// limit if and only if a non-EOF error occurs. A negative limit means the
169169-// first read should return io.EOF and no data.
166166+// the line at limit and the line number. If the error is nil or io.EOF, the
167167+// line number equals limit. A negative limit checks that the source has no
168168+// more lines to read.
170169func 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 {
···137137 return fl.Op.String() + fl.Line
138138}
139139140140+// Old returns true if the line appears in the old content of the fragment.
141141+func (fl Line) Old() bool {
142142+ return fl.Op != OpAdd
143143+}
144144+145145+// New returns true if the line appears in the new content of the fragment.
146146+func (fl Line) New() bool {
147147+ return fl.Op == OpAdd
148148+}
149149+150150+// NoEOL returns true if the line is missing a trailing newline character.
151151+func (fl Line) NoEOL() bool {
152152+ return len(fl.Line) == 0 || fl.Line[len(fl.Line)-1] != '\n'
153153+}
154154+140155// LineOp describes the type of a text fragment line: context, added, or removed.
141156type LineOp int
142157
···11+diff --git a/gitdiff/testdata/apply/text_fragment_change_single_noeol.src b/gitdiff/testdata/apply/text_fragment_change_single_noeol.src
22+--- a/gitdiff/testdata/apply/text_fragment_change_single_noeol.src
33++++ b/gitdiff/testdata/apply/text_fragment_change_single_noeol.src
44+@@ -1 +1 @@
55+-line 1
66+\ No newline at end of file
77++new line a
88+\ No newline at end of file