From a039ee6757c19b7f341e9796564ea7c93faca6b5 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 12 Aug 2026 09:25:49 -0700 Subject: [PATCH 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 --- contrib/completion/git-completion.bash | 63 ++++++++++++++------------ 1 file changed, 34 insertions(+), 29 deletions(-) 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 From 4b1b7a4e95af51c87e023fd96d9da0d4719e33bd Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 12 Aug 2026 09:25:50 -0700 Subject: [PATCH 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 ' 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 ' 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". Signed-off-by: Junio C Hamano --- contrib/completion/git-completion.bash | 4 +++ t/t9902-completion.sh | 40 ++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) 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 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 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 From 354d1bf3a094c1ac9ff6c1b4931a0f7e563aef93 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 12 Aug 2026 09:25:51 -0700 Subject: [PATCH 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 finds the 'untracked' file in another-directory and offers it as a completion candidate. Signed-off-by: Junio C Hamano --- contrib/completion/git-completion.bash | 4 ++++ t/t9902-completion.sh | 20 +++++++++++++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) 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 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 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 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' '