Volume XXII, number 279Tuesday, October 6, 2026Latest message 15 minutes ago

The Git List

News and archive of git@vger.kernel.org, since April 2005

patchcompletion: complete tracked paths for 'git diff'

34 messages between Aug 3, 2026 and Aug 12, 2026, from Junio C Hamano, SZEDER Gábor, D. Ben Knoble, Elijah Newren.

Plain Markdown or JSON for tools and agents. Diffs are folded; open one to read it.

Junio C HamanoAug 3, 2026, 00:58 UTC on lore

When completing arguments for 'git diff', _git_diff() delegates to __git_complete_revlist_file(), which only completes revision references. This is good [*], as mixing both revs and paths in a single list to have the user pick is simply too confusing.

If no reference matches, or if '--' is given, however, _git_diff() leaves COMPREPLY empty. Bash then falls back to default filename completion in $PWD. This fails when 'git -C <path>' is used because $PWD is not the target repository.

Update _git_diff() to use __git_complete_index_file() when '--' is present, or when revision reference completion yields no matching candidates, so that tracked paths are offered as candidates.

[Footnote]
 * In https://lore.kernel.org/git/al%2Fw2qgBfhe9qMg6@szeder.dev/
   SZEDER made the same argument for "git send-email 0<TAB>".
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 * This is one of my pet peeves that I have raised a few times on
   the list but nobody reacted.  So I did a "monkey see, monkey do"
   patch without deeply understanding what is going on in the code
   paths.  When preparing the CC: list, I pulled a few folks, some
   very recognizable, some not recognizable immediately by me, out
   of "git shortlog --since=3.years" on this file.  The contribution
   by any of them looked more expertly done by whatever I did here.
   The use case is that I have a checkout of the 'todo' branch in an
   untracked subdirectory 'Meta' in my primary source tree.  I would
   do
    $ git -C Meta status wh<TAB>
   and it completes to whats-cooking.txt just fine, 'add' also adds
   it, but 'diff' dies not work, not because I have refs that 'wh'
   completes, but because bash completion is unaware that I want
   paths completed in the other directory.
 contrib/completion/git-completion.bash |  8 +++++-
 t/t9902-completion.sh                  | 40 ++++++++++++++++++++++++++
 2 files changed, 47 insertions(+), 1 deletion(-)
Show changes to 2 files +47 −1

contrib/completion/git-completion.bash, t/t9902-completion.sh

diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index e875787710..8f5773292b 100644
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -1947,7 +1947,10 @@ __git_diff_difftool_options="--cached --staged
 
 _git_diff ()
 {
-	__git_has_doubledash && return
+	if __git_has_doubledash; then
+		__git_complete_index_file
+		return
+	fi
 
 	case "$cur" in
 	--diff-algorithm=*)
@@ -1976,6 +1979,9 @@ _git_diff ()
 		;;
 	esac
 	__git_complete_revlist_file
+	if [ ${#COMPREPLY[@]} -eq 0 ]; then
+		__git_complete_index_file
+	fi
 }
 
 __git_mergetools_common="diffuse diffmerge ecmerge emerge kdiff3 meld opendiff
diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh
index 55dc9eabfc..eecd53f097 100755
--- a/t/t9902-completion.sh
+++ b/t/t9902-completion.sh
@@ -2663,6 +2663,7 @@ test_expect_success 'setup for integration tests' '
 	echo content >file1 &&
 	echo more >file2 &&
 	git add file1 file2 &&
+	echo untracked >file3 &&
 	git commit -m one &&
 	git branch mybranch &&
 	git tag mytag
@@ -2712,6 +2713,45 @@ test_expect_success 'git -C <path> checkout uses the right repo' '
 	EOF
 '
 
+test_expect_success 'git diff completes tracked paths when no refs match' '
+	# file1 and file2 are tracked but file3 is not
+	test_completion "git diff f" <<-\EOF
+	file1
+	file2
+	EOF
+'
+
+test_expect_success 'git diff -- completes tracked paths' '
+	# file1 and file2 are tracked but file3 is not
+	test_completion "git diff -- f" <<-\EOF
+	file1
+	file2
+	EOF
+'
+
+test_expect_success 'git -C <path> diff completes tracked paths in specified repo' '
+	test_when_finished "rm -rf repo-for-diff" &&
+	git init repo-for-diff &&
+	echo content >repo-for-diff/otherfile &&
+	git -C repo-for-diff add otherfile &&
+	echo untracked >repo-for-diff/oops &&
+	git -C repo-for-diff commit -m otherfile &&
+	test_completion "git -C repo-for-diff diff o" <<-\EOF
+	otherfile
+	EOF
+'
+
+test_expect_success 'git -C <path> diff -- completes pathspecs in specified repo' '
+	test_when_finished "rm -rf repo-for-diff" &&
+	git init repo-for-diff &&
+	echo content >repo-for-diff/otherfile &&
+	git -C repo-for-diff add otherfile &&
+	git -C repo-for-diff commit -m otherfile &&
+	test_completion "git -C repo-for-diff diff -- o" <<-\EOF
+	otherfile
+	EOF
+'
+
 test_expect_success 'show completes all refs' '
 	test_completion "git show m" <<-\EOF
 	main Z
-- 
2.55.0-607-g47e9082d35
Junio C HamanoAug 3, 2026, 01:07 UTC in reply to Junio C Hamano on lore

Re: [PATCH] completion: complete tracked paths for 'git diff'

Junio C Hamano <gitster@pobox.com> writes:
Show 13 quoted lines
> When completing arguments for 'git diff', _git_diff() delegates to
> __git_complete_revlist_file(), which only completes revision
> references.  This is good [*], as mixing both revs and paths in a
> single list to have the user pick is simply too confusing.
>
> If no reference matches, or if '--' is given, however, _git_diff()
> leaves COMPREPLY empty. Bash then falls back to default filename
> completion in $PWD. This fails when 'git -C <path>' is used because
> $PWD is not the target repository.
>
> Update _git_diff() to use __git_complete_index_file() when '--' is
> present, or when revision reference completion yields no matching
> candidates, so that tracked paths are offered as candidates.

This changes behavior even in the case where '-C <there>' is not used. The new behavior omits untracked paths from suggestions, which is clearly better behavior.

I'll add the above paragraph to the proposed log message when I queue this on 'seen'.

SZEDER GáborAug 3, 2026, 05:44 UTC in reply to Junio C Hamano on lore

Re: [PATCH] completion: complete tracked paths for 'git diff'

On Sun, Aug 02, 2026 at 05:58:01PM -0700, Junio C Hamano wrote:
Show 13 quoted lines
> When completing arguments for 'git diff', _git_diff() delegates to
> __git_complete_revlist_file(), which only completes revision
> references.  This is good [*], as mixing both revs and paths in a
> single list to have the user pick is simply too confusing.
> 
> If no reference matches, or if '--' is given, however, _git_diff()
> leaves COMPREPLY empty. Bash then falls back to default filename
> completion in $PWD. This fails when 'git -C <path>' is used because
> $PWD is not the target repository.
> 
> Update _git_diff() to use __git_complete_index_file() when '--' is
> present, or when revision reference completion yields no matching
> candidates, so that tracked paths are offered as candidates.
Makes sense.
Show 14 quoted lines
> [Footnote]
> 
>  * In https://lore.kernel.org/git/al%2Fw2qgBfhe9qMg6@szeder.dev/
>    SZEDER made the same argument for "git send-email 0<TAB>".
> 
> Signed-off-by: Junio C Hamano <gitster@pobox.com>
> ---
> 
>  * This is one of my pet peeves that I have raised a few times on
>    the list but nobody reacted.  So I did a "monkey see, monkey do"
>    patch without deeply understanding what is going on in the code
>    paths.  When preparing the CC: list, I pulled a few folks, some
>    very recognizable, some not recognizable immediately by me, out
>    of "git shortlog --since=3.years" on this file.

Will have to finally polish and submit a completion patch from my vaults to get myself back on this list ;)

>    The contribution
>    by any of them looked more expertly done by whatever I did here.
I think your changes are fine.

However, there is 'git diff --no-index' which happily accepts untracked files as well, but with this patch the user can complete only those untracked files that don't match the current word on the command line (because then __git_complete_index_file() won't list anything, and we'll fall back to Bash filename completion like before).

I think we should check whether the '--no-index' option is present on the command line, and simply not call __git_complete_index_file() if it is, to let Bash list all paths; i.e. each of those calls should be protected by an additional 'if test -z "$(__git_find_on_cmdline "--no-index")' condition, perhaps.

Show 102 quoted lines
>    The use case is that I have a checkout of the 'todo' branch in an
>    untracked subdirectory 'Meta' in my primary source tree.  I would
>    do
> 
>     $ git -C Meta status wh<TAB>
> 
>    and it completes to whats-cooking.txt just fine, 'add' also adds
>    it, but 'diff' dies not work, not because I have refs that 'wh'
>    completes, but because bash completion is unaware that I want
>    paths completed in the other directory.
> 
>  contrib/completion/git-completion.bash |  8 +++++-
>  t/t9902-completion.sh                  | 40 ++++++++++++++++++++++++++
>  2 files changed, 47 insertions(+), 1 deletion(-)
> 
> diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
> index e875787710..8f5773292b 100644
> --- a/contrib/completion/git-completion.bash
> +++ b/contrib/completion/git-completion.bash
> @@ -1947,7 +1947,10 @@ __git_diff_difftool_options="--cached --staged
>  
>  _git_diff ()
>  {
> -	__git_has_doubledash && return
> +	if __git_has_doubledash; then
> +		__git_complete_index_file
> +		return
> +	fi
>  
>  	case "$cur" in
>  	--diff-algorithm=*)
> @@ -1976,6 +1979,9 @@ _git_diff ()
>  		;;
>  	esac
>  	__git_complete_revlist_file
> +	if [ ${#COMPREPLY[@]} -eq 0 ]; then
> +		__git_complete_index_file
> +	fi
>  }
>  
>  __git_mergetools_common="diffuse diffmerge ecmerge emerge kdiff3 meld opendiff
> diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh
> index 55dc9eabfc..eecd53f097 100755
> --- a/t/t9902-completion.sh
> +++ b/t/t9902-completion.sh
> @@ -2663,6 +2663,7 @@ test_expect_success 'setup for integration tests' '
>  	echo content >file1 &&
>  	echo more >file2 &&
>  	git add file1 file2 &&
> +	echo untracked >file3 &&
>  	git commit -m one &&
>  	git branch mybranch &&
>  	git tag mytag
> @@ -2712,6 +2713,45 @@ test_expect_success 'git -C <path> checkout uses the right repo' '
>  	EOF
>  '
>  
> +test_expect_success 'git diff completes tracked paths when no refs match' '
> +	# file1 and file2 are tracked but file3 is not
> +	test_completion "git diff f" <<-\EOF
> +	file1
> +	file2
> +	EOF
> +'
> +
> +test_expect_success 'git diff -- completes tracked paths' '
> +	# file1 and file2 are tracked but file3 is not
> +	test_completion "git diff -- f" <<-\EOF
> +	file1
> +	file2
> +	EOF
> +'
> +
> +test_expect_success 'git -C <path> diff completes tracked paths in specified repo' '
> +	test_when_finished "rm -rf repo-for-diff" &&
> +	git init repo-for-diff &&
> +	echo content >repo-for-diff/otherfile &&
> +	git -C repo-for-diff add otherfile &&
> +	echo untracked >repo-for-diff/oops &&
> +	git -C repo-for-diff commit -m otherfile &&
> +	test_completion "git -C repo-for-diff diff o" <<-\EOF
> +	otherfile
> +	EOF
> +'
> +
> +test_expect_success 'git -C <path> diff -- completes pathspecs in specified repo' '
> +	test_when_finished "rm -rf repo-for-diff" &&
> +	git init repo-for-diff &&
> +	echo content >repo-for-diff/otherfile &&
> +	git -C repo-for-diff add otherfile &&
> +	git -C repo-for-diff commit -m otherfile &&
> +	test_completion "git -C repo-for-diff diff -- o" <<-\EOF
> +	otherfile
> +	EOF
> +'
> +
>  test_expect_success 'show completes all refs' '
>  	test_completion "git show m" <<-\EOF
>  	main Z
> -- 
> 2.55.0-607-g47e9082d35
> 
Junio C HamanoAug 3, 2026, 13:41 UTC in reply to SZEDER Gábor on lore

Re: [PATCH] completion: complete tracked paths for 'git diff'

SZEDER Gábor <szeder.dev@gmail.com> writes:
Show 5 quoted lines
> I think we should check whether the '--no-index' option is present on
> the command line, and simply not call __git_complete_index_file() if
> it is, to let Bash list all paths; i.e. each of those calls should be
> protected by an additional 'if test -z "$(__git_find_on_cmdline
> "--no-index")' condition, perhaps.

Ah, I did not think of the "we made 'git diff' work without Git" mode at all.

But I would avoid scanning the command line for '--no-index' for two reasons:

 (1) "git diff -S --no-index maint master" would not give you the
     '--no-index' mode.
 (2) When run outside the working tree of a repository, you do not
     have to say '--no-index'.

These make detecting the "'git diff' but not Git" mode tedious and error-prone.

I have not tried this, but if we arranged the code to fall back further to Bash-native "paths in $PWD" after the completion code in the posted patch found nothing, would it be sufficient?

When trying to complete an untracked file in the working tree of a repository, if the file shares the same prefix with a tracked file, you cannot complete the untracked file without a prefix long enough to disambiguate it from the tracked one. Such a prefix may not even exist (e.g., 'foo' is tracked and 'foo~' is untracked; when you type "git diff --no-index foo<TAB>", you get 'foo' but not 'foo~', so you end up typing 'foo~' in full). In that sense, it is a regression, but supporting Git usage is the primary mission of 'git diff', so the trade-off may not be so bad.

Thoughts?
Junio C HamanoAug 3, 2026, 15:45 UTC in reply to Junio C Hamano on lore

