Skip to content

# Bug Report: Parse failed for valid multi-hunk patch #66

@geeker-smallwhite

Description

@geeker-smallwhite

Description

gitdiff.Parse incorrectly rejects a valid unified diff containing multiple hunks,
returning the error:

The patch is generated by standard git diff and is well-formed — all hunk headers
have correct line counts that match the actual content.

Reproduction

package main

import (
	"fmt"
	"strings"

	"github.com/bluekeyes/go-gitdiff/gitdiff"
)

const patch = `diff --git a/foo.go b/foo.go
index 7551f3d..856e345 100644
--- a/foo.go
+++ b/foo.go
@@ -669,13 +669,34 @@ func oldFunc() {
 	line1
 	line2
 	line3
-	old line
+	new line 1
+	new line 2
+	new line 3
+	new line 4
+	new line 5
+	new line 6
+	new line 7
+	new line 8
+	new line 9
+	new line 10
+	new line 11
+	new line 12
+	new line 13
+	new line 14
+	new line 15
+	new line 16
+	new line 17
+	new line 18
+	new line 19
+	new line 20
 	line5
 	line6
 	line7
 	line8
 	line9
 	line10
 	line11
 	line12
@@ -684,6 +705,24 @@ func anotherFunc() {
 	ctx1
 	ctx2
 	ctx3
+	added1
+	added2
+	added3
+	added4
+	added5
+	added6
+	added7
+	added8
+	added9
+	added10
+	added11
+	added12
+	added13
+	added14
+	added15
+	added16
+	added17
+	added18
 	ctx4
 	ctx5
 	ctx6
`

func main() {
	files, _, err := gitdiff.Parse(strings.NewReader(patch))
	fmt.Println("files:", files)
	fmt.Println("err:", err)
}

Expected: parse succeeds, returns 1 file with 2 hunks

Actual:

files: []
err: gitdiff: line 41: fragment header miscounts lines: -1 old, -1 new

Root Cause Analysis

In text.go, ParseTextChunk uses the loop condition oldLines > 0 || newLines > 0
to consume lines. When the first hunk has significantly more + lines than - lines
(i.e. newLines is much larger than oldLines), oldLines reaches 0 first.

At that point the loop continues consuming lines to drain newLines, but it reads
into the second hunk's content. The second hunk's context lines are counted as +
lines of the first hunk, causing newLines to go negative and the final check:

if oldLines != 0 || newLines != 0 {
    return p.Errorf(-hdr, "fragment header miscounts lines: %+d old, %+d new", -oldLines, -newLines)
}

fires with -1 old, -1 new.

The fix should stop consuming lines when a new hunk header (@@) or a new file
header (diff --git) is encountered, even if the counters haven't reached zero yet.

Environment

  • go-gitdiff version: v0.8.1
  • Go version: go1.20

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions