Geek Post: Git Bash Completion Mysteries and shoutouts to gx and git vimdiff magic

Well time for the geek post of the week. A new feature, I’m labeling my really nerdy programming hacks. So don’t bother reading unless you have this exact problem.


Command Completion Basics

OK I admit it I’ve gotten kind of spoiled. One of the advances features of shells is what is called Tab Completion. This means that you can type say ls and then when you hit the tab, you get a list of all the files. The real magic here is a bash command called, completion what this does is pretty simple, just run the bash command (there are similar ones for fish and zsh:

complete -W "help list execute" mycommand

What this says is that if you type mycommand and then hit the tab, you will get a choice of words which are

$ mycommand <tab> <tab>
help    list    execute

And now if you type in partials like mycommand h<tab> it will complete it for you. Pretty neat right? There are also some alias for common things so

complete -A directory another_command

Means that when you type another_command <tab><tab> you will get a list of all directories in the current working directory. It gets way more complicated than this as you can use compgen which generates possible words based on what’s been typed so far.


Homebrew Bash completion is your friend

Well most of the time you don’t need to work about this, if you are on the Mac, then there is a very convenient Homebrew package called bash-completion called brew install bash-completion@2 for bash v4 and above that takes care of all this. You just need to load it up and everything works. When you do other brew installs like brew install git for instance, those command know how to load new completions in and it all just works. So you get completions for everything that supports it if you load with Homebrew.

The only real trick is you need to add some magic lines into your .bash_profile which is you read it says to look for a magic script bash_completion.sh and run it, otherwise, go to bash_completion.d and source all the scripts there. This is the magic directory that keeps all the completions that Homebrew has.

As an aside, they tell you to put it into .bash_profile, but it turns out I normally put in .bashrc. The reason is that some systems like Terminal do not execute .bash_profile but once whereas .bashrc is executed each time so if you want completions in all your windows, then put it in .bashrc.

if type brew &>/dev/null; then
  HOMEBREW_PREFIX="$(brew --prefix)"
  if [[ -r "${HOMEBREW_PREFIX}/etc/profile.d/bash_completion.sh" ]]; then
    source "${HOMEBREW_PREFIX}/etc/profile.d/bash_completion.sh"
  else
    for COMPLETION in "${HOMEBREW_PREFIX}/etc/bash_completion.d/"*; do
      [[ -r "$COMPLETION" ]] && source "$COMPLETION"
    done
  fi
fi

But what is Git completions no longer work, lessons in debugging and conflicts with hub completions

Ok this has been bothering me for a month, but with git, having completions is really nice, for instance if you do a git checkout <tab><tab> your will get a list of all branches.

This mysteriously stopped working a while ago and I wasn’t sure what was wrong, so I spent, wasted four hours of my day finally saying I was going to get to the bottom of it.

When something like this happens, it’s really hard to figure out what happened, so here are some of the tricks for figuring it out:

  1. Well the first trick is to just find that darn bash_completion.sh script and see if it is being run. One of the nice things about the way that Homebrew installs things is that it is all for a single user (actually that’s horrible if you have multiple users in your computer, but you can fix that by dancing with groups and allowing the homebrew directories, normally /usr/local to be readable by all Users.
  2. So I started putting echo in to see what was running and the first thing is learned is that there is a variable BASH_COMPLETION_VERSINFO which is checked so that you only run the completion script once. That makes sense, you don’t want to keep running completions if they are already there.
  3. Then in looking at the bash_completion.sh, wow that is a complicated script. So I tried another approach. You can see that there is a default that says if the script isn’t there, then run all the files in bash_completion.d
  4. So in doing this manually (actually I used a trick, I just changed the if looking for the bash_completion.sh script to do a not there. Curiously adding false && does *not* work in a bash script, I need to think about why.
  5. And I discovered that manually executing all those scripts does not work either.

So then comes the next insight, wow I have a bunch of git related completions in there. I have hub which is the old command line tools and gh which are the new ones. You can see with the complete command that you can overwrite completions. So this is a good debugging technique:

  1. The entries in that directory are symlinked deep into home-brew, so I created another symlink call z-test.sh that points to the git-completion.bash that has what I want. And ran it and the git completions worked!
  2. So now I know that there is an overwrite problem. Doing a little bisecting, that is somewhere after the git-completion and z is the offending script. So just move that test.sh alphabetically and it turns out it is the hub.sh completion. And I see that when I type hub <tab> <tab> I don’t get completions. So then a simple brew uninstall hub and it all works.

Net, net, this is a good example of not having to understand everything, but knowing how bash works. The main issue is that with this completion mechanisms when everyone is partying on completions, you can get conflicts like this, so the next time your completions don’t work, give this a try 🙂

I’m Rich & Co.

Welcome to Tongfamily, our cozy corner of the internet dedicated to all things technology and interesting. Here, we invite you to join us on a journey of tips, tricks, and traps. Let’s get geeky!

Let’s connect