Re: [PATCH] completion: complete tracked paths for 'git diff'

Junio C Hamano <gitster@pobox.com> writes:
Show 27 quoted lines
> SZEDER Gábor <szeder.dev@gmail.com> writes:
>
>> I think we should check whether the '--no-index' option is present on
>> the command line, and simply not call __git_complete_index_file() if
>> it is, to let Bash list all paths; i.e. each of those calls should be
>> protected by an additional 'if test -z "$(__git_find_on_cmdline
>> "--no-index")' condition, perhaps.
>
>
> Ah, I did not think of the "we made 'git diff' work without Git"
> mode at all.
>
> But I would avoid scanning the command line for '--no-index' for two
> reasons:
>
>  (1) "git diff -S --no-index maint master" would not give you the
>      '--no-index' mode.
>
>  (2) When run outside the working tree of a repository, you do not
>      have to say '--no-index'.
>
> These make detecting the "'git diff' but not Git" mode tedious and
> error-prone.
>
> I have not tried this, but if we arranged the code to fall back
> further to Bash-native "paths in $PWD" after the completion code in
> the posted patch found nothing, would it be sufficient?

Eh, isn't the code already arranged to do so? With the posted completion script loaded, I do

	$ cd $HOME
	$ git diff [--no-index] w/git.git/C<TAB>

where (1) my $HOME is not under version control (dotfiles are installed after getting built from their sources that are version controlled elsewhere), and (2) ~/w/git.git/ is the primary working tree I work in. I see

	Cargo.toml  CODE_OF_CONDUCT.md  COPYING

offered as choices. As there is no index or rev when I am in my $HOME directory, naturally the only completion that kicks in is the bash native "paths we see".

So I think I do not need anything special to "arrange" the fallback.
Thanks.
Junio C HamanoAug 4, 2026, 16:22 UTC in reply to Junio C Hamano on lore

[PATCH v2] completion: complete tracked paths for 'git diff'

When completing arguments for 'git diff', _git_diff() delegates to __git_complete_revlist_file(), which only completes revision references. This is good [*], as mixing both revisions and paths in a single list for the user to pick from is simply too confusing.

If no reference matches, or if '--' is given, however, _git_diff() leaves COMPREPLY empty. Bash then falls back to default filename completion in $PWD. This fails when 'git -C <path>' is used because $PWD is not the target repository.

Update _git_diff() to use __git_complete_index_file() when '--' is present, or when revision reference completion yields no matching candidates, so that tracked paths are offered as candidates.

This changes behavior even in the case where '-C <there>' is not used. The new behavior omits untracked paths from suggestions when no revs match the prefix but matching tracked paths exist, which is more useful in the context of 'git diff'.

When run outside the working tree of a repository, or when nothing matches from revisions or tracked paths, Bash still falls back to default filename completion in $PWD, so such a use case would be just like completing paths for any 'diff' command, rather than for 'git diff'.

[Footnote]
 * In https://lore.kernel.org/git/al%2Fw2qgBfhe9qMg6@szeder.dev/
   SZEDER made the same argument for "git send-email 0<TAB>".
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 * The last two paragraphs in the proposed commit log message are
   new, to explain why the code posted as-is would be sufficient to
   support the "'git diff --no-index' is not Git but is diff" usage,
   and there is no code change between v1 and this iteration.
   By the way, I, as a relative newbie to the completion script, had
   trouble with the test_completion helper and wasted some time
   wondering why an additional test:
    test_expect_success 'git diff completes untracked paths if  nothing matches' '
	    >untracked &&
	    test_completion "git diff -- u" <<-\EOF
	    untracked
	    EOF
    '
    did not work, even though under manual testing, u<TAB> completed
    'untracked' just fine.  The reason is that test_completion
    does not test the final "Bash default" fallback.  It might not
    be necessary for those who are familiar with the completion test
    suite, but I thought it would help others.
    This message comes with a range-diff that shows only the commit
    log changes.
 contrib/completion/git-completion.bash |  8 +++++-
 t/t9902-completion.sh                  | 40 ++++++++++++++++++++++++++
 2 files changed, 47 insertions(+), 1 deletion(-)
Show changes to 2 files +47 −1

contrib/completion/git-completion.bash, t/t9902-completion.sh

diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index e875787710..8f5773292b 100644
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -1947,7 +1947,10 @@ __git_diff_difftool_options="--cached --staged
 
 _git_diff ()
 {
-	__git_has_doubledash && return
+	if __git_has_doubledash; then
+		__git_complete_index_file
+		return
+	fi
 
 	case "$cur" in
 	--diff-algorithm=*)
@@ -1976,6 +1979,9 @@ _git_diff ()
 		;;
 	esac
 	__git_complete_revlist_file
+	if [ ${#COMPREPLY[@]} -eq 0 ]; then
+		__git_complete_index_file
+	fi
 }
 
 __git_mergetools_common="diffuse diffmerge ecmerge emerge kdiff3 meld opendiff
diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh
index 9ae3c48ebd..82488f3b50 100755
--- a/t/t9902-completion.sh
+++ b/t/t9902-completion.sh
@@ -2663,6 +2663,7 @@ test_expect_success 'setup for integration tests' '
 	echo content >file1 &&
 	echo more >file2 &&
 	git add file1 file2 &&
+	echo untracked >file3 &&
 	git commit -m one &&
 	git branch mybranch &&
 	git tag mytag
@@ -2712,6 +2713,45 @@ test_expect_success 'git -C <path> checkout uses the right repo' '
 	EOF
 '
 
+test_expect_success 'git diff completes tracked paths when no refs match' '
+	# file1 and file2 are tracked but file3 is not
+	test_completion "git diff f" <<-\EOF
+	file1
+	file2
+	EOF
+'
+
+test_expect_success 'git diff -- completes tracked paths' '
+	# file1 and file2 are tracked but file3 is not
+	test_completion "git diff -- f" <<-\EOF
+	file1
+	file2
+	EOF
+'
+
+test_expect_success 'git -C <path> diff completes tracked paths in specified repo' '
+	test_when_finished "rm -rf repo-for-diff" &&
+	git init repo-for-diff &&
+	echo content >repo-for-diff/otherfile &&
+	git -C repo-for-diff add otherfile &&
+	echo untracked >repo-for-diff/oops &&
+	git -C repo-for-diff commit -m otherfile &&
+	test_completion "git -C repo-for-diff diff o" <<-\EOF
+	otherfile
+	EOF
+'
+
+test_expect_success 'git -C <path> diff -- completes pathspecs in specified repo' '
+	test_when_finished "rm -rf repo-for-diff" &&
+	git init repo-for-diff &&
+	echo content >repo-for-diff/otherfile &&
+	git -C repo-for-diff add otherfile &&
+	git -C repo-for-diff commit -m otherfile &&
+	test_completion "git -C repo-for-diff diff -- o" <<-\EOF
+	otherfile
+	EOF
+'
+
 test_expect_success 'show completes all refs' '
 	test_completion "git show m" <<-\EOF
 	main Z

Range-diff against v1:
1:  100043822f ! 1:  fa4461a192 completion: complete tracked paths for 'git diff'
    @@ Commit message
     
         When completing arguments for 'git diff', _git_diff() delegates to
         __git_complete_revlist_file(), which only completes revision
    -    references.  This is good [*], as mixing both revs and paths in a
    -    single list to have the user pick is simply too confusing.
    +    references.  This is good [*], as mixing both revisions and paths in a
    +    single list for the user to pick from is simply too confusing.
     
         If no reference matches, or if '--' is given, however, _git_diff()
    -    leaves COMPREPLY empty. Bash then falls back to default filename
    -    completion in $PWD. This fails when 'git -C <path>' is used because
    +    leaves COMPREPLY empty.  Bash then falls back to default filename
    +    completion in $PWD.  This fails when 'git -C <path>' is used because
         $PWD is not the target repository.
     
         Update _git_diff() to use __git_complete_index_file() when '--' is
         present, or when revision reference completion yields no matching
         candidates, so that tracked paths are offered as candidates.
     
    -    [Footnote]
    +    This changes behavior even in the case where '-C <there>' is not
    +    used.  The new behavior omits untracked paths from suggestions when
    +    no revs match the prefix but matching tracked paths exist, which is
    +    more useful in the context of 'git diff'.
    +
    +    When run outside the working tree of a repository, or when nothing
    +    matches from revisions or tracked paths, Bash still falls back to
    +    default filename completion in $PWD, so such a use case would be
    +    just like completing paths for any 'diff' command, rather than for
    +    'git diff'.
     
    +    [Footnote]
          * In https://lore.kernel.org/git/al%2Fw2qgBfhe9qMg6@szeder.dev/
            SZEDER made the same argument for "git send-email 0<TAB>".
     
         Signed-off-by: Junio C Hamano <gitster@pobox.com>
    +    ---
    +
    +     * The last two paragraphs in the proposed commit log message are
    +       new, to explain why the code posted as-is would be sufficient to
    +       support the "'git diff --no-index' is not Git but is diff" usage.
    +       I, as a relative newbie to the completion script, had trouble
    +       with the test_completion helper and wasted some time wondering
    +       why an additional test:
    +
    +        test_expect_success 'git diff completes untracked paths if  nothing matches' '
    +                >untracked &&
    +                test_completion "git diff -- u" <<-\EOF
    +                untracked
    +                EOF
    +        '
    +
    +        did not work, even though under manual testing, u<TAB> completed
    +        'untracked' just fine.  The reason is that test_completion
    +        does not test the final "Bash default" fallback.  It might not
    +        be necessary for those who are familiar with the completion test
    +        suite, but I thought it would help others.
     
      ## contrib/completion/git-completion.bash ##
     @@ contrib/completion/git-completion.bash: __git_diff_difftool_options="--cached --staged
-- 
2.55.0-624-gcdeb5fd34c
Junio C HamanoAug 5, 2026, 19:42 UTC in reply to Junio C Hamano on lore

[PATCH v3 0/3] completion of 'git [-C <dir>] diff'

Here is another reroll.

The primary motivation for this topic is that the command-line completion of 'git diff' does not handle paths (unlike 'git status' and 'git add') and instead relies on the default behavior of Bash command-line completion, which completes files in $PWD; this does not work at all with the '-C <directory>' option.

Previous iterations of the patch taught the completion script to offer tracked paths that match the prefix before <TAB> to improve the situation.

This time, we also complete untracked paths ourselves, so that even the following commands, which compare files like 'file[12]' that are not under the control of Git in a different directory, are completed:

        $ git -C not-a-git-dir diff fil<TAB>
        $ git -C not-a-git-dir diff --no-index fil<TAB>
 1/3: completion: no-op refactoring of diff completion
 2/3: completion: complete tracked paths for 'git diff'
 3/3: completion: 'git diff' completes untracked paths as a last resort
 contrib/completion/git-completion.bash | 69 +++++++++++++++-----------
 t/t9902-completion.sh                  | 59 ++++++++++++++++++++++
 2 files changed, 100 insertions(+), 28 deletions(-)
Junio C HamanoAug 5, 2026, 19:42 UTC in reply to Junio C Hamano on lore

[PATCH v3 1/3] completion: no-op refactoring of diff completion

The "git diff" completion function punts very early when it sees "--" on the command line, since it is a sign that options or revisions can appear and the current completion does not need to do anything "git diff" specific. By returning, it lets Bash default action that completes the names of the files in $PWD to kick in.

In preparation for the next step to change what happens when we "punt", arrange the code flow to avoid this early return. The behaviour at this step is unchanged, but the control flow just falls straight to the end.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 contrib/completion/git-completion.bash | 61 ++++++++++++++------------
 1 file changed, 33 insertions(+), 28 deletions(-)
Show changes to contrib/completion/git-completion.bash +33 −28
diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index e875787710..ccd3b2a372 100644
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -1947,35 +1947,40 @@ __git_diff_difftool_options="--cached --staged
 
 _git_diff ()
 {
-	__git_has_doubledash && return
-
-	case "$cur" in
-	--diff-algorithm=*)
-		__gitcomp "$__git_diff_algorithms" "" "${cur##--diff-algorithm=}"
-		return
-		;;
-	--submodule=*)
-		__gitcomp "$__git_diff_submodule_formats" "" "${cur##--submodule=}"
-		return
-		;;
-	--color-moved=*)
-		__gitcomp "$__git_color_moved_opts" "" "${cur##--color-moved=}"
-		return
-		;;
-	--color-moved-ws=*)
-		__gitcomp "$__git_color_moved_ws_opts" "" "${cur##--color-moved-ws=}"
-		return
-		;;
-	--ws-error-highlight=*)
-		__gitcomp "$__git_ws_error_highlight_opts" "" "${cur##--ws-error-highlight=}"
-		return
-		;;
-	--*)
-		__gitcomp "$__git_diff_difftool_options"
-		return
+	if ! __git_has_doubledash; then
+		case "$cur" in
+		--diff-algorithm=*)
+			__gitcomp "$__git_diff_algorithms" \
+				"" "${cur##--diff-algorithm=}"
+			return
 		;;
-	esac
-	__git_complete_revlist_file
+		--submodule=*)
+			__gitcomp "$__git_diff_submodule_formats" \
+				"" "${cur##--submodule=}"
+			return
+			;;
+		--color-moved=*)
+			__gitcomp "$__git_color_moved_opts" \
+				"" "${cur##--color-moved=}"
+			return
+			;;
+		--color-moved-ws=*)
+			__gitcomp "$__git_color_moved_ws_opts" \
+				"" "${cur##--color-moved-ws=}"
+			return
+			;;
+		--ws-error-highlight=*)
+			__gitcomp "$__git_ws_error_highlight_opts" \
+				"" "${cur##--ws-error-highlight=}"
+			return
+			;;
+		--*)
+			__gitcomp "$__git_diff_difftool_options"
+			return
+			;;
+		esac
+		__git_complete_revlist_file
+	fi
 }
 
 __git_mergetools_common="diffuse diffmerge ecmerge emerge kdiff3 meld opendiff
