Tweaking zsh completion for git checkout

By default, zsh completion allows you to complete according to “git checkout” semantics. So git checkout path/to/<TAB> will actually work for updating files from the index (and some other similar cases).

There’s nothing to say about it, it’s obviously correct. Now, there are 2 things that make it inconvenient for me:

  • first of all, I happen to work regularly with huge repositories, with huge working directories (approximately 35GB). Since the completion code ends up calling git ls-files, even caching doesn’t help that much reducing the time spent building the completion table. And I wouldn’t want to pay the price even the first time.
  • second, my branches generally use a directory-like layout (like t/bugfix/<id>, so that it makes the whole completion list rather confusing.

Due to the second point, I happen to always use - - to make it clear I’m refering to filenames in the above scenario. So instead of git checkout path/to/<TAB>, I would actually always write git checkout - - path/to/<TAB>.

From there, the solution to my problem is obvious, I just have to disable filename completion until - - is seen.

Here’s the code (put it as a _git-checkout function in your $fpath):

#compdef git-checkout
local curcontext=$curcontext state line
declare -A opt_args

local new_branch_reflog_arg

if (( words[(I)-b] > 0 )); then
    new_branch_reflog_arg='-l[create the new branch'\''s reflog]'
else
    new_branch_reflog_arg='-l[create the branch'\''s reflog]'
fi

if compset -N '--'; then
    __git_cached_files
else
    _arguments -C -S \
        '-q[suppress feedback messages]' \
                                                                                  \
        - switch-branch \
        '-f[force a complete re-read]' \
        '-b[create a new branch based at given branch]: :__git_guard_branch-name' \
        {-t,--track}'[set up configuration so pull merges from the start point]' \
        '--no-track[override the branch.autosetupmerge configuration variable]' \
        $new_branch_reflog_arg \
        '-m[3way merge current branch, working tree and new branch]' \
        '::branch:__git_revisions' \
                                                                                  \
        - update-files \
        '::tree-ish:__git_tree_ishs' && ret=0
fi

You can compare to the original version in /usr/share/zsh/functions/Completion/Unix/_git

Edit: thanks to Friedrich for pointing out the need for a #compdef cookie to ensure proper behavior