Skip to content

ReverseFileDiff on a git binary patch gives a diff that git apply rejects #98

Description

@DRMacIver

Reversing a git diff --binary patch with ReverseFileDiff and printing it produces a patch that git apply refuses with "creates incorrect result", while git apply -R of the original patch undoes the same change without complaint.

main.go, in a module requiring go-diff v0.9.0:

package main

import (
	"io"
	"os"

	"github.com/sourcegraph/go-diff/diff"
)

func main() {
	in, _ := io.ReadAll(os.Stdin)
	fd, err := diff.ParseFileDiff(in)
	if err != nil {
		panic(err)
	}
	rev, err := diff.ReverseFileDiff(fd)
	if err != nil {
		panic(err)
	}
	out, err := diff.PrintFileDiff(rev)
	if err != nil {
		panic(err)
	}
	os.Stdout.Write(out)
}

Run from that module's directory (the diff below is git diff --binary of bin.dat going from the two bytes 00 01 to the four bytes 00 01 02 03; the file starts at the four-byte state):

go build -o revdiff .
rm -rf t && mkdir t && cd t && git init -q
printf '\x00\x01\x02\x03' > bin.dat
cat > fwd.diff <<'EOF'
diff --git a/bin.dat b/bin.dat
index bdc955b7b2e610ad5a72302b139a2e6cb325519a..eaf36c1daccfdf325514461cd1a2ffbc139b5464 100644
GIT binary patch
literal 4
LcmZQzWMT#Y01f~L

literal 2
JcmZQz1ONa700IC2

EOF
git apply -R --check fwd.diff && echo "git apply -R: ok"
../revdiff < fwd.diff > rev.diff
cat rev.diff
git apply --check rev.diff

Output:

git apply -R: ok
diff --git b/bin.dat a/bin.dat
index eaf36c1daccfdf325514461cd1a2ffbc139b5464..bdc955b7b2e610ad5a72302b139a2e6cb325519a 100644
GIT binary patch
literal 4
LcmZQzWMT#Y01f~L

literal 2
JcmZQz1ONa700IC2

error: binary patch to 'bin.dat' creates incorrect result (expecting bdc955b7b2e610ad5a72302b139a2e6cb325519a, got eaf36c1daccfdf325514461cd1a2ffbc139b5464)
error: bin.dat: patch does not apply

The reversed patch has the index hashes and the diff --git arguments swapped, but the two literal sections are still in the original order; git diff -R --binary of the same change writes literal 2 before literal 4. Running git apply rev.diff without --check fails the same way and leaves bin.dat unchanged.

Tested on go-diff v0.9.0 and on current master (cf64c62, the same commit), with git 2.50.1.

BTW, this was found by an automated program that writes property-based tests for various open source projects using hegel (but it has been reviewed by hand before reporting). We've also potentially found (but not yet hand validated) 2 other bugs in go-diff. You can see the tests at https://github.com/hegeldev/hegel-zoo/tree/main/targets/go/go-diff. Let us know if you would like us to file the other bugs found and/or contribute the tests. NB the tests are currently LLM generated and probably not yet suitable for inclusion as is, but we're happy to help get them into a better state if you want them.

Activity

  1. methakon commented on Sep 30, 2026

    @methakon

    I couldn't reproduce this on master (cf64c62, which includes #96). Tested with fixtures produced by real git diff --binary output, reversing them via ParseFileDiff -> ReverseFileDiff -> PrintFileDiff, then applying the result for real and comparing the file byte-for-byte against the original blob:

    case git apply result == pre-image
    small in-place edit exit 0 yes
    appended tail exit 0 yes
    two separate changes exit 0 yes
    tiny 64-byte file exit 0 yes

    git apply -R on the original patch works in every case too, so the two paths agree.

    One thing that looks like a bug but isn't, in case it tripped up the original report: ReverseFileDiff swaps the headers and leaves the GIT binary patch payload byte-identical. That is correct. A git binary patch encodes both directions, and git apply picks the inverse based on the index hashes, which is what the reversal changes. TestReverseFileDiffGitBinarySemantics in diff/reverse_git_test.go asserts exactly this for both literal and delta encodings, with a comment explaining the reasoning.

    #96 landed about two weeks before this was filed, and it reworked the header handling this report points at. If the failure was real on v0.9.0, it looks like #96 already resolved it, so this may just need closing. Happy to be wrong, though: if you still see git apply rejecting a reversed binary patch, send the patch and I'll dig in.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions