{"thread":{"id":"66345","subject":"[PATCH] diff --no-index: fix -R with file/directory conflicts","startedAt":"2026-09-18T07:19:09Z","lastAt":"2026-09-21T18:01:20Z","messageCount":4,"participants":["Haokai Ding via GitGitGadget","René Scharfe","Jacob Keller","Junio C Hamano"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"552851","messageId":"pull.2232.git.1789715946888.gitgitgadget@gmail.com","threadId":"66345","inReplyTo":null,"subject":"[PATCH] diff --no-index: fix -R with file/directory conflicts","fromName":"Haokai Ding via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-09-18T07:19:06Z","receivedAt":"2026-09-18T07:19:09Z","isPatch":true,"body":"From: Haokai Ding <hk_ding@outlook.com>\n\nWhen a path is a file on one side and a directory on the other,\nqueue_diff() queues the file separately before recursing into the\ndirectory. This early queue entry bypasses the reverse_diff handling\nused for ordinary files.\n\nAs a result, comparing directories d and e where d/sub is a file and\ne/sub/file is another file reports both paths as deleted with -R.\nReversing the operands reports both paths as added instead.\n\nSwap the filespecs of the early queue entry when reverse_diff is set,\nso that -R reverses the file change as well as the directory contents.\nAdd regression tests for both directions.\n\nSigned-off-by: Haokai Ding <hk_ding@outlook.com>\n---\n    diff --no-index: fix -R with file/directory conflicts\n\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-2232%2FHaokaiDing%2Ffix%2Fno-index-reverse-file-directory-mail-v1\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-2232/HaokaiDing/fix/no-index-reverse-file-directory-mail-v1\nPull-Request: https://github.com/gitgitgadget/git/pull/2232\n\n diff-no-index.c          |  2 ++\n t/t4053-diff-no-index.sh | 18 ++++++++++++++++++\n 2 files changed, 20 insertions(+)\n\ndiff --git a/diff-no-index.c b/diff-no-index.c\nindex f320424f05..a9151cc27a 100644\n--- a/diff-no-index.c\n+++ b/diff-no-index.c\n@@ -188,6 +188,8 @@ static int queue_diff(struct diff_options *o, const struct git_hash_algo *algop,\n \t\t\tmode1 = 0;\n \t\t}\n \t\t/* emit that file */\n+\t\tif (o->flags.reverse_diff)\n+\t\t\tSWAP(d1, d2);\n \t\tdiff_queue(&diff_queued_diff, d1, d2);\n \n \t\t/* and then let the entire directory be created or deleted */\ndiff --git a/t/t4053-diff-no-index.sh b/t/t4053-diff-no-index.sh\nindex 8e0394cf5a..c3c703f4f4 100755\n--- a/t/t4053-diff-no-index.sh\n+++ b/t/t4053-diff-no-index.sh\n@@ -120,6 +120,24 @@ test_expect_success 'turning a file into a directory' '\n \t)\n '\n \n+test_expect_success 'reverse diff when turning a file into a directory' '\n+\t(\n+\t\tcd non/git &&\n+\t\tprintf \"A\\td/sub\\nD\\te/sub/file\\n\" >expect &&\n+\t\ttest_expect_code 1 git diff --no-index -R --name-status d e >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'reverse diff when turning a directory into a file' '\n+\t(\n+\t\tcd non/git &&\n+\t\tprintf \"D\\td/sub\\nA\\te/sub/file\\n\" >expect &&\n+\t\ttest_expect_code 1 git diff --no-index -R --name-status e d >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n test_expect_success 'diff from repo subdir shows real paths (explicit)' '\n \techo \"diff --git a/../../non/git/a b/../../non/git/b\" >expect &&\n \ttest_expect_code 1 \\\n\nbase-commit: d38352cd43ab9745686d697872408bc3249a153f\n-- \ngitgitgadget\n"},{"id":"552911","messageId":"9b97c14b-1d25-409b-a72c-d8caf298bf87@web.de","threadId":"66345","inReplyTo":"pull.2232.git.1789715946888.gitgitgadget@gmail.com","subject":"Re: [PATCH] diff --no-index: fix -R with file/directory conflicts","fromName":"René Scharfe","fromEmail":"l.s.r@web.de","sentAt":"2026-09-20T15:23:43Z","receivedAt":"2026-09-20T15:24:05Z","isPatch":true,"body":"On 9/18/26 9:19 AM, Haokai Ding via GitGitGadget wrote:\n> From: Haokai Ding <hk_ding@outlook.com>\n> \n> When a path is a file on one side and a directory on the other,\n> queue_diff() queues the file separately before recursing into the\n> directory. This early queue entry bypasses the reverse_diff handling\n> used for ordinary files.\n> \n> As a result, comparing directories d and e where d/sub is a file and\n> e/sub/file is another file reports both paths as deleted with -R.\n> Reversing the operands reports both paths as added instead.\n> \n> Swap the filespecs of the early queue entry when reverse_diff is set,\n> so that -R reverses the file change as well as the directory contents.\n> Add regression tests for both directions.\n> \n> Signed-off-by: Haokai Ding <hk_ding@outlook.com>\n> ---\n>     diff --no-index: fix -R with file/directory conflicts\n> \n> Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-2232%2FHaokaiDing%2Ffix%2Fno-index-reverse-file-directory-mail-v1\n> Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-2232/HaokaiDing/fix/no-index-reverse-file-directory-mail-v1\n> Pull-Request: https://github.com/gitgitgadget/git/pull/2232\n> \n>  diff-no-index.c          |  2 ++\n>  t/t4053-diff-no-index.sh | 18 ++++++++++++++++++\n>  2 files changed, 20 insertions(+)\n> \n> diff --git a/diff-no-index.c b/diff-no-index.c\n> index f320424f05..a9151cc27a 100644\n> --- a/diff-no-index.c\n> +++ b/diff-no-index.c\n> @@ -188,6 +188,8 @@ static int queue_diff(struct diff_options *o, const struct git_hash_algo *algop,\n>  \t\t\tmode1 = 0;\n>  \t\t}\n>  \t\t/* emit that file */\n> +\t\tif (o->flags.reverse_diff)\n> +\t\t\tSWAP(d1, d2);\n\nGood find!\n\n>  \t\tdiff_queue(&diff_queued_diff, d1, d2);\n>  \n>  \t\t/* and then let the entire directory be created or deleted */\n> diff --git a/t/t4053-diff-no-index.sh b/t/t4053-diff-no-index.sh\n> index 8e0394cf5a..c3c703f4f4 100755\n> --- a/t/t4053-diff-no-index.sh\n> +++ b/t/t4053-diff-no-index.sh\n> @@ -120,6 +120,24 @@ test_expect_success 'turning a file into a directory' '\n>  \t)\n>  '\n>  \n> +test_expect_success 'reverse diff when turning a file into a directory' '\n> +\t(\n> +\t\tcd non/git &&\n> +\t\tprintf \"A\\td/sub\\nD\\te/sub/file\\n\" >expect &&\n> +\t\ttest_expect_code 1 git diff --no-index -R --name-status d e >actual &&\n> +\t\ttest_cmp expect actual\n> +\t)\n> +'\n> +\n> +test_expect_success 'reverse diff when turning a directory into a file' '\n> +\t(\n> +\t\tcd non/git &&\n> +\t\tprintf \"D\\td/sub\\nA\\te/sub/file\\n\" >expect &&\n> +\t\ttest_expect_code 1 git diff --no-index -R --name-status e d >actual &&\n> +\t\ttest_cmp expect actual\n> +\t)\n\nLooks good.  I like the explicit exit code expectation.\n\n> +'\n> +\n>  test_expect_success 'diff from repo subdir shows real paths (explicit)' '\n>  \techo \"diff --git a/../../non/git/a b/../../non/git/b\" >expect &&\n>  \ttest_expect_code 1 \\\n> \n> base-commit: d38352cd43ab9745686d697872408bc3249a153f\n\n"},{"id":"552917","messageId":"0c82d50e-f2c0-4db6-ade8-7a403cac73da@intel.com","threadId":"66345","inReplyTo":"pull.2232.git.1789715946888.gitgitgadget@gmail.com","subject":"Re: [PATCH] diff --no-index: fix -R with file/directory conflicts","fromName":"Jacob Keller","fromEmail":"jacob.e.keller@intel.com","sentAt":"2026-09-20T23:04:32Z","receivedAt":"2026-09-20T23:04:55Z","isPatch":true,"body":"On 9/18/2026 12:19 AM, Haokai Ding via GitGitGadget wrote:\n> From: Haokai Ding <hk_ding@outlook.com>\n> \n> When a path is a file on one side and a directory on the other,\n> queue_diff() queues the file separately before recursing into the\n> directory. This early queue entry bypasses the reverse_diff handling\n> used for ordinary files.\n> \n> As a result, comparing directories d and e where d/sub is a file and\n> e/sub/file is another file reports both paths as deleted with -R.\n> Reversing the operands reports both paths as added instead.\n> \n> Swap the filespecs of the early queue entry when reverse_diff is set,\n> so that -R reverses the file change as well as the directory contents.\n> Add regression tests for both directions.\n> \n> Signed-off-by: Haokai Ding <hk_ding@outlook.com>\n> ---\n\nThanks for fixing!\n\nReviewed-by: Jacob Keller <jacob.e.keller@intel.com>\n\n>     diff --no-index: fix -R with file/directory conflicts\n> \n> Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-2232%2FHaokaiDing%2Ffix%2Fno-index-reverse-file-directory-mail-v1\n> Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-2232/HaokaiDing/fix/no-index-reverse-file-directory-mail-v1\n> Pull-Request: https://github.com/gitgitgadget/git/pull/2232\n> \n>  diff-no-index.c          |  2 ++\n>  t/t4053-diff-no-index.sh | 18 ++++++++++++++++++\n>  2 files changed, 20 insertions(+)\n> \n> diff --git a/diff-no-index.c b/diff-no-index.c\n> index f320424f05..a9151cc27a 100644\n> --- a/diff-no-index.c\n> +++ b/diff-no-index.c\n> @@ -188,6 +188,8 @@ static int queue_diff(struct diff_options *o, const struct git_hash_algo *algop,\n>  \t\t\tmode1 = 0;\n>  \t\t}\n>  \t\t/* emit that file */\n> +\t\tif (o->flags.reverse_diff)\n> +\t\t\tSWAP(d1, d2);\n>  \t\tdiff_queue(&diff_queued_diff, d1, d2);\n>  \n>  \t\t/* and then let the entire directory be created or deleted */\n> diff --git a/t/t4053-diff-no-index.sh b/t/t4053-diff-no-index.sh\n> index 8e0394cf5a..c3c703f4f4 100755\n> --- a/t/t4053-diff-no-index.sh\n> +++ b/t/t4053-diff-no-index.sh\n> @@ -120,6 +120,24 @@ test_expect_success 'turning a file into a directory' '\n>  \t)\n>  '\n>  \n> +test_expect_success 'reverse diff when turning a file into a directory' '\n> +\t(\n> +\t\tcd non/git &&\n> +\t\tprintf \"A\\td/sub\\nD\\te/sub/file\\n\" >expect &&\n> +\t\ttest_expect_code 1 git diff --no-index -R --name-status d e >actual &&\n> +\t\ttest_cmp expect actual\n> +\t)\n> +'\n> +\n> +test_expect_success 'reverse diff when turning a directory into a file' '\n> +\t(\n> +\t\tcd non/git &&\n> +\t\tprintf \"D\\td/sub\\nA\\te/sub/file\\n\" >expect &&\n> +\t\ttest_expect_code 1 git diff --no-index -R --name-status e d >actual &&\n> +\t\ttest_cmp expect actual\n> +\t)\n> +'\n> +\n>  test_expect_success 'diff from repo subdir shows real paths (explicit)' '\n>  \techo \"diff --git a/../../non/git/a b/../../non/git/b\" >expect &&\n>  \ttest_expect_code 1 \\\n> \n> base-commit: d38352cd43ab9745686d697872408bc3249a153f\n\n"},{"id":"552944","messageId":"xmqqfqz2mqkx.fsf@gitster.g","threadId":"66345","inReplyTo":"0c82d50e-f2c0-4db6-ade8-7a403cac73da@intel.com","subject":"Re: [PATCH] diff --no-index: fix -R with file/directory conflicts","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-09-21T18:01:18Z","receivedAt":"2026-09-21T18:01:20Z","isPatch":true,"body":"Jacob Keller <jacob.e.keller@intel.com> writes:\n\n> On 9/18/2026 12:19 AM, Haokai Ding via GitGitGadget wrote:\n>> From: Haokai Ding <hk_ding@outlook.com>\n>> \n>> When a path is a file on one side and a directory on the other,\n>> queue_diff() queues the file separately before recursing into the\n>> directory. This early queue entry bypasses the reverse_diff handling\n>> used for ordinary files.\n>> \n>> As a result, comparing directories d and e where d/sub is a file and\n>> e/sub/file is another file reports both paths as deleted with -R.\n>> Reversing the operands reports both paths as added instead.\n>> \n>> Swap the filespecs of the early queue entry when reverse_diff is set,\n>> so that -R reverses the file change as well as the directory contents.\n>> Add regression tests for both directions.\n>> \n>> Signed-off-by: Haokai Ding <hk_ding@outlook.com>\n>> ---\n>\n> Thanks for fixing!\n>\n> Reviewed-by: Jacob Keller <jacob.e.keller@intel.com>\n\nThanks, both.  Will queue.\n"}]}