Re: [RFC PATCH] git-apply: Permit change of file mode when filename does not change
On Tue, Mar 24, 2020 at 09:00:54AM -0700, Daniel Sommermann wrote:
Show 6 quoted lines
> The documentation for git's diff format does not expressly disallow
> changing the mode of a file without splitting it into a delete and
> create. Mercurial's `hg diff --git` in fact produces git diffs with such
> format. When applying such patches in Git, this assert can be hit. The check
> preventing this type of diff has been around since 2005 in
> 3cca928d4aae691572ef9a73dcc29a04f66900a1.
This description confused me for a moment, because in Git we generally refer to "mode changes" as flipping the executable bit. And anything further is a "type change" (and this isn't just academic; options like --diff-filter distinguish the two).
And we do indeed allow a simple mode change like:
$ git show c9d4999155700651a37f4eb577cec1f4b5b8d6be --format=
diff --git a/t/perf/p0004-lazy-init-name-hash.sh b/t/perf/p0004-lazy-init-name-hash.sh
old mode 100644
new mode 100755
But you're talking about typechanges here, and we do always represent those as a deletion/addition pair:
$ git show --format= -D 2efbb7f5218d5ca9d50cbcb86a365a08b2981d77 RelNotes
diff --git a/RelNotes b/RelNotes
deleted file mode 100644
index 007bc065dd..0000000000
diff --git a/RelNotes b/RelNotes
new file mode 120000
index 0000000000..8d0b1654d2
--- /dev/null
+++ b/RelNotes
@@ -0,0 +1 @@
+Documentation/RelNotes/2.20.0.txt
\ No newline at end of file
I don't think we'd want to switch how we generate these diffs, but I can't offhand think of a reason why it would be a bad idea to accept such a patch converting a file to a symlink or vice versa.
But...
> Simply deleting the check that prevents changing the mode when not
> renaming allows such diffs to work out of the box, as the attached test
> case shows.
What about other more exotic typechanges, like a directory becoming a file? Or a file to a gitlink? I guess we'd never mention a directory in --patch format anyway, but I wonder to what degree these lines in check_patch() are protecting downstream code from doing something stupid.
If I fake a diff like:
diff --git a/file b/file
old mode 100644
new mode 040000
we seem to silently accept it but not write any mode change (we do write a content change to the file). Swapping 040000 (a tree) out for 160000 (a gitlink) seems to delete file but not apply any content-level change.
Also, I'm not sure your patch works for the reverse case: a symlink becoming a file. If I add this to your test:
Show changes to t/t4115-apply-symlink.sh +16 −2
diff --git a/t/t4115-apply-symlink.sh b/t/t4115-apply-symlink.sh
index 593e6142b4..acd94a07a7 100755
--- a/t/t4115-apply-symlink.sh
+++ b/t/t4115-apply-symlink.sh
@@ -67,4 +67,20 @@ test_expect_success 'apply file-to-symlink patch' '
'
+test_expect_success 'apply symlink-to-file patch' '
+
+ cat >reverse-patch <<-\EOF &&
+ diff --git a/file_to_be_link b/file_to_be_link
+ new mode 120000
+ old mode 100644
+ --- a/file_to_be_link
+ +++ b/file_to_be_link
+ @@ -1,1 +1,1 @@
+ -target
+ +file
+ EOF
+
+ git apply reverse-patch
+'
+
test_done
it fails with "error: file_to_be_link: wrong type".
> diff --git a/t/t4115-apply-symlink.sh b/t/t4115-apply-symlink.sh
> index 872fcda6cb..593e6142b4 100755
> --- a/t/t4115-apply-symlink.sh
> +++ b/t/t4115-apply-symlink.sh
If we do go this route, two small fixes for the tests:
> @@ -44,4 +44,27 @@ test_expect_success 'apply --index symlink patch' '
>
> '
>
> +cat >move_patch <<\EOF
> +diff --git a/file_to_be_link b/file_to_be_link
> +old mode 100644
> +new mode 120000
> +--- a/file_to_be_link
> ++++ b/file_to_be_link
> +@@ -0,0 +1,1 @@
> ++target
> +\ No newline at end of file
> +EOF
We prefer this kind of setup to go inside the test_expect_success block
(you can use "<<-\EOF" to strip leading tabs and get nice indentation).
Some older tests haven't been updated yet, so you may have picked this
up from a bad example, but we try to follow it when writing new ones.
> +test_expect_success 'apply file-to-symlink patch' '
> +
> + git checkout -f master &&
> + touch file_to_be_link &&
> + git add file_to_be_link &&
> + git commit -m initial &&
> +
> + git apply move_patch &&
> + test target = $(readlink file_to_be_link)
> +
> +'
This probably needs a SYMLINKS prerequisite, since we'd write the actual
symlink to the filesystem. We could work around that with "apply
--index", but I think it's important to test the full patch application.
-Peff