{"thread":{"id":"64992","subject":"[PATCH] apply: strip ./ prefix from --directory argument","startedAt":"2026-02-13T17:08:33Z","lastAt":"2026-02-20T20:27:02Z","messageCount":7,"participants":["Joaquim Rocha via GitGitGadget","Patrick Steinhardt","Junio C Hamano","Joaquim Rocha"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"535944","messageId":"pull.2198.git.git.1771002510709.gitgitgadget@gmail.com","threadId":"64992","inReplyTo":null,"subject":"[PATCH] apply: strip ./ prefix from --directory argument","fromName":"Joaquim Rocha via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-02-13T17:08:30Z","receivedAt":"2026-02-13T17:08:33Z","isPatch":true,"sender":{"key":"joaquim@amutable.com","avatar":"https://avatars.githubusercontent.com/u/1029635?v=4"},"body":"From: Joaquim Rocha <joaquim@amutable.com>\n\nWhen passing a relative path like --directory=./some/sub, the leading\n\"./\" caused apply to prepend it literally to patch filenames, resulting\nin an error (invalid path).\n\nSince using \"./\" is almost memory muscle for many, strip the \"./\"\nprefix so it behaves the same as --directory=some/sub.\n\nSigned-off-by: Joaquim Rocha <joaquim@amutable.com>\n---\n    apply: strip ./ prefix from --directory argument\n\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2198%2Fjoaquimrocha%2Fapply-directory-dot-slash-prefix-v1\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2198/joaquimrocha/apply-directory-dot-slash-prefix-v1\nPull-Request: https://github.com/git/git/pull/2198\n\n apply.c               | 4 ++++\n t/t4128-apply-root.sh | 9 +++++++++\n 2 files changed, 13 insertions(+)\n\ndiff --git a/apply.c b/apply.c\nindex 3de4aa4d2e..a44c54077c 100644\n--- a/apply.c\n+++ b/apply.c\n@@ -5001,6 +5001,10 @@ static int apply_option_parse_directory(const struct option *opt,\n \tBUG_ON_OPT_NEG(unset);\n \n \tstrbuf_reset(&state->root);\n+\n+\tif (starts_with(arg, \"./\"))\n+\t\targ += 2;\n+\n \tstrbuf_addstr(&state->root, arg);\n \tstrbuf_complete(&state->root, '/');\n \treturn 0;\ndiff --git a/t/t4128-apply-root.sh b/t/t4128-apply-root.sh\nindex f6db5a79dd..2f446a4d69 100755\n--- a/t/t4128-apply-root.sh\n+++ b/t/t4128-apply-root.sh\n@@ -43,6 +43,15 @@ test_expect_success 'apply --directory -p (2) ' '\n \n '\n \n+test_expect_success 'apply --directory (./ prefix)' '\n+\tgit reset --hard initial &&\n+\tgit apply --directory=./some/sub -p3 --index patch &&\n+\techo Bello >expect &&\n+\tgit show :some/sub/dir/file >actual &&\n+\ttest_cmp expect actual &&\n+\ttest_cmp expect some/sub/dir/file\n+'\n+\n cat > patch << EOF\n diff --git a/newfile b/newfile\n new file mode 100644\n\nbase-commit: 6fcee4785280a08e7f271bd015a4dc33753e2886\n-- \ngitgitgadget\n"},{"id":"536150","messageId":"aZQhnIcPa9sCPpBb@pks.im","threadId":"64992","inReplyTo":"pull.2198.git.git.1771002510709.gitgitgadget@gmail.com","subject":"Re: [PATCH] apply: strip ./ prefix from --directory argument","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-02-17T08:06:52Z","receivedAt":"2026-02-17T08:06:59Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Fri, Feb 13, 2026 at 05:08:30PM +0000, Joaquim Rocha via GitGitGadget wrote:\n> From: Joaquim Rocha <joaquim@amutable.com>\n> \n> When passing a relative path like --directory=./some/sub, the leading\n> \"./\" caused apply to prepend it literally to patch filenames, resulting\n> in an error (invalid path).\n> \n> Since using \"./\" is almost memory muscle for many, strip the \"./\"\n> prefix so it behaves the same as --directory=some/sub.\n\nIsn't the problem wider than that though? For example, if you had\n\"././some/sub\" it would break again. Or if you had \"some/./sub\", or\n\"some/sub/../sub\", or \"some//sub\".\n\n> diff --git a/apply.c b/apply.c\n> index 3de4aa4d2e..a44c54077c 100644\n> --- a/apply.c\n> +++ b/apply.c\n> @@ -5001,6 +5001,10 @@ static int apply_option_parse_directory(const struct option *opt,\n>  \tBUG_ON_OPT_NEG(unset);\n>  \n>  \tstrbuf_reset(&state->root);\n> +\n> +\tif (starts_with(arg, \"./\"))\n> +\t\targ += 2;\n> +\n>  \tstrbuf_addstr(&state->root, arg);\n>  \tstrbuf_complete(&state->root, '/');\n>  \treturn 0;\n\nWhile this change here fixes your observed issues, the next person might\nrun into a totally different one. So more generally, I think what we'd\nrather want to do is to fully normalize the path. How about this\ninstead:\n\ndiff --git a/apply.c b/apply.c\nindex 9de2eb953e..8946b133a3 100644\n--- a/apply.c\n+++ b/apply.c\n@@ -5002,6 +5002,7 @@ static int apply_option_parse_directory(const struct option *opt,\n \n \tstrbuf_reset(&state->root);\n \tstrbuf_addstr(&state->root, arg);\n+\tstrbuf_normalize_path(&state->root);\n \tstrbuf_complete(&state->root, '/');\n \treturn 0;\n }\n\n`strbuf_normalize_path()` drops \".\" components, removes \"..\" and it\nsquashes multiple directory separators. So it handles your specific use\ncase, but also others.\n\nThanks!\n\nPatrick\n"},{"id":"536224","messageId":"xmqqzf579kuc.fsf@gitster.g","threadId":"64992","inReplyTo":"aZQhnIcPa9sCPpBb@pks.im","subject":"Re: [PATCH] apply: strip ./ prefix from --directory argument","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-02-17T20:27:39Z","receivedAt":"2026-02-17T20:27:42Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> While this change here fixes your observed issues, the next person might\n> run into a totally different one. So more generally, I think what we'd\n> rather want to do is to fully normalize the path. How about this\n> instead:\n\nSorry, but I am confused.  Why isn't \"don't do it then\" a good\nanswer for a case like this?\n\n>\n> diff --git a/apply.c b/apply.c\n> index 9de2eb953e..8946b133a3 100644\n> --- a/apply.c\n> +++ b/apply.c\n> @@ -5002,6 +5002,7 @@ static int apply_option_parse_directory(const struct option *opt,\n>  \n>  \tstrbuf_reset(&state->root);\n>  \tstrbuf_addstr(&state->root, arg);\n> +\tstrbuf_normalize_path(&state->root);\n>  \tstrbuf_complete(&state->root, '/');\n>  \treturn 0;\n>  }\n>\n> `strbuf_normalize_path()` drops \".\" components, removes \"..\" and it\n> squashes multiple directory separators. So it handles your specific use\n> case, but also others.\n>\n> Thanks!\n>\n> Patrick\n"},{"id":"536236","messageId":"pull.2198.v2.git.git.1771373732749.gitgitgadget@gmail.com","threadId":"64992","inReplyTo":"pull.2198.git.git.1771002510709.gitgitgadget@gmail.com","subject":"[PATCH v2] apply: normalize path in --directory argument","fromName":"Joaquim Rocha via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-02-18T00:15:32Z","receivedAt":"2026-02-18T00:15:36Z","isPatch":true,"sender":{"key":"joaquim@amutable.com","avatar":"https://avatars.githubusercontent.com/u/1029635?v=4"},"body":"From: Joaquim Rocha <joaquim@amutable.com>\n\nWhen passing a relative path like --directory=./some/sub, the leading\n\"./\" caused apply to prepend it literally to patch filenames, resulting\nin an error (invalid path).\nThere may be more cases like this where users pass some/./path to the\ndirectory which can easily be normalized to an acceptable path, so\nthese changes try to normalize the path before using it.\n\nSigned-off-by: Joaquim Rocha <joaquim@amutable.com>\n---\n    apply: strip ./ prefix from --directory argument\n    \n    Changes since v1:\n    \n     * Normalized the path as Patrick recommended\n     * Return error is we have ../ at the beginning of the --directory\n       argument\n\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2198%2Fjoaquimrocha%2Fapply-directory-dot-slash-prefix-v2\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2198/joaquimrocha/apply-directory-dot-slash-prefix-v2\nPull-Request: https://github.com/git/git/pull/2198\n\nRange-diff vs v1:\n\n 1:  82e24c3471 ! 1:  8ac66a876d apply: strip ./ prefix from --directory argument\n     @@ Metadata\n      Author: Joaquim Rocha <joaquim@amutable.com>\n      \n       ## Commit message ##\n     -    apply: strip ./ prefix from --directory argument\n     +    apply: normalize path in --directory argument\n      \n          When passing a relative path like --directory=./some/sub, the leading\n          \"./\" caused apply to prepend it literally to patch filenames, resulting\n          in an error (invalid path).\n     -\n     -    Since using \"./\" is almost memory muscle for many, strip the \"./\"\n     -    prefix so it behaves the same as --directory=some/sub.\n     +    There may be more cases like this where users pass some/./path to the\n     +    directory which can easily be normalized to an acceptable path, so\n     +    these changes try to normalize the path before using it.\n      \n          Signed-off-by: Joaquim Rocha <joaquim@amutable.com>\n      \n       ## apply.c ##\n      @@ apply.c: static int apply_option_parse_directory(const struct option *opt,\n     - \tBUG_ON_OPT_NEG(unset);\n       \n       \tstrbuf_reset(&state->root);\n     + \tstrbuf_addstr(&state->root, arg);\n      +\n     -+\tif (starts_with(arg, \"./\"))\n     -+\t\targ += 2;\n     ++\tif (strbuf_normalize_path(&state->root) < 0)\n     ++\t\treturn error(_(\"unable to normalize directory: '%s'\"), arg);\n      +\n     - \tstrbuf_addstr(&state->root, arg);\n       \tstrbuf_complete(&state->root, '/');\n       \treturn 0;\n     + }\n      \n       ## t/t4128-apply-root.sh ##\n      @@ t/t4128-apply-root.sh: test_expect_success 'apply --directory -p (2) ' '\n     @@ t/t4128-apply-root.sh: test_expect_success 'apply --directory -p (2) ' '\n      +\ttest_cmp expect actual &&\n      +\ttest_cmp expect some/sub/dir/file\n      +'\n     ++\n     ++test_expect_success 'apply --directory (double slash)' '\n     ++\tgit reset --hard initial &&\n     ++\tgit apply --directory=some//sub -p3 --index patch &&\n     ++\techo Bello >expect &&\n     ++\tgit show :some/sub/dir/file >actual &&\n     ++\ttest_cmp expect actual &&\n     ++\ttest_cmp expect some/sub/dir/file\n     ++'\n     ++\n     ++test_expect_success 'apply --directory (./ in the middle)' '\n     ++\tgit reset --hard initial &&\n     ++\tgit apply --directory=some/./sub -p3 --index patch &&\n     ++\techo Bello >expect &&\n     ++\tgit show :some/sub/dir/file >actual &&\n     ++\ttest_cmp expect actual &&\n     ++\ttest_cmp expect some/sub/dir/file\n     ++'\n     ++\n     ++test_expect_success 'apply --directory (../ in the middle)' '\n     ++\tgit reset --hard initial &&\n     ++\tgit apply --directory=some/../some/sub -p3 --index patch &&\n     ++\techo Bello >expect &&\n     ++\tgit show :some/sub/dir/file >actual &&\n     ++\ttest_cmp expect actual &&\n     ++\ttest_cmp expect some/sub/dir/file\n     ++'\n     ++\n     ++test_expect_success 'apply --directory rejects leading ../' '\n     ++\ttest_must_fail git apply --directory=../foo -p3 patch 2>err &&\n     ++\ttest_grep \"unable to normalize directory\" err\n     ++'\n      +\n       cat > patch << EOF\n       diff --git a/newfile b/newfile\n\n\n apply.c               |  4 ++++\n t/t4128-apply-root.sh | 41 +++++++++++++++++++++++++++++++++++++++++\n 2 files changed, 45 insertions(+)\n\ndiff --git a/apply.c b/apply.c\nindex 3de4aa4d2e..7f0ce5918b 100644\n--- a/apply.c\n+++ b/apply.c\n@@ -5002,6 +5002,10 @@ static int apply_option_parse_directory(const struct option *opt,\n \n \tstrbuf_reset(&state->root);\n \tstrbuf_addstr(&state->root, arg);\n+\n+\tif (strbuf_normalize_path(&state->root) < 0)\n+\t\treturn error(_(\"unable to normalize directory: '%s'\"), arg);\n+\n \tstrbuf_complete(&state->root, '/');\n \treturn 0;\n }\ndiff --git a/t/t4128-apply-root.sh b/t/t4128-apply-root.sh\nindex f6db5a79dd..5eba15fa66 100755\n--- a/t/t4128-apply-root.sh\n+++ b/t/t4128-apply-root.sh\n@@ -43,6 +43,47 @@ test_expect_success 'apply --directory -p (2) ' '\n \n '\n \n+test_expect_success 'apply --directory (./ prefix)' '\n+\tgit reset --hard initial &&\n+\tgit apply --directory=./some/sub -p3 --index patch &&\n+\techo Bello >expect &&\n+\tgit show :some/sub/dir/file >actual &&\n+\ttest_cmp expect actual &&\n+\ttest_cmp expect some/sub/dir/file\n+'\n+\n+test_expect_success 'apply --directory (double slash)' '\n+\tgit reset --hard initial &&\n+\tgit apply --directory=some//sub -p3 --index patch &&\n+\techo Bello >expect &&\n+\tgit show :some/sub/dir/file >actual &&\n+\ttest_cmp expect actual &&\n+\ttest_cmp expect some/sub/dir/file\n+'\n+\n+test_expect_success 'apply --directory (./ in the middle)' '\n+\tgit reset --hard initial &&\n+\tgit apply --directory=some/./sub -p3 --index patch &&\n+\techo Bello >expect &&\n+\tgit show :some/sub/dir/file >actual &&\n+\ttest_cmp expect actual &&\n+\ttest_cmp expect some/sub/dir/file\n+'\n+\n+test_expect_success 'apply --directory (../ in the middle)' '\n+\tgit reset --hard initial &&\n+\tgit apply --directory=some/../some/sub -p3 --index patch &&\n+\techo Bello >expect &&\n+\tgit show :some/sub/dir/file >actual &&\n+\ttest_cmp expect actual &&\n+\ttest_cmp expect some/sub/dir/file\n+'\n+\n+test_expect_success 'apply --directory rejects leading ../' '\n+\ttest_must_fail git apply --directory=../foo -p3 patch 2>err &&\n+\ttest_grep \"unable to normalize directory\" err\n+'\n+\n cat > patch << EOF\n diff --git a/newfile b/newfile\n new file mode 100644\n\nbase-commit: 6fcee4785280a08e7f271bd015a4dc33753e2886\n-- \ngitgitgadget\n"},{"id":"536289","messageId":"aZXJVtgRYQAnxNnE@pks.im","threadId":"64992","inReplyTo":"xmqqzf579kuc.fsf@gitster.g","subject":"Re: [PATCH] apply: strip ./ prefix from --directory argument","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-02-18T14:14:46Z","receivedAt":"2026-02-18T14:14:57Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Tue, Feb 17, 2026 at 12:27:39PM -0800, Junio C Hamano wrote:\n> Patrick Steinhardt <ps@pks.im> writes:\n> \n> > While this change here fixes your observed issues, the next person might\n> > run into a totally different one. So more generally, I think what we'd\n> > rather want to do is to fully normalize the path. How about this\n> > instead:\n> \n> Sorry, but I am confused.  Why isn't \"don't do it then\" a good\n> answer for a case like this?\n\nI guess that's fair, but especially with command line completion it\nmight be easy to arrive at such paths. That may or may not be a good\nargument, I'm not sure myself.\n\nPatrick\n"},{"id":"536294","messageId":"CAFJE3DEro4B+UReO0+639hrdUvFUSB6-v7LOxPFU05q15zgakQ@mail.gmail.com","threadId":"64992","inReplyTo":"aZXJVtgRYQAnxNnE@pks.im","subject":"Re: [PATCH] apply: strip ./ prefix from --directory argument","fromName":"Joaquim Rocha","fromEmail":"joaquim@amutable.com","sentAt":"2026-02-18T14:40:27Z","receivedAt":"2026-02-18T14:40:40Z","isPatch":true,"sender":{"key":"joaquim@amutable.com","avatar":"https://avatars.githubusercontent.com/u/1029635?v=4"},"body":"On Wed, Feb 18, 2026 at 2:14 PM Patrick Steinhardt <ps@pks.im> wrote:\n>\n> I guess that's fair, but especially with command line completion it\n> might be easy to arrive at such paths. That may or may not be a good\n> argument, I'm not sure myself.\n>\n> Patrick\n\nI understand that. TBH, while not at your (plural) level of expertise\nin git, I am certainly an experienced user and still the error from\nthe ./ use got me confused. So my proposal goes towards developer\nexperience, like the similar command suggestions it also has. If\nthere's no down side to it, I think it makes sense to have it.\n\nAn alternative would be to have a better error message if we detect\nthere are redundant path artifacts, but in that case why wouldn't we\njust normalize the path as it's the user's clear intention?\n\nCheers,\n\nJoaquim Rocha\n"},{"id":"536551","messageId":"xmqqpl5zw48c.fsf@gitster.g","threadId":"64992","inReplyTo":"pull.2198.v2.git.git.1771373732749.gitgitgadget@gmail.com","subject":"Re: [PATCH v2] apply: normalize path in --directory argument","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-02-20T20:26:59Z","receivedAt":"2026-02-20T20:27:02Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Joaquim Rocha via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Joaquim Rocha <joaquim@amutable.com>\n>\n> When passing a relative path like --directory=./some/sub, the leading\n> \"./\" caused apply to prepend it literally to patch filenames, resulting\n> in an error (invalid path).\n> There may be more cases like this where users pass some/./path to the\n> directory which can easily be normalized to an acceptable path, so\n> these changes try to normalize the path before using it.\n>\n> Signed-off-by: Joaquim Rocha <joaquim@amutable.com>\n> ---\n>     apply: strip ./ prefix from --directory argument\n>     \n>     Changes since v1:\n>     \n>      * Normalized the path as Patrick recommended\n\nSounds like a sensible direction to go.\n\nWill queue.  Thanks.  (of course, further reviews welcome).\n\n"}]}