-- 
2.55.0-653-g9745b9777e
Junio C HamanoAug 5, 2026, 19:42 UTC in reply to Junio C Hamano on lore

[PATCH v3 2/3] completion: complete tracked paths for 'git diff'

When completing arguments for 'git diff', _git_diff() delegates to __git_complete_revlist_file(), which only completes revision references. This is good [*], as mixing both revisions and paths in a single list for the user to pick from is simply too confusing.

If no reference matches, or if '--' is given, however, _git_diff() leaves COMPREPLY empty. Bash then falls back to default filename completion in $PWD. This fails when 'git -C <path>' is used because $PWD is not the target repository.

Update _git_diff() to use __git_complete_index_file() when '--' is present, or when revision reference completion yields no matching candidates, so that tracked paths are offered as candidates.

This changes behavior even in the case where '-C <there>' is not used. The new behavior omits untracked paths from suggestions when no revs match the prefix but matching tracked paths exist, which is more useful in the context of 'git diff'.

When run outside the working tree of a repository, or when nothing matches from revisions or tracked paths, Bash still falls back to default filename completion in $PWD, so such a use case would be just like completing paths for any 'diff' command, rather than for 'git diff'.

[Footnote]
 * In https://lore.kernel.org/git/al%2Fw2qgBfhe9qMg6@szeder.dev/
   SZEDER made the same argument for "git send-email 0<TAB>".
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 contrib/completion/git-completion.bash |  4 +++
 t/t9902-completion.sh                  | 39 ++++++++++++++++++++++++++
 2 files changed, 43 insertions(+)
Show changes to 2 files +43 −0

contrib/completion/git-completion.bash, t/t9902-completion.sh

diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index ccd3b2a372..845fd19f70 100644
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -1981,6 +1981,10 @@ _git_diff ()
 		esac
 		__git_complete_revlist_file
 	fi
+
+	if [ ${#COMPREPLY[@]} -eq 0 ]; then
+		__git_complete_index_file
+	fi
 }
 
 __git_mergetools_common="diffuse diffmerge ecmerge emerge kdiff3 meld opendiff
diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh
index 55dc9eabfc..10ac690e21 100755
--- a/t/t9902-completion.sh
+++ b/t/t9902-completion.sh
@@ -2712,6 +2712,45 @@ test_expect_success 'git -C <path> checkout uses the right repo' '
 	EOF
 '
 
+test_expect_success 'git diff completes tracked paths when no refs match' '
+	# file1 and file2 are tracked but file3 is not
+	test_completion "git diff f" <<-\EOF
+	file1
+	file2
+	EOF
+'
+
+test_expect_success 'git diff -- completes tracked paths' '
+	# file1 and file2 are tracked but file3 is not
+	test_completion "git diff -- f" <<-\EOF
+	file1
+	file2
+	EOF
+'
+
+test_expect_success 'git -C <path> diff completes tracked paths in specified repo' '
+	test_when_finished "rm -rf repo-for-diff" &&
+	git init repo-for-diff &&
+	echo content >repo-for-diff/otherfile &&
+	git -C repo-for-diff add otherfile &&
+	echo untracked >repo-for-diff/oops &&
+	git -C repo-for-diff commit -m otherfile &&
+	test_completion "git -C repo-for-diff diff o" <<-\EOF
+	otherfile
+	EOF
+'
+
+test_expect_success 'git -C <path> diff -- completes pathspecs in specified repo' '
+	test_when_finished "rm -rf repo-for-diff" &&
+	git init repo-for-diff &&
+	echo content >repo-for-diff/otherfile &&
+	git -C repo-for-diff add otherfile &&
+	git -C repo-for-diff commit -m otherfile &&
+	test_completion "git -C repo-for-diff diff -- o" <<-\EOF
+	otherfile
+	EOF
+'
+
 test_expect_success 'show completes all refs' '
 	test_completion "git show m" <<-\EOF
 	main Z
-- 
2.55.0-653-g9745b9777e
Junio C HamanoAug 5, 2026, 19:42 UTC in reply to Junio C Hamano on lore

[PATCH v3 3/3] completion: 'git diff' completes untracked paths as a last resort

We taught 'git diff' to first try to complete revisions (unless '--' is present on the command line) and, failing that, to complete tracked paths. If this yields nothing, it lets the Bash default, which offers paths in $PWD, kick in.

Teach it to complete untracked paths before giving up and letting the Bash default kick in. With this change,

    $ git -C another-directory diff un<TAB>

finds the 'untracked' file in another-directory and offers it as a completion candidate.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 contrib/completion/git-completion.bash |  4 ++++
 t/t9902-completion.sh                  | 22 +++++++++++++++++++++-
 2 files changed, 25 insertions(+), 1 deletion(-)
Show changes to 2 files +25 −1

contrib/completion/git-completion.bash, t/t9902-completion.sh

diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index 845fd19f70..7741789e41 100644
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -1985,6 +1985,10 @@ _git_diff ()
 	if [ ${#COMPREPLY[@]} -eq 0 ]; then
 		__git_complete_index_file
 	fi
+
+	if [ ${#COMPREPLY[@]} -eq 0 ]; then
+		__git_complete_index_file "--others --directory"
+	fi
 }
 
 __git_mergetools_common="diffuse diffmerge ecmerge emerge kdiff3 meld opendiff
diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh
index 10ac690e21..53a2bfb2ac 100755
--- a/t/t9902-completion.sh
+++ b/t/t9902-completion.sh
@@ -2663,6 +2663,7 @@ test_expect_success 'setup for integration tests' '
 	echo content >file1 &&
 	echo more >file2 &&
 	git add file1 file2 &&
+	echo untracked >ufile &&
 	git commit -m one &&
 	git branch mybranch &&
 	git tag mytag
@@ -2728,6 +2729,15 @@ test_expect_success 'git diff -- completes tracked paths' '
 	EOF
 '
 
+test_expect_success 'git diff [--] completes untracked paths, too' '
+	test_completion "git diff u" <<-\EOF &&
+	ufile
+	EOF
+	test_completion "git diff -- u" <<-\EOF
+	ufile
+	EOF
+'
+
 test_expect_success 'git -C <path> diff completes tracked paths in specified repo' '
 	test_when_finished "rm -rf repo-for-diff" &&
 	git init repo-for-diff &&
@@ -2744,11 +2754,21 @@ test_expect_success 'git -C <path> diff -- completes pathspecs in specified repo
 	test_when_finished "rm -rf repo-for-diff" &&
 	git init repo-for-diff &&
 	echo content >repo-for-diff/otherfile &&
+	echo untracked >repo-for-diff/untracked &&
 	git -C repo-for-diff add otherfile &&
 	git -C repo-for-diff commit -m otherfile &&
-	test_completion "git -C repo-for-diff diff -- o" <<-\EOF
+	test_completion "git -C repo-for-diff diff o" <<-\EOF &&
+	otherfile
+	EOF
+	test_completion "git -C repo-for-diff diff -- o" <<-\EOF &&
 	otherfile
 	EOF
+	test_completion "git -C repo-for-diff diff u" <<-\EOF &&
+	untracked
+	EOF
+	test_completion "git -C repo-for-diff diff -- u" <<-\EOF
+	untracked
+	EOF
 '
 
 test_expect_success 'show completes all refs' '
-- 
2.55.0-653-g9745b9777e
D. Ben KnobleAug 6, 2026, 11:30 UTC in reply to Junio C Hamano on lore

Re: [PATCH v3 3/3] completion: 'git diff' completes untracked paths as a last resort

Hello Junio,
On Wed, Aug 5, 2026 at 3:45 PM Junio C Hamano <gitster@pobox.com> wrote:
Show 60 quoted lines
>
> We taught 'git diff' to first try to complete revisions (unless '--'
> is present on the command line) and, failing that, to complete
> tracked paths.  If this yields nothing, it lets the Bash default,
> which offers paths in $PWD, kick in.
>
> Teach it to complete untracked paths before giving up and letting
> the Bash default kick in.  With this change,
>
>     $ git -C another-directory diff un<TAB>
>
> finds the 'untracked' file in another-directory and offers it as a
> completion candidate.
>
> Signed-off-by: Junio C Hamano <gitster@pobox.com>
> ---
>  contrib/completion/git-completion.bash |  4 ++++
>  t/t9902-completion.sh                  | 22 +++++++++++++++++++++-
>  2 files changed, 25 insertions(+), 1 deletion(-)
>
> diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
> index 845fd19f70..7741789e41 100644
> --- a/contrib/completion/git-completion.bash
> +++ b/contrib/completion/git-completion.bash
> @@ -1985,6 +1985,10 @@ _git_diff ()
>         if [ ${#COMPREPLY[@]} -eq 0 ]; then
>                 __git_complete_index_file
>         fi
> +
> +       if [ ${#COMPREPLY[@]} -eq 0 ]; then
> +               __git_complete_index_file "--others --directory"
> +       fi
>  }
>
>  __git_mergetools_common="diffuse diffmerge ecmerge emerge kdiff3 meld opendiff
> diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh
> index 10ac690e21..53a2bfb2ac 100755
> --- a/t/t9902-completion.sh
> +++ b/t/t9902-completion.sh
> @@ -2663,6 +2663,7 @@ test_expect_success 'setup for integration tests' '
>         echo content >file1 &&
>         echo more >file2 &&
>         git add file1 file2 &&
> +       echo untracked >ufile &&
>         git commit -m one &&
>         git branch mybranch &&
>         git tag mytag
> @@ -2728,6 +2729,15 @@ test_expect_success 'git diff -- completes tracked paths' '
>         EOF
>  '
>
> +test_expect_success 'git diff [--] completes untracked paths, too' '
> +       test_completion "git diff u" <<-\EOF &&
> +       ufile
> +       EOF
> +       test_completion "git diff -- u" <<-\EOF
> +       ufile
> +       EOF
> +'
> +
LGTM up to here.
Show 14 quoted lines
>  test_expect_success 'git -C <path> diff completes tracked paths in specified repo' '
>         test_when_finished "rm -rf repo-for-diff" &&
>         git init repo-for-diff &&
> @@ -2744,11 +2754,21 @@ test_expect_success 'git -C <path> diff -- completes pathspecs in specified repo
>         test_when_finished "rm -rf repo-for-diff" &&
>         git init repo-for-diff &&
>         echo content >repo-for-diff/otherfile &&
> +       echo untracked >repo-for-diff/untracked &&
>         git -C repo-for-diff add otherfile &&
>         git -C repo-for-diff commit -m otherfile &&
> -       test_completion "git -C repo-for-diff diff -- o" <<-\EOF
> +       test_completion "git -C repo-for-diff diff o" <<-\EOF &&
> +       otherfile
> +       EOF

Here, with more context (which I won't paste, because GMail…), it looks like this test is redundant with the test just before?

Show 10 quoted lines
> +       test_completion "git -C repo-for-diff diff -- o" <<-\EOF &&
>         otherfile
>         EOF
> +       test_completion "git -C repo-for-diff diff u" <<-\EOF &&
> +       untracked
> +       EOF
> +       test_completion "git -C repo-for-diff diff -- u" <<-\EOF
> +       untracked
> +       EOF
>  '
These tests intermingle with -- and without; the other tests separated them.

I don't think I have a strong preference, but perhaps consistency is a good ideal?

-- 
D. Ben Knoble
D. Ben KnobleAug 6, 2026, 11:30 UTC in reply to Junio C Hamano on lore

Re: [PATCH v3 0/3] completion of 'git [-C <dir>] diff'

Hi Junio,
On Wed, Aug 5, 2026 at 3:44 PM Junio C Hamano <gitster@pobox.com> wrote:
Show 29 quoted lines
>
> Here is another reroll.
>
> The primary motivation for this topic is that the command-line
> completion of 'git diff' does not handle paths (unlike 'git status'
> and 'git add') and instead relies on the default behavior of Bash
> command-line completion, which completes files in $PWD; this does
> not work at all with the '-C <directory>' option.
>
> Previous iterations of the patch taught the completion script to
> offer tracked paths that match the prefix before <TAB> to improve
> the situation.
>
> This time, we also complete untracked paths ourselves, so that even
> the following commands, which compare files like 'file[12]' that are
> not under the control of Git in a different directory, are
> completed:
>
>         $ git -C not-a-git-dir diff fil<TAB>
>         $ git -C not-a-git-dir diff --no-index fil<TAB>
>
>  1/3: completion: no-op refactoring of diff completion
>  2/3: completion: complete tracked paths for 'git diff'
>  3/3: completion: 'git diff' completes untracked paths as a last resort
>
>  contrib/completion/git-completion.bash | 69 +++++++++++++++-----------
>  t/t9902-completion.sh                  | 59 ++++++++++++++++++++++
>  2 files changed, 100 insertions(+), 28 deletions(-)
>
Left one comment on 3/3, but the rest looks good!
-- 
D. Ben Knoble
Junio C HamanoAug 6, 2026, 15:06 UTC in reply to D. Ben Knoble on lore

Re: [PATCH v3 3/3] completion: 'git diff' completes untracked paths as a last resort

"D. Ben Knoble" <ben.knoble@gmail.com> writes:
Show 17 quoted lines
>>  test_expect_success 'git -C <path> diff completes tracked paths in specified repo' '
>>         test_when_finished "rm -rf repo-for-diff" &&
>>         git init repo-for-diff &&
>> @@ -2744,11 +2754,21 @@ test_expect_success 'git -C <path> diff -- completes pathspecs in specified repo
>>         test_when_finished "rm -rf repo-for-diff" &&
>>         git init repo-for-diff &&
>>         echo content >repo-for-diff/otherfile &&
>> +       echo untracked >repo-for-diff/untracked &&
>>         git -C repo-for-diff add otherfile &&
>>         git -C repo-for-diff commit -m otherfile &&
>> -       test_completion "git -C repo-for-diff diff -- o" <<-\EOF
>> +       test_completion "git -C repo-for-diff diff o" <<-\EOF &&
>> +       otherfile
>> +       EOF
>
> Here, with more context (which I won't paste, because GMail…), it
> looks like this test is redundant with the test just before?

You're right. As these prefix strings ('f', 'o', or 'u') tested do not begin any valid refs, the result should be the same with or without '--', and I wanted to test both in a single test block, but I botched the conversion.

Will fix in v4, which hopefully will be test-only updates.
Thanks.
Junio C HamanoAug 7, 2026, 01:38 UTC in reply to Junio C Hamano on lore

[PATCH v4 0/3] completion of 'git [-C <dir>] diff'

The primary motivation for this topic is that the command-line completion of 'git diff' does not handle paths (unlike 'git status' and 'git add') and instead relies on the default behavior of Bash command-line completion, which completes files in $PWD; this does not work at all with the '-C <directory>' option.

This series teaches the completion machinery to complete revisions (unless '--' exists), then tracked paths, and then untracked paths, before letting the Bash default kick in. This way, we correctly complete 'git diff' command line even when '-C <directory>' is in effect.

The tests are the only changes relative to v2. In the step where tracked paths are completed, v2 did not demonstrate that untracked ones are *not* completed at the same time. Now we do by having untracked 'file3' next to 'file1' and 'file2' that are tracked. In the last step, we demonstrate untracked paths that do not share prefix with refs or tracked paths are completed, with or without the "-C <dir>" option.

 1/3: completion: no-op refactoring of diff completion
 2/3: completion: complete tracked paths for 'git diff'
 3/3: completion: 'git diff' completes untracked paths as a last
        resort
 contrib/completion/git-completion.bash | 69 +++++++++++++++-----------
 t/t9902-completion.sh                  | 49 ++++++++++++++++++
 2 files changed, 90 insertions(+), 28 deletions(-)
Range-diff against v3:
1:  d3c51c042c = 1:  3b99b45fee completion: no-op refactoring of diff completion
2:  c3658d6ca2 ! 2:  bcc24b6bda completion: complete tracked paths for 'git diff'
    @@ contrib/completion/git-completion.bash: _git_diff ()
      __git_mergetools_common="diffuse diffmerge ecmerge emerge kdiff3 meld opendiff
     
      ## t/t9902-completion.sh ##
    +@@ t/t9902-completion.sh: test_expect_success 'setup for integration tests' '
    + 	echo content >file1 &&
    + 	echo more >file2 &&
    + 	git add file1 file2 &&
    ++	echo untracked >file3 &&
    + 	git commit -m one &&
    + 	git branch mybranch &&
    + 	git tag mytag
     @@ t/t9902-completion.sh: test_expect_success 'git -C <path> checkout uses the right repo' '
      	EOF
      '
      
     +test_expect_success 'git diff completes tracked paths when no refs match' '
     +	# file1 and file2 are tracked but file3 is not
    -+	test_completion "git diff f" <<-\EOF
    ++	# there is no ref that begins with f
    ++	test_completion "git diff f" <<-\EOF &&
     +	file1
     +	file2
     +	EOF
    -+'
    -+
    -+test_expect_success 'git diff -- completes tracked paths' '
    -+	# file1 and file2 are tracked but file3 is not
     +	test_completion "git diff -- f" <<-\EOF
     +	file1
     +	file2
     +	EOF
     +'
     +
    -+test_expect_success 'git -C <path> diff completes tracked paths in specified repo' '
    ++test_expect_success 'git -C <path> diff completes in the specified repo' '
     +	test_when_finished "rm -rf repo-for-diff" &&
     +	git init repo-for-diff &&
    ++
    ++	# otherfile is tracked, oops is untracked
     +	echo content >repo-for-diff/otherfile &&
     +	git -C repo-for-diff add otherfile &&
    -+	echo untracked >repo-for-diff/oops &&
     +	git -C repo-for-diff commit -m otherfile &&
    -+	test_completion "git -C repo-for-diff diff o" <<-\EOF
    ++	echo untracked >repo-for-diff/oops &&
    ++	test_completion "git -C repo-for-diff diff o" <<-\EOF &&
     +	otherfile
     +	EOF
    -+'
    -+
    -+test_expect_success 'git -C <path> diff -- completes pathspecs in specified repo' '
    -+	test_when_finished "rm -rf repo-for-diff" &&
    -+	git init repo-for-diff &&
    -+	echo content >repo-for-diff/otherfile &&
    -+	git -C repo-for-diff add otherfile &&
    -+	git -C repo-for-diff commit -m otherfile &&
     +	test_completion "git -C repo-for-diff diff -- o" <<-\EOF
     +	otherfile
     +	EOF
3:  ba5dc6f164 ! 3:  34720a30ab completion: 'git diff' completes untracked paths as a last resort
    @@ contrib/completion/git-completion.bash: _git_diff ()
     
      ## t/t9902-completion.sh ##
     @@ t/t9902-completion.sh: test_expect_success 'setup for integration tests' '
    - 	echo content >file1 &&
      	echo more >file2 &&
      	git add file1 file2 &&
    + 	echo untracked >file3 &&
     +	echo untracked >ufile &&
      	git commit -m one &&
      	git branch mybranch &&
      	git tag mytag
    -@@ t/t9902-completion.sh: test_expect_success 'git diff -- completes tracked paths' '
    +@@ t/t9902-completion.sh: test_expect_success 'git diff completes tracked paths when no refs match' '
      	EOF
      '
      
     +test_expect_success 'git diff [--] completes untracked paths, too' '
    ++	# there is no ref or tracked path that begin with u
     +	test_completion "git diff u" <<-\EOF &&
     +	ufile
     +	EOF
    @@ t/t9902-completion.sh: test_expect_success 'git diff -- completes tracked paths'
     +	EOF
     +'
     +
    - test_expect_success 'git -C <path> diff completes tracked paths in specified repo' '
    - 	test_when_finished "rm -rf repo-for-diff" &&
    - 	git init repo-for-diff &&
    -@@ t/t9902-completion.sh: test_expect_success 'git -C <path> diff -- completes pathspecs in specified repo
    + test_expect_success 'git -C <path> diff completes in the specified repo' '
      	test_when_finished "rm -rf repo-for-diff" &&
      	git init repo-for-diff &&
    + 
    +-	# otherfile is tracked, oops is untracked
    ++	# otherfile is tracked, oops and ufile are untracked
      	echo content >repo-for-diff/otherfile &&
    -+	echo untracked >repo-for-diff/untracked &&
      	git -C repo-for-diff add otherfile &&
      	git -C repo-for-diff commit -m otherfile &&
    + 	echo untracked >repo-for-diff/oops &&
    ++	echo untracked >repo-for-diff/ufile &&
    + 	test_completion "git -C repo-for-diff diff o" <<-\EOF &&
    + 	otherfile
    + 	EOF
     -	test_completion "git -C repo-for-diff diff -- o" <<-\EOF
    -+	test_completion "git -C repo-for-diff diff o" <<-\EOF &&
    -+	otherfile
    -+	EOF
     +	test_completion "git -C repo-for-diff diff -- o" <<-\EOF &&
      	otherfile
      	EOF
     +	test_completion "git -C repo-for-diff diff u" <<-\EOF &&
    -+	untracked
    ++	ufile
     +	EOF
     +	test_completion "git -C repo-for-diff diff -- u" <<-\EOF
    -+	untracked
    ++	ufile
     +	EOF
      '
      
-- 
2.55.0-655-gb2c071042d
Junio C HamanoAug 7, 2026, 01:38 UTC in reply to Junio C Hamano on lore

[PATCH v4 1/3] completion: no-op refactoring of diff completion

The "git diff" completion function punts very early when it sees "--" on the command line, since it is a sign that options or revisions can appear and the current completion does not need to do anything "git diff" specific. By returning, it lets Bash default action that completes the names of the files in $PWD to kick in.

In preparation for the next step to change what happens when we "punt", arrange the code flow to avoid this early return. The behaviour at this step is unchanged, but the control flow just falls straight to the end.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 contrib/completion/git-completion.bash | 61 ++++++++++++++------------
 1 file changed, 33 insertions(+), 28 deletions(-)
Show changes to contrib/completion/git-completion.bash +33 −28
diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index e875787710..ccd3b2a372 100644
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -1947,35 +1947,40 @@ __git_diff_difftool_options="--cached --staged
 
 _git_diff ()
 {
-	__git_has_doubledash && return
-
-	case "$cur" in
-	--diff-algorithm=*)
-		__gitcomp "$__git_diff_algorithms" "" "${cur##--diff-algorithm=}"
-		return
-		;;
-	--submodule=*)
-		__gitcomp "$__git_diff_submodule_formats" "" "${cur##--submodule=}"
-		return
-		;;
-	--color-moved=*)
-		__gitcomp "$__git_color_moved_opts" "" "${cur##--color-moved=}"
-		return
-		;;
-	--color-moved-ws=*)
-		__gitcomp "$__git_color_moved_ws_opts" "" "${cur##--color-moved-ws=}"
-		return
-		;;
-	--ws-error-highlight=*)
-		__gitcomp "$__git_ws_error_highlight_opts" "" "${cur##--ws-error-highlight=}"
-		return
-		;;
-	--*)
-		__gitcomp "$__git_diff_difftool_options"
-		return
+	if ! __git_has_doubledash; then
+		case "$cur" in
+		--diff-algorithm=*)
+			__gitcomp "$__git_diff_algorithms" \
+				"" "${cur##--diff-algorithm=}"
+			return
 		;;
-	esac
-	__git_complete_revlist_file
+		--submodule=*)
+			__gitcomp "$__git_diff_submodule_formats" \
+				"" "${cur##--submodule=}"
+			return
+			;;
+		--color-moved=*)
+			__gitcomp "$__git_color_moved_opts" \
+				"" "${cur##--color-moved=}"
+			return
+			;;
+		--color-moved-ws=*)
+			__gitcomp "$__git_color_moved_ws_opts" \
+				"" "${cur##--color-moved-ws=}"
+			return
+			;;
+		--ws-error-highlight=*)
+			__gitcomp "$__git_ws_error_highlight_opts" \
+				"" "${cur##--ws-error-highlight=}"
+			return
+			;;
+		--*)
+			__gitcomp "$__git_diff_difftool_options"
+			return
+			;;
+		esac
+		__git_complete_revlist_file
+	fi
 }
 
 __git_mergetools_common="diffuse diffmerge ecmerge emerge kdiff3 meld opendiff
