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, which is clearly better behavior. 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>

Junio C Hamano committed Aug 2, 2026 at 17:58 UTC 8983cb342d038c081a081147faee8bda3b1b9b97
2 files changed +47 -1
contrib/completion/git-completion.bash
+7 -1
@@ -1947,7 +1947,10 @@ __git_diff_difftool_options="--cached --staged
1947
1948 _git_diff ()
1949 {
1950 - __git_has_doubledash && return
1950 + if __git_has_doubledash; then
1951 + __git_complete_index_file
1952 + return
1953 + fi
1954
1955 case "$cur" in
1956 --diff-algorithm=*)
@@ -1976,6 +1979,9 @@ _git_diff ()
1979 ;;
1980 esac
1981 __git_complete_revlist_file
1982 + if [ ${#COMPREPLY[@]} -eq 0 ]; then
1983 + __git_complete_index_file
1984 + fi
1985 }
1986
1987 __git_mergetools_common="diffuse diffmerge ecmerge emerge kdiff3 meld opendiff
t/t9902-completion.sh
+40
@@ -2663,6 +2663,7 @@ test_expect_success 'setup for integration tests' '
2663 echo content >file1 &&
2664 echo more >file2 &&
2665 git add file1 file2 &&
2666 + echo untracked >file3 &&
2667 git commit -m one &&
2668 git branch mybranch &&
2669 git tag mytag
@@ -2712,6 +2713,45 @@ test_expect_success 'git -C <path> checkout uses the right repo' '
2713 EOF
2714 '
2715
2716 +test_expect_success 'git diff completes tracked paths when no refs match' '
2717 + # file1 and file2 are tracked but file3 is not
2718 + test_completion "git diff f" <<-\EOF
2719 + file1
2720 + file2
2721 + EOF
2722 +'
2723 +
2724 +test_expect_success 'git diff -- completes tracked paths' '
2725 + # file1 and file2 are tracked but file3 is not
2726 + test_completion "git diff -- f" <<-\EOF
2727 + file1
2728 + file2
2729 + EOF
2730 +'
2731 +
2732 +test_expect_success 'git -C <path> diff completes tracked paths in specified repo' '
2733 + test_when_finished "rm -rf repo-for-diff" &&
2734 + git init repo-for-diff &&
2735 + echo content >repo-for-diff/otherfile &&
2736 + git -C repo-for-diff add otherfile &&
2737 + echo untracked >repo-for-diff/oops &&
2738 + git -C repo-for-diff commit -m otherfile &&
2739 + test_completion "git -C repo-for-diff diff o" <<-\EOF
2740 + otherfile
2741 + EOF
2742 +'
2743 +
2744 +test_expect_success 'git -C <path> diff -- completes pathspecs in specified repo' '
2745 + test_when_finished "rm -rf repo-for-diff" &&
2746 + git init repo-for-diff &&
2747 + echo content >repo-for-diff/otherfile &&
2748 + git -C repo-for-diff add otherfile &&
2749 + git -C repo-for-diff commit -m otherfile &&
2750 + test_completion "git -C repo-for-diff diff -- o" <<-\EOF
2751 + otherfile
2752 + EOF
2753 +'
2754 +
2755 test_expect_success 'show completes all refs' '
2756 test_completion "git show m" <<-\EOF
2757 main Z