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>
main
Junio C Hamano 2026-08-12 09:25:50 -07:00
parent a039ee6757
commit 4b1b7a4e95
2 changed files with 44 additions and 0 deletions

View File

@ -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

View File

@ -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