-- 
2.55.0-655-gb2c071042d
Junio C HamanoAug 7, 2026, 01:38 UTC in reply to Junio C Hamano on lore

[PATCH v4 3/3] completion: 'git diff' completes untracked paths as a last resort

We taught 'git diff' to first try to complete revisions (unless '--' is present on the command line) and, failing that, to complete tracked paths. If this yields nothing, it lets the Bash default, which offers paths in $PWD, kick in.

Teach it to complete untracked paths before giving up and letting the Bash default kick in. With this change,

    $ git -C another-directory diff un<TAB>

finds the 'untracked' file in another-directory and offers it as a completion candidate.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 contrib/completion/git-completion.bash |  4 ++++
 t/t9902-completion.sh                  | 22 ++++++++++++++++++++--
 2 files changed, 24 insertions(+), 2 deletions(-)
Show changes to 2 files +24 −2

contrib/completion/git-completion.bash, t/t9902-completion.sh

diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index 845fd19f70..7741789e41 100644
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -1985,6 +1985,10 @@ _git_diff ()
 	if [ ${#COMPREPLY[@]} -eq 0 ]; then
 		__git_complete_index_file
 	fi
+
+	if [ ${#COMPREPLY[@]} -eq 0 ]; then
+		__git_complete_index_file "--others --directory"
+	fi
 }
 
 __git_mergetools_common="diffuse diffmerge ecmerge emerge kdiff3 meld opendiff
diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh
index adfaf414fd..eea4bdbb7e 100755
--- a/t/t9902-completion.sh
+++ b/t/t9902-completion.sh
@@ -2664,6 +2664,7 @@ test_expect_success 'setup for integration tests' '
 	echo more >file2 &&
 	git add file1 file2 &&
 	echo untracked >file3 &&
+	echo untracked >ufile &&
 	git commit -m one &&
 	git branch mybranch &&
 	git tag mytag
@@ -2726,21 +2727,38 @@ test_expect_success 'git diff completes tracked paths when no refs match' '
 	EOF
 '
 
+test_expect_success 'git diff [--] completes untracked paths, too' '
+	# there is no ref or tracked path that begin with u
+	test_completion "git diff u" <<-\EOF &&
+	ufile
+	EOF
+	test_completion "git diff -- u" <<-\EOF
+	ufile
+	EOF
+'
+
 test_expect_success 'git -C <path> diff completes in the specified repo' '
 	test_when_finished "rm -rf repo-for-diff" &&
 	git init repo-for-diff &&
 
-	# otherfile is tracked, oops is untracked
+	# otherfile is tracked, oops and ufile are untracked
 	echo content >repo-for-diff/otherfile &&
 	git -C repo-for-diff add otherfile &&
 	git -C repo-for-diff commit -m otherfile &&
 	echo untracked >repo-for-diff/oops &&
+	echo untracked >repo-for-diff/ufile &&
 	test_completion "git -C repo-for-diff diff o" <<-\EOF &&
 	otherfile
 	EOF
-	test_completion "git -C repo-for-diff diff -- o" <<-\EOF
+	test_completion "git -C repo-for-diff diff -- o" <<-\EOF &&
 	otherfile
 	EOF
+	test_completion "git -C repo-for-diff diff u" <<-\EOF &&
+	ufile
+	EOF
+	test_completion "git -C repo-for-diff diff -- u" <<-\EOF
+	ufile
+	EOF
 '
 
 test_expect_success 'show completes all refs' '
-- 
2.55.0-655-gb2c071042d
Junio C HamanoAug 7, 2026, 01:38 UTC in reply to Junio C Hamano on lore

[PATCH v4 2/3] completion: complete tracked paths for 'git diff'

When completing arguments for 'git diff', _git_diff() delegates to __git_complete_revlist_file(), which only completes revision references. This is good [*], as mixing both revisions and paths in a single list for the user to pick from is simply too confusing.

If no reference matches, or if '--' is given, however, _git_diff() leaves COMPREPLY empty. Bash then falls back to default filename completion in $PWD. This fails when 'git -C <path>' is used because $PWD is not the target repository.

Update _git_diff() to use __git_complete_index_file() when '--' is present, or when revision reference completion yields no matching candidates, so that tracked paths are offered as candidates.

This changes behavior even in the case where '-C <there>' is not used. The new behavior omits untracked paths from suggestions when no revs match the prefix but matching tracked paths exist, which is more useful in the context of 'git diff'.

When run outside the working tree of a repository, or when nothing matches from revisions or tracked paths, Bash still falls back to default filename completion in $PWD, so such a use case would be just like completing paths for any 'diff' command, rather than for 'git diff'.

[Footnote]
 * In https://lore.kernel.org/git/al%2Fw2qgBfhe9qMg6@szeder.dev/
   SZEDER made the same argument for "git send-email 0<TAB>".
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 contrib/completion/git-completion.bash |  4 ++++
 t/t9902-completion.sh                  | 31 ++++++++++++++++++++++++++
 2 files changed, 35 insertions(+)
Show changes to 2 files +35 −0

contrib/completion/git-completion.bash, t/t9902-completion.sh

diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index ccd3b2a372..845fd19f70 100644
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -1981,6 +1981,10 @@ _git_diff ()
 		esac
 		__git_complete_revlist_file
 	fi
+
+	if [ ${#COMPREPLY[@]} -eq 0 ]; then
+		__git_complete_index_file
+	fi
 }
 
 __git_mergetools_common="diffuse diffmerge ecmerge emerge kdiff3 meld opendiff
diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh
index 55dc9eabfc..adfaf414fd 100755
--- a/t/t9902-completion.sh
+++ b/t/t9902-completion.sh
@@ -2663,6 +2663,7 @@ test_expect_success 'setup for integration tests' '
 	echo content >file1 &&
 	echo more >file2 &&
 	git add file1 file2 &&
+	echo untracked >file3 &&
 	git commit -m one &&
 	git branch mybranch &&
 	git tag mytag
@@ -2712,6 +2713,36 @@ test_expect_success 'git -C <path> checkout uses the right repo' '
 	EOF
 '
 
+test_expect_success 'git diff completes tracked paths when no refs match' '
+	# file1 and file2 are tracked but file3 is not
+	# there is no ref that begins with f
+	test_completion "git diff f" <<-\EOF &&
+	file1
+	file2
+	EOF
+	test_completion "git diff -- f" <<-\EOF
+	file1
+	file2
+	EOF
+'
+
+test_expect_success 'git -C <path> diff completes in the specified repo' '
+	test_when_finished "rm -rf repo-for-diff" &&
+	git init repo-for-diff &&
+
+	# otherfile is tracked, oops is untracked
+	echo content >repo-for-diff/otherfile &&
+	git -C repo-for-diff add otherfile &&
+	git -C repo-for-diff commit -m otherfile &&
+	echo untracked >repo-for-diff/oops &&
+	test_completion "git -C repo-for-diff diff o" <<-\EOF &&
+	otherfile
+	EOF
+	test_completion "git -C repo-for-diff diff -- o" <<-\EOF
+	otherfile
+	EOF
+'
+
 test_expect_success 'show completes all refs' '
 	test_completion "git show m" <<-\EOF
 	main Z
-- 
2.55.0-655-gb2c071042d
Elijah NewrenAug 7, 2026, 06:15 UTC in reply to Junio C Hamano on lore

Re: [PATCH v4 1/3] completion: no-op refactoring of diff completion

On Thu, Aug 6, 2026 at 6:38 PM Junio C Hamano <gitster@pobox.com> wrote:
Show 25 quoted lines
>
> The "git diff" completion function punts very early when it sees
> "--" on the command line, since it is a sign that options or
> revisions can appear and the current completion does not need to do
> anything "git diff" specific. By returning, it lets Bash default
> action that completes the names of the files in $PWD to kick in.
>
> In preparation for the next step to change what happens when we
> "punt", arrange the code flow to avoid this early return.  The
> behaviour at this step is unchanged, but the control flow just
> falls straight to the end.
>
> Signed-off-by: Junio C Hamano <gitster@pobox.com>
> ---
>  contrib/completion/git-completion.bash | 61 ++++++++++++++------------
>  1 file changed, 33 insertions(+), 28 deletions(-)
>
> diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
> index e875787710..ccd3b2a372 100644
> --- a/contrib/completion/git-completion.bash
> +++ b/contrib/completion/git-completion.bash
> @@ -1947,35 +1947,40 @@ __git_diff_difftool_options="--cached --staged
>
>  _git_diff ()
>  {
[...]
Show 7 quoted lines
> +       if ! __git_has_doubledash; then
> +               case "$cur" in
> +               --diff-algorithm=*)
> +                       __gitcomp "$__git_diff_algorithms" \
> +                               "" "${cur##--diff-algorithm=}"
> +                       return
>                 ;;

The refactor in this commit is a faithful no-op -- every arm got re-indented by one tab as expected. One tiny slip, though: this first case's ";;" didn't get the extra tab that every other arm received.

Elijah NewrenAug 7, 2026, 06:18 UTC in reply to Junio C Hamano on lore

Re: [PATCH v4 2/3] completion: complete tracked paths for 'git diff'

On Thu, Aug 6, 2026 at 6:38 PM Junio C Hamano <gitster@pobox.com> wrote:
Show 19 quoted lines
>
> When completing arguments for 'git diff', _git_diff() delegates to
> __git_complete_revlist_file(), which only completes revision
> references.  This is good [*], as mixing both revisions and paths in a
> single list for the user to pick from is simply too confusing.
>
> If no reference matches, or if '--' is given, however, _git_diff()
> leaves COMPREPLY empty.  Bash then falls back to default filename
> completion in $PWD.  This fails when 'git -C <path>' is used because
> $PWD is not the target repository.
>
> Update _git_diff() to use __git_complete_index_file() when '--' is
> present, or when revision reference completion yields no matching
> candidates, so that tracked paths are offered as candidates.
>
> This changes behavior even in the case where '-C <there>' is not
> used.  The new behavior omits untracked paths from suggestions when
> no revs match the prefix but matching tracked paths exist, which is
> more useful in the context of 'git diff'.
I'm looking forward to using this.  :-)
[...]
Show 13 quoted lines
> diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
> index ccd3b2a372..845fd19f70 100644
> --- a/contrib/completion/git-completion.bash
> +++ b/contrib/completion/git-completion.bash
> @@ -1981,6 +1981,10 @@ _git_diff ()
>                 esac
>                 __git_complete_revlist_file
>         fi
> +
> +       if [ ${#COMPREPLY[@]} -eq 0 ]; then
> +               __git_complete_index_file
> +       fi
>  }

Curious; __git_complete_index_file() is documented as "requires 1 argument", but you pass none here. As far as I can tell, it works anyway, but feels like an accident:

1.   __git_complete_index_file CALLS
      __git_index_files "$1" ...
      (Here, "$1" == "")
2.   __git_index_files "$1" ... CALLS
      __git_ls_files_helper "$root" "$1" ...
      (Here, "$1" == "", again)
3.   __git_ls_files_helper "$root" "$1" CALLS
      __git -C "$1" -c core.quotePath=false ls-files
--exclude-standard $2 -- ...
      (Note that $2 is unquoted, and since it's empty, it disappears)

It seems like it'd be better to pass an explicit "" to __git_complete_index_file than to implicitly get it.

[...] The rest looks good.

Elijah NewrenAug 7, 2026, 06:31 UTC in reply to Junio C Hamano on lore

Re: [PATCH v4 0/3] completion of 'git [-C <dir>] diff'

On Thu, Aug 6, 2026 at 6:38 PM Junio C Hamano <gitster@pobox.com> wrote:
Show 12 quoted lines
>
> The primary motivation for this topic is that the command-line
> completion of 'git diff' does not handle paths (unlike 'git status'
> and 'git add') and instead relies on the default behavior of Bash
> command-line completion, which completes files in $PWD; this does
> not work at all with the '-C <directory>' option.
>
> This series teaches the completion machinery to complete revisions
> (unless '--' exists), then tracked paths, and then untracked paths,
> before letting the Bash default kick in.  This way, we correctly
> complete 'git diff' command line even when '-C <directory>' is in
> effect.
Nice.  :-)
Show 7 quoted lines
> The tests are the only changes relative to v2.  In the step where
> tracked paths are completed, v2 did not demonstrate that untracked
> ones are *not* completed at the same time.  Now we do by having
> untracked 'file3' next to 'file1' and 'file2' that are tracked.  In
> the last step, we demonstrate untracked paths that do not share
> prefix with refs or tracked paths are completed, with or without the
> "-C <dir>" option.
I found this paragraph slightly hard to parse.  I think this means the same as:

Only the tests changed since v2. The tracked-paths step now also shows that untracked paths are not mixed in: an untracked 'file3' next to tracked 'file1'/'file2' means "git diff f" completes only the latter two. The untracked-paths step shows that an untracked path which shares no prefix with a ref or tracked path still completes, with or without "-C ".

(Not that it matters, since this is just the cover letter, but...)
>  1/3: completion: no-op refactoring of diff completion
>  2/3: completion: complete tracked paths for 'git diff'
>  3/3: completion: 'git diff' completes untracked paths as a last
>         resort

I found two minor nits in the first two patches, and didn't spot any issues with the third patch.

D. Ben KnobleAug 7, 2026, 11:02 UTC in reply to Elijah Newren on lore

Re: [PATCH v4 2/3] completion: complete tracked paths for 'git diff'

On Fri, Aug 7, 2026 at 2:18 AM Elijah Newren <newren@gmail.com> wrote:
Show 56 quoted lines
>
> On Thu, Aug 6, 2026 at 6:38 PM Junio C Hamano <gitster@pobox.com> wrote:
> >
> > When completing arguments for 'git diff', _git_diff() delegates to
> > __git_complete_revlist_file(), which only completes revision
> > references.  This is good [*], as mixing both revisions and paths in a
> > single list for the user to pick from is simply too confusing.
> >
> > If no reference matches, or if '--' is given, however, _git_diff()
> > leaves COMPREPLY empty.  Bash then falls back to default filename
> > completion in $PWD.  This fails when 'git -C <path>' is used because
> > $PWD is not the target repository.
> >
> > Update _git_diff() to use __git_complete_index_file() when '--' is
> > present, or when revision reference completion yields no matching
> > candidates, so that tracked paths are offered as candidates.
> >
> > This changes behavior even in the case where '-C <there>' is not
> > used.  The new behavior omits untracked paths from suggestions when
> > no revs match the prefix but matching tracked paths exist, which is
> > more useful in the context of 'git diff'.
>
> I'm looking forward to using this.  :-)
>
> [...]
> > diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
> > index ccd3b2a372..845fd19f70 100644
> > --- a/contrib/completion/git-completion.bash
> > +++ b/contrib/completion/git-completion.bash
> > @@ -1981,6 +1981,10 @@ _git_diff ()
> >                 esac
> >                 __git_complete_revlist_file
> >         fi
> > +
> > +       if [ ${#COMPREPLY[@]} -eq 0 ]; then
> > +               __git_complete_index_file
> > +       fi
> >  }
>
> Curious; __git_complete_index_file() is documented as "requires 1
> argument", but you pass none here.  As far as I can tell, it works
> anyway, but feels like an accident:
>
> 1.   __git_complete_index_file CALLS
>       __git_index_files "$1" ...
>       (Here, "$1" == "")
> 2.   __git_index_files "$1" ... CALLS
>       __git_ls_files_helper "$root" "$1" ...
>       (Here, "$1" == "", again)
> 3.   __git_ls_files_helper "$root" "$1" CALLS
>       __git -C "$1" -c core.quotePath=false ls-files
> --exclude-standard $2 -- ...
>       (Note that $2 is unquoted, and since it's empty, it disappears)
>
> It seems like it'd be better to pass an explicit "" to
> __git_complete_index_file than to implicitly get it.
Good spot. All the other callers pass an argument.
-- 
D. Ben Knoble
D. Ben KnobleAug 7, 2026, 11:05 UTC in reply to Elijah Newren on lore

Re: [PATCH v4 0/3] completion of 'git [-C <dir>] diff'

On Fri, Aug 7, 2026 at 2:32 AM Elijah Newren <newren@gmail.com> wrote:
Show 43 quoted lines
>
> On Thu, Aug 6, 2026 at 6:38 PM Junio C Hamano <gitster@pobox.com> wrote:
> >
> > The primary motivation for this topic is that the command-line
> > completion of 'git diff' does not handle paths (unlike 'git status'
> > and 'git add') and instead relies on the default behavior of Bash
> > command-line completion, which completes files in $PWD; this does
> > not work at all with the '-C <directory>' option.
> >
> > This series teaches the completion machinery to complete revisions
> > (unless '--' exists), then tracked paths, and then untracked paths,
> > before letting the Bash default kick in.  This way, we correctly
> > complete 'git diff' command line even when '-C <directory>' is in
> > effect.
>
> Nice.  :-)
>
> > The tests are the only changes relative to v2.  In the step where
> > tracked paths are completed, v2 did not demonstrate that untracked
> > ones are *not* completed at the same time.  Now we do by having
> > untracked 'file3' next to 'file1' and 'file2' that are tracked.  In
> > the last step, we demonstrate untracked paths that do not share
> > prefix with refs or tracked paths are completed, with or without the
> > "-C <dir>" option.
>
> I found this paragraph slightly hard to parse.  I think this means the same as:
>
> Only the tests changed since v2.  The tracked-paths step now also
> shows that untracked paths are not mixed in: an untracked 'file3'
> next to tracked 'file1'/'file2' means "git diff f" completes only the
> latter two.  The untracked-paths step shows that an untracked path
> which shares no prefix with a ref or tracked path still completes,
> with or without "-C ".
>
> (Not that it matters, since this is just the cover letter, but...)
>
> >  1/3: completion: no-op refactoring of diff completion
> >  2/3: completion: complete tracked paths for 'git diff'
> >  3/3: completion: 'git diff' completes untracked paths as a last
> >         resort
>
> I found two minor nits in the first two patches, and didn't spot any
> issues with the third patch.
Yep, modulo Elijah's comments (many eyes, thanks!) I'm happy with this round.
-- 
D. Ben Knoble
Junio C HamanoAug 7, 2026, 15:09 UTC in reply to Elijah Newren on lore

Re: [PATCH v4 1/3] completion: no-op refactoring of diff completion

Elijah Newren <newren@gmail.com> writes:
Show 38 quoted lines
> On Thu, Aug 6, 2026 at 6:38 PM Junio C Hamano <gitster@pobox.com> wrote:
>>
>> The "git diff" completion function punts very early when it sees
>> "--" on the command line, since it is a sign that options or
>> revisions can appear and the current completion does not need to do
>> anything "git diff" specific. By returning, it lets Bash default
>> action that completes the names of the files in $PWD to kick in.
>>
>> In preparation for the next step to change what happens when we
>> "punt", arrange the code flow to avoid this early return.  The
>> behaviour at this step is unchanged, but the control flow just
>> falls straight to the end.
>>
>> Signed-off-by: Junio C Hamano <gitster@pobox.com>
>> ---
>>  contrib/completion/git-completion.bash | 61 ++++++++++++++------------
>>  1 file changed, 33 insertions(+), 28 deletions(-)
>>
>> diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
>> index e875787710..ccd3b2a372 100644
>> --- a/contrib/completion/git-completion.bash
>> +++ b/contrib/completion/git-completion.bash
>> @@ -1947,35 +1947,40 @@ __git_diff_difftool_options="--cached --staged
>>
>>  _git_diff ()
>>  {
> [...]
>> +       if ! __git_has_doubledash; then
>> +               case "$cur" in
>> +               --diff-algorithm=*)
>> +                       __gitcomp "$__git_diff_algorithms" \
>> +                               "" "${cur##--diff-algorithm=}"
>> +                       return
>>                 ;;
>
> The refactor in this commit is a faithful no-op -- every arm got
> re-indented by one tab as expected.  One tiny slip, though: this first
> case's ";;" didn't get the extra tab that every other arm received.
Good eyes.  Will fix.
Junio C HamanoAug 7, 2026, 15:13 UTC in reply to Elijah Newren on lore

Re: [PATCH v4 2/3] completion: complete tracked paths for 'git diff'

Elijah Newren <newren@gmail.com> writes:
Show 55 quoted lines
> On Thu, Aug 6, 2026 at 6:38 PM Junio C Hamano <gitster@pobox.com> wrote:
>>
>> When completing arguments for 'git diff', _git_diff() delegates to
>> __git_complete_revlist_file(), which only completes revision
>> references.  This is good [*], as mixing both revisions and paths in a
>> single list for the user to pick from is simply too confusing.
>>
>> If no reference matches, or if '--' is given, however, _git_diff()
>> leaves COMPREPLY empty.  Bash then falls back to default filename
>> completion in $PWD.  This fails when 'git -C <path>' is used because
>> $PWD is not the target repository.
>>
>> Update _git_diff() to use __git_complete_index_file() when '--' is
>> present, or when revision reference completion yields no matching
>> candidates, so that tracked paths are offered as candidates.
>>
>> This changes behavior even in the case where '-C <there>' is not
>> used.  The new behavior omits untracked paths from suggestions when
>> no revs match the prefix but matching tracked paths exist, which is
>> more useful in the context of 'git diff'.
>
> I'm looking forward to using this.  :-)
>
> [...]
>> diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
>> index ccd3b2a372..845fd19f70 100644
>> --- a/contrib/completion/git-completion.bash
>> +++ b/contrib/completion/git-completion.bash
>> @@ -1981,6 +1981,10 @@ _git_diff ()
>>                 esac
>>                 __git_complete_revlist_file
>>         fi
>> +
>> +       if [ ${#COMPREPLY[@]} -eq 0 ]; then
>> +               __git_complete_index_file
>> +       fi
>>  }
>
> Curious; __git_complete_index_file() is documented as "requires 1
> argument", but you pass none here.  As far as I can tell, it works
> anyway, but feels like an accident:
>
> 1.   __git_complete_index_file CALLS
>       __git_index_files "$1" ...
>       (Here, "$1" == "")
> 2.   __git_index_files "$1" ... CALLS
>       __git_ls_files_helper "$root" "$1" ...
>       (Here, "$1" == "", again)
> 3.   __git_ls_files_helper "$root" "$1" CALLS
>       __git -C "$1" -c core.quotePath=false ls-files
> --exclude-standard $2 -- ...
>       (Note that $2 is unquoted, and since it's empty, it disappears)
>
> It seems like it'd be better to pass an explicit "" to
> __git_complete_index_file than to implicitly get it.

OK. It feels a bit strange as an API for the function to insist taking one and only one option, which forces the caller to do

	__git_complete_index_file "--cached --others --directory"

when the intention clearly is "we take zero or more options that we pass to ls-files", which would have been more obvious if the above were written as three separate parameters, but I'll do as Romans in the (hopefully small and final) reroll.

Thanks.
Elijah NewrenAug 7, 2026, 15:22 UTC in reply to Junio C Hamano on lore

Re: [PATCH v4 2/3] completion: complete tracked paths for 'git diff'

On Fri, Aug 7, 2026 at 8:13 AM Junio C Hamano <gitster@pobox.com> wrote:
Show 70 quoted lines
>
> Elijah Newren <newren@gmail.com> writes:
>
> > On Thu, Aug 6, 2026 at 6:38 PM Junio C Hamano <gitster@pobox.com> wrote:
> >>
> >> When completing arguments for 'git diff', _git_diff() delegates to
> >> __git_complete_revlist_file(), which only completes revision
> >> references.  This is good [*], as mixing both revisions and paths in a
> >> single list for the user to pick from is simply too confusing.
> >>
> >> If no reference matches, or if '--' is given, however, _git_diff()
> >> leaves COMPREPLY empty.  Bash then falls back to default filename
> >> completion in $PWD.  This fails when 'git -C <path>' is used because
> >> $PWD is not the target repository.
> >>
> >> Update _git_diff() to use __git_complete_index_file() when '--' is
> >> present, or when revision reference completion yields no matching
> >> candidates, so that tracked paths are offered as candidates.
> >>
> >> This changes behavior even in the case where '-C <there>' is not
> >> used.  The new behavior omits untracked paths from suggestions when
> >> no revs match the prefix but matching tracked paths exist, which is
> >> more useful in the context of 'git diff'.
> >
> > I'm looking forward to using this.  :-)
> >
> > [...]
> >> diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
> >> index ccd3b2a372..845fd19f70 100644
> >> --- a/contrib/completion/git-completion.bash
> >> +++ b/contrib/completion/git-completion.bash
> >> @@ -1981,6 +1981,10 @@ _git_diff ()
> >>                 esac
> >>                 __git_complete_revlist_file
> >>         fi
> >> +
> >> +       if [ ${#COMPREPLY[@]} -eq 0 ]; then
> >> +               __git_complete_index_file
> >> +       fi
> >>  }
> >
> > Curious; __git_complete_index_file() is documented as "requires 1
> > argument", but you pass none here.  As far as I can tell, it works
> > anyway, but feels like an accident:
> >
> > 1.   __git_complete_index_file CALLS
> >       __git_index_files "$1" ...
> >       (Here, "$1" == "")
> > 2.   __git_index_files "$1" ... CALLS
> >       __git_ls_files_helper "$root" "$1" ...
> >       (Here, "$1" == "", again)
> > 3.   __git_ls_files_helper "$root" "$1" CALLS
> >       __git -C "$1" -c core.quotePath=false ls-files
> > --exclude-standard $2 -- ...
> >       (Note that $2 is unquoted, and since it's empty, it disappears)
> >
> > It seems like it'd be better to pass an explicit "" to
> > __git_complete_index_file than to implicitly get it.
>
> OK.  It feels a bit strange as an API for the function to insist
> taking one and only one option, which forces the caller to do
>
>         __git_complete_index_file "--cached --others --directory"
>
> when the intention clearly is "we take zero or more options that we
> pass to ls-files", which would have been more obvious if the above
> were written as three separate parameters, but I'll do as Romans in
> the (hopefully small and final) reroll.
>
> Thanks.

Yeah, I don't disagree. I would be equally happy with an update to the __git_complete_index_file function to change the comment and explain what the first argument, if given, means (making it clear that no arguments are okay); but without either that or having your new caller pass an argument, the inconsistency between the documentation and this new caller felt like an issue someone might trip over in the future.

Junio C HamanoAug 7, 2026, 16:19 UTC in reply to Junio C Hamano on lore

[PATCH v5 0/3] completion of 'git [-C <dir>] diff'

The primary motivation for this topic is that the command-line completion of 'git diff' does not handle paths (unlike 'git status' and 'git add') and instead relies on the default behavior of Bash command-line completion, which completes files in $PWD; this does not work at all with the '-C <directory>' option.

This series teaches the completion machinery to complete revisions (unless '--' exists), then tracked paths, and then untracked paths, before letting the Bash default kick in. This way, we correctly complete 'git diff' command line even when '-C <directory>' is in effect.

The v5 iteration addresses two points identified by Elijah in v4. Hopefully this will be the "small and final" reroll.

 1/3: completion: no-op refactoring of diff completion
 2/3: completion: complete tracked paths for 'git diff'
 3/3: completion: 'git diff' completes untracked paths as a last
        resort
 contrib/completion/git-completion.bash | 69 +++++++++++++++-----------
 t/t9902-completion.sh                  | 59 ++++++++++++++++++++++
 2 files changed, 100 insertions(+), 28 deletions(-)
Range-diff against v4:
1:  d3c51c042c ! 1:  8295035d13 completion: no-op refactoring of diff completion
    @@ contrib/completion/git-completion.bash: __git_diff_difftool_options="--cached --
     -	--*)
     -		__gitcomp "$__git_diff_difftool_options"
     -		return
    +-		;;
    +-	esac
    +-	__git_complete_revlist_file
     +	if ! __git_has_doubledash; then
     +		case "$cur" in
     +		--diff-algorithm=*)
     +			__gitcomp "$__git_diff_algorithms" \
     +				"" "${cur##--diff-algorithm=}"
     +			return
    - 		;;
    --	esac
    --	__git_complete_revlist_file
    ++			;;
     +		--submodule=*)
     +			__gitcomp "$__git_diff_submodule_formats" \
     +				"" "${cur##--submodule=}"
2:  c3658d6ca2 ! 2:  dbb14298c1 completion: complete tracked paths for 'git diff'
    @@ contrib/completion/git-completion.bash: _git_diff ()
      	fi
     +
     +	if [ ${#COMPREPLY[@]} -eq 0 ]; then
    -+		__git_complete_index_file
    ++		__git_complete_index_file ""
     +	fi
      }
      
3:  ba5dc6f164 ! 3:  d661a1a5dd completion: 'git diff' completes untracked paths as a last resort
    @@ Commit message
      ## contrib/completion/git-completion.bash ##
     @@ contrib/completion/git-completion.bash: _git_diff ()
      	if [ ${#COMPREPLY[@]} -eq 0 ]; then
    - 		__git_complete_index_file
    + 		__git_complete_index_file ""
      	fi
     +
     +	if [ ${#COMPREPLY[@]} -eq 0 ]; then
-- 
2.55.0-655-g8b87133eb9
Junio C HamanoAug 7, 2026, 16:19 UTC in reply to Junio C Hamano on lore

[PATCH v5 1/3] completion: no-op refactoring of diff completion

The "git diff" completion function punts very early when it sees "--" on the command line, since it is a sign that options or revisions can appear and the current completion does not need to do anything "git diff" specific. By returning, it lets Bash default action that completes the names of the files in $PWD to kick in.

In preparation for the next step to change what happens when we "punt", arrange the code flow to avoid this early return. The behaviour at this step is unchanged, but the control flow just falls straight to the end.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 contrib/completion/git-completion.bash | 63 ++++++++++++++------------
 1 file changed, 34 insertions(+), 29 deletions(-)
Show changes to contrib/completion/git-completion.bash +34 −29
diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index e875787710..a61b6ed59a 100644
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -1947,35 +1947,40 @@ __git_diff_difftool_options="--cached --staged
 
 _git_diff ()
 {
-	__git_has_doubledash && return
-
-	case "$cur" in
-	--diff-algorithm=*)
-		__gitcomp "$__git_diff_algorithms" "" "${cur##--diff-algorithm=}"
-		return
-		;;
-	--submodule=*)
-		__gitcomp "$__git_diff_submodule_formats" "" "${cur##--submodule=}"
-		return
-		;;
-	--color-moved=*)
-		__gitcomp "$__git_color_moved_opts" "" "${cur##--color-moved=}"
-		return
-		;;
-	--color-moved-ws=*)
-		__gitcomp "$__git_color_moved_ws_opts" "" "${cur##--color-moved-ws=}"
-		return
-		;;
-	--ws-error-highlight=*)
-		__gitcomp "$__git_ws_error_highlight_opts" "" "${cur##--ws-error-highlight=}"
-		return
-		;;
-	--*)
-		__gitcomp "$__git_diff_difftool_options"
-		return
-		;;
-	esac
-	__git_complete_revlist_file
+	if ! __git_has_doubledash; then
+		case "$cur" in
+		--diff-algorithm=*)
+			__gitcomp "$__git_diff_algorithms" \
+				"" "${cur##--diff-algorithm=}"
+			return
+			;;
+		--submodule=*)
+			__gitcomp "$__git_diff_submodule_formats" \
+				"" "${cur##--submodule=}"
+			return
+			;;
+		--color-moved=*)
+			__gitcomp "$__git_color_moved_opts" \
+				"" "${cur##--color-moved=}"
+			return
+			;;
+		--color-moved-ws=*)
+			__gitcomp "$__git_color_moved_ws_opts" \
+				"" "${cur##--color-moved-ws=}"
+			return
+			;;
+		--ws-error-highlight=*)
+			__gitcomp "$__git_ws_error_highlight_opts" \
+				"" "${cur##--ws-error-highlight=}"
+			return
+			;;
+		--*)
+			__gitcomp "$__git_diff_difftool_options"
+			return
+			;;
+		esac
+		__git_complete_revlist_file
+	fi
 }
 
 __git_mergetools_common="diffuse diffmerge ecmerge emerge kdiff3 meld opendiff
