From d3c51c042ccc54939bd0bda02d4a2bd3ec9b88f2 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 5 Aug 2026 12:42:48 -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 | 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 () { - __git_has_doubledash && return - - case "$cur" in - --diff-algorithm=*) - __gitcomp "$__git_diff_algorithms" "" "${cur##--diff-algorithm=}" - return + 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 + --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 c3658d6ca286c41e74709c4304c8cd2635619c45 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 5 Aug 2026 12:42:49 -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 | 39 ++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) 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 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 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 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 From ba5dc6f164d53b872fff8067930ae9fa1280f3b3 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 5 Aug 2026 12:42:50 -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 | 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 +' + test_expect_success 'git -C 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 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' '