{"thread":{"id":"66105","subject":"[PATCH] completion: complete tracked paths for 'git diff'","startedAt":"2026-08-03T00:58:04Z","lastAt":"2026-08-12T16:25:59Z","messageCount":34,"participants":["Junio C Hamano","SZEDER Gábor","D. Ben Knoble","Elijah Newren"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"549467","messageId":"xmqqcxw010me.fsf@gitster.g","threadId":"66105","inReplyTo":null,"subject":"[PATCH] completion: complete tracked paths for 'git diff'","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-08-03T00:58:01Z","receivedAt":"2026-08-03T00:58:04Z","isPatch":true,"body":"When completing arguments for 'git diff', _git_diff() delegates to\n__git_complete_revlist_file(), which only completes revision\nreferences.  This is good [*], as mixing both revs and paths in a\nsingle list to have the user pick is simply too confusing.\n\nIf no reference matches, or if '--' is given, however, _git_diff()\nleaves COMPREPLY empty. Bash then falls back to default filename\ncompletion in $PWD. This fails when 'git -C <path>' is used because\n$PWD is not the target repository.\n\nUpdate _git_diff() to use __git_complete_index_file() when '--' is\npresent, or when revision reference completion yields no matching\ncandidates, so that tracked paths are offered as candidates.\n\n[Footnote]\n\n * In https://lore.kernel.org/git/al%2Fw2qgBfhe9qMg6@szeder.dev/\n   SZEDER made the same argument for \"git send-email 0<TAB>\".\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n\n * This is one of my pet peeves that I have raised a few times on\n   the list but nobody reacted.  So I did a \"monkey see, monkey do\"\n   patch without deeply understanding what is going on in the code\n   paths.  When preparing the CC: list, I pulled a few folks, some\n   very recognizable, some not recognizable immediately by me, out\n   of \"git shortlog --since=3.years\" on this file.  The contribution\n   by any of them looked more expertly done by whatever I did here.\n\n   The use case is that I have a checkout of the 'todo' branch in an\n   untracked subdirectory 'Meta' in my primary source tree.  I would\n   do\n\n    $ git -C Meta status wh<TAB>\n\n   and it completes to whats-cooking.txt just fine, 'add' also adds\n   it, but 'diff' dies not work, not because I have refs that 'wh'\n   completes, but because bash completion is unaware that I want\n   paths completed in the other directory.\n\n contrib/completion/git-completion.bash |  8 +++++-\n t/t9902-completion.sh                  | 40 ++++++++++++++++++++++++++\n 2 files changed, 47 insertions(+), 1 deletion(-)\n\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex e875787710..8f5773292b 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -1947,7 +1947,10 @@ __git_diff_difftool_options=\"--cached --staged\n \n _git_diff ()\n {\n-\t__git_has_doubledash && return\n+\tif __git_has_doubledash; then\n+\t\t__git_complete_index_file\n+\t\treturn\n+\tfi\n \n \tcase \"$cur\" in\n \t--diff-algorithm=*)\n@@ -1976,6 +1979,9 @@ _git_diff ()\n \t\t;;\n \tesac\n \t__git_complete_revlist_file\n+\tif [ ${#COMPREPLY[@]} -eq 0 ]; then\n+\t\t__git_complete_index_file\n+\tfi\n }\n \n __git_mergetools_common=\"diffuse diffmerge ecmerge emerge kdiff3 meld opendiff\ndiff --git a/t/t9902-completion.sh b/t/t9902-completion.sh\nindex 55dc9eabfc..eecd53f097 100755\n--- a/t/t9902-completion.sh\n+++ b/t/t9902-completion.sh\n@@ -2663,6 +2663,7 @@ test_expect_success 'setup for integration tests' '\n \techo content >file1 &&\n \techo more >file2 &&\n \tgit add file1 file2 &&\n+\techo untracked >file3 &&\n \tgit commit -m one &&\n \tgit branch mybranch &&\n \tgit tag mytag\n@@ -2712,6 +2713,45 @@ test_expect_success 'git -C <path> checkout uses the right repo' '\n \tEOF\n '\n \n+test_expect_success 'git diff completes tracked paths when no refs match' '\n+\t# file1 and file2 are tracked but file3 is not\n+\ttest_completion \"git diff f\" <<-\\EOF\n+\tfile1\n+\tfile2\n+\tEOF\n+'\n+\n+test_expect_success 'git diff -- completes tracked paths' '\n+\t# file1 and file2 are tracked but file3 is not\n+\ttest_completion \"git diff -- f\" <<-\\EOF\n+\tfile1\n+\tfile2\n+\tEOF\n+'\n+\n+test_expect_success 'git -C <path> diff completes tracked paths in specified repo' '\n+\ttest_when_finished \"rm -rf repo-for-diff\" &&\n+\tgit init repo-for-diff &&\n+\techo content >repo-for-diff/otherfile &&\n+\tgit -C repo-for-diff add otherfile &&\n+\techo untracked >repo-for-diff/oops &&\n+\tgit -C repo-for-diff commit -m otherfile &&\n+\ttest_completion \"git -C repo-for-diff diff o\" <<-\\EOF\n+\totherfile\n+\tEOF\n+'\n+\n+test_expect_success 'git -C <path> diff -- completes pathspecs in specified repo' '\n+\ttest_when_finished \"rm -rf repo-for-diff\" &&\n+\tgit init repo-for-diff &&\n+\techo content >repo-for-diff/otherfile &&\n+\tgit -C repo-for-diff add otherfile &&\n+\tgit -C repo-for-diff commit -m otherfile &&\n+\ttest_completion \"git -C repo-for-diff diff -- o\" <<-\\EOF\n+\totherfile\n+\tEOF\n+'\n+\n test_expect_success 'show completes all refs' '\n \ttest_completion \"git show m\" <<-\\EOF\n \tmain Z\n-- \n2.55.0-607-g47e9082d35\n\n"},{"id":"549468","messageId":"xmqq8q6o106k.fsf@gitster.g","threadId":"66105","inReplyTo":"xmqqcxw010me.fsf@gitster.g","subject":"Re: [PATCH] completion: complete tracked paths for 'git diff'","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-08-03T01:07:31Z","receivedAt":"2026-08-03T01:07:34Z","isPatch":true,"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> When completing arguments for 'git diff', _git_diff() delegates to\n> __git_complete_revlist_file(), which only completes revision\n> references.  This is good [*], as mixing both revs and paths in a\n> single list to have the user pick is simply too confusing.\n>\n> If no reference matches, or if '--' is given, however, _git_diff()\n> leaves COMPREPLY empty. Bash then falls back to default filename\n> completion in $PWD. This fails when 'git -C <path>' is used because\n> $PWD is not the target repository.\n>\n> Update _git_diff() to use __git_complete_index_file() when '--' is\n> present, or when revision reference completion yields no matching\n> candidates, so that tracked paths are offered as candidates.\n\nThis changes behavior even in the case where '-C <there>' is not\nused.  The new behavior omits untracked paths from suggestions,\nwhich is clearly better behavior.\n\nI'll add the above paragraph to the proposed log message when I\nqueue this on 'seen'.\n"},{"id":"549472","messageId":"anAqfqcEueSKmAH+@szeder.dev","threadId":"66105","inReplyTo":"xmqqcxw010me.fsf@gitster.g","subject":"Re: [PATCH] completion: complete tracked paths for 'git diff'","fromName":"SZEDER Gábor","fromEmail":"szeder.dev@gmail.com","sentAt":"2026-08-03T05:44:25Z","receivedAt":"2026-08-03T05:44:29Z","isPatch":true,"body":"On Sun, Aug 02, 2026 at 05:58:01PM -0700, Junio C Hamano wrote:\n> When completing arguments for 'git diff', _git_diff() delegates to\n> __git_complete_revlist_file(), which only completes revision\n> references.  This is good [*], as mixing both revs and paths in a\n> single list to have the user pick is simply too confusing.\n> \n> If no reference matches, or if '--' is given, however, _git_diff()\n> leaves COMPREPLY empty. Bash then falls back to default filename\n> completion in $PWD. This fails when 'git -C <path>' is used because\n> $PWD is not the target repository.\n> \n> Update _git_diff() to use __git_complete_index_file() when '--' is\n> present, or when revision reference completion yields no matching\n> candidates, so that tracked paths are offered as candidates.\n\nMakes sense.\n\n> [Footnote]\n> \n>  * In https://lore.kernel.org/git/al%2Fw2qgBfhe9qMg6@szeder.dev/\n>    SZEDER made the same argument for \"git send-email 0<TAB>\".\n> \n> Signed-off-by: Junio C Hamano <gitster@pobox.com>\n> ---\n> \n>  * This is one of my pet peeves that I have raised a few times on\n>    the list but nobody reacted.  So I did a \"monkey see, monkey do\"\n>    patch without deeply understanding what is going on in the code\n>    paths.  When preparing the CC: list, I pulled a few folks, some\n>    very recognizable, some not recognizable immediately by me, out\n>    of \"git shortlog --since=3.years\" on this file.\n\nWill have to finally polish and submit a completion patch from my\nvaults to get myself back on this list ;)\n\n>    The contribution\n>    by any of them looked more expertly done by whatever I did here.\n\nI think your changes are fine.\n\nHowever, there is 'git diff --no-index' which happily accepts\nuntracked files as well, but with this patch the user can complete\nonly those untracked files that don't match the current word on the\ncommand line (because then __git_complete_index_file() won't list\nanything, and we'll fall back to Bash filename completion like\nbefore).\n\nI think we should check whether the '--no-index' option is present on\nthe command line, and simply not call __git_complete_index_file() if\nit is, to let Bash list all paths; i.e. each of those calls should be\nprotected by an additional 'if test -z \"$(__git_find_on_cmdline\n\"--no-index\")' condition, perhaps.\n\n>    The use case is that I have a checkout of the 'todo' branch in an\n>    untracked subdirectory 'Meta' in my primary source tree.  I would\n>    do\n> \n>     $ git -C Meta status wh<TAB>\n> \n>    and it completes to whats-cooking.txt just fine, 'add' also adds\n>    it, but 'diff' dies not work, not because I have refs that 'wh'\n>    completes, but because bash completion is unaware that I want\n>    paths completed in the other directory.\n> \n>  contrib/completion/git-completion.bash |  8 +++++-\n>  t/t9902-completion.sh                  | 40 ++++++++++++++++++++++++++\n>  2 files changed, 47 insertions(+), 1 deletion(-)\n> \n> diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\n> index e875787710..8f5773292b 100644\n> --- a/contrib/completion/git-completion.bash\n> +++ b/contrib/completion/git-completion.bash\n> @@ -1947,7 +1947,10 @@ __git_diff_difftool_options=\"--cached --staged\n>  \n>  _git_diff ()\n>  {\n> -\t__git_has_doubledash && return\n> +\tif __git_has_doubledash; then\n> +\t\t__git_complete_index_file\n> +\t\treturn\n> +\tfi\n>  \n>  \tcase \"$cur\" in\n>  \t--diff-algorithm=*)\n> @@ -1976,6 +1979,9 @@ _git_diff ()\n>  \t\t;;\n>  \tesac\n>  \t__git_complete_revlist_file\n> +\tif [ ${#COMPREPLY[@]} -eq 0 ]; then\n> +\t\t__git_complete_index_file\n> +\tfi\n>  }\n>  \n>  __git_mergetools_common=\"diffuse diffmerge ecmerge emerge kdiff3 meld opendiff\n> diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh\n> index 55dc9eabfc..eecd53f097 100755\n> --- a/t/t9902-completion.sh\n> +++ b/t/t9902-completion.sh\n> @@ -2663,6 +2663,7 @@ test_expect_success 'setup for integration tests' '\n>  \techo content >file1 &&\n>  \techo more >file2 &&\n>  \tgit add file1 file2 &&\n> +\techo untracked >file3 &&\n>  \tgit commit -m one &&\n>  \tgit branch mybranch &&\n>  \tgit tag mytag\n> @@ -2712,6 +2713,45 @@ test_expect_success 'git -C <path> checkout uses the right repo' '\n>  \tEOF\n>  '\n>  \n> +test_expect_success 'git diff completes tracked paths when no refs match' '\n> +\t# file1 and file2 are tracked but file3 is not\n> +\ttest_completion \"git diff f\" <<-\\EOF\n> +\tfile1\n> +\tfile2\n> +\tEOF\n> +'\n> +\n> +test_expect_success 'git diff -- completes tracked paths' '\n> +\t# file1 and file2 are tracked but file3 is not\n> +\ttest_completion \"git diff -- f\" <<-\\EOF\n> +\tfile1\n> +\tfile2\n> +\tEOF\n> +'\n> +\n> +test_expect_success 'git -C <path> diff completes tracked paths in specified repo' '\n> +\ttest_when_finished \"rm -rf repo-for-diff\" &&\n> +\tgit init repo-for-diff &&\n> +\techo content >repo-for-diff/otherfile &&\n> +\tgit -C repo-for-diff add otherfile &&\n> +\techo untracked >repo-for-diff/oops &&\n> +\tgit -C repo-for-diff commit -m otherfile &&\n> +\ttest_completion \"git -C repo-for-diff diff o\" <<-\\EOF\n> +\totherfile\n> +\tEOF\n> +'\n> +\n> +test_expect_success 'git -C <path> diff -- completes pathspecs in specified repo' '\n> +\ttest_when_finished \"rm -rf repo-for-diff\" &&\n> +\tgit init repo-for-diff &&\n> +\techo content >repo-for-diff/otherfile &&\n> +\tgit -C repo-for-diff add otherfile &&\n> +\tgit -C repo-for-diff commit -m otherfile &&\n> +\ttest_completion \"git -C repo-for-diff diff -- o\" <<-\\EOF\n> +\totherfile\n> +\tEOF\n> +'\n> +\n>  test_expect_success 'show completes all refs' '\n>  \ttest_completion \"git show m\" <<-\\EOF\n>  \tmain Z\n> -- \n> 2.55.0-607-g47e9082d35\n> \n"},{"id":"549487","messageId":"xmqq33wv1fts.fsf@gitster.g","threadId":"66105","inReplyTo":"anAqfqcEueSKmAH+@szeder.dev","subject":"Re: [PATCH] completion: complete tracked paths for 'git diff'","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-08-03T13:41:51Z","receivedAt":"2026-08-03T13:41:54Z","isPatch":true,"body":"SZEDER Gábor <szeder.dev@gmail.com> writes:\n\n> I think we should check whether the '--no-index' option is present on\n> the command line, and simply not call __git_complete_index_file() if\n> it is, to let Bash list all paths; i.e. each of those calls should be\n> protected by an additional 'if test -z \"$(__git_find_on_cmdline\n> \"--no-index\")' condition, perhaps.\n\n\nAh, I did not think of the \"we made 'git diff' work without Git\"\nmode at all.\n\nBut I would avoid scanning the command line for '--no-index' for two\nreasons:\n\n (1) \"git diff -S --no-index maint master\" would not give you the\n     '--no-index' mode.\n\n (2) When run outside the working tree of a repository, you do not\n     have to say '--no-index'.\n\nThese make detecting the \"'git diff' but not Git\" mode tedious and\nerror-prone.\n\nI have not tried this, but if we arranged the code to fall back\nfurther to Bash-native \"paths in $PWD\" after the completion code in\nthe posted patch found nothing, would it be sufficient?\n\nWhen trying to complete an untracked file in the working tree of a\nrepository, if the file shares the same prefix with a tracked file,\nyou cannot complete the untracked file without a prefix long enough\nto disambiguate it from the tracked one.  Such a prefix may not even\nexist (e.g., 'foo' is tracked and 'foo~' is untracked; when you type\n\"git diff --no-index foo<TAB>\", you get 'foo' but not 'foo~', so you\nend up typing 'foo~' in full).  In that sense, it is a regression,\nbut supporting Git usage is the primary mission of 'git diff', so\nthe trade-off may not be so bad.\n\nThoughts?\n"},{"id":"549500","messageId":"xmqqpkzzyzq4.fsf@gitster.g","threadId":"66105","inReplyTo":"xmqq33wv1fts.fsf@gitster.g","subject":"Re: [PATCH] completion: complete tracked paths for 'git diff'","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-08-03T15:45:39Z","receivedAt":"2026-08-03T15:45:41Z","isPatch":true,"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> SZEDER Gábor <szeder.dev@gmail.com> writes:\n>\n>> I think we should check whether the '--no-index' option is present on\n>> the command line, and simply not call __git_complete_index_file() if\n>> it is, to let Bash list all paths; i.e. each of those calls should be\n>> protected by an additional 'if test -z \"$(__git_find_on_cmdline\n>> \"--no-index\")' condition, perhaps.\n>\n>\n> Ah, I did not think of the \"we made 'git diff' work without Git\"\n> mode at all.\n>\n> But I would avoid scanning the command line for '--no-index' for two\n> reasons:\n>\n>  (1) \"git diff -S --no-index maint master\" would not give you the\n>      '--no-index' mode.\n>\n>  (2) When run outside the working tree of a repository, you do not\n>      have to say '--no-index'.\n>\n> These make detecting the \"'git diff' but not Git\" mode tedious and\n> error-prone.\n>\n> I have not tried this, but if we arranged the code to fall back\n> further to Bash-native \"paths in $PWD\" after the completion code in\n> the posted patch found nothing, would it be sufficient?\n\nEh, isn't the code already arranged to do so?  With the posted\ncompletion script loaded, I do\n\n\t$ cd $HOME\n\t$ git diff [--no-index] w/git.git/C<TAB>\n\nwhere (1) my $HOME is not under version control (dotfiles are\ninstalled after getting built from their sources that are version\ncontrolled elsewhere), and (2) ~/w/git.git/ is the primary working\ntree I work in.  I see\n\n\tCargo.toml  CODE_OF_CONDUCT.md  COPYING\n\noffered as choices.  As there is no index or rev when I am in my\n$HOME directory, naturally the only completion that kicks in is the\nbash native \"paths we see\".\n\nSo I think I do not need anything special to \"arrange\" the fallback.\n\nThanks.\n\n\n"},{"id":"549576","messageId":"xmqqfr0tx3cu.fsf@gitster.g","threadId":"66105","inReplyTo":"xmqqcxw010me.fsf@gitster.g","subject":"[PATCH v2] completion: complete tracked paths for 'git diff'","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-08-04T16:22:25Z","receivedAt":"2026-08-04T16:22:28Z","isPatch":true,"body":"When completing arguments for 'git diff', _git_diff() delegates to\n__git_complete_revlist_file(), which only completes revision\nreferences.  This is good [*], as mixing both revisions and paths in a\nsingle list for the user to pick from is simply too confusing.\n\nIf no reference matches, or if '--' is given, however, _git_diff()\nleaves COMPREPLY empty.  Bash then falls back to default filename\ncompletion in $PWD.  This fails when 'git -C <path>' is used because\n$PWD is not the target repository.\n\nUpdate _git_diff() to use __git_complete_index_file() when '--' is\npresent, or when revision reference completion yields no matching\ncandidates, so that tracked paths are offered as candidates.\n\nThis changes behavior even in the case where '-C <there>' is not\nused.  The new behavior omits untracked paths from suggestions when\nno revs match the prefix but matching tracked paths exist, which is\nmore useful in the context of 'git diff'.\n\nWhen run outside the working tree of a repository, or when nothing\nmatches from revisions or tracked paths, Bash still falls back to\ndefault filename completion in $PWD, so such a use case would be\njust like completing paths for any 'diff' command, rather than for\n'git diff'.\n\n[Footnote]\n * In https://lore.kernel.org/git/al%2Fw2qgBfhe9qMg6@szeder.dev/\n   SZEDER made the same argument for \"git send-email 0<TAB>\".\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n\n * The last two paragraphs in the proposed commit log message are\n   new, to explain why the code posted as-is would be sufficient to\n   support the \"'git diff --no-index' is not Git but is diff\" usage,\n   and there is no code change between v1 and this iteration.\n\n   By the way, I, as a relative newbie to the completion script, had\n   trouble with the test_completion helper and wasted some time\n   wondering why an additional test:\n\n    test_expect_success 'git diff completes untracked paths if  nothing matches' '\n\t    >untracked &&\n\t    test_completion \"git diff -- u\" <<-\\EOF\n\t    untracked\n\t    EOF\n    '\n\n    did not work, even though under manual testing, u<TAB> completed\n    'untracked' just fine.  The reason is that test_completion\n    does not test the final \"Bash default\" fallback.  It might not\n    be necessary for those who are familiar with the completion test\n    suite, but I thought it would help others.\n\n    This message comes with a range-diff that shows only the commit\n    log changes.\n\n contrib/completion/git-completion.bash |  8 +++++-\n t/t9902-completion.sh                  | 40 ++++++++++++++++++++++++++\n 2 files changed, 47 insertions(+), 1 deletion(-)\n\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex e875787710..8f5773292b 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -1947,7 +1947,10 @@ __git_diff_difftool_options=\"--cached --staged\n \n _git_diff ()\n {\n-\t__git_has_doubledash && return\n+\tif __git_has_doubledash; then\n+\t\t__git_complete_index_file\n+\t\treturn\n+\tfi\n \n \tcase \"$cur\" in\n \t--diff-algorithm=*)\n@@ -1976,6 +1979,9 @@ _git_diff ()\n \t\t;;\n \tesac\n \t__git_complete_revlist_file\n+\tif [ ${#COMPREPLY[@]} -eq 0 ]; then\n+\t\t__git_complete_index_file\n+\tfi\n }\n \n __git_mergetools_common=\"diffuse diffmerge ecmerge emerge kdiff3 meld opendiff\ndiff --git a/t/t9902-completion.sh b/t/t9902-completion.sh\nindex 9ae3c48ebd..82488f3b50 100755\n--- a/t/t9902-completion.sh\n+++ b/t/t9902-completion.sh\n@@ -2663,6 +2663,7 @@ test_expect_success 'setup for integration tests' '\n \techo content >file1 &&\n \techo more >file2 &&\n \tgit add file1 file2 &&\n+\techo untracked >file3 &&\n \tgit commit -m one &&\n \tgit branch mybranch &&\n \tgit tag mytag\n@@ -2712,6 +2713,45 @@ test_expect_success 'git -C <path> checkout uses the right repo' '\n \tEOF\n '\n \n+test_expect_success 'git diff completes tracked paths when no refs match' '\n+\t# file1 and file2 are tracked but file3 is not\n+\ttest_completion \"git diff f\" <<-\\EOF\n+\tfile1\n+\tfile2\n+\tEOF\n+'\n+\n+test_expect_success 'git diff -- completes tracked paths' '\n+\t# file1 and file2 are tracked but file3 is not\n+\ttest_completion \"git diff -- f\" <<-\\EOF\n+\tfile1\n+\tfile2\n+\tEOF\n+'\n+\n+test_expect_success 'git -C <path> diff completes tracked paths in specified repo' '\n+\ttest_when_finished \"rm -rf repo-for-diff\" &&\n+\tgit init repo-for-diff &&\n+\techo content >repo-for-diff/otherfile &&\n+\tgit -C repo-for-diff add otherfile &&\n+\techo untracked >repo-for-diff/oops &&\n+\tgit -C repo-for-diff commit -m otherfile &&\n+\ttest_completion \"git -C repo-for-diff diff o\" <<-\\EOF\n+\totherfile\n+\tEOF\n+'\n+\n+test_expect_success 'git -C <path> diff -- completes pathspecs in specified repo' '\n+\ttest_when_finished \"rm -rf repo-for-diff\" &&\n+\tgit init repo-for-diff &&\n+\techo content >repo-for-diff/otherfile &&\n+\tgit -C repo-for-diff add otherfile &&\n+\tgit -C repo-for-diff commit -m otherfile &&\n+\ttest_completion \"git -C repo-for-diff diff -- o\" <<-\\EOF\n+\totherfile\n+\tEOF\n+'\n+\n test_expect_success 'show completes all refs' '\n \ttest_completion \"git show m\" <<-\\EOF\n \tmain Z\n\nRange-diff against v1:\n1:  100043822f ! 1:  fa4461a192 completion: complete tracked paths for 'git diff'\n    @@ Commit message\n     \n         When completing arguments for 'git diff', _git_diff() delegates to\n         __git_complete_revlist_file(), which only completes revision\n    -    references.  This is good [*], as mixing both revs and paths in a\n    -    single list to have the user pick is simply too confusing.\n    +    references.  This is good [*], as mixing both revisions and paths in a\n    +    single list for the user to pick from is simply too confusing.\n     \n         If no reference matches, or if '--' is given, however, _git_diff()\n    -    leaves COMPREPLY empty. Bash then falls back to default filename\n    -    completion in $PWD. This fails when 'git -C <path>' is used because\n    +    leaves COMPREPLY empty.  Bash then falls back to default filename\n    +    completion in $PWD.  This fails when 'git -C <path>' is used because\n         $PWD is not the target repository.\n     \n         Update _git_diff() to use __git_complete_index_file() when '--' is\n         present, or when revision reference completion yields no matching\n         candidates, so that tracked paths are offered as candidates.\n     \n    -    [Footnote]\n    +    This changes behavior even in the case where '-C <there>' is not\n    +    used.  The new behavior omits untracked paths from suggestions when\n    +    no revs match the prefix but matching tracked paths exist, which is\n    +    more useful in the context of 'git diff'.\n    +\n    +    When run outside the working tree of a repository, or when nothing\n    +    matches from revisions or tracked paths, Bash still falls back to\n    +    default filename completion in $PWD, so such a use case would be\n    +    just like completing paths for any 'diff' command, rather than for\n    +    'git diff'.\n     \n    +    [Footnote]\n          * In https://lore.kernel.org/git/al%2Fw2qgBfhe9qMg6@szeder.dev/\n            SZEDER made the same argument for \"git send-email 0<TAB>\".\n     \n         Signed-off-by: Junio C Hamano <gitster@pobox.com>\n    +    ---\n    +\n    +     * The last two paragraphs in the proposed commit log message are\n    +       new, to explain why the code posted as-is would be sufficient to\n    +       support the \"'git diff --no-index' is not Git but is diff\" usage.\n    +       I, as a relative newbie to the completion script, had trouble\n    +       with the test_completion helper and wasted some time wondering\n    +       why an additional test:\n    +\n    +        test_expect_success 'git diff completes untracked paths if  nothing matches' '\n    +                >untracked &&\n    +                test_completion \"git diff -- u\" <<-\\EOF\n    +                untracked\n    +                EOF\n    +        '\n    +\n    +        did not work, even though under manual testing, u<TAB> completed\n    +        'untracked' just fine.  The reason is that test_completion\n    +        does not test the final \"Bash default\" fallback.  It might not\n    +        be necessary for those who are familiar with the completion test\n    +        suite, but I thought it would help others.\n     \n      ## contrib/completion/git-completion.bash ##\n     @@ contrib/completion/git-completion.bash: __git_diff_difftool_options=\"--cached --staged\n-- \n2.55.0-624-gcdeb5fd34c\n\n"},{"id":"549770","messageId":"20260805194250.3316220-1-gitster@pobox.com","threadId":"66105","inReplyTo":"xmqqcxw010me.fsf@gitster.g","subject":"[PATCH v3 0/3] completion of 'git [-C <dir>] diff'","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-08-05T19:42:47Z","receivedAt":"2026-08-05T19:42:53Z","isPatch":true,"body":"Here is another reroll.\n\nThe primary motivation for this topic is that the command-line\ncompletion of 'git diff' does not handle paths (unlike 'git status'\nand 'git add') and instead relies on the default behavior of Bash\ncommand-line completion, which completes files in $PWD; this does\nnot work at all with the '-C <directory>' option.\n\nPrevious iterations of the patch taught the completion script to\noffer tracked paths that match the prefix before <TAB> to improve\nthe situation.\n\nThis time, we also complete untracked paths ourselves, so that even\nthe following commands, which compare files like 'file[12]' that are\nnot under the control of Git in a different directory, are\ncompleted:\n\n        $ git -C not-a-git-dir diff fil<TAB>\n        $ git -C not-a-git-dir diff --no-index fil<TAB>\n\n 1/3: completion: no-op refactoring of diff completion\n 2/3: completion: complete tracked paths for 'git diff'\n 3/3: completion: 'git diff' completes untracked paths as a last resort\n\n contrib/completion/git-completion.bash | 69 +++++++++++++++-----------\n t/t9902-completion.sh                  | 59 ++++++++++++++++++++++\n 2 files changed, 100 insertions(+), 28 deletions(-)\n"},{"id":"549771","messageId":"20260805194250.3316220-2-gitster@pobox.com","threadId":"66105","inReplyTo":"20260805194250.3316220-1-gitster@pobox.com","subject":"[PATCH v3 1/3] completion: no-op refactoring of diff completion","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-08-05T19:42:48Z","receivedAt":"2026-08-05T19:42:55Z","isPatch":true,"body":"The \"git diff\" completion function punts very early when it sees\n\"--\" on the command line, since it is a sign that options or\nrevisions can appear and the current completion does not need to do\nanything \"git diff\" specific. By returning, it lets Bash default\naction that completes the names of the files in $PWD to kick in.\n\nIn preparation for the next step to change what happens when we\n\"punt\", arrange the code flow to avoid this early return.  The\nbehaviour at this step is unchanged, but the control flow just\nfalls straight to the end.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n contrib/completion/git-completion.bash | 61 ++++++++++++++------------\n 1 file changed, 33 insertions(+), 28 deletions(-)\n\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex e875787710..ccd3b2a372 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -1947,35 +1947,40 @@ __git_diff_difftool_options=\"--cached --staged\n \n _git_diff ()\n {\n-\t__git_has_doubledash && return\n-\n-\tcase \"$cur\" in\n-\t--diff-algorithm=*)\n-\t\t__gitcomp \"$__git_diff_algorithms\" \"\" \"${cur##--diff-algorithm=}\"\n-\t\treturn\n-\t\t;;\n-\t--submodule=*)\n-\t\t__gitcomp \"$__git_diff_submodule_formats\" \"\" \"${cur##--submodule=}\"\n-\t\treturn\n-\t\t;;\n-\t--color-moved=*)\n-\t\t__gitcomp \"$__git_color_moved_opts\" \"\" \"${cur##--color-moved=}\"\n-\t\treturn\n-\t\t;;\n-\t--color-moved-ws=*)\n-\t\t__gitcomp \"$__git_color_moved_ws_opts\" \"\" \"${cur##--color-moved-ws=}\"\n-\t\treturn\n-\t\t;;\n-\t--ws-error-highlight=*)\n-\t\t__gitcomp \"$__git_ws_error_highlight_opts\" \"\" \"${cur##--ws-error-highlight=}\"\n-\t\treturn\n-\t\t;;\n-\t--*)\n-\t\t__gitcomp \"$__git_diff_difftool_options\"\n-\t\treturn\n+\tif ! __git_has_doubledash; then\n+\t\tcase \"$cur\" in\n+\t\t--diff-algorithm=*)\n+\t\t\t__gitcomp \"$__git_diff_algorithms\" \\\n+\t\t\t\t\"\" \"${cur##--diff-algorithm=}\"\n+\t\t\treturn\n \t\t;;\n-\tesac\n-\t__git_complete_revlist_file\n+\t\t--submodule=*)\n+\t\t\t__gitcomp \"$__git_diff_submodule_formats\" \\\n+\t\t\t\t\"\" \"${cur##--submodule=}\"\n+\t\t\treturn\n+\t\t\t;;\n+\t\t--color-moved=*)\n+\t\t\t__gitcomp \"$__git_color_moved_opts\" \\\n+\t\t\t\t\"\" \"${cur##--color-moved=}\"\n+\t\t\treturn\n+\t\t\t;;\n+\t\t--color-moved-ws=*)\n+\t\t\t__gitcomp \"$__git_color_moved_ws_opts\" \\\n+\t\t\t\t\"\" \"${cur##--color-moved-ws=}\"\n+\t\t\treturn\n+\t\t\t;;\n+\t\t--ws-error-highlight=*)\n+\t\t\t__gitcomp \"$__git_ws_error_highlight_opts\" \\\n+\t\t\t\t\"\" \"${cur##--ws-error-highlight=}\"\n+\t\t\treturn\n+\t\t\t;;\n+\t\t--*)\n+\t\t\t__gitcomp \"$__git_diff_difftool_options\"\n+\t\t\treturn\n+\t\t\t;;\n+\t\tesac\n+\t\t__git_complete_revlist_file\n+\tfi\n }\n \n __git_mergetools_common=\"diffuse diffmerge ecmerge emerge kdiff3 meld opendiff\n-- \n2.55.0-653-g9745b9777e\n\n"},{"id":"549772","messageId":"20260805194250.3316220-3-gitster@pobox.com","threadId":"66105","inReplyTo":"20260805194250.3316220-1-gitster@pobox.com","subject":"[PATCH v3 2/3] completion: complete tracked paths for 'git diff'","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-08-05T19:42:49Z","receivedAt":"2026-08-05T19:42:57Z","isPatch":true,"body":"When completing arguments for 'git diff', _git_diff() delegates to\n__git_complete_revlist_file(), which only completes revision\nreferences.  This is good [*], as mixing both revisions and paths in a\nsingle list for the user to pick from is simply too confusing.\n\nIf no reference matches, or if '--' is given, however, _git_diff()\nleaves COMPREPLY empty.  Bash then falls back to default filename\ncompletion in $PWD.  This fails when 'git -C <path>' is used because\n$PWD is not the target repository.\n\nUpdate _git_diff() to use __git_complete_index_file() when '--' is\npresent, or when revision reference completion yields no matching\ncandidates, so that tracked paths are offered as candidates.\n\nThis changes behavior even in the case where '-C <there>' is not\nused.  The new behavior omits untracked paths from suggestions when\nno revs match the prefix but matching tracked paths exist, which is\nmore useful in the context of 'git diff'.\n\nWhen run outside the working tree of a repository, or when nothing\nmatches from revisions or tracked paths, Bash still falls back to\ndefault filename completion in $PWD, so such a use case would be\njust like completing paths for any 'diff' command, rather than for\n'git diff'.\n\n[Footnote]\n * In https://lore.kernel.org/git/al%2Fw2qgBfhe9qMg6@szeder.dev/\n   SZEDER made the same argument for \"git send-email 0<TAB>\".\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n contrib/completion/git-completion.bash |  4 +++\n t/t9902-completion.sh                  | 39 ++++++++++++++++++++++++++\n 2 files changed, 43 insertions(+)\n\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex ccd3b2a372..845fd19f70 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -1981,6 +1981,10 @@ _git_diff ()\n \t\tesac\n \t\t__git_complete_revlist_file\n \tfi\n+\n+\tif [ ${#COMPREPLY[@]} -eq 0 ]; then\n+\t\t__git_complete_index_file\n+\tfi\n }\n \n __git_mergetools_common=\"diffuse diffmerge ecmerge emerge kdiff3 meld opendiff\ndiff --git a/t/t9902-completion.sh b/t/t9902-completion.sh\nindex 55dc9eabfc..10ac690e21 100755\n--- a/t/t9902-completion.sh\n+++ b/t/t9902-completion.sh\n@@ -2712,6 +2712,45 @@ test_expect_success 'git -C <path> checkout uses the right repo' '\n \tEOF\n '\n \n+test_expect_success 'git diff completes tracked paths when no refs match' '\n+\t# file1 and file2 are tracked but file3 is not\n+\ttest_completion \"git diff f\" <<-\\EOF\n+\tfile1\n+\tfile2\n+\tEOF\n+'\n+\n+test_expect_success 'git diff -- completes tracked paths' '\n+\t# file1 and file2 are tracked but file3 is not\n+\ttest_completion \"git diff -- f\" <<-\\EOF\n+\tfile1\n+\tfile2\n+\tEOF\n+'\n+\n+test_expect_success 'git -C <path> diff completes tracked paths in specified repo' '\n+\ttest_when_finished \"rm -rf repo-for-diff\" &&\n+\tgit init repo-for-diff &&\n+\techo content >repo-for-diff/otherfile &&\n+\tgit -C repo-for-diff add otherfile &&\n+\techo untracked >repo-for-diff/oops &&\n+\tgit -C repo-for-diff commit -m otherfile &&\n+\ttest_completion \"git -C repo-for-diff diff o\" <<-\\EOF\n+\totherfile\n+\tEOF\n+'\n+\n+test_expect_success 'git -C <path> diff -- completes pathspecs in specified repo' '\n+\ttest_when_finished \"rm -rf repo-for-diff\" &&\n+\tgit init repo-for-diff &&\n+\techo content >repo-for-diff/otherfile &&\n+\tgit -C repo-for-diff add otherfile &&\n+\tgit -C repo-for-diff commit -m otherfile &&\n+\ttest_completion \"git -C repo-for-diff diff -- o\" <<-\\EOF\n+\totherfile\n+\tEOF\n+'\n+\n test_expect_success 'show completes all refs' '\n \ttest_completion \"git show m\" <<-\\EOF\n \tmain Z\n-- \n2.55.0-653-g9745b9777e\n\n"},{"id":"549773","messageId":"20260805194250.3316220-4-gitster@pobox.com","threadId":"66105","inReplyTo":"20260805194250.3316220-1-gitster@pobox.com","subject":"[PATCH v3 3/3] completion: 'git diff' completes untracked paths as a last resort","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-08-05T19:42:50Z","receivedAt":"2026-08-05T19:43:00Z","isPatch":true,"body":"We taught 'git diff' to first try to complete revisions (unless '--'\nis present on the command line) and, failing that, to complete\ntracked paths.  If this yields nothing, it lets the Bash default,\nwhich offers paths in $PWD, kick in.\n\nTeach it to complete untracked paths before giving up and letting\nthe Bash default kick in.  With this change,\n\n    $ git -C another-directory diff un<TAB>\n\nfinds the 'untracked' file in another-directory and offers it as a\ncompletion candidate.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n contrib/completion/git-completion.bash |  4 ++++\n t/t9902-completion.sh                  | 22 +++++++++++++++++++++-\n 2 files changed, 25 insertions(+), 1 deletion(-)\n\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex 845fd19f70..7741789e41 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -1985,6 +1985,10 @@ _git_diff ()\n \tif [ ${#COMPREPLY[@]} -eq 0 ]; then\n \t\t__git_complete_index_file\n \tfi\n+\n+\tif [ ${#COMPREPLY[@]} -eq 0 ]; then\n+\t\t__git_complete_index_file \"--others --directory\"\n+\tfi\n }\n \n __git_mergetools_common=\"diffuse diffmerge ecmerge emerge kdiff3 meld opendiff\ndiff --git a/t/t9902-completion.sh b/t/t9902-completion.sh\nindex 10ac690e21..53a2bfb2ac 100755\n--- a/t/t9902-completion.sh\n+++ b/t/t9902-completion.sh\n@@ -2663,6 +2663,7 @@ test_expect_success 'setup for integration tests' '\n \techo content >file1 &&\n \techo more >file2 &&\n \tgit add file1 file2 &&\n+\techo untracked >ufile &&\n \tgit commit -m one &&\n \tgit branch mybranch &&\n \tgit tag mytag\n@@ -2728,6 +2729,15 @@ test_expect_success 'git diff -- completes tracked paths' '\n \tEOF\n '\n \n+test_expect_success 'git diff [--] completes untracked paths, too' '\n+\ttest_completion \"git diff u\" <<-\\EOF &&\n+\tufile\n+\tEOF\n+\ttest_completion \"git diff -- u\" <<-\\EOF\n+\tufile\n+\tEOF\n+'\n+\n test_expect_success 'git -C <path> diff completes tracked paths in specified repo' '\n \ttest_when_finished \"rm -rf repo-for-diff\" &&\n \tgit init repo-for-diff &&\n@@ -2744,11 +2754,21 @@ test_expect_success 'git -C <path> diff -- completes pathspecs in specified repo\n \ttest_when_finished \"rm -rf repo-for-diff\" &&\n \tgit init repo-for-diff &&\n \techo content >repo-for-diff/otherfile &&\n+\techo untracked >repo-for-diff/untracked &&\n \tgit -C repo-for-diff add otherfile &&\n \tgit -C repo-for-diff commit -m otherfile &&\n-\ttest_completion \"git -C repo-for-diff diff -- o\" <<-\\EOF\n+\ttest_completion \"git -C repo-for-diff diff o\" <<-\\EOF &&\n+\totherfile\n+\tEOF\n+\ttest_completion \"git -C repo-for-diff diff -- o\" <<-\\EOF &&\n \totherfile\n \tEOF\n+\ttest_completion \"git -C repo-for-diff diff u\" <<-\\EOF &&\n+\tuntracked\n+\tEOF\n+\ttest_completion \"git -C repo-for-diff diff -- u\" <<-\\EOF\n+\tuntracked\n+\tEOF\n '\n \n test_expect_success 'show completes all refs' '\n-- \n2.55.0-653-g9745b9777e\n\n"},{"id":"549846","messageId":"CALnO6CA8sjivVfz45OEDWnvw516z9Nd9SnC+zfJ=FK=Y6Q7MJg@mail.gmail.com","threadId":"66105","inReplyTo":"20260805194250.3316220-4-gitster@pobox.com","subject":"Re: [PATCH v3 3/3] completion: 'git diff' completes untracked paths as a last resort","fromName":"D. Ben Knoble","fromEmail":"ben.knoble@gmail.com","sentAt":"2026-08-06T11:30:36Z","receivedAt":"2026-08-06T11:30:48Z","isPatch":true,"body":"Hello Junio,\n\nOn Wed, Aug 5, 2026 at 3:45 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> We taught 'git diff' to first try to complete revisions (unless '--'\n> is present on the command line) and, failing that, to complete\n> tracked paths.  If this yields nothing, it lets the Bash default,\n> which offers paths in $PWD, kick in.\n>\n> Teach it to complete untracked paths before giving up and letting\n> the Bash default kick in.  With this change,\n>\n>     $ git -C another-directory diff un<TAB>\n>\n> finds the 'untracked' file in another-directory and offers it as a\n> completion candidate.\n>\n> Signed-off-by: Junio C Hamano <gitster@pobox.com>\n> ---\n>  contrib/completion/git-completion.bash |  4 ++++\n>  t/t9902-completion.sh                  | 22 +++++++++++++++++++++-\n>  2 files changed, 25 insertions(+), 1 deletion(-)\n>\n> diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\n> index 845fd19f70..7741789e41 100644\n> --- a/contrib/completion/git-completion.bash\n> +++ b/contrib/completion/git-completion.bash\n> @@ -1985,6 +1985,10 @@ _git_diff ()\n>         if [ ${#COMPREPLY[@]} -eq 0 ]; then\n>                 __git_complete_index_file\n>         fi\n> +\n> +       if [ ${#COMPREPLY[@]} -eq 0 ]; then\n> +               __git_complete_index_file \"--others --directory\"\n> +       fi\n>  }\n>\n>  __git_mergetools_common=\"diffuse diffmerge ecmerge emerge kdiff3 meld opendiff\n> diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh\n> index 10ac690e21..53a2bfb2ac 100755\n> --- a/t/t9902-completion.sh\n> +++ b/t/t9902-completion.sh\n> @@ -2663,6 +2663,7 @@ test_expect_success 'setup for integration tests' '\n>         echo content >file1 &&\n>         echo more >file2 &&\n>         git add file1 file2 &&\n> +       echo untracked >ufile &&\n>         git commit -m one &&\n>         git branch mybranch &&\n>         git tag mytag\n> @@ -2728,6 +2729,15 @@ test_expect_success 'git diff -- completes tracked paths' '\n>         EOF\n>  '\n>\n> +test_expect_success 'git diff [--] completes untracked paths, too' '\n> +       test_completion \"git diff u\" <<-\\EOF &&\n> +       ufile\n> +       EOF\n> +       test_completion \"git diff -- u\" <<-\\EOF\n> +       ufile\n> +       EOF\n> +'\n> +\n\nLGTM up to here.\n\n>  test_expect_success 'git -C <path> diff completes tracked paths in specified repo' '\n>         test_when_finished \"rm -rf repo-for-diff\" &&\n>         git init repo-for-diff &&\n> @@ -2744,11 +2754,21 @@ test_expect_success 'git -C <path> diff -- completes pathspecs in specified repo\n>         test_when_finished \"rm -rf repo-for-diff\" &&\n>         git init repo-for-diff &&\n>         echo content >repo-for-diff/otherfile &&\n> +       echo untracked >repo-for-diff/untracked &&\n>         git -C repo-for-diff add otherfile &&\n>         git -C repo-for-diff commit -m otherfile &&\n> -       test_completion \"git -C repo-for-diff diff -- o\" <<-\\EOF\n> +       test_completion \"git -C repo-for-diff diff o\" <<-\\EOF &&\n> +       otherfile\n> +       EOF\n\nHere, with more context (which I won't paste, because GMail…), it\nlooks like this test is redundant with the test just before?\n\n> +       test_completion \"git -C repo-for-diff diff -- o\" <<-\\EOF &&\n>         otherfile\n>         EOF\n> +       test_completion \"git -C repo-for-diff diff u\" <<-\\EOF &&\n> +       untracked\n> +       EOF\n> +       test_completion \"git -C repo-for-diff diff -- u\" <<-\\EOF\n> +       untracked\n> +       EOF\n>  '\n\nThese tests intermingle with -- and without; the other tests separated them.\n\nI don't think I have a strong preference, but perhaps consistency is a\ngood ideal?\n\n-- \nD. Ben Knoble\n"},{"id":"549847","messageId":"CALnO6CAFYZb+x0W8sxLe8cicCuvhCZFrxiEJSRQs-aMF=cd14A@mail.gmail.com","threadId":"66105","inReplyTo":"20260805194250.3316220-1-gitster@pobox.com","subject":"Re: [PATCH v3 0/3] completion of 'git [-C <dir>] diff'","fromName":"D. Ben Knoble","fromEmail":"ben.knoble@gmail.com","sentAt":"2026-08-06T11:30:57Z","receivedAt":"2026-08-06T11:31:09Z","isPatch":true,"body":"Hi Junio,\n\nOn Wed, Aug 5, 2026 at 3:44 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Here is another reroll.\n>\n> The primary motivation for this topic is that the command-line\n> completion of 'git diff' does not handle paths (unlike 'git status'\n> and 'git add') and instead relies on the default behavior of Bash\n> command-line completion, which completes files in $PWD; this does\n> not work at all with the '-C <directory>' option.\n>\n> Previous iterations of the patch taught the completion script to\n> offer tracked paths that match the prefix before <TAB> to improve\n> the situation.\n>\n> This time, we also complete untracked paths ourselves, so that even\n> the following commands, which compare files like 'file[12]' that are\n> not under the control of Git in a different directory, are\n> completed:\n>\n>         $ git -C not-a-git-dir diff fil<TAB>\n>         $ git -C not-a-git-dir diff --no-index fil<TAB>\n>\n>  1/3: completion: no-op refactoring of diff completion\n>  2/3: completion: complete tracked paths for 'git diff'\n>  3/3: completion: 'git diff' completes untracked paths as a last resort\n>\n>  contrib/completion/git-completion.bash | 69 +++++++++++++++-----------\n>  t/t9902-completion.sh                  | 59 ++++++++++++++++++++++\n>  2 files changed, 100 insertions(+), 28 deletions(-)\n>\n\nLeft one comment on 3/3, but the rest looks good!\n\n-- \nD. Ben Knoble\n"},{"id":"549857","messageId":"xmqqecgbmgoq.fsf@gitster.g","threadId":"66105","inReplyTo":"CALnO6CA8sjivVfz45OEDWnvw516z9Nd9SnC+zfJ=FK=Y6Q7MJg@mail.gmail.com","subject":"Re: [PATCH v3 3/3] completion: 'git diff' completes untracked paths as a last resort","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-08-06T15:06:45Z","receivedAt":"2026-08-06T15:06:48Z","isPatch":true,"body":"\"D. Ben Knoble\" <ben.knoble@gmail.com> writes:\n\n>>  test_expect_success 'git -C <path> diff completes tracked paths in specified repo' '\n>>         test_when_finished \"rm -rf repo-for-diff\" &&\n>>         git init repo-for-diff &&\n>> @@ -2744,11 +2754,21 @@ test_expect_success 'git -C <path> diff -- completes pathspecs in specified repo\n>>         test_when_finished \"rm -rf repo-for-diff\" &&\n>>         git init repo-for-diff &&\n>>         echo content >repo-for-diff/otherfile &&\n>> +       echo untracked >repo-for-diff/untracked &&\n>>         git -C repo-for-diff add otherfile &&\n>>         git -C repo-for-diff commit -m otherfile &&\n>> -       test_completion \"git -C repo-for-diff diff -- o\" <<-\\EOF\n>> +       test_completion \"git -C repo-for-diff diff o\" <<-\\EOF &&\n>> +       otherfile\n>> +       EOF\n>\n> Here, with more context (which I won't paste, because GMail…), it\n> looks like this test is redundant with the test just before?\n\nYou're right.   As these prefix strings ('f', 'o', or 'u') tested\ndo not begin any valid refs, the result should be the same with or\nwithout '--', and I wanted to test both in a single test block, but\nI botched the conversion.\n\nWill fix in v4, which hopefully will be test-only updates.\n\nThanks.\n"},{"id":"549902","messageId":"20260807013830.698340-1-gitster@pobox.com","threadId":"66105","inReplyTo":"xmqqcxw010me.fsf@gitster.g","subject":"[PATCH v4 0/3] completion of 'git [-C <dir>] diff'","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-08-07T01:38:27Z","receivedAt":"2026-08-07T01:38:36Z","isPatch":true,"body":"The primary motivation for this topic is that the command-line\ncompletion of 'git diff' does not handle paths (unlike 'git status'\nand 'git add') and instead relies on the default behavior of Bash\ncommand-line completion, which completes files in $PWD; this does\nnot work at all with the '-C <directory>' option.\n\nThis series teaches the completion machinery to complete revisions\n(unless '--' exists), then tracked paths, and then untracked paths,\nbefore letting the Bash default kick in.  This way, we correctly\ncomplete 'git diff' command line even when '-C <directory>' is in\neffect.\n\nThe tests are the only changes relative to v2.  In the step where\ntracked paths are completed, v2 did not demonstrate that untracked\nones are *not* completed at the same time.  Now we do by having\nuntracked 'file3' next to 'file1' and 'file2' that are tracked.  In\nthe last step, we demonstrate untracked paths that do not share\nprefix with refs or tracked paths are completed, with or without the\n\"-C <dir>\" option.\n\n 1/3: completion: no-op refactoring of diff completion\n 2/3: completion: complete tracked paths for 'git diff'\n 3/3: completion: 'git diff' completes untracked paths as a last\n        resort\n\n contrib/completion/git-completion.bash | 69 +++++++++++++++-----------\n t/t9902-completion.sh                  | 49 ++++++++++++++++++\n 2 files changed, 90 insertions(+), 28 deletions(-)\n\nRange-diff against v3:\n1:  d3c51c042c = 1:  3b99b45fee completion: no-op refactoring of diff completion\n2:  c3658d6ca2 ! 2:  bcc24b6bda completion: complete tracked paths for 'git diff'\n    @@ contrib/completion/git-completion.bash: _git_diff ()\n      __git_mergetools_common=\"diffuse diffmerge ecmerge emerge kdiff3 meld opendiff\n     \n      ## t/t9902-completion.sh ##\n    +@@ t/t9902-completion.sh: test_expect_success 'setup for integration tests' '\n    + \techo content >file1 &&\n    + \techo more >file2 &&\n    + \tgit add file1 file2 &&\n    ++\techo untracked >file3 &&\n    + \tgit commit -m one &&\n    + \tgit branch mybranch &&\n    + \tgit tag mytag\n     @@ t/t9902-completion.sh: test_expect_success 'git -C <path> checkout uses the right repo' '\n      \tEOF\n      '\n      \n     +test_expect_success 'git diff completes tracked paths when no refs match' '\n     +\t# file1 and file2 are tracked but file3 is not\n    -+\ttest_completion \"git diff f\" <<-\\EOF\n    ++\t# there is no ref that begins with f\n    ++\ttest_completion \"git diff f\" <<-\\EOF &&\n     +\tfile1\n     +\tfile2\n     +\tEOF\n    -+'\n    -+\n    -+test_expect_success 'git diff -- completes tracked paths' '\n    -+\t# file1 and file2 are tracked but file3 is not\n     +\ttest_completion \"git diff -- f\" <<-\\EOF\n     +\tfile1\n     +\tfile2\n     +\tEOF\n     +'\n     +\n    -+test_expect_success 'git -C <path> diff completes tracked paths in specified repo' '\n    ++test_expect_success 'git -C <path> diff completes in the specified repo' '\n     +\ttest_when_finished \"rm -rf repo-for-diff\" &&\n     +\tgit init repo-for-diff &&\n    ++\n    ++\t# otherfile is tracked, oops is untracked\n     +\techo content >repo-for-diff/otherfile &&\n     +\tgit -C repo-for-diff add otherfile &&\n    -+\techo untracked >repo-for-diff/oops &&\n     +\tgit -C repo-for-diff commit -m otherfile &&\n    -+\ttest_completion \"git -C repo-for-diff diff o\" <<-\\EOF\n    ++\techo untracked >repo-for-diff/oops &&\n    ++\ttest_completion \"git -C repo-for-diff diff o\" <<-\\EOF &&\n     +\totherfile\n     +\tEOF\n    -+'\n    -+\n    -+test_expect_success 'git -C <path> diff -- completes pathspecs in specified repo' '\n    -+\ttest_when_finished \"rm -rf repo-for-diff\" &&\n    -+\tgit init repo-for-diff &&\n    -+\techo content >repo-for-diff/otherfile &&\n    -+\tgit -C repo-for-diff add otherfile &&\n    -+\tgit -C repo-for-diff commit -m otherfile &&\n     +\ttest_completion \"git -C repo-for-diff diff -- o\" <<-\\EOF\n     +\totherfile\n     +\tEOF\n3:  ba5dc6f164 ! 3:  34720a30ab completion: 'git diff' completes untracked paths as a last resort\n    @@ contrib/completion/git-completion.bash: _git_diff ()\n     \n      ## t/t9902-completion.sh ##\n     @@ t/t9902-completion.sh: test_expect_success 'setup for integration tests' '\n    - \techo content >file1 &&\n      \techo more >file2 &&\n      \tgit add file1 file2 &&\n    + \techo untracked >file3 &&\n     +\techo untracked >ufile &&\n      \tgit commit -m one &&\n      \tgit branch mybranch &&\n      \tgit tag mytag\n    -@@ t/t9902-completion.sh: test_expect_success 'git diff -- completes tracked paths' '\n    +@@ t/t9902-completion.sh: test_expect_success 'git diff completes tracked paths when no refs match' '\n      \tEOF\n      '\n      \n     +test_expect_success 'git diff [--] completes untracked paths, too' '\n    ++\t# there is no ref or tracked path that begin with u\n     +\ttest_completion \"git diff u\" <<-\\EOF &&\n     +\tufile\n     +\tEOF\n    @@ t/t9902-completion.sh: test_expect_success 'git diff -- completes tracked paths'\n     +\tEOF\n     +'\n     +\n    - test_expect_success 'git -C <path> diff completes tracked paths in specified repo' '\n    - \ttest_when_finished \"rm -rf repo-for-diff\" &&\n    - \tgit init repo-for-diff &&\n    -@@ t/t9902-completion.sh: test_expect_success 'git -C <path> diff -- completes pathspecs in specified repo\n    + test_expect_success 'git -C <path> diff completes in the specified repo' '\n      \ttest_when_finished \"rm -rf repo-for-diff\" &&\n      \tgit init repo-for-diff &&\n    + \n    +-\t# otherfile is tracked, oops is untracked\n    ++\t# otherfile is tracked, oops and ufile are untracked\n      \techo content >repo-for-diff/otherfile &&\n    -+\techo untracked >repo-for-diff/untracked &&\n      \tgit -C repo-for-diff add otherfile &&\n      \tgit -C repo-for-diff commit -m otherfile &&\n    + \techo untracked >repo-for-diff/oops &&\n    ++\techo untracked >repo-for-diff/ufile &&\n    + \ttest_completion \"git -C repo-for-diff diff o\" <<-\\EOF &&\n    + \totherfile\n    + \tEOF\n     -\ttest_completion \"git -C repo-for-diff diff -- o\" <<-\\EOF\n    -+\ttest_completion \"git -C repo-for-diff diff o\" <<-\\EOF &&\n    -+\totherfile\n    -+\tEOF\n     +\ttest_completion \"git -C repo-for-diff diff -- o\" <<-\\EOF &&\n      \totherfile\n      \tEOF\n     +\ttest_completion \"git -C repo-for-diff diff u\" <<-\\EOF &&\n    -+\tuntracked\n    ++\tufile\n     +\tEOF\n     +\ttest_completion \"git -C repo-for-diff diff -- u\" <<-\\EOF\n    -+\tuntracked\n    ++\tufile\n     +\tEOF\n      '\n      \n-- \n2.55.0-655-gb2c071042d\n\n"},{"id":"549903","messageId":"20260807013830.698340-2-gitster@pobox.com","threadId":"66105","inReplyTo":"20260807013830.698340-1-gitster@pobox.com","subject":"[PATCH v4 1/3] completion: no-op refactoring of diff completion","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-08-07T01:38:28Z","receivedAt":"2026-08-07T01:38:39Z","isPatch":true,"body":"The \"git diff\" completion function punts very early when it sees\n\"--\" on the command line, since it is a sign that options or\nrevisions can appear and the current completion does not need to do\nanything \"git diff\" specific. By returning, it lets Bash default\naction that completes the names of the files in $PWD to kick in.\n\nIn preparation for the next step to change what happens when we\n\"punt\", arrange the code flow to avoid this early return.  The\nbehaviour at this step is unchanged, but the control flow just\nfalls straight to the end.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n contrib/completion/git-completion.bash | 61 ++++++++++++++------------\n 1 file changed, 33 insertions(+), 28 deletions(-)\n\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex e875787710..ccd3b2a372 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -1947,35 +1947,40 @@ __git_diff_difftool_options=\"--cached --staged\n \n _git_diff ()\n {\n-\t__git_has_doubledash && return\n-\n-\tcase \"$cur\" in\n-\t--diff-algorithm=*)\n-\t\t__gitcomp \"$__git_diff_algorithms\" \"\" \"${cur##--diff-algorithm=}\"\n-\t\treturn\n-\t\t;;\n-\t--submodule=*)\n-\t\t__gitcomp \"$__git_diff_submodule_formats\" \"\" \"${cur##--submodule=}\"\n-\t\treturn\n-\t\t;;\n-\t--color-moved=*)\n-\t\t__gitcomp \"$__git_color_moved_opts\" \"\" \"${cur##--color-moved=}\"\n-\t\treturn\n-\t\t;;\n-\t--color-moved-ws=*)\n-\t\t__gitcomp \"$__git_color_moved_ws_opts\" \"\" \"${cur##--color-moved-ws=}\"\n-\t\treturn\n-\t\t;;\n-\t--ws-error-highlight=*)\n-\t\t__gitcomp \"$__git_ws_error_highlight_opts\" \"\" \"${cur##--ws-error-highlight=}\"\n-\t\treturn\n-\t\t;;\n-\t--*)\n-\t\t__gitcomp \"$__git_diff_difftool_options\"\n-\t\treturn\n+\tif ! __git_has_doubledash; then\n+\t\tcase \"$cur\" in\n+\t\t--diff-algorithm=*)\n+\t\t\t__gitcomp \"$__git_diff_algorithms\" \\\n+\t\t\t\t\"\" \"${cur##--diff-algorithm=}\"\n+\t\t\treturn\n \t\t;;\n-\tesac\n-\t__git_complete_revlist_file\n+\t\t--submodule=*)\n+\t\t\t__gitcomp \"$__git_diff_submodule_formats\" \\\n+\t\t\t\t\"\" \"${cur##--submodule=}\"\n+\t\t\treturn\n+\t\t\t;;\n+\t\t--color-moved=*)\n+\t\t\t__gitcomp \"$__git_color_moved_opts\" \\\n+\t\t\t\t\"\" \"${cur##--color-moved=}\"\n+\t\t\treturn\n+\t\t\t;;\n+\t\t--color-moved-ws=*)\n+\t\t\t__gitcomp \"$__git_color_moved_ws_opts\" \\\n+\t\t\t\t\"\" \"${cur##--color-moved-ws=}\"\n+\t\t\treturn\n+\t\t\t;;\n+\t\t--ws-error-highlight=*)\n+\t\t\t__gitcomp \"$__git_ws_error_highlight_opts\" \\\n+\t\t\t\t\"\" \"${cur##--ws-error-highlight=}\"\n+\t\t\treturn\n+\t\t\t;;\n+\t\t--*)\n+\t\t\t__gitcomp \"$__git_diff_difftool_options\"\n+\t\t\treturn\n+\t\t\t;;\n+\t\tesac\n+\t\t__git_complete_revlist_file\n+\tfi\n }\n \n __git_mergetools_common=\"diffuse diffmerge ecmerge emerge kdiff3 meld opendiff\n-- \n2.55.0-655-gb2c071042d\n\n"},{"id":"549904","messageId":"20260807013830.698340-4-gitster@pobox.com","threadId":"66105","inReplyTo":"20260807013830.698340-1-gitster@pobox.com","subject":"[PATCH v4 3/3] completion: 'git diff' completes untracked paths as a last resort","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-08-07T01:38:30Z","receivedAt":"2026-08-07T01:38:42Z","isPatch":true,"body":"We taught 'git diff' to first try to complete revisions (unless '--'\nis present on the command line) and, failing that, to complete\ntracked paths.  If this yields nothing, it lets the Bash default,\nwhich offers paths in $PWD, kick in.\n\nTeach it to complete untracked paths before giving up and letting\nthe Bash default kick in.  With this change,\n\n    $ git -C another-directory diff un<TAB>\n\nfinds the 'untracked' file in another-directory and offers it as a\ncompletion candidate.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n contrib/completion/git-completion.bash |  4 ++++\n t/t9902-completion.sh                  | 22 ++++++++++++++++++++--\n 2 files changed, 24 insertions(+), 2 deletions(-)\n\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex 845fd19f70..7741789e41 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -1985,6 +1985,10 @@ _git_diff ()\n \tif [ ${#COMPREPLY[@]} -eq 0 ]; then\n \t\t__git_complete_index_file\n \tfi\n+\n+\tif [ ${#COMPREPLY[@]} -eq 0 ]; then\n+\t\t__git_complete_index_file \"--others --directory\"\n+\tfi\n }\n \n __git_mergetools_common=\"diffuse diffmerge ecmerge emerge kdiff3 meld opendiff\ndiff --git a/t/t9902-completion.sh b/t/t9902-completion.sh\nindex adfaf414fd..eea4bdbb7e 100755\n--- a/t/t9902-completion.sh\n+++ b/t/t9902-completion.sh\n@@ -2664,6 +2664,7 @@ test_expect_success 'setup for integration tests' '\n \techo more >file2 &&\n \tgit add file1 file2 &&\n \techo untracked >file3 &&\n+\techo untracked >ufile &&\n \tgit commit -m one &&\n \tgit branch mybranch &&\n \tgit tag mytag\n@@ -2726,21 +2727,38 @@ test_expect_success 'git diff completes tracked paths when no refs match' '\n \tEOF\n '\n \n+test_expect_success 'git diff [--] completes untracked paths, too' '\n+\t# there is no ref or tracked path that begin with u\n+\ttest_completion \"git diff u\" <<-\\EOF &&\n+\tufile\n+\tEOF\n+\ttest_completion \"git diff -- u\" <<-\\EOF\n+\tufile\n+\tEOF\n+'\n+\n test_expect_success 'git -C <path> diff completes in the specified repo' '\n \ttest_when_finished \"rm -rf repo-for-diff\" &&\n \tgit init repo-for-diff &&\n \n-\t# otherfile is tracked, oops is untracked\n+\t# otherfile is tracked, oops and ufile are untracked\n \techo content >repo-for-diff/otherfile &&\n \tgit -C repo-for-diff add otherfile &&\n \tgit -C repo-for-diff commit -m otherfile &&\n \techo untracked >repo-for-diff/oops &&\n+\techo untracked >repo-for-diff/ufile &&\n \ttest_completion \"git -C repo-for-diff diff o\" <<-\\EOF &&\n \totherfile\n \tEOF\n-\ttest_completion \"git -C repo-for-diff diff -- o\" <<-\\EOF\n+\ttest_completion \"git -C repo-for-diff diff -- o\" <<-\\EOF &&\n \totherfile\n \tEOF\n+\ttest_completion \"git -C repo-for-diff diff u\" <<-\\EOF &&\n+\tufile\n+\tEOF\n+\ttest_completion \"git -C repo-for-diff diff -- u\" <<-\\EOF\n+\tufile\n+\tEOF\n '\n \n test_expect_success 'show completes all refs' '\n-- \n2.55.0-655-gb2c071042d\n\n"},{"id":"549905","messageId":"20260807013830.698340-3-gitster@pobox.com","threadId":"66105","inReplyTo":"20260807013830.698340-1-gitster@pobox.com","subject":"[PATCH v4 2/3] completion: complete tracked paths for 'git diff'","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-08-07T01:38:29Z","receivedAt":"2026-08-07T01:38:42Z","isPatch":true,"body":"When completing arguments for 'git diff', _git_diff() delegates to\n__git_complete_revlist_file(), which only completes revision\nreferences.  This is good [*], as mixing both revisions and paths in a\nsingle list for the user to pick from is simply too confusing.\n\nIf no reference matches, or if '--' is given, however, _git_diff()\nleaves COMPREPLY empty.  Bash then falls back to default filename\ncompletion in $PWD.  This fails when 'git -C <path>' is used because\n$PWD is not the target repository.\n\nUpdate _git_diff() to use __git_complete_index_file() when '--' is\npresent, or when revision reference completion yields no matching\ncandidates, so that tracked paths are offered as candidates.\n\nThis changes behavior even in the case where '-C <there>' is not\nused.  The new behavior omits untracked paths from suggestions when\nno revs match the prefix but matching tracked paths exist, which is\nmore useful in the context of 'git diff'.\n\nWhen run outside the working tree of a repository, or when nothing\nmatches from revisions or tracked paths, Bash still falls back to\ndefault filename completion in $PWD, so such a use case would be\njust like completing paths for any 'diff' command, rather than for\n'git diff'.\n\n[Footnote]\n * In https://lore.kernel.org/git/al%2Fw2qgBfhe9qMg6@szeder.dev/\n   SZEDER made the same argument for \"git send-email 0<TAB>\".\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n contrib/completion/git-completion.bash |  4 ++++\n t/t9902-completion.sh                  | 31 ++++++++++++++++++++++++++\n 2 files changed, 35 insertions(+)\n\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex ccd3b2a372..845fd19f70 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -1981,6 +1981,10 @@ _git_diff ()\n \t\tesac\n \t\t__git_complete_revlist_file\n \tfi\n+\n+\tif [ ${#COMPREPLY[@]} -eq 0 ]; then\n+\t\t__git_complete_index_file\n+\tfi\n }\n \n __git_mergetools_common=\"diffuse diffmerge ecmerge emerge kdiff3 meld opendiff\ndiff --git a/t/t9902-completion.sh b/t/t9902-completion.sh\nindex 55dc9eabfc..adfaf414fd 100755\n--- a/t/t9902-completion.sh\n+++ b/t/t9902-completion.sh\n@@ -2663,6 +2663,7 @@ test_expect_success 'setup for integration tests' '\n \techo content >file1 &&\n \techo more >file2 &&\n \tgit add file1 file2 &&\n+\techo untracked >file3 &&\n \tgit commit -m one &&\n \tgit branch mybranch &&\n \tgit tag mytag\n@@ -2712,6 +2713,36 @@ test_expect_success 'git -C <path> checkout uses the right repo' '\n \tEOF\n '\n \n+test_expect_success 'git diff completes tracked paths when no refs match' '\n+\t# file1 and file2 are tracked but file3 is not\n+\t# there is no ref that begins with f\n+\ttest_completion \"git diff f\" <<-\\EOF &&\n+\tfile1\n+\tfile2\n+\tEOF\n+\ttest_completion \"git diff -- f\" <<-\\EOF\n+\tfile1\n+\tfile2\n+\tEOF\n+'\n+\n+test_expect_success 'git -C <path> diff completes in the specified repo' '\n+\ttest_when_finished \"rm -rf repo-for-diff\" &&\n+\tgit init repo-for-diff &&\n+\n+\t# otherfile is tracked, oops is untracked\n+\techo content >repo-for-diff/otherfile &&\n+\tgit -C repo-for-diff add otherfile &&\n+\tgit -C repo-for-diff commit -m otherfile &&\n+\techo untracked >repo-for-diff/oops &&\n+\ttest_completion \"git -C repo-for-diff diff o\" <<-\\EOF &&\n+\totherfile\n+\tEOF\n+\ttest_completion \"git -C repo-for-diff diff -- o\" <<-\\EOF\n+\totherfile\n+\tEOF\n+'\n+\n test_expect_success 'show completes all refs' '\n \ttest_completion \"git show m\" <<-\\EOF\n \tmain Z\n-- \n2.55.0-655-gb2c071042d\n\n"},{"id":"549925","messageId":"CABPp-BHhNHBN0Mt0LVjGEcNL_y7mad7rS6NRX6p14ELrRD-+bg@mail.gmail.com","threadId":"66105","inReplyTo":"20260807013830.698340-2-gitster@pobox.com","subject":"Re: [PATCH v4 1/3] completion: no-op refactoring of diff completion","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2026-08-07T06:15:52Z","receivedAt":"2026-08-07T06:16:05Z","isPatch":true,"body":"On Thu, Aug 6, 2026 at 6:38 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> The \"git diff\" completion function punts very early when it sees\n> \"--\" on the command line, since it is a sign that options or\n> revisions can appear and the current completion does not need to do\n> anything \"git diff\" specific. By returning, it lets Bash default\n> action that completes the names of the files in $PWD to kick in.\n>\n> In preparation for the next step to change what happens when we\n> \"punt\", arrange the code flow to avoid this early return.  The\n> behaviour at this step is unchanged, but the control flow just\n> falls straight to the end.\n>\n> Signed-off-by: Junio C Hamano <gitster@pobox.com>\n> ---\n>  contrib/completion/git-completion.bash | 61 ++++++++++++++------------\n>  1 file changed, 33 insertions(+), 28 deletions(-)\n>\n> diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\n> index e875787710..ccd3b2a372 100644\n> --- a/contrib/completion/git-completion.bash\n> +++ b/contrib/completion/git-completion.bash\n> @@ -1947,35 +1947,40 @@ __git_diff_difftool_options=\"--cached --staged\n>\n>  _git_diff ()\n>  {\n[...]\n> +       if ! __git_has_doubledash; then\n> +               case \"$cur\" in\n> +               --diff-algorithm=*)\n> +                       __gitcomp \"$__git_diff_algorithms\" \\\n> +                               \"\" \"${cur##--diff-algorithm=}\"\n> +                       return\n>                 ;;\n\nThe refactor in this commit is a faithful no-op -- every arm got\nre-indented by one tab as expected.  One tiny slip, though: this first\ncase's \";;\" didn't get the extra tab that every other arm received.\n"},{"id":"549928","messageId":"CABPp-BEAtpT208afwSNoBbR-Nowss8OsLsL8ynETuBfN_xvWag@mail.gmail.com","threadId":"66105","inReplyTo":"20260807013830.698340-3-gitster@pobox.com","subject":"Re: [PATCH v4 2/3] completion: complete tracked paths for 'git diff'","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2026-08-07T06:18:05Z","receivedAt":"2026-08-07T06:18:18Z","isPatch":true,"body":"On Thu, Aug 6, 2026 at 6:38 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> When completing arguments for 'git diff', _git_diff() delegates to\n> __git_complete_revlist_file(), which only completes revision\n> references.  This is good [*], as mixing both revisions and paths in a\n> single list for the user to pick from is simply too confusing.\n>\n> If no reference matches, or if '--' is given, however, _git_diff()\n> leaves COMPREPLY empty.  Bash then falls back to default filename\n> completion in $PWD.  This fails when 'git -C <path>' is used because\n> $PWD is not the target repository.\n>\n> Update _git_diff() to use __git_complete_index_file() when '--' is\n> present, or when revision reference completion yields no matching\n> candidates, so that tracked paths are offered as candidates.\n>\n> This changes behavior even in the case where '-C <there>' is not\n> used.  The new behavior omits untracked paths from suggestions when\n> no revs match the prefix but matching tracked paths exist, which is\n> more useful in the context of 'git diff'.\n\nI'm looking forward to using this.  :-)\n\n[...]\n> diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\n> index ccd3b2a372..845fd19f70 100644\n> --- a/contrib/completion/git-completion.bash\n> +++ b/contrib/completion/git-completion.bash\n> @@ -1981,6 +1981,10 @@ _git_diff ()\n>                 esac\n>                 __git_complete_revlist_file\n>         fi\n> +\n> +       if [ ${#COMPREPLY[@]} -eq 0 ]; then\n> +               __git_complete_index_file\n> +       fi\n>  }\n\nCurious; __git_complete_index_file() is documented as \"requires 1\nargument\", but you pass none here.  As far as I can tell, it works\nanyway, but feels like an accident:\n\n1.   __git_complete_index_file CALLS\n      __git_index_files \"$1\" ...\n      (Here, \"$1\" == \"\")\n2.   __git_index_files \"$1\" ... CALLS\n      __git_ls_files_helper \"$root\" \"$1\" ...\n      (Here, \"$1\" == \"\", again)\n3.   __git_ls_files_helper \"$root\" \"$1\" CALLS\n      __git -C \"$1\" -c core.quotePath=false ls-files\n--exclude-standard $2 -- ...\n      (Note that $2 is unquoted, and since it's empty, it disappears)\n\nIt seems like it'd be better to pass an explicit \"\" to\n__git_complete_index_file than to implicitly get it.\n\n[...]\nThe rest looks good.\n"},{"id":"549935","messageId":"CABPp-BE0WOD1jRT-fVqxP0uaVuV0-Qu4rqCyL63vaRCEiAUmAA@mail.gmail.com","threadId":"66105","inReplyTo":"20260807013830.698340-1-gitster@pobox.com","subject":"Re: [PATCH v4 0/3] completion of 'git [-C <dir>] diff'","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2026-08-07T06:31:49Z","receivedAt":"2026-08-07T06:32:02Z","isPatch":true,"body":"On Thu, Aug 6, 2026 at 6:38 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> The primary motivation for this topic is that the command-line\n> completion of 'git diff' does not handle paths (unlike 'git status'\n> and 'git add') and instead relies on the default behavior of Bash\n> command-line completion, which completes files in $PWD; this does\n> not work at all with the '-C <directory>' option.\n>\n> This series teaches the completion machinery to complete revisions\n> (unless '--' exists), then tracked paths, and then untracked paths,\n> before letting the Bash default kick in.  This way, we correctly\n> complete 'git diff' command line even when '-C <directory>' is in\n> effect.\n\nNice.  :-)\n\n> The tests are the only changes relative to v2.  In the step where\n> tracked paths are completed, v2 did not demonstrate that untracked\n> ones are *not* completed at the same time.  Now we do by having\n> untracked 'file3' next to 'file1' and 'file2' that are tracked.  In\n> the last step, we demonstrate untracked paths that do not share\n> prefix with refs or tracked paths are completed, with or without the\n> \"-C <dir>\" option.\n\nI found this paragraph slightly hard to parse.  I think this means the same as:\n\nOnly the tests changed since v2.  The tracked-paths step now also\nshows that untracked paths are not mixed in: an untracked 'file3'\nnext to tracked 'file1'/'file2' means \"git diff f\" completes only the\nlatter two.  The untracked-paths step shows that an untracked path\nwhich shares no prefix with a ref or tracked path still completes,\nwith or without \"-C \".\n\n(Not that it matters, since this is just the cover letter, but...)\n\n>  1/3: completion: no-op refactoring of diff completion\n>  2/3: completion: complete tracked paths for 'git diff'\n>  3/3: completion: 'git diff' completes untracked paths as a last\n>         resort\n\nI found two minor nits in the first two patches, and didn't spot any\nissues with the third patch.\n"},{"id":"549980","messageId":"CALnO6CBdZT3nVco+AREz-SVj7QG2P3Q24Hov_HJ2rAzELFm10A@mail.gmail.com","threadId":"66105","inReplyTo":"CABPp-BEAtpT208afwSNoBbR-Nowss8OsLsL8ynETuBfN_xvWag@mail.gmail.com","subject":"Re: [PATCH v4 2/3] completion: complete tracked paths for 'git diff'","fromName":"D. Ben Knoble","fromEmail":"ben.knoble@gmail.com","sentAt":"2026-08-07T11:02:40Z","receivedAt":"2026-08-07T11:02:52Z","isPatch":true,"body":"On Fri, Aug 7, 2026 at 2:18 AM Elijah Newren <newren@gmail.com> wrote:\n>\n> On Thu, Aug 6, 2026 at 6:38 PM Junio C Hamano <gitster@pobox.com> wrote:\n> >\n> > When completing arguments for 'git diff', _git_diff() delegates to\n> > __git_complete_revlist_file(), which only completes revision\n> > references.  This is good [*], as mixing both revisions and paths in a\n> > single list for the user to pick from is simply too confusing.\n> >\n> > If no reference matches, or if '--' is given, however, _git_diff()\n> > leaves COMPREPLY empty.  Bash then falls back to default filename\n> > completion in $PWD.  This fails when 'git -C <path>' is used because\n> > $PWD is not the target repository.\n> >\n> > Update _git_diff() to use __git_complete_index_file() when '--' is\n> > present, or when revision reference completion yields no matching\n> > candidates, so that tracked paths are offered as candidates.\n> >\n> > This changes behavior even in the case where '-C <there>' is not\n> > used.  The new behavior omits untracked paths from suggestions when\n> > no revs match the prefix but matching tracked paths exist, which is\n> > more useful in the context of 'git diff'.\n>\n> I'm looking forward to using this.  :-)\n>\n> [...]\n> > diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\n> > index ccd3b2a372..845fd19f70 100644\n> > --- a/contrib/completion/git-completion.bash\n> > +++ b/contrib/completion/git-completion.bash\n> > @@ -1981,6 +1981,10 @@ _git_diff ()\n> >                 esac\n> >                 __git_complete_revlist_file\n> >         fi\n> > +\n> > +       if [ ${#COMPREPLY[@]} -eq 0 ]; then\n> > +               __git_complete_index_file\n> > +       fi\n> >  }\n>\n> Curious; __git_complete_index_file() is documented as \"requires 1\n> argument\", but you pass none here.  As far as I can tell, it works\n> anyway, but feels like an accident:\n>\n> 1.   __git_complete_index_file CALLS\n>       __git_index_files \"$1\" ...\n>       (Here, \"$1\" == \"\")\n> 2.   __git_index_files \"$1\" ... CALLS\n>       __git_ls_files_helper \"$root\" \"$1\" ...\n>       (Here, \"$1\" == \"\", again)\n> 3.   __git_ls_files_helper \"$root\" \"$1\" CALLS\n>       __git -C \"$1\" -c core.quotePath=false ls-files\n> --exclude-standard $2 -- ...\n>       (Note that $2 is unquoted, and since it's empty, it disappears)\n>\n> It seems like it'd be better to pass an explicit \"\" to\n> __git_complete_index_file than to implicitly get it.\n\nGood spot. All the other callers pass an argument.\n\n-- \nD. Ben Knoble\n"},{"id":"549982","messageId":"CALnO6CBDqCnYX4ph-BsDEr+TEs+oRbbXKb6rjYmbAaV_6H=HiQ@mail.gmail.com","threadId":"66105","inReplyTo":"CABPp-BE0WOD1jRT-fVqxP0uaVuV0-Qu4rqCyL63vaRCEiAUmAA@mail.gmail.com","subject":"Re: [PATCH v4 0/3] completion of 'git [-C <dir>] diff'","fromName":"D. Ben Knoble","fromEmail":"ben.knoble@gmail.com","sentAt":"2026-08-07T11:05:53Z","receivedAt":"2026-08-07T11:06:05Z","isPatch":true,"body":"On Fri, Aug 7, 2026 at 2:32 AM Elijah Newren <newren@gmail.com> wrote:\n>\n> On Thu, Aug 6, 2026 at 6:38 PM Junio C Hamano <gitster@pobox.com> wrote:\n> >\n> > The primary motivation for this topic is that the command-line\n> > completion of 'git diff' does not handle paths (unlike 'git status'\n> > and 'git add') and instead relies on the default behavior of Bash\n> > command-line completion, which completes files in $PWD; this does\n> > not work at all with the '-C <directory>' option.\n> >\n> > This series teaches the completion machinery to complete revisions\n> > (unless '--' exists), then tracked paths, and then untracked paths,\n> > before letting the Bash default kick in.  This way, we correctly\n> > complete 'git diff' command line even when '-C <directory>' is in\n> > effect.\n>\n> Nice.  :-)\n>\n> > The tests are the only changes relative to v2.  In the step where\n> > tracked paths are completed, v2 did not demonstrate that untracked\n> > ones are *not* completed at the same time.  Now we do by having\n> > untracked 'file3' next to 'file1' and 'file2' that are tracked.  In\n> > the last step, we demonstrate untracked paths that do not share\n> > prefix with refs or tracked paths are completed, with or without the\n> > \"-C <dir>\" option.\n>\n> I found this paragraph slightly hard to parse.  I think this means the same as:\n>\n> Only the tests changed since v2.  The tracked-paths step now also\n> shows that untracked paths are not mixed in: an untracked 'file3'\n> next to tracked 'file1'/'file2' means \"git diff f\" completes only the\n> latter two.  The untracked-paths step shows that an untracked path\n> which shares no prefix with a ref or tracked path still completes,\n> with or without \"-C \".\n>\n> (Not that it matters, since this is just the cover letter, but...)\n>\n> >  1/3: completion: no-op refactoring of diff completion\n> >  2/3: completion: complete tracked paths for 'git diff'\n> >  3/3: completion: 'git diff' completes untracked paths as a last\n> >         resort\n>\n> I found two minor nits in the first two patches, and didn't spot any\n> issues with the third patch.\n\nYep, modulo Elijah's comments (many eyes, thanks!) I'm happy with this round.\n\n-- \nD. Ben Knoble\n"},{"id":"550010","messageId":"xmqqpkzuezmz.fsf@gitster.g","threadId":"66105","inReplyTo":"CABPp-BHhNHBN0Mt0LVjGEcNL_y7mad7rS6NRX6p14ELrRD-+bg@mail.gmail.com","subject":"Re: [PATCH v4 1/3] completion: no-op refactoring of diff completion","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-08-07T15:09:08Z","receivedAt":"2026-08-07T15:09:11Z","isPatch":true,"body":"Elijah Newren <newren@gmail.com> writes:\n\n> On Thu, Aug 6, 2026 at 6:38 PM Junio C Hamano <gitster@pobox.com> wrote:\n>>\n>> The \"git diff\" completion function punts very early when it sees\n>> \"--\" on the command line, since it is a sign that options or\n>> revisions can appear and the current completion does not need to do\n>> anything \"git diff\" specific. By returning, it lets Bash default\n>> action that completes the names of the files in $PWD to kick in.\n>>\n>> In preparation for the next step to change what happens when we\n>> \"punt\", arrange the code flow to avoid this early return.  The\n>> behaviour at this step is unchanged, but the control flow just\n>> falls straight to the end.\n>>\n>> Signed-off-by: Junio C Hamano <gitster@pobox.com>\n>> ---\n>>  contrib/completion/git-completion.bash | 61 ++++++++++++++------------\n>>  1 file changed, 33 insertions(+), 28 deletions(-)\n>>\n>> diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\n>> index e875787710..ccd3b2a372 100644\n>> --- a/contrib/completion/git-completion.bash\n>> +++ b/contrib/completion/git-completion.bash\n>> @@ -1947,35 +1947,40 @@ __git_diff_difftool_options=\"--cached --staged\n>>\n>>  _git_diff ()\n>>  {\n> [...]\n>> +       if ! __git_has_doubledash; then\n>> +               case \"$cur\" in\n>> +               --diff-algorithm=*)\n>> +                       __gitcomp \"$__git_diff_algorithms\" \\\n>> +                               \"\" \"${cur##--diff-algorithm=}\"\n>> +                       return\n>>                 ;;\n>\n> The refactor in this commit is a faithful no-op -- every arm got\n> re-indented by one tab as expected.  One tiny slip, though: this first\n> case's \";;\" didn't get the extra tab that every other arm received.\n\nGood eyes.  Will fix.\n"},{"id":"550012","messageId":"xmqqldaiezgd.fsf@gitster.g","threadId":"66105","inReplyTo":"CABPp-BEAtpT208afwSNoBbR-Nowss8OsLsL8ynETuBfN_xvWag@mail.gmail.com","subject":"Re: [PATCH v4 2/3] completion: complete tracked paths for 'git diff'","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-08-07T15:13:06Z","receivedAt":"2026-08-07T15:13:09Z","isPatch":true,"body":"Elijah Newren <newren@gmail.com> writes:\n\n> On Thu, Aug 6, 2026 at 6:38 PM Junio C Hamano <gitster@pobox.com> wrote:\n>>\n>> When completing arguments for 'git diff', _git_diff() delegates to\n>> __git_complete_revlist_file(), which only completes revision\n>> references.  This is good [*], as mixing both revisions and paths in a\n>> single list for the user to pick from is simply too confusing.\n>>\n>> If no reference matches, or if '--' is given, however, _git_diff()\n>> leaves COMPREPLY empty.  Bash then falls back to default filename\n>> completion in $PWD.  This fails when 'git -C <path>' is used because\n>> $PWD is not the target repository.\n>>\n>> Update _git_diff() to use __git_complete_index_file() when '--' is\n>> present, or when revision reference completion yields no matching\n>> candidates, so that tracked paths are offered as candidates.\n>>\n>> This changes behavior even in the case where '-C <there>' is not\n>> used.  The new behavior omits untracked paths from suggestions when\n>> no revs match the prefix but matching tracked paths exist, which is\n>> more useful in the context of 'git diff'.\n>\n> I'm looking forward to using this.  :-)\n>\n> [...]\n>> diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\n>> index ccd3b2a372..845fd19f70 100644\n>> --- a/contrib/completion/git-completion.bash\n>> +++ b/contrib/completion/git-completion.bash\n>> @@ -1981,6 +1981,10 @@ _git_diff ()\n>>                 esac\n>>                 __git_complete_revlist_file\n>>         fi\n>> +\n>> +       if [ ${#COMPREPLY[@]} -eq 0 ]; then\n>> +               __git_complete_index_file\n>> +       fi\n>>  }\n>\n> Curious; __git_complete_index_file() is documented as \"requires 1\n> argument\", but you pass none here.  As far as I can tell, it works\n> anyway, but feels like an accident:\n>\n> 1.   __git_complete_index_file CALLS\n>       __git_index_files \"$1\" ...\n>       (Here, \"$1\" == \"\")\n> 2.   __git_index_files \"$1\" ... CALLS\n>       __git_ls_files_helper \"$root\" \"$1\" ...\n>       (Here, \"$1\" == \"\", again)\n> 3.   __git_ls_files_helper \"$root\" \"$1\" CALLS\n>       __git -C \"$1\" -c core.quotePath=false ls-files\n> --exclude-standard $2 -- ...\n>       (Note that $2 is unquoted, and since it's empty, it disappears)\n>\n> It seems like it'd be better to pass an explicit \"\" to\n> __git_complete_index_file than to implicitly get it.\n\nOK.  It feels a bit strange as an API for the function to insist\ntaking one and only one option, which forces the caller to do\n\n\t__git_complete_index_file \"--cached --others --directory\"\n\nwhen the intention clearly is \"we take zero or more options that we\npass to ls-files\", which would have been more obvious if the above\nwere written as three separate parameters, but I'll do as Romans in\nthe (hopefully small and final) reroll.\n\nThanks.\n"},{"id":"550015","messageId":"CABPp-BFmrqdEP1AQx1mB50LX1q63c9cgMeOsVeUZfzgLUA9wLg@mail.gmail.com","threadId":"66105","inReplyTo":"xmqqldaiezgd.fsf@gitster.g","subject":"Re: [PATCH v4 2/3] completion: complete tracked paths for 'git diff'","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2026-08-07T15:22:45Z","receivedAt":"2026-08-07T15:22:58Z","isPatch":true,"body":"On Fri, Aug 7, 2026 at 8:13 AM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Elijah Newren <newren@gmail.com> writes:\n>\n> > On Thu, Aug 6, 2026 at 6:38 PM Junio C Hamano <gitster@pobox.com> wrote:\n> >>\n> >> When completing arguments for 'git diff', _git_diff() delegates to\n> >> __git_complete_revlist_file(), which only completes revision\n> >> references.  This is good [*], as mixing both revisions and paths in a\n> >> single list for the user to pick from is simply too confusing.\n> >>\n> >> If no reference matches, or if '--' is given, however, _git_diff()\n> >> leaves COMPREPLY empty.  Bash then falls back to default filename\n> >> completion in $PWD.  This fails when 'git -C <path>' is used because\n> >> $PWD is not the target repository.\n> >>\n> >> Update _git_diff() to use __git_complete_index_file() when '--' is\n> >> present, or when revision reference completion yields no matching\n> >> candidates, so that tracked paths are offered as candidates.\n> >>\n> >> This changes behavior even in the case where '-C <there>' is not\n> >> used.  The new behavior omits untracked paths from suggestions when\n> >> no revs match the prefix but matching tracked paths exist, which is\n> >> more useful in the context of 'git diff'.\n> >\n> > I'm looking forward to using this.  :-)\n> >\n> > [...]\n> >> diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\n> >> index ccd3b2a372..845fd19f70 100644\n> >> --- a/contrib/completion/git-completion.bash\n> >> +++ b/contrib/completion/git-completion.bash\n> >> @@ -1981,6 +1981,10 @@ _git_diff ()\n> >>                 esac\n> >>                 __git_complete_revlist_file\n> >>         fi\n> >> +\n> >> +       if [ ${#COMPREPLY[@]} -eq 0 ]; then\n> >> +               __git_complete_index_file\n> >> +       fi\n> >>  }\n> >\n> > Curious; __git_complete_index_file() is documented as \"requires 1\n> > argument\", but you pass none here.  As far as I can tell, it works\n> > anyway, but feels like an accident:\n> >\n> > 1.   __git_complete_index_file CALLS\n> >       __git_index_files \"$1\" ...\n> >       (Here, \"$1\" == \"\")\n> > 2.   __git_index_files \"$1\" ... CALLS\n> >       __git_ls_files_helper \"$root\" \"$1\" ...\n> >       (Here, \"$1\" == \"\", again)\n> > 3.   __git_ls_files_helper \"$root\" \"$1\" CALLS\n> >       __git -C \"$1\" -c core.quotePath=false ls-files\n> > --exclude-standard $2 -- ...\n> >       (Note that $2 is unquoted, and since it's empty, it disappears)\n> >\n> > It seems like it'd be better to pass an explicit \"\" to\n> > __git_complete_index_file than to implicitly get it.\n>\n> OK.  It feels a bit strange as an API for the function to insist\n> taking one and only one option, which forces the caller to do\n>\n>         __git_complete_index_file \"--cached --others --directory\"\n>\n> when the intention clearly is \"we take zero or more options that we\n> pass to ls-files\", which would have been more obvious if the above\n> were written as three separate parameters, but I'll do as Romans in\n> the (hopefully small and final) reroll.\n>\n> Thanks.\n\nYeah, I don't disagree.  I would be equally happy with an update to\nthe __git_complete_index_file function to change the comment and\nexplain what the first argument, if given, means (making it clear that\nno arguments are okay); but without either that or having your new\ncaller pass an argument, the inconsistency between the documentation\nand this new caller felt like an issue someone might trip over in the\nfuture.\n"},{"id":"550028","messageId":"20260807161956.1004889-1-gitster@pobox.com","threadId":"66105","inReplyTo":"xmqqcxw010me.fsf@gitster.g","subject":"[PATCH v5 0/3] completion of 'git [-C <dir>] diff'","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-08-07T16:19:53Z","receivedAt":"2026-08-07T16:19:58Z","isPatch":true,"body":"The primary motivation for this topic is that the command-line\ncompletion of 'git diff' does not handle paths (unlike 'git status'\nand 'git add') and instead relies on the default behavior of Bash\ncommand-line completion, which completes files in $PWD; this does\nnot work at all with the '-C <directory>' option.\n\nThis series teaches the completion machinery to complete revisions\n(unless '--' exists), then tracked paths, and then untracked paths,\nbefore letting the Bash default kick in.  This way, we correctly\ncomplete 'git diff' command line even when '-C <directory>' is in\neffect.\n\nThe v5 iteration addresses two points identified by Elijah in v4.\nHopefully this will be the \"small and final\" reroll.\n\n 1/3: completion: no-op refactoring of diff completion\n 2/3: completion: complete tracked paths for 'git diff'\n 3/3: completion: 'git diff' completes untracked paths as a last\n        resort\n\n contrib/completion/git-completion.bash | 69 +++++++++++++++-----------\n t/t9902-completion.sh                  | 59 ++++++++++++++++++++++\n 2 files changed, 100 insertions(+), 28 deletions(-)\n\nRange-diff against v4:\n1:  d3c51c042c ! 1:  8295035d13 completion: no-op refactoring of diff completion\n    @@ contrib/completion/git-completion.bash: __git_diff_difftool_options=\"--cached --\n     -\t--*)\n     -\t\t__gitcomp \"$__git_diff_difftool_options\"\n     -\t\treturn\n    +-\t\t;;\n    +-\tesac\n    +-\t__git_complete_revlist_file\n     +\tif ! __git_has_doubledash; then\n     +\t\tcase \"$cur\" in\n     +\t\t--diff-algorithm=*)\n     +\t\t\t__gitcomp \"$__git_diff_algorithms\" \\\n     +\t\t\t\t\"\" \"${cur##--diff-algorithm=}\"\n     +\t\t\treturn\n    - \t\t;;\n    --\tesac\n    --\t__git_complete_revlist_file\n    ++\t\t\t;;\n     +\t\t--submodule=*)\n     +\t\t\t__gitcomp \"$__git_diff_submodule_formats\" \\\n     +\t\t\t\t\"\" \"${cur##--submodule=}\"\n2:  c3658d6ca2 ! 2:  dbb14298c1 completion: complete tracked paths for 'git diff'\n    @@ contrib/completion/git-completion.bash: _git_diff ()\n      \tfi\n     +\n     +\tif [ ${#COMPREPLY[@]} -eq 0 ]; then\n    -+\t\t__git_complete_index_file\n    ++\t\t__git_complete_index_file \"\"\n     +\tfi\n      }\n      \n3:  ba5dc6f164 ! 3:  d661a1a5dd completion: 'git diff' completes untracked paths as a last resort\n    @@ Commit message\n      ## contrib/completion/git-completion.bash ##\n     @@ contrib/completion/git-completion.bash: _git_diff ()\n      \tif [ ${#COMPREPLY[@]} -eq 0 ]; then\n    - \t\t__git_complete_index_file\n    + \t\t__git_complete_index_file \"\"\n      \tfi\n     +\n     +\tif [ ${#COMPREPLY[@]} -eq 0 ]; then\n-- \n2.55.0-655-g8b87133eb9\n\n"},{"id":"550029","messageId":"20260807161956.1004889-2-gitster@pobox.com","threadId":"66105","inReplyTo":"20260807161956.1004889-1-gitster@pobox.com","subject":"[PATCH v5 1/3] completion: no-op refactoring of diff completion","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-08-07T16:19:54Z","receivedAt":"2026-08-07T16:20:00Z","isPatch":true,"body":"The \"git diff\" completion function punts very early when it sees\n\"--\" on the command line, since it is a sign that options or\nrevisions can appear and the current completion does not need to do\nanything \"git diff\" specific. By returning, it lets Bash default\naction that completes the names of the files in $PWD to kick in.\n\nIn preparation for the next step to change what happens when we\n\"punt\", arrange the code flow to avoid this early return.  The\nbehaviour at this step is unchanged, but the control flow just\nfalls straight to the end.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n contrib/completion/git-completion.bash | 63 ++++++++++++++------------\n 1 file changed, 34 insertions(+), 29 deletions(-)\n\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex e875787710..a61b6ed59a 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -1947,35 +1947,40 @@ __git_diff_difftool_options=\"--cached --staged\n \n _git_diff ()\n {\n-\t__git_has_doubledash && return\n-\n-\tcase \"$cur\" in\n-\t--diff-algorithm=*)\n-\t\t__gitcomp \"$__git_diff_algorithms\" \"\" \"${cur##--diff-algorithm=}\"\n-\t\treturn\n-\t\t;;\n-\t--submodule=*)\n-\t\t__gitcomp \"$__git_diff_submodule_formats\" \"\" \"${cur##--submodule=}\"\n-\t\treturn\n-\t\t;;\n-\t--color-moved=*)\n-\t\t__gitcomp \"$__git_color_moved_opts\" \"\" \"${cur##--color-moved=}\"\n-\t\treturn\n-\t\t;;\n-\t--color-moved-ws=*)\n-\t\t__gitcomp \"$__git_color_moved_ws_opts\" \"\" \"${cur##--color-moved-ws=}\"\n-\t\treturn\n-\t\t;;\n-\t--ws-error-highlight=*)\n-\t\t__gitcomp \"$__git_ws_error_highlight_opts\" \"\" \"${cur##--ws-error-highlight=}\"\n-\t\treturn\n-\t\t;;\n-\t--*)\n-\t\t__gitcomp \"$__git_diff_difftool_options\"\n-\t\treturn\n-\t\t;;\n-\tesac\n-\t__git_complete_revlist_file\n+\tif ! __git_has_doubledash; then\n+\t\tcase \"$cur\" in\n+\t\t--diff-algorithm=*)\n+\t\t\t__gitcomp \"$__git_diff_algorithms\" \\\n+\t\t\t\t\"\" \"${cur##--diff-algorithm=}\"\n+\t\t\treturn\n+\t\t\t;;\n+\t\t--submodule=*)\n+\t\t\t__gitcomp \"$__git_diff_submodule_formats\" \\\n+\t\t\t\t\"\" \"${cur##--submodule=}\"\n+\t\t\treturn\n+\t\t\t;;\n+\t\t--color-moved=*)\n+\t\t\t__gitcomp \"$__git_color_moved_opts\" \\\n+\t\t\t\t\"\" \"${cur##--color-moved=}\"\n+\t\t\treturn\n+\t\t\t;;\n+\t\t--color-moved-ws=*)\n+\t\t\t__gitcomp \"$__git_color_moved_ws_opts\" \\\n+\t\t\t\t\"\" \"${cur##--color-moved-ws=}\"\n+\t\t\treturn\n+\t\t\t;;\n+\t\t--ws-error-highlight=*)\n+\t\t\t__gitcomp \"$__git_ws_error_highlight_opts\" \\\n+\t\t\t\t\"\" \"${cur##--ws-error-highlight=}\"\n+\t\t\treturn\n+\t\t\t;;\n+\t\t--*)\n+\t\t\t__gitcomp \"$__git_diff_difftool_options\"\n+\t\t\treturn\n+\t\t\t;;\n+\t\tesac\n+\t\t__git_complete_revlist_file\n+\tfi\n }\n \n __git_mergetools_common=\"diffuse diffmerge ecmerge emerge kdiff3 meld opendiff\n-- \n2.55.0-655-g8b87133eb9\n\n"},{"id":"550030","messageId":"20260807161956.1004889-3-gitster@pobox.com","threadId":"66105","inReplyTo":"20260807161956.1004889-1-gitster@pobox.com","subject":"[PATCH v5 2/3] completion: complete tracked paths for 'git diff'","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-08-07T16:19:55Z","receivedAt":"2026-08-07T16:20:06Z","isPatch":true,"body":"When completing arguments for 'git diff', _git_diff() delegates to\n__git_complete_revlist_file(), which only completes revision\nreferences.  This is good [*], as mixing both revisions and paths in a\nsingle list for the user to pick from is simply too confusing.\n\nIf no reference matches, or if '--' is given, however, _git_diff()\nleaves COMPREPLY empty.  Bash then falls back to default filename\ncompletion in $PWD.  This fails when 'git -C <path>' is used because\n$PWD is not the target repository.\n\nUpdate _git_diff() to use __git_complete_index_file() when '--' is\npresent, or when revision reference completion yields no matching\ncandidates, so that tracked paths are offered as candidates.\n\nThis changes behavior even in the case where '-C <there>' is not\nused.  The new behavior omits untracked paths from suggestions when\nno revs match the prefix but matching tracked paths exist, which is\nmore useful in the context of 'git diff'.\n\nWhen run outside the working tree of a repository, or when nothing\nmatches from revisions or tracked paths, Bash still falls back to\ndefault filename completion in $PWD, so such a use case would be\njust like completing paths for any 'diff' command, rather than for\n'git diff'.\n\n[Footnote]\n * In https://lore.kernel.org/git/al%2Fw2qgBfhe9qMg6@szeder.dev/\n   SZEDER made the same argument for \"git send-email 0<TAB>\".\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n contrib/completion/git-completion.bash |  4 +++\n t/t9902-completion.sh                  | 39 ++++++++++++++++++++++++++\n 2 files changed, 43 insertions(+)\n\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex a61b6ed59a..76181e8714 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -1981,6 +1981,10 @@ _git_diff ()\n \t\tesac\n \t\t__git_complete_revlist_file\n \tfi\n+\n+\tif [ ${#COMPREPLY[@]} -eq 0 ]; then\n+\t\t__git_complete_index_file \"\"\n+\tfi\n }\n \n __git_mergetools_common=\"diffuse diffmerge ecmerge emerge kdiff3 meld opendiff\ndiff --git a/t/t9902-completion.sh b/t/t9902-completion.sh\nindex 9ae3c48ebd..55361a89e1 100755\n--- a/t/t9902-completion.sh\n+++ b/t/t9902-completion.sh\n@@ -2712,6 +2712,45 @@ test_expect_success 'git -C <path> checkout uses the right repo' '\n \tEOF\n '\n \n+test_expect_success 'git diff completes tracked paths when no refs match' '\n+\t# file1 and file2 are tracked but file3 is not\n+\ttest_completion \"git diff f\" <<-\\EOF\n+\tfile1\n+\tfile2\n+\tEOF\n+'\n+\n+test_expect_success 'git diff -- completes tracked paths' '\n+\t# file1 and file2 are tracked but file3 is not\n+\ttest_completion \"git diff -- f\" <<-\\EOF\n+\tfile1\n+\tfile2\n+\tEOF\n+'\n+\n+test_expect_success 'git -C <path> diff completes tracked paths in specified repo' '\n+\ttest_when_finished \"rm -rf repo-for-diff\" &&\n+\tgit init repo-for-diff &&\n+\techo content >repo-for-diff/otherfile &&\n+\tgit -C repo-for-diff add otherfile &&\n+\techo untracked >repo-for-diff/oops &&\n+\tgit -C repo-for-diff commit -m otherfile &&\n+\ttest_completion \"git -C repo-for-diff diff o\" <<-\\EOF\n+\totherfile\n+\tEOF\n+'\n+\n+test_expect_success 'git -C <path> diff -- completes pathspecs in specified repo' '\n+\ttest_when_finished \"rm -rf repo-for-diff\" &&\n+\tgit init repo-for-diff &&\n+\techo content >repo-for-diff/otherfile &&\n+\tgit -C repo-for-diff add otherfile &&\n+\tgit -C repo-for-diff commit -m otherfile &&\n+\ttest_completion \"git -C repo-for-diff diff -- o\" <<-\\EOF\n+\totherfile\n+\tEOF\n+'\n+\n test_expect_success 'show completes all refs' '\n \ttest_completion \"git show m\" <<-\\EOF\n \tmain Z\n-- \n2.55.0-655-g8b87133eb9\n\n"},{"id":"550031","messageId":"20260807161956.1004889-4-gitster@pobox.com","threadId":"66105","inReplyTo":"20260807161956.1004889-1-gitster@pobox.com","subject":"[PATCH v5 3/3] completion: 'git diff' completes untracked paths as a last resort","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-08-07T16:19:56Z","receivedAt":"2026-08-07T16:20:07Z","isPatch":true,"body":"We taught 'git diff' to first try to complete revisions (unless '--'\nis present on the command line) and, failing that, to complete\ntracked paths.  If this yields nothing, it lets the Bash default,\nwhich offers paths in $PWD, kick in.\n\nTeach it to complete untracked paths before giving up and letting\nthe Bash default kick in.  With this change,\n\n    $ git -C another-directory diff un<TAB>\n\nfinds the 'untracked' file in another-directory and offers it as a\ncompletion candidate.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n contrib/completion/git-completion.bash |  4 ++++\n t/t9902-completion.sh                  | 22 +++++++++++++++++++++-\n 2 files changed, 25 insertions(+), 1 deletion(-)\n\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex 76181e8714..d35b4f3024 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -1985,6 +1985,10 @@ _git_diff ()\n \tif [ ${#COMPREPLY[@]} -eq 0 ]; then\n \t\t__git_complete_index_file \"\"\n \tfi\n+\n+\tif [ ${#COMPREPLY[@]} -eq 0 ]; then\n+\t\t__git_complete_index_file \"--others --directory\"\n+\tfi\n }\n \n __git_mergetools_common=\"diffuse diffmerge ecmerge emerge kdiff3 meld opendiff\ndiff --git a/t/t9902-completion.sh b/t/t9902-completion.sh\nindex 55361a89e1..85f92c552d 100755\n--- a/t/t9902-completion.sh\n+++ b/t/t9902-completion.sh\n@@ -2663,6 +2663,7 @@ test_expect_success 'setup for integration tests' '\n \techo content >file1 &&\n \techo more >file2 &&\n \tgit add file1 file2 &&\n+\techo untracked >ufile &&\n \tgit commit -m one &&\n \tgit branch mybranch &&\n \tgit tag mytag\n@@ -2728,6 +2729,15 @@ test_expect_success 'git diff -- completes tracked paths' '\n \tEOF\n '\n \n+test_expect_success 'git diff [--] completes untracked paths, too' '\n+\ttest_completion \"git diff u\" <<-\\EOF &&\n+\tufile\n+\tEOF\n+\ttest_completion \"git diff -- u\" <<-\\EOF\n+\tufile\n+\tEOF\n+'\n+\n test_expect_success 'git -C <path> diff completes tracked paths in specified repo' '\n \ttest_when_finished \"rm -rf repo-for-diff\" &&\n \tgit init repo-for-diff &&\n@@ -2744,11 +2754,21 @@ test_expect_success 'git -C <path> diff -- completes pathspecs in specified repo\n \ttest_when_finished \"rm -rf repo-for-diff\" &&\n \tgit init repo-for-diff &&\n \techo content >repo-for-diff/otherfile &&\n+\techo untracked >repo-for-diff/untracked &&\n \tgit -C repo-for-diff add otherfile &&\n \tgit -C repo-for-diff commit -m otherfile &&\n-\ttest_completion \"git -C repo-for-diff diff -- o\" <<-\\EOF\n+\ttest_completion \"git -C repo-for-diff diff o\" <<-\\EOF &&\n+\totherfile\n+\tEOF\n+\ttest_completion \"git -C repo-for-diff diff -- o\" <<-\\EOF &&\n \totherfile\n \tEOF\n+\ttest_completion \"git -C repo-for-diff diff u\" <<-\\EOF &&\n+\tuntracked\n+\tEOF\n+\ttest_completion \"git -C repo-for-diff diff -- u\" <<-\\EOF\n+\tuntracked\n+\tEOF\n '\n \n test_expect_success 'show completes all refs' '\n-- \n2.55.0-655-g8b87133eb9\n\n"},{"id":"550035","messageId":"CABPp-BFdwxTvkcWKNP0-Lk+mqQLnULoYuLHZe0aot4VYfHnjSw@mail.gmail.com","threadId":"66105","inReplyTo":"20260807161956.1004889-1-gitster@pobox.com","subject":"Re: [PATCH v5 0/3] completion of 'git [-C <dir>] diff'","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2026-08-07T16:53:24Z","receivedAt":"2026-08-07T16:53:37Z","isPatch":true,"body":"On Fri, Aug 7, 2026 at 9:19 AM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> The primary motivation for this topic is that the command-line\n> completion of 'git diff' does not handle paths (unlike 'git status'\n> and 'git add') and instead relies on the default behavior of Bash\n> command-line completion, which completes files in $PWD; this does\n> not work at all with the '-C <directory>' option.\n>\n> This series teaches the completion machinery to complete revisions\n> (unless '--' exists), then tracked paths, and then untracked paths,\n> before letting the Bash default kick in.  This way, we correctly\n> complete 'git diff' command line even when '-C <directory>' is in\n> effect.\n>\n> The v5 iteration addresses two points identified by Elijah in v4.\n> Hopefully this will be the \"small and final\" reroll.\n\nLooks good to me!\n"},{"id":"550419","messageId":"20260812162551.2229680-1-gitster@pobox.com","threadId":"66105","inReplyTo":"xmqqcxw010me.fsf@gitster.g","subject":"[PATCH v6 0/3] completion of 'git [-C <dir>] diff'","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-08-12T16:25:48Z","receivedAt":"2026-08-12T16:25:54Z","isPatch":true,"body":"The primary motivation for this topic is that the command-line\ncompletion of 'git diff' does not handle paths (unlike 'git status'\nand 'git add') and instead relies on the default behavior of Bash\ncommand-line completion, which completes files in $PWD; this does\nnot work at all with the '-C <directory>' option.\n\nThis series teaches the completion machinery to complete revisions\n(unless '--' exists), then tracked paths, and then untracked paths,\nbefore letting the Bash default kick in.  This way, we correctly\ncomplete 'git diff' command line even when '-C <directory>' is in\neffect.\n\nThe changes since v5 are all concentrated in the tests.  They test\nthe same thing, but organized in a more logical and regular way.\n\nThis round hopefully will be the last one (famous last words).\n\n 1/3: completion: no-op refactoring of diff completion\n 2/3: completion: complete tracked paths for 'git diff'\n 3/3: completion: 'git diff' completes untracked paths as a last\n        resort\n\n contrib/completion/git-completion.bash | 69 +++++++++++++++-----------\n t/t9902-completion.sh                  | 58 ++++++++++++++++++++++\n 2 files changed, 99 insertions(+), 28 deletions(-)\n\nRange-diff against v5:\n1:  74bfe67e24 = 1:  a5b2bdcd21 completion: no-op refactoring of diff completion\n2:  f6892a66e9 ! 2:  cb84c1407c completion: complete tracked paths for 'git diff'\n    @@ contrib/completion/git-completion.bash: _git_diff ()\n      __git_mergetools_common=\"diffuse diffmerge ecmerge emerge kdiff3 meld opendiff\n     \n      ## t/t9902-completion.sh ##\n    +@@ t/t9902-completion.sh: test_expect_success 'setup for integration tests' '\n    + \techo content >file1 &&\n    + \techo more >file2 &&\n    + \tgit add file1 file2 &&\n    ++\techo untracked >file3 &&\n    + \tgit commit -m one &&\n    + \tgit branch mybranch &&\n    + \tgit tag mytag\n     @@ t/t9902-completion.sh: test_expect_success 'git -C <path> checkout uses the right repo' '\n      \tEOF\n      '\n      \n     +test_expect_success 'git diff completes tracked paths when no refs match' '\n     +\t# file1 and file2 are tracked but file3 is not\n    -+\ttest_completion \"git diff f\" <<-\\EOF\n    ++\t# there is no ref that begins with f\n    ++\ttest_completion \"git diff f\" <<-\\EOF &&\n     +\tfile1\n     +\tfile2\n     +\tEOF\n    -+'\n    -+\n    -+test_expect_success 'git diff -- completes tracked paths' '\n    -+\t# file1 and file2 are tracked but file3 is not\n     +\ttest_completion \"git diff -- f\" <<-\\EOF\n     +\tfile1\n     +\tfile2\n     +\tEOF\n     +'\n     +\n    -+test_expect_success 'git -C <path> diff completes tracked paths in specified repo' '\n    ++test_expect_success 'git -C <path> diff completes paths in specified repo' '\n     +\ttest_when_finished \"rm -rf repo-for-diff\" &&\n     +\tgit init repo-for-diff &&\n     +\techo content >repo-for-diff/otherfile &&\n    ++\techo content >repo-for-diff/lostfile &&\n     +\tgit -C repo-for-diff add otherfile &&\n    -+\techo untracked >repo-for-diff/oops &&\n    ++\tgit -C repo-for-diff add lostfile &&\n     +\tgit -C repo-for-diff commit -m otherfile &&\n    -+\ttest_completion \"git -C repo-for-diff diff o\" <<-\\EOF\n    ++\techo untracked >repo-for-diff/oops &&\n    ++\trm -f repo-for-diff/lostfile &&\n    ++\n    ++\ttest_completion \"git -C repo-for-diff diff o\" <<-\\EOF &&\n     +\totherfile\n     +\tEOF\n    -+'\n    ++\ttest_completion \"git -C repo-for-diff diff l\" <<-\\EOF &&\n    ++\tlostfile\n    ++\tEOF\n     +\n    -+test_expect_success 'git -C <path> diff -- completes pathspecs in specified repo' '\n    -+\ttest_when_finished \"rm -rf repo-for-diff\" &&\n    -+\tgit init repo-for-diff &&\n    -+\techo content >repo-for-diff/otherfile &&\n    -+\tgit -C repo-for-diff add otherfile &&\n    -+\tgit -C repo-for-diff commit -m otherfile &&\n    -+\ttest_completion \"git -C repo-for-diff diff -- o\" <<-\\EOF\n    ++\ttest_completion \"git -C repo-for-diff diff -- o\" <<-\\EOF &&\n     +\totherfile\n     +\tEOF\n    ++\ttest_completion \"git -C repo-for-diff diff -- l\" <<-\\EOF\n    ++\tlostfile\n    ++\tEOF\n     +'\n     +\n      test_expect_success 'show completes all refs' '\n3:  dcc5f881f2 ! 3:  d73c18e876 completion: 'git diff' completes untracked paths as a last resort\n    @@ contrib/completion/git-completion.bash: _git_diff ()\n     \n      ## t/t9902-completion.sh ##\n     @@ t/t9902-completion.sh: test_expect_success 'setup for integration tests' '\n    - \techo content >file1 &&\n      \techo more >file2 &&\n      \tgit add file1 file2 &&\n    + \techo untracked >file3 &&\n     +\techo untracked >ufile &&\n      \tgit commit -m one &&\n      \tgit branch mybranch &&\n      \tgit tag mytag\n    -@@ t/t9902-completion.sh: test_expect_success 'git diff -- completes tracked paths' '\n    +@@ t/t9902-completion.sh: test_expect_success 'git diff completes tracked paths when no refs match' '\n      \tEOF\n      '\n      \n     +test_expect_success 'git diff [--] completes untracked paths, too' '\n    ++\t# ufile is not tracked and there is no ref that begins with u\n     +\ttest_completion \"git diff u\" <<-\\EOF &&\n     +\tufile\n     +\tEOF\n    @@ t/t9902-completion.sh: test_expect_success 'git diff -- completes tracked paths'\n     +\tEOF\n     +'\n     +\n    - test_expect_success 'git -C <path> diff completes tracked paths in specified repo' '\n    + test_expect_success 'git -C <path> diff completes paths in specified repo' '\n      \ttest_when_finished \"rm -rf repo-for-diff\" &&\n      \tgit init repo-for-diff &&\n    -@@ t/t9902-completion.sh: test_expect_success 'git -C <path> diff -- completes pathspecs in specified repo\n    - \ttest_when_finished \"rm -rf repo-for-diff\" &&\n    - \tgit init repo-for-diff &&\n    - \techo content >repo-for-diff/otherfile &&\n    -+\techo untracked >repo-for-diff/untracked &&\n    - \tgit -C repo-for-diff add otherfile &&\n    +@@ t/t9902-completion.sh: test_expect_success 'git -C <path> diff completes paths in specified repo' '\n    + \tgit -C repo-for-diff add lostfile &&\n      \tgit -C repo-for-diff commit -m otherfile &&\n    --\ttest_completion \"git -C repo-for-diff diff -- o\" <<-\\EOF\n    -+\ttest_completion \"git -C repo-for-diff diff o\" <<-\\EOF &&\n    -+\totherfile\n    -+\tEOF\n    -+\ttest_completion \"git -C repo-for-diff diff -- o\" <<-\\EOF &&\n    - \totherfile\n    + \techo untracked >repo-for-diff/oops &&\n    ++\techo untracked >repo-for-diff/ufile &&\n    + \trm -f repo-for-diff/lostfile &&\n    + \n    + \ttest_completion \"git -C repo-for-diff diff o\" <<-\\EOF &&\n    +@@ t/t9902-completion.sh: test_expect_success 'git -C <path> diff completes paths in specified repo' '\n    + \ttest_completion \"git -C repo-for-diff diff l\" <<-\\EOF &&\n    + \tlostfile\n      \tEOF\n     +\ttest_completion \"git -C repo-for-diff diff u\" <<-\\EOF &&\n    -+\tuntracked\n    ++\tufile\n     +\tEOF\n    + \n    + \ttest_completion \"git -C repo-for-diff diff -- o\" <<-\\EOF &&\n    + \totherfile\n    + \tEOF\n    +-\ttest_completion \"git -C repo-for-diff diff -- l\" <<-\\EOF\n    ++\ttest_completion \"git -C repo-for-diff diff -- l\" <<-\\EOF &&\n    + \tlostfile\n    + \tEOF\n     +\ttest_completion \"git -C repo-for-diff diff -- u\" <<-\\EOF\n    -+\tuntracked\n    ++\tufile\n     +\tEOF\n      '\n      \n-- \n2.55.0-721-gd75157efe4\n\n"},{"id":"550420","messageId":"20260812162551.2229680-2-gitster@pobox.com","threadId":"66105","inReplyTo":"20260812162551.2229680-1-gitster@pobox.com","subject":"[PATCH v6 1/3] completion: no-op refactoring of diff completion","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-08-12T16:25:49Z","receivedAt":"2026-08-12T16:25:56Z","isPatch":true,"body":"The \"git diff\" completion function punts very early when it sees\n\"--\" on the command line, since it is a sign that options or\nrevisions can appear and the current completion does not need to do\nanything \"git diff\" specific. By returning, it lets Bash default\naction that completes the names of the files in $PWD to kick in.\n\nIn preparation for the next step to change what happens when we\n\"punt\", arrange the code flow to avoid this early return.  The\nbehaviour at this step is unchanged, but the control flow just\nfalls straight to the end.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n contrib/completion/git-completion.bash | 63 ++++++++++++++------------\n 1 file changed, 34 insertions(+), 29 deletions(-)\n\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex e875787710..a61b6ed59a 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -1947,35 +1947,40 @@ __git_diff_difftool_options=\"--cached --staged\n \n _git_diff ()\n {\n-\t__git_has_doubledash && return\n-\n-\tcase \"$cur\" in\n-\t--diff-algorithm=*)\n-\t\t__gitcomp \"$__git_diff_algorithms\" \"\" \"${cur##--diff-algorithm=}\"\n-\t\treturn\n-\t\t;;\n-\t--submodule=*)\n-\t\t__gitcomp \"$__git_diff_submodule_formats\" \"\" \"${cur##--submodule=}\"\n-\t\treturn\n-\t\t;;\n-\t--color-moved=*)\n-\t\t__gitcomp \"$__git_color_moved_opts\" \"\" \"${cur##--color-moved=}\"\n-\t\treturn\n-\t\t;;\n-\t--color-moved-ws=*)\n-\t\t__gitcomp \"$__git_color_moved_ws_opts\" \"\" \"${cur##--color-moved-ws=}\"\n-\t\treturn\n-\t\t;;\n-\t--ws-error-highlight=*)\n-\t\t__gitcomp \"$__git_ws_error_highlight_opts\" \"\" \"${cur##--ws-error-highlight=}\"\n-\t\treturn\n-\t\t;;\n-\t--*)\n-\t\t__gitcomp \"$__git_diff_difftool_options\"\n-\t\treturn\n-\t\t;;\n-\tesac\n-\t__git_complete_revlist_file\n+\tif ! __git_has_doubledash; then\n+\t\tcase \"$cur\" in\n+\t\t--diff-algorithm=*)\n+\t\t\t__gitcomp \"$__git_diff_algorithms\" \\\n+\t\t\t\t\"\" \"${cur##--diff-algorithm=}\"\n+\t\t\treturn\n+\t\t\t;;\n+\t\t--submodule=*)\n+\t\t\t__gitcomp \"$__git_diff_submodule_formats\" \\\n+\t\t\t\t\"\" \"${cur##--submodule=}\"\n+\t\t\treturn\n+\t\t\t;;\n+\t\t--color-moved=*)\n+\t\t\t__gitcomp \"$__git_color_moved_opts\" \\\n+\t\t\t\t\"\" \"${cur##--color-moved=}\"\n+\t\t\treturn\n+\t\t\t;;\n+\t\t--color-moved-ws=*)\n+\t\t\t__gitcomp \"$__git_color_moved_ws_opts\" \\\n+\t\t\t\t\"\" \"${cur##--color-moved-ws=}\"\n+\t\t\treturn\n+\t\t\t;;\n+\t\t--ws-error-highlight=*)\n+\t\t\t__gitcomp \"$__git_ws_error_highlight_opts\" \\\n+\t\t\t\t\"\" \"${cur##--ws-error-highlight=}\"\n+\t\t\treturn\n+\t\t\t;;\n+\t\t--*)\n+\t\t\t__gitcomp \"$__git_diff_difftool_options\"\n+\t\t\treturn\n+\t\t\t;;\n+\t\tesac\n+\t\t__git_complete_revlist_file\n+\tfi\n }\n \n __git_mergetools_common=\"diffuse diffmerge ecmerge emerge kdiff3 meld opendiff\n-- \n2.55.0-721-gd75157efe4\n\n"},{"id":"550421","messageId":"20260812162551.2229680-3-gitster@pobox.com","threadId":"66105","inReplyTo":"20260812162551.2229680-1-gitster@pobox.com","subject":"[PATCH v6 2/3] completion: complete tracked paths for 'git diff'","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-08-12T16:25:50Z","receivedAt":"2026-08-12T16:25:58Z","isPatch":true,"body":"When completing arguments for 'git diff', _git_diff() delegates to\n__git_complete_revlist_file(), which only completes revision\nreferences.  This is good [*], as mixing both revisions and paths in a\nsingle list for the user to pick from is simply too confusing.\n\nIf no reference matches, or if '--' is given, however, _git_diff()\nleaves COMPREPLY empty.  Bash then falls back to default filename\ncompletion in $PWD.  This fails when 'git -C <path>' is used because\n$PWD is not the target repository.\n\nUpdate _git_diff() to use __git_complete_index_file() when '--' is\npresent, or when revision reference completion yields no matching\ncandidates, so that tracked paths are offered as candidates.\n\nThis changes behavior even in the case where '-C <there>' is not\nused.  The new behavior omits untracked paths from suggestions when\nno revs match the prefix but matching tracked paths exist, which is\nmore useful in the context of 'git diff'.\n\nWhen run outside the working tree of a repository, or when nothing\nmatches from revisions or tracked paths, Bash still falls back to\ndefault filename completion in $PWD, so such a use case would be\njust like completing paths for any 'diff' command, rather than for\n'git diff'.\n\n[Footnote]\n * In https://lore.kernel.org/git/al%2Fw2qgBfhe9qMg6@szeder.dev/\n   SZEDER made the same argument for \"git send-email 0<TAB>\".\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n contrib/completion/git-completion.bash |  4 +++\n t/t9902-completion.sh                  | 40 ++++++++++++++++++++++++++\n 2 files changed, 44 insertions(+)\n\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex a61b6ed59a..76181e8714 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -1981,6 +1981,10 @@ _git_diff ()\n \t\tesac\n \t\t__git_complete_revlist_file\n \tfi\n+\n+\tif [ ${#COMPREPLY[@]} -eq 0 ]; then\n+\t\t__git_complete_index_file \"\"\n+\tfi\n }\n \n __git_mergetools_common=\"diffuse diffmerge ecmerge emerge kdiff3 meld opendiff\ndiff --git a/t/t9902-completion.sh b/t/t9902-completion.sh\nindex 55dc9eabfc..32e5d484c7 100755\n--- a/t/t9902-completion.sh\n+++ b/t/t9902-completion.sh\n@@ -2663,6 +2663,7 @@ test_expect_success 'setup for integration tests' '\n \techo content >file1 &&\n \techo more >file2 &&\n \tgit add file1 file2 &&\n+\techo untracked >file3 &&\n \tgit commit -m one &&\n \tgit branch mybranch &&\n \tgit tag mytag\n@@ -2712,6 +2713,45 @@ test_expect_success 'git -C <path> checkout uses the right repo' '\n \tEOF\n '\n \n+test_expect_success 'git diff completes tracked paths when no refs match' '\n+\t# file1 and file2 are tracked but file3 is not\n+\t# there is no ref that begins with f\n+\ttest_completion \"git diff f\" <<-\\EOF &&\n+\tfile1\n+\tfile2\n+\tEOF\n+\ttest_completion \"git diff -- f\" <<-\\EOF\n+\tfile1\n+\tfile2\n+\tEOF\n+'\n+\n+test_expect_success 'git -C <path> diff completes paths in specified repo' '\n+\ttest_when_finished \"rm -rf repo-for-diff\" &&\n+\tgit init repo-for-diff &&\n+\techo content >repo-for-diff/otherfile &&\n+\techo content >repo-for-diff/lostfile &&\n+\tgit -C repo-for-diff add otherfile &&\n+\tgit -C repo-for-diff add lostfile &&\n+\tgit -C repo-for-diff commit -m otherfile &&\n+\techo untracked >repo-for-diff/oops &&\n+\trm -f repo-for-diff/lostfile &&\n+\n+\ttest_completion \"git -C repo-for-diff diff o\" <<-\\EOF &&\n+\totherfile\n+\tEOF\n+\ttest_completion \"git -C repo-for-diff diff l\" <<-\\EOF &&\n+\tlostfile\n+\tEOF\n+\n+\ttest_completion \"git -C repo-for-diff diff -- o\" <<-\\EOF &&\n+\totherfile\n+\tEOF\n+\ttest_completion \"git -C repo-for-diff diff -- l\" <<-\\EOF\n+\tlostfile\n+\tEOF\n+'\n+\n test_expect_success 'show completes all refs' '\n \ttest_completion \"git show m\" <<-\\EOF\n \tmain Z\n-- \n2.55.0-721-gd75157efe4\n\n"},{"id":"550422","messageId":"20260812162551.2229680-4-gitster@pobox.com","threadId":"66105","inReplyTo":"20260812162551.2229680-1-gitster@pobox.com","subject":"[PATCH v6 3/3] completion: 'git diff' completes untracked paths as a last resort","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-08-12T16:25:51Z","receivedAt":"2026-08-12T16:25:59Z","isPatch":true,"body":"We taught 'git diff' to first try to complete revisions (unless '--'\nis present on the command line) and, failing that, to complete\ntracked paths.  If this yields nothing, it lets the Bash default,\nwhich offers paths in $PWD, kick in.\n\nTeach it to complete untracked paths before giving up and letting\nthe Bash default kick in.  With this change,\n\n    $ git -C another-directory diff un<TAB>\n\nfinds the 'untracked' file in another-directory and offers it as a\ncompletion candidate.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n contrib/completion/git-completion.bash |  4 ++++\n t/t9902-completion.sh                  | 20 +++++++++++++++++++-\n 2 files changed, 23 insertions(+), 1 deletion(-)\n\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex 76181e8714..d35b4f3024 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -1985,6 +1985,10 @@ _git_diff ()\n \tif [ ${#COMPREPLY[@]} -eq 0 ]; then\n \t\t__git_complete_index_file \"\"\n \tfi\n+\n+\tif [ ${#COMPREPLY[@]} -eq 0 ]; then\n+\t\t__git_complete_index_file \"--others --directory\"\n+\tfi\n }\n \n __git_mergetools_common=\"diffuse diffmerge ecmerge emerge kdiff3 meld opendiff\ndiff --git a/t/t9902-completion.sh b/t/t9902-completion.sh\nindex 32e5d484c7..b889ec8c77 100755\n--- a/t/t9902-completion.sh\n+++ b/t/t9902-completion.sh\n@@ -2664,6 +2664,7 @@ test_expect_success 'setup for integration tests' '\n \techo more >file2 &&\n \tgit add file1 file2 &&\n \techo untracked >file3 &&\n+\techo untracked >ufile &&\n \tgit commit -m one &&\n \tgit branch mybranch &&\n \tgit tag mytag\n@@ -2726,6 +2727,16 @@ test_expect_success 'git diff completes tracked paths when no refs match' '\n \tEOF\n '\n \n+test_expect_success 'git diff [--] completes untracked paths, too' '\n+\t# ufile is not tracked and there is no ref that begins with u\n+\ttest_completion \"git diff u\" <<-\\EOF &&\n+\tufile\n+\tEOF\n+\ttest_completion \"git diff -- u\" <<-\\EOF\n+\tufile\n+\tEOF\n+'\n+\n test_expect_success 'git -C <path> diff completes paths in specified repo' '\n \ttest_when_finished \"rm -rf repo-for-diff\" &&\n \tgit init repo-for-diff &&\n@@ -2735,6 +2746,7 @@ test_expect_success 'git -C <path> diff completes paths in specified repo' '\n \tgit -C repo-for-diff add lostfile &&\n \tgit -C repo-for-diff commit -m otherfile &&\n \techo untracked >repo-for-diff/oops &&\n+\techo untracked >repo-for-diff/ufile &&\n \trm -f repo-for-diff/lostfile &&\n \n \ttest_completion \"git -C repo-for-diff diff o\" <<-\\EOF &&\n@@ -2743,13 +2755,19 @@ test_expect_success 'git -C <path> diff completes paths in specified repo' '\n \ttest_completion \"git -C repo-for-diff diff l\" <<-\\EOF &&\n \tlostfile\n \tEOF\n+\ttest_completion \"git -C repo-for-diff diff u\" <<-\\EOF &&\n+\tufile\n+\tEOF\n \n \ttest_completion \"git -C repo-for-diff diff -- o\" <<-\\EOF &&\n \totherfile\n \tEOF\n-\ttest_completion \"git -C repo-for-diff diff -- l\" <<-\\EOF\n+\ttest_completion \"git -C repo-for-diff diff -- l\" <<-\\EOF &&\n \tlostfile\n \tEOF\n+\ttest_completion \"git -C repo-for-diff diff -- u\" <<-\\EOF\n+\tufile\n+\tEOF\n '\n \n test_expect_success 'show completes all refs' '\n-- \n2.55.0-721-gd75157efe4\n\n"}]}