-- 
2.55.0-655-g8b87133eb9
Junio C HamanoAug 7, 2026, 16:19 UTC in reply to Junio C Hamano on lore

[PATCH v5 2/3] completion: complete tracked paths for 'git diff'

When completing arguments for 'git diff', _git_diff() delegates to __git_complete_revlist_file(), which only completes revision references. This is good [*], as mixing both revisions and paths in a single list for the user to pick from is simply too confusing.

If no reference matches, or if '--' is given, however, _git_diff() leaves COMPREPLY empty. Bash then falls back to default filename completion in $PWD. This fails when 'git -C <path>' is used because $PWD is not the target repository.

Update _git_diff() to use __git_complete_index_file() when '--' is present, or when revision reference completion yields no matching candidates, so that tracked paths are offered as candidates.

This changes behavior even in the case where '-C <there>' is not used. The new behavior omits untracked paths from suggestions when no revs match the prefix but matching tracked paths exist, which is more useful in the context of 'git diff'.

When run outside the working tree of a repository, or when nothing matches from revisions or tracked paths, Bash still falls back to default filename completion in $PWD, so such a use case would be just like completing paths for any 'diff' command, rather than for 'git diff'.

[Footnote]
 * In https://lore.kernel.org/git/al%2Fw2qgBfhe9qMg6@szeder.dev/
   SZEDER made the same argument for "git send-email 0<TAB>".
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 contrib/completion/git-completion.bash |  4 +++
 t/t9902-completion.sh                  | 39 ++++++++++++++++++++++++++
 2 files changed, 43 insertions(+)
