Skip to content

git diff output incompatibility with BSD patch for new files #5049

@botovq

Description

@botovq

Description

The diff hunk header between two pairs of @ for a newly created file indicates that it applies to the first line of /dev/null file and confuses the patch implementations on FreeBSD and OpenBSD which then ask for a filename to be specified when one tries to apply the patch.

Steps to Reproduce the Problem

Create and commit a file containing a single line, generate patches from that commit and try to apply the patches in an empty directory.

Git's output has a hunk indicator @@ -0,0 +1 @@:

$ git show
[...]
diff --git a/hello b/hello
new file mode 100644
index 0000000..ce01362
--- /dev/null
+++ b/hello
@@ -0,0 +1, @@
+hello

whereas the equivalent output of jj has @@ -1,0 +1,1 @@:

$ jj show --git -r @-
[...]
diff --git a/hello b/hello
new file mode 100644
index 0000000000..ce01362503
--- /dev/null
+++ b/hello
@@ -1,0 +1,1 @@
+hello

The problem is the first -1 here. The BSD's patch implementations (derived from Larry Wall's patch) special case @@ 0 and use that as a hint that a new file needs to be created. Whether that's a great approach is a different question, but it has been this way for more than thirty years. If it's not @@ 0, it doesn't find the file and enters interactive mode, which doesn't work.

A simlar problem exists for the reverted diff, but it doesn't confuse BSD patch.

I think the following passage in the POSIX 2024 specification indicates that git's behavior is the expected one:

"%1d,%1d", <beginning line number>, <number of lines>

otherwise. If a range is empty, its beginning line number shall be the number of the line just before the range, or 0 if the empty range starts the file.

Expected Behavior

The diffs generated by git and jujutsu apply cleanly for the system-provided patch and create files as needed.

Actual Behavior

Attempting to apply the diffs in an empty directory results in the following ouptut on FreeBSD (tested on 14.2) and OpenBSD (tested on 7.6):

$ patch < /tmp/jj.diff
Hmm...  Looks like a unified diff to me...
The text leading up to this was:
--------------------------
|Commit ID: 58258525c7bc0bb5afa9c672df4570600da27803
|Change ID: nzylvuozztzlpxkovltwqukupmmzwmlw
|Bookmarks: master master@git
|Author: Theo Buehler <tb@openbsd.org> (2024-12-07 22:04:11)
|Committer: Theo Buehler <tb@openbsd.org> (2024-12-07 22:04:11)
|
|    msg
|
|diff --git a/hello b/hello
|new file mode 100644
|index 0000000000..ce01362503
|--- /dev/null
|+++ b/hello
--------------------------
File to patch:

and as you can see, you're asked to enter the file name. This won't work of course since the file doesn't exist yet:

File to patch: hello
No file found--skip this patch? [n] n
patch: **** can't find hello

The diff generated by git on the other hand applies cleanly:

$ patch < /tmp/git.diff
Hmm...  Looks like a unified diff to me...
The text leading up to this was:
--------------------------
|commit 58258525c7bc0bb5afa9c672df4570600da27803
|Author: Theo Buehler <tb@openbsd.org>
|Date:   Sat Dec 7 22:04:11 2024 +0100
|
|    msg
|
|diff --git a/hello b/hello
|new file mode 100644
|index 0000000..ce01362
|--- /dev/null
|+++ b/hello
--------------------------
(Creating file hello...)
Patching file hello using Plan A...
Empty context always matches.
Hunk #1 succeeded at 1.
done

Editing the patch and changing the -1 to 0 or using GNU's patch works around this issue.

Specifications

  • Platform: FreeBSD 14.x, OpenBSD 7.6
  • Version: Affects jj versions 0.22 to 0.24 at least, probably present for much longer.

Metadata

Metadata

Assignees

No one assigned

    Labels

    polish🪒🐃Make existing features more convenient and more consistent🐛bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions