After setting up new machine I noticed my Git alias tab completion had stopped working. I created this blog post to document how I tracked down this issue and resolved it.
The Issue
I have a Git alias that I use called git can-ff-merge based on Check If We Can Do a Git Fast-Forward Merge (source).
When using this alias we give it a branch name (e.g. git can-ff-merge feat/add-fallback-value).
Using the Git alias tab completion trick of putting : git branch ; \ in my alias, typing git can-ff-merge followed by tab outputs the list of branches.
Example where I hit tab after git can-ff-merge
$ git can-ff-merge
HEAD
ORIG_HEAD
feat/set-dates
main
origin/HEAD
origin/chore/improve-backwards-compat-for-branch-name
origin/chore/update-installation-location-instructions-20
origin/feat/add-git-blame-ignore-revs-alias-29
origin/feat/git-blame-ignore-some-commits
origin/feat/push-default-current
origin/feat/set-auto-setup-remote-true
origin/feat/show-branch-name-w-dash-34
origin/main
Expected Behavior
The branches are show as above (this is the behavior on my old machine).
Actual Behavior
The files in the directory are displayed (this is the behavior on the new machine).
$ git can-ff-merge
LICENSE
README.md
gitconfig
Note: Other tab completion (e.g. tab after git branch) works as expected.
Details
Both machines are using zsh version 5.9 (x86_64-apple-darwin23.0) and git version 2.46.0
How I Fixed It Summary
First, I added these lines to my ~/.zshrc file
# Use brew installed program completions
# See https://docs.brew.sh/Shell-Completion#configuring-completions-in-zsh
if type brew &>/dev/null
then
FPATH="$(brew --prefix)/share/zsh/site-functions:${FPATH}"
autoload -Uz compinit
compinit
fi
# Use Git's tab completion (_git) installed by brew instead of zsh's default
if type brew &>/dev/null && [ -f "$(brew --prefix)/share/zsh/site-functions/_git" ];
then
_git () {
builtin autoload -XUz "$(brew --prefix)/share/zsh/site-functions"
}
fi
Then I ran this command from the command line
rm ~/.zcompdump
How I Fixed It Details
What Controls Tab Completion
In tab completion is controlled by a function or file in path with a matching name that starts with an underscore (_), therefore the git tab completion is handled by _git
zsh has built in functions for many common commands (e.g. _git for git). You can get information about _git by opening a new terminal window and running
type _git
_git is an autoload shell function from /usr/share/zsh/5.9/functions/_git
Based on this path we can see _git is part of zsh
Brew Shell Completion
I install many of my programs, including git, with the Homebrew Package Manager (a.k.a. “brew”) by default when brew installs a program it does NOT setup the tab completion for that program. If we want tab completion for brew installed programs, we need to follow the brew Shell Completion documentation.
Following these instructions I ran
brew completions link
and added the necessary lines to my zsh configuration for configuring completions in zsh – see PR 63 Add brew completions to FPATH.
Now I had tab completion for other programs I installed with brew like ripgrep and GitHub CLI but Git tab completion for aliases still did not work.
Two Competing Completion Functions
At this point, I opened a new terminal window I gathered some information about the completion functions.
$ type _gh
_gh is an autoload shell function from /opt/homebrew/share/zsh/site-functions/_gh
$ type _rg
_rg is an autoload shell function from /opt/homebrew/share/zsh/site-functions/_rg
$ type _git
_git is an autoload shell function from /usr/share/zsh/5.9/functions/_git
What I noticed is the other programs installed by brew (e.g. gh and rg) were loading the completion function from one directory, while git was loading the completion function from another directory.
I suspected that I should be loading the _git completion function from the same directory as the other brew installed completions and indeed when I added that to my zsh configuration (PR 64 When available, use brew installed _git completion ) and I deleted the ~/.zcompdump cache file (which is the “dumped configuration” of the zsh completion created by zsh’s built-in compinit), it worked!
Alternate Solution
As more than one of the clever developers I know pointed out, I likely could have corrected this issue by using the zsh configuration framework Oh My Zsh. This is a great point and probably would have been the path of least resistance, however the minimalist in me (and the drive of curiosity) led me down the path that resulted in this blog post.
Leave a Reply