Show changes to 2 files +43 −0

contrib/completion/git-completion.bash, t/t9902-completion.sh

diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index a61b6ed59a..76181e8714 100644
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -1981,6 +1981,10 @@ _git_diff ()
 		esac
 		__git_complete_revlist_file
 	fi
+
+	if [ ${#COMPREPLY[@]} -eq 0 ]; then
+		__git_complete_index_file ""
+	fi
 }
 
 __git_mergetools_common="diffuse diffmerge ecmerge emerge kdiff3 meld opendiff
diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh
index 9ae3c48ebd..55361a89e1 100755
--- a/t/t9902-completion.sh
+++ b/t/t9902-completion.sh
@@ -2712,6 +2712,45 @@ test_expect_success 'git -C <path> checkout uses the right repo' '
 	EOF
 '
 
+test_expect_success 'git diff completes tracked paths when no refs match' '
+	# file1 and file2 are tracked but file3 is not
+	test_completion "git diff f" <<-\EOF
+	file1
+	file2
+	EOF
+'
+
+test_expect_success 'git diff -- completes tracked paths' '
+	# file1 and file2 are tracked but file3 is not
+	test_completion "git diff -- f" <<-\EOF
+	file1
+	file2
+	EOF
+'
+
+test_expect_success 'git -C <path> diff completes tracked paths in specified repo' '
+	test_when_finished "rm -rf repo-for-diff" &&
+	git init repo-for-diff &&
+	echo content >repo-for-diff/otherfile &&
+	git -C repo-for-diff add otherfile &&
+	echo untracked >repo-for-diff/oops &&
+	git -C repo-for-diff commit -m otherfile &&
+	test_completion "git -C repo-for-diff diff o" <<-\EOF
+	otherfile
+	EOF
+'
+
+test_expect_success 'git -C <path> diff -- completes pathspecs in specified repo' '
+	test_when_finished "rm -rf repo-for-diff" &&
+	git init repo-for-diff &&
+	echo content >repo-for-diff/otherfile &&
+	git -C repo-for-diff add otherfile &&
+	git -C repo-for-diff commit -m otherfile &&
+	test_completion "git -C repo-for-diff diff -- o" <<-\EOF
+	otherfile
+	EOF
+'
+
 test_expect_success 'show completes all refs' '
 	test_completion "git show m" <<-\EOF
 	main Z
-- 
2.55.0-655-g8b87133eb9
Junio C HamanoAug 7, 2026, 16:19 UTC in reply to Junio C Hamano on lore

[PATCH v5 3/3] completion: 'git diff' completes untracked paths as a last resort

We taught 'git diff' to first try to complete revisions (unless '--' is present on the command line) and, failing that, to complete tracked paths. If this yields nothing, it lets the Bash default, which offers paths in $PWD, kick in.

Teach it to complete untracked paths before giving up and letting the Bash default kick in. With this change,

    $ git -C another-directory diff un<TAB>

finds the 'untracked' file in another-directory and offers it as a completion candidate.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 contrib/completion/git-completion.bash |  4 ++++
 t/t9902-completion.sh                  | 22 +++++++++++++++++++++-
 2 files changed, 25 insertions(+), 1 deletion(-)
Show changes to 2 files +25 −1

contrib/completion/git-completion.bash, t/t9902-completion.sh

diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index 76181e8714..d35b4f3024 100644
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -1985,6 +1985,10 @@ _git_diff ()
 	if [ ${#COMPREPLY[@]} -eq 0 ]; then
 		__git_complete_index_file ""
 	fi
+
+	if [ ${#COMPREPLY[@]} -eq 0 ]; then
+		__git_complete_index_file "--others --directory"
+	fi
 }
 
 __git_mergetools_common="diffuse diffmerge ecmerge emerge kdiff3 meld opendiff
diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh
index 55361a89e1..85f92c552d 100755
--- a/t/t9902-completion.sh
+++ b/t/t9902-completion.sh
@@ -2663,6 +2663,7 @@ test_expect_success 'setup for integration tests' '
 	echo content >file1 &&
 	echo more >file2 &&
 	git add file1 file2 &&
+	echo untracked >ufile &&
 	git commit -m one &&
 	git branch mybranch &&
 	git tag mytag
@@ -2728,6 +2729,15 @@ test_expect_success 'git diff -- completes tracked paths' '
 	EOF
 '
 
+test_expect_success 'git diff [--] completes untracked paths, too' '
+	test_completion "git diff u" <<-\EOF &&
+	ufile
+	EOF
+	test_completion "git diff -- u" <<-\EOF
+	ufile
+	EOF
+'
+
 test_expect_success 'git -C <path> diff completes tracked paths in specified repo' '
 	test_when_finished "rm -rf repo-for-diff" &&
 	git init repo-for-diff &&
@@ -2744,11 +2754,21 @@ test_expect_success 'git -C <path> diff -- completes pathspecs in specified repo
 	test_when_finished "rm -rf repo-for-diff" &&
 	git init repo-for-diff &&
 	echo content >repo-for-diff/otherfile &&
+	echo untracked >repo-for-diff/untracked &&
 	git -C repo-for-diff add otherfile &&
 	git -C repo-for-diff commit -m otherfile &&
-	test_completion "git -C repo-for-diff diff -- o" <<-\EOF
+	test_completion "git -C repo-for-diff diff o" <<-\EOF &&
+	otherfile
+	EOF
+	test_completion "git -C repo-for-diff diff -- o" <<-\EOF &&
 	otherfile
 	EOF
+	test_completion "git -C repo-for-diff diff u" <<-\EOF &&
+	untracked
+	EOF
+	test_completion "git -C repo-for-diff diff -- u" <<-\EOF
+	untracked
+	EOF
 '
 
 test_expect_success 'show completes all refs' '
-- 
2.55.0-655-g8b87133eb9
Elijah NewrenAug 7, 2026, 16:53 UTC in reply to Junio C Hamano on lore

Re: [PATCH v5 0/3] completion of 'git [-C <dir>] diff'

On Fri, Aug 7, 2026 at 9:19 AM Junio C Hamano <gitster@pobox.com> wrote:
Show 15 quoted lines
>
> The primary motivation for this topic is that the command-line
> completion of 'git diff' does not handle paths (unlike 'git status'
> and 'git add') and instead relies on the default behavior of Bash
> command-line completion, which completes files in $PWD; this does
> not work at all with the '-C <directory>' option.
>
> This series teaches the completion machinery to complete revisions
> (unless '--' exists), then tracked paths, and then untracked paths,
> before letting the Bash default kick in.  This way, we correctly
> complete 'git diff' command line even when '-C <directory>' is in
> effect.
>
> The v5 iteration addresses two points identified by Elijah in v4.
> Hopefully this will be the "small and final" reroll.
Looks good to me!
Junio C HamanoAug 12, 2026, 16:25 UTC in reply to Junio C Hamano on lore

[PATCH v6 0/3] completion of 'git [-C <dir>] diff'

The primary motivation for this topic is that the command-line completion of 'git diff' does not handle paths (unlike 'git status' and 'git add') and instead relies on the default behavior of Bash command-line completion, which completes files in $PWD; this does not work at all with the '-C <directory>' option.

This series teaches the completion machinery to complete revisions (unless '--' exists), then tracked paths, and then untracked paths, before letting the Bash default kick in. This way, we correctly complete 'git diff' command line even when '-C <directory>' is in effect.

The changes since v5 are all concentrated in the tests. They test the same thing, but organized in a more logical and regular way.

This round hopefully will be the last one (famous last words).
 1/3: completion: no-op refactoring of diff completion
 2/3: completion: complete tracked paths for 'git diff'
 3/3: completion: 'git diff' completes untracked paths as a last
        resort
 contrib/completion/git-completion.bash | 69 +++++++++++++++-----------
 t/t9902-completion.sh                  | 58 ++++++++++++++++++++++
 2 files changed, 99 insertions(+), 28 deletions(-)
Range-diff against v5:
1:  74bfe67e24 = 1:  a5b2bdcd21 completion: no-op refactoring of diff completion
2:  f6892a66e9 ! 2:  cb84c1407c completion: complete tracked paths for 'git diff'
    @@ contrib/completion/git-completion.bash: _git_diff ()
      __git_mergetools_common="diffuse diffmerge ecmerge emerge kdiff3 meld opendiff
     
      ## t/t9902-completion.sh ##
    +@@ t/t9902-completion.sh: test_expect_success 'setup for integration tests' '
    + 	echo content >file1 &&
    + 	echo more >file2 &&
    + 	git add file1 file2 &&
    ++	echo untracked >file3 &&
    + 	git commit -m one &&
    + 	git branch mybranch &&
    + 	git tag mytag
     @@ t/t9902-completion.sh: test_expect_success 'git -C <path> checkout uses the right repo' '
      	EOF
      '
      
     +test_expect_success 'git diff completes tracked paths when no refs match' '
     +	# file1 and file2 are tracked but file3 is not
    -+	test_completion "git diff f" <<-\EOF
    ++	# there is no ref that begins with f
    ++	test_completion "git diff f" <<-\EOF &&
     +	file1
     +	file2
     +	EOF
    -+'
    -+
    -+test_expect_success 'git diff -- completes tracked paths' '
    -+	# file1 and file2 are tracked but file3 is not
     +	test_completion "git diff -- f" <<-\EOF
     +	file1
     +	file2
     +	EOF
     +'
     +
    -+test_expect_success 'git -C <path> diff completes tracked paths in specified repo' '
    ++test_expect_success 'git -C <path> diff completes paths in specified repo' '
     +	test_when_finished "rm -rf repo-for-diff" &&
     +	git init repo-for-diff &&
     +	echo content >repo-for-diff/otherfile &&
    ++	echo content >repo-for-diff/lostfile &&
     +	git -C repo-for-diff add otherfile &&
    -+	echo untracked >repo-for-diff/oops &&
    ++	git -C repo-for-diff add lostfile &&
     +	git -C repo-for-diff commit -m otherfile &&
    -+	test_completion "git -C repo-for-diff diff o" <<-\EOF
    ++	echo untracked >repo-for-diff/oops &&
    ++	rm -f repo-for-diff/lostfile &&
    ++
    ++	test_completion "git -C repo-for-diff diff o" <<-\EOF &&
     +	otherfile
     +	EOF
    -+'
    ++	test_completion "git -C repo-for-diff diff l" <<-\EOF &&
    ++	lostfile
    ++	EOF
     +
    -+test_expect_success 'git -C <path> diff -- completes pathspecs in specified repo' '
    -+	test_when_finished "rm -rf repo-for-diff" &&
    -+	git init repo-for-diff &&
    -+	echo content >repo-for-diff/otherfile &&
    -+	git -C repo-for-diff add otherfile &&
    -+	git -C repo-for-diff commit -m otherfile &&
    -+	test_completion "git -C repo-for-diff diff -- o" <<-\EOF
    ++	test_completion "git -C repo-for-diff diff -- o" <<-\EOF &&
     +	otherfile
     +	EOF
    ++	test_completion "git -C repo-for-diff diff -- l" <<-\EOF
    ++	lostfile
    ++	EOF
     +'
     +
      test_expect_success 'show completes all refs' '
3:  dcc5f881f2 ! 3:  d73c18e876 completion: 'git diff' completes untracked paths as a last resort
    @@ contrib/completion/git-completion.bash: _git_diff ()
     
      ## t/t9902-completion.sh ##
     @@ t/t9902-completion.sh: test_expect_success 'setup for integration tests' '
    - 	echo content >file1 &&
      	echo more >file2 &&
      	git add file1 file2 &&
    + 	echo untracked >file3 &&
     +	echo untracked >ufile &&
      	git commit -m one &&
      	git branch mybranch &&
      	git tag mytag
    -@@ t/t9902-completion.sh: test_expect_success 'git diff -- completes tracked paths' '
    +@@ t/t9902-completion.sh: test_expect_success 'git diff completes tracked paths when no refs match' '
      	EOF
      '
      
     +test_expect_success 'git diff [--] completes untracked paths, too' '
    ++	# ufile is not tracked and there is no ref that begins with u
     +	test_completion "git diff u" <<-\EOF &&
     +	ufile
     +	EOF
    @@ t/t9902-completion.sh: test_expect_success 'git diff -- completes tracked paths'
     +	EOF
     +'
     +
    - test_expect_success 'git -C <path> diff completes tracked paths in specified repo' '
    + test_expect_success 'git -C <path> diff completes paths in specified repo' '
      	test_when_finished "rm -rf repo-for-diff" &&
      	git init repo-for-diff &&
    -@@ t/t9902-completion.sh: test_expect_success 'git -C <path> diff -- completes pathspecs in specified repo
    - 	test_when_finished "rm -rf repo-for-diff" &&
    - 	git init repo-for-diff &&
    - 	echo content >repo-for-diff/otherfile &&
    -+	echo untracked >repo-for-diff/untracked &&
    - 	git -C repo-for-diff add otherfile &&
    +@@ t/t9902-completion.sh: test_expect_success 'git -C <path> diff completes paths in specified repo' '
    + 	git -C repo-for-diff add lostfile &&
      	git -C repo-for-diff commit -m otherfile &&
    --	test_completion "git -C repo-for-diff diff -- o" <<-\EOF
    -+	test_completion "git -C repo-for-diff diff o" <<-\EOF &&
    -+	otherfile
    -+	EOF
    -+	test_completion "git -C repo-for-diff diff -- o" <<-\EOF &&
    - 	otherfile
    + 	echo untracked >repo-for-diff/oops &&
    ++	echo untracked >repo-for-diff/ufile &&
    + 	rm -f repo-for-diff/lostfile &&
    + 
    + 	test_completion "git -C repo-for-diff diff o" <<-\EOF &&
    +@@ t/t9902-completion.sh: test_expect_success 'git -C <path> diff completes paths in specified repo' '
    + 	test_completion "git -C repo-for-diff diff l" <<-\EOF &&
    + 	lostfile
      	EOF
     +	test_completion "git -C repo-for-diff diff u" <<-\EOF &&
    -+	untracked
    ++	ufile
     +	EOF
    + 
    + 	test_completion "git -C repo-for-diff diff -- o" <<-\EOF &&
    + 	otherfile
    + 	EOF
    +-	test_completion "git -C repo-for-diff diff -- l" <<-\EOF
    ++	test_completion "git -C repo-for-diff diff -- l" <<-\EOF &&
    + 	lostfile
    + 	EOF
     +	test_completion "git -C repo-for-diff diff -- u" <<-\EOF
    -+	untracked
    ++	ufile
     +	EOF
      '
      
-- 
2.55.0-721-gd75157efe4
Junio C HamanoAug 12, 2026, 16:25 UTC in reply to Junio C Hamano on lore

[PATCH v6 1/3] completion: no-op refactoring of diff completion

The "git diff" completion function punts very early when it sees "--" on the command line, since it is a sign that options or revisions can appear and the current completion does not need to do anything "git diff" specific. By returning, it lets Bash default action that completes the names of the files in $PWD to kick in.

In preparation for the next step to change what happens when we "punt", arrange the code flow to avoid this early return. The behaviour at this step is unchanged, but the control flow just falls straight to the end.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 contrib/completion/git-completion.bash | 63 ++++++++++++++------------
 1 file changed, 34 insertions(+), 29 deletions(-)
Show changes to contrib/completion/git-completion.bash +34 −29
diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index e875787710..a61b6ed59a 100644
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -1947,35 +1947,40 @@ __git_diff_difftool_options="--cached --staged
 
 _git_diff ()
 {
-	__git_has_doubledash && return
-
-	case "$cur" in
-	--diff-algorithm=*)
-		__gitcomp "$__git_diff_algorithms" "" "${cur##--diff-algorithm=}"
-		return
-		;;
-	--submodule=*)
-		__gitcomp "$__git_diff_submodule_formats" "" "${cur##--submodule=}"
-		return
-		;;
-	--color-moved=*)
-		__gitcomp "$__git_color_moved_opts" "" "${cur##--color-moved=}"
-		return
-		;;
-	--color-moved-ws=*)
-		__gitcomp "$__git_color_moved_ws_opts" "" "${cur##--color-moved-ws=}"
-		return
-		;;
-	--ws-error-highlight=*)
-		__gitcomp "$__git_ws_error_highlight_opts" "" "${cur##--ws-error-highlight=}"
-		return
-		;;
-	--*)
-		__gitcomp "$__git_diff_difftool_options"
-		return
-		;;
-	esac
-	__git_complete_revlist_file
+	if ! __git_has_doubledash; then
+		case "$cur" in
+		--diff-algorithm=*)
+			__gitcomp "$__git_diff_algorithms" \
+				"" "${cur##--diff-algorithm=}"
+			return
+			;;
+		--submodule=*)
+			__gitcomp "$__git_diff_submodule_formats" \
+				"" "${cur##--submodule=}"
+			return
+			;;
+		--color-moved=*)
+			__gitcomp "$__git_color_moved_opts" \
+				"" "${cur##--color-moved=}"
+			return
+			;;
+		--color-moved-ws=*)
+			__gitcomp "$__git_color_moved_ws_opts" \
+				"" "${cur##--color-moved-ws=}"
+			return
+			;;
+		--ws-error-highlight=*)
+			__gitcomp "$__git_ws_error_highlight_opts" \
+				"" "${cur##--ws-error-highlight=}"
+			return
+			;;
+		--*)
+			__gitcomp "$__git_diff_difftool_options"
+			return
+			;;
+		esac
+		__git_complete_revlist_file
+	fi
 }
 
 __git_mergetools_common="diffuse diffmerge ecmerge emerge kdiff3 meld opendiff
