Re: [PATCH v2] completion: zsh: support completion after "git -C <path>"
From: D. Ben Knoble <hidden>
Date: 2026-08-20 12:28:45
On Wed, Aug 19, 2026 at 9:07 AM Lutz Lengemann via GitGitGadget [off-list ref] wrote:
From: Lutz Lengemann <redacted>
The zsh completion wrapper does not handle the global -C option, so
git -C <path> <command> <TAB>
offers nothing. -C is not part of the _arguments specification, and the
wrapper hard-codes __git_cmd_idx=1, i.e. it assumes that the command is
the first argument, so the bash helpers look at the wrong word. The
latter is not specific to -C; the assumption breaks after any global
option, e.g. "git -p checkout <TAB>" does not complete branch names.
Add -C to the specification, and find the command by skipping over the
global options and, where they take one, their arguments, as __git_main
in git-completion.bash does. The index is one less than zsh's, as the
helpers count the words from zero. Collect the paths given to -C into
__git_C_args, or else the helpers run git in the current directory and
fail to resolve the aliases and refs of the repository the command runs
in.
The argument of a -C is still completed without regard for the -C
options before it, i.e. "git -C dir -C <TAB>" offers the directories in
".", not the ones in "dir".
Signed-off-by: Lutz Lengemann <redacted>
---
completion: zsh: support completion after "git -C "
* The command is now located by walking the global options in front of
it, mirroring the loop at the beginning of __git_main in
git-completion.bash, instead of skipping only leading -C options.
This also fixes argument completion after other global options, e.g.
git -p checkout <TAB>.
* The log message uses the present tense for the pre-image and notes
that the argument of a -C is completed without regard for the -C
options before it.
cc: Ben Knoble ben.knoble@gmail.com cc: Junio C Hamano gitster@pobox.com
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-2155%2Fmobilutz%2Fzsh-complete-global-C-v2
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-2155/mobilutz/zsh-complete-global-C-v2
Pull-Request: https://github.com/gitgitgadget/git/pull/2155
Range-diff vs v1:
1: 9739cde6fc ! 1: 9984228f1f completion: zsh: support completion after "git -C <path>"
@@ Metadata[snip]
Signed-off-by: Lutz Lengemann [off-list ref]
@@ contrib/completion/git-completion.zsh: __git_zsh_main ()
'(- :)--version[prints the git suite version]' \
'--exec-path=-[path to where your core git programs are installed]:: :_directories' \
@@ contrib/completion/git-completion.zsh: __git_zsh_main ()
+ done
;;
(arg)
- local command="${words[1]}" __git_dir __git_cmd_idx=1
+- local command="${words[1]}" __git_dir __git_cmd_idx=1
++ local command="${words[1]}" __git_dir __git_cmd_idxOk, this matches what the message describes about __git_cmd_idx not being able to assume=1; it's different in this version because we are a bit more sophisticated in our parsing.
+ local -a __git_C_args
+ local -i i=2
+
-+ while [[ ${orig_words[i]} == -C ]]; do
-+ __git_C_args+=(-C ${orig_words[i+1]})
-+ (( __git_cmd_idx += 2 ))
-+ (( i += 2 ))
++ while (( i <= $#orig_words )); do
++ case ${orig_words[i]} in
++ -C)
++ __git_C_args+=(-C ${orig_words[i+1]})
++ (( i++ ))At first I thought "should that be i+=2?"; then I saw the unconditional i++ later. Reasonable, though I'm not sure what happens if we walk off the end of the array here: If i=#orig_words, then __git_C_args has (-C) and i becomes #orig_words+2; later, __git_cmd_idx becomes #orig_words+1, which is empty. I'll keep that in mind when looking at how we handle those variables. …Ok, those are handled in the Bash completion. AFAICT, they don't do anything special when the dir is missing either. A bit strange, but not something this patch needs to solve, I suppose. __git_cmd_idx is used many places, as we would imagine, and I didn't look carefully at what happens when it indexes an empty spot (but it looks to mostly be used in comparisons where that would just go falsy, or in arithmetic I haven't really checked at all). (I also haven't thought carefully about the difference between Zsh's 1-based indexing and Bash's 0-based, so I'm not sure if there's an issue lurking there.)
++ ;;
++ -c|--git-dir|--work-tree|--namespace)
++ (( i++ ))
++ ;;
++ -*)
++ ;;Yep, unlike Bash (which requires at least one command in the "list" part between a pattern and the terminator), Zsh accepts empty actions here.
quoted hunk ↗ jump to hunk
++ *) ++ break ++ ;; ++ esac ++ (( i++ )) + done ++ ++ __git_cmd_idx=$(( i - 1 )) if (( $+opt_args[--bare] )); then __git_dir='.' contrib/completion/git-completion.zsh | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-)diff --git a/contrib/completion/git-completion.zsh b/contrib/completion/git-completion.zsh index c32186a977..d5c526665b 100644 --- a/contrib/completion/git-completion.zsh +++ b/contrib/completion/git-completion.zsh@@ -227,6 +227,7 @@ __git_zsh_main () '(-p --paginate --no-pager)'{-p,--paginate}'[pipe all output into ''less'']' \ '(-p --paginate)--no-pager[do not pipe git output into a pager]' \ '--git-dir=-[set the path to the repository]: :_directories' \ + '*-C[run as if git was started in <path>]: :_directories' \
At first I wasn't sure about the blank description (space between 2 colons) of the argument to -C, but I see that _directories automatically describes the completed thing as "directory," so that's fine. Overall, if this version works, I think I'm happy with it. Confirming the index math works out between the 2 shells might be a useful exercise, but /shrug. -- D. Ben Knoble