Thread (15 messages) flat view 15 messages, 4 authors, 21d ago

Re: [PATCH] completion: zsh: support completion after "git -C <path>"

From: D. Ben Knoble <hidden>
Date: 2026-06-18 17:43:36

[apologies in advance for the strange format below]

On Wed, Jun 17, 2026 at 11:37 AM Lutz Lengemann via GitGitGadget
[off-list ref] wrote:
From: Lutz Lengemann <redacted>

The zsh completion wrapper (__git_zsh_main) did not handle the global -C
option, so "git -C <path> <command> <TAB>" offered nothing and could not
complete a command's arguments.

Three things are needed to make it work, all scoped to -C:

  - Add -C to the _arguments specification, so completion no longer stops
    at it.

  - Advance __git_cmd_idx past any leading "-C <path>" options. The index
    is hard-coded to 1, i.e. the command is assumed to be the first
    argument; with -C present the command sits two words later for each
    -C, so the bash helpers otherwise look at the wrong word and produce
    nothing.

  - Collect the -C paths into __git_C_args, as __git_main does. The bash
    helpers run git to resolve aliases and list refs; without the -C
    paths they run in the current directory, so completion fails whenever
    the cwd is not the target repository or the command is an alias.

With these, "git -C <path> <command> <TAB>" completes the command, its
options and its arguments, including outside the repository, through
aliases, and with repeated -C options.

Signed-off-by: Lutz Lengemann <redacted>
---
    completion: zsh: support completion after "git -C "

    This patch is intentionally scoped to -C, but the underlying problem is
    more general. The zsh wrapper hard-codes __git_cmd_idx=1, i.e. it
    assumes the command is always the first argument. That assumption breaks
    argument completion after any global option that precedes the command,
    not just -C — e.g. --git-dir, --work-tree, --namespace, -c, and
    -p/--paginate. After those, git <opt> <command> <TAB> currently
    completes the command name but not its arguments.

    The same approach generalizes cleanly: instead of skipping only leading
    -C options, walk all leading global options and their arguments to
    locate the command and its true index (mirroring the option scan in
    __git_main in git-completion.bash), while collecting -C into
    __git_C_args and --git-dir into __git_dir as today.

    I kept this revision narrow for reviewability and because git -C is the
    case where I miss the completion, but I'm happy to extend it to cover
    the other global options in a follow-up (or fold it into this patch) if
    that's preferred.
See Junio's review for whether we should expand in this patch or a follow-up.

In reply to Junio:
[the new handling only knows about -C]
Doesn't it want to do something similar to what __git_main in
git-completion.bash does at the beginning, namely, this part?
Yeah, we probably do want to skip over -c, etc. (I see some support for
--bare and --git-dir, but not skipping over it.) Still, this patch makes
things no worse in that regard, and improves the situation for -C
AFAICT.

In reply to Lutz:
+        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 ))
+        done
I don't see either of these 2 local variables used anywhere else…

…well, except the Bash completion helpers, I suppose. But we mark these
local, so how do they propagate to the other functions?

Still, I was able to try this out with the somewhat hacky

    zsh # new shell :)
    # absolute path important
    autoload -Uz $PWD/contrib/completion/git-completion.zsh
    compdef git-completion.zsh git

    git -C <tab>

and it does prioritize directories there (though I still get a listing
of files afterwards, so the screen is taken up by that gigantic listing
in git.git, for example).

By the way, I've realized that "git -<tab>" has the same problem (a
giant list of files after the other option completions), and worse has
some _funky_ output!

    git -<tab> # without patch
    (option)
    --bare
    --exec-path
    --git-dir
    --help
    --html-path
    --info-path
    --man-path
    --namespace
    --no-pager
    --no-replace-objects
    --paginate
    --version
    --work-tree

    -p

    # treat the repository as a bare repository
    # path to where your core git programs are installed
    # set the path to the repository
    # prints the synopsis and a list of the most commonly used commands
    # print the path where gits HTML documentation is installed
    # print the path where the Info files are installed
    # print the manpath (see `man(1)`) for the man pages
    # set the git namespace
    # do not pipe git output into a pager
    # do not use replacement refs to replace git objects
    # pipe all output into less
    # prints the git suite version
    # set the path to the working tree
    [ed: the above block repeats twice more before the (file) listing below]
    (file)
    […]

Here's the output of _complete_help (^Xh by default) in both situations,
in case that helps to understand either the extra files listing (1) in
the example further back or the issue with single letter options (2)
just mentioned:

1: tags in context :completion::complete:git::
    option-C-1     (_arguments __git_zsh_main _git git-completion.zsh)
    use-compctl    (_default _git git-completion.zsh)
    globbed-files  (_files _default _git git-completion.zsh)
tags in context :completion::complete:git:option-C-1:
    directories    (_directories _arguments __git_zsh_main _git
git-completion.zsh)
    globbed-files  (_files _directories _arguments __git_zsh_main _git
git-completion.zsh)
    all-files      (_files _directories _arguments __git_zsh_main _git
git-completion.zsh)

2: tags in context :completion::complete:git::
    argument-1 options  (_arguments __git_zsh_main _git)
    use-compctl         (_default _git)
    globbed-files       (_files _default _git)
tags in context :completion::complete:git:argument-1:
    common-commands alias-commands all-commands  (__git_zsh_main _git)
    common-commands                              (__git_zsh_cmd_common
__git_zsh_main _git)
    alias-commands                               (__git_zsh_cmd_alias
__git_zsh_main _git)
    all-commands                                 (__git_zsh_cmd_all
__git_zsh_main _git)
tags in context :completion::complete:git:options:
    options  (_arguments __git_zsh_main _git)
+        '*-C[run as if git was started in <path>]: :_directories' \
We should probably note in the log message that the _directories
completion will not account for previous -C; that is, after typing

    git -C dir -C <tab>

we will complete directories in ".", not "dir". That's probably a
reasonable limitation for now, but I think we could do _slightly_ better
by using a state "->dir" or something, accumulating the current prefix,
and passing that to _directories as a prefix with -W (see _path_files in
zshcompsys, which _directories delegates to via _files, IIUC).

-- 
D. Ben Knoble
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help