-- 
2.55.0-721-gd75157efe4
Junio C HamanoAug 12, 2026, 16:25 UTC in reply to Junio C Hamano on lore

[PATCH v6 2/3] completion: complete tracked paths for 'git diff'

When completing arguments for 'git diff', _git_diff() delegates to __git_complete_revlist_file(), which only completes revision references. This is good [*], as mixing both revisions and paths in a single list for the user to pick from is simply too confusing.

If no reference matches, or if '--' is given, however, _git_diff() leaves COMPREPLY empty. Bash then falls back to default filename completion in $PWD. This fails when 'git -C <path>' is used because $PWD is not the target repository.

Update _git_diff() to use __git_complete_index_file() when '--' is present, or when revision reference completion yields no matching candidates, so that tracked paths are offered as candidates.

This changes behavior even in the case where '-C <there>' is not used. The new behavior omits untracked paths from suggestions when no revs match the prefix but matching tracked paths exist, which is more useful in the context of 'git diff'.

When run outside the working tree of a repository, or when nothing matches from revisions or tracked paths, Bash still falls back to default filename completion in $PWD, so such a use case would be just like completing paths for any 'diff' command, rather than for 'git diff'.

[Footnote]
 * In https://lore.kernel.org/git/al%2Fw2qgBfhe9qMg6@szeder.dev/
   SZEDER made the same argument for "git send-email 0<TAB>".
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 contrib/completion/git-completion.bash |  4 +++
 t/t9902-completion.sh                  | 40 ++++++++++++++++++++++++++
 2 files changed, 44 insertions(+)
Show changes to 2 files +44 −0

contrib/completion/git-completion.bash, t/t9902-completion.sh

diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index a61b6ed59a..76181e8714 100644
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -1981,6 +1981,10 @@ _git_diff ()
 		esac
 		__git_complete_revlist_file
 	fi
+
+	if [ ${#COMPREPLY[@]} -eq 0 ]; then
+		__git_complete_index_file ""
+	fi
 }
 
 __git_mergetools_common="diffuse diffmerge ecmerge emerge kdiff3 meld opendiff
diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh
index 55dc9eabfc..32e5d484c7 100755
--- a/t/t9902-completion.sh
+++ b/t/t9902-completion.sh
@@ -2663,6 +2663,7 @@ test_expect_success 'setup for integration tests' '
 	echo content >file1 &&
 	echo more >file2 &&
 	git add file1 file2 &&
+	echo untracked >file3 &&
 	git commit -m one &&
 	git branch mybranch &&
 	git tag mytag
@@ -2712,6 +2713,45 @@ test_expect_success 'git -C <path> checkout uses the right repo' '
 	EOF
 '
 
+test_expect_success 'git diff completes tracked paths when no refs match' '
+	# file1 and file2 are tracked but file3 is not
+	# there is no ref that begins with f
+	test_completion "git diff f" <<-\EOF &&
+	file1
+	file2
+	EOF
+	test_completion "git diff -- f" <<-\EOF
+	file1
+	file2
+	EOF
+'
+
+test_expect_success 'git -C <path> diff completes paths in specified repo' '
+	test_when_finished "rm -rf repo-for-diff" &&
+	git init repo-for-diff &&
+	echo content >repo-for-diff/otherfile &&
+	echo content >repo-for-diff/lostfile &&
+	git -C repo-for-diff add otherfile &&
+	git -C repo-for-diff add lostfile &&
+	git -C repo-for-diff commit -m otherfile &&
+	echo untracked >repo-for-diff/oops &&
+	rm -f repo-for-diff/lostfile &&
+
+	test_completion "git -C repo-for-diff diff o" <<-\EOF &&
+	otherfile
+	EOF
+	test_completion "git -C repo-for-diff diff l" <<-\EOF &&
+	lostfile
+	EOF
+
+	test_completion "git -C repo-for-diff diff -- o" <<-\EOF &&
+	otherfile
+	EOF
+	test_completion "git -C repo-for-diff diff -- l" <<-\EOF
+	lostfile
+	EOF
+'
+
 test_expect_success 'show completes all refs' '
 	test_completion "git show m" <<-\EOF
 	main Z
-- 
2.55.0-721-gd75157efe4
Junio C HamanoAug 12, 2026, 16:25 UTC in reply to Junio C Hamano on lore

[PATCH v6 3/3] completion: 'git diff' completes untracked paths as a last resort

We taught 'git diff' to first try to complete revisions (unless '--' is present on the command line) and, failing that, to complete tracked paths. If this yields nothing, it lets the Bash default, which offers paths in $PWD, kick in.

Teach it to complete untracked paths before giving up and letting the Bash default kick in. With this change,

    $ git -C another-directory diff un<TAB>

finds the 'untracked' file in another-directory and offers it as a completion candidate.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 contrib/completion/git-completion.bash |  4 ++++
 t/t9902-completion.sh                  | 20 +++++++++++++++++++-
 2 files changed, 23 insertions(+), 1 deletion(-)
Show changes to 2 files +23 −1

contrib/completion/git-completion.bash, t/t9902-completion.sh

diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index 76181e8714..d35b4f3024 100644
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -1985,6 +1985,10 @@ _git_diff ()
 	if [ ${#COMPREPLY[@]} -eq 0 ]; then
 		__git_complete_index_file ""
 	fi
+
+	if [ ${#COMPREPLY[@]} -eq 0 ]; then
+		__git_complete_index_file "--others --directory"
+	fi
 }
 
 __git_mergetools_common="diffuse diffmerge ecmerge emerge kdiff3 meld opendiff
diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh
index 32e5d484c7..b889ec8c77 100755
--- a/t/t9902-completion.sh
+++ b/t/t9902-completion.sh
@@ -2664,6 +2664,7 @@ test_expect_success 'setup for integration tests' '
 	echo more >file2 &&
 	git add file1 file2 &&
 	echo untracked >file3 &&
+	echo untracked >ufile &&
 	git commit -m one &&
 	git branch mybranch &&
 	git tag mytag
@@ -2726,6 +2727,16 @@ test_expect_success 'git diff completes tracked paths when no refs match' '
 	EOF
 '
 
+test_expect_success 'git diff [--] completes untracked paths, too' '
+	# ufile is not tracked and there is no ref that begins with u
+	test_completion "git diff u" <<-\EOF &&
+	ufile
+	EOF
+	test_completion "git diff -- u" <<-\EOF
+	ufile
+	EOF
+'
+
 test_expect_success 'git -C <path> diff completes paths in specified repo' '
 	test_when_finished "rm -rf repo-for-diff" &&
 	git init repo-for-diff &&
@@ -2735,6 +2746,7 @@ test_expect_success 'git -C <path> diff completes paths in specified repo' '
 	git -C repo-for-diff add lostfile &&
 	git -C repo-for-diff commit -m otherfile &&
 	echo untracked >repo-for-diff/oops &&
+	echo untracked >repo-for-diff/ufile &&
 	rm -f repo-for-diff/lostfile &&
 
 	test_completion "git -C repo-for-diff diff o" <<-\EOF &&
@@ -2743,13 +2755,19 @@ test_expect_success 'git -C <path> diff completes paths in specified repo' '
 	test_completion "git -C repo-for-diff diff l" <<-\EOF &&
 	lostfile
 	EOF
+	test_completion "git -C repo-for-diff diff u" <<-\EOF &&
+	ufile
+	EOF
 
 	test_completion "git -C repo-for-diff diff -- o" <<-\EOF &&
 	otherfile
 	EOF
-	test_completion "git -C repo-for-diff diff -- l" <<-\EOF
+	test_completion "git -C repo-for-diff diff -- l" <<-\EOF &&
 	lostfile
 	EOF
+	test_completion "git -C repo-for-diff diff -- u" <<-\EOF
+	ufile
+	EOF
 '
 
 test_expect_success 'show completes all refs' '
-- 
2.55.0-721-gd75157efe4

Back to recent threads