Alex headshot

AlBlue’s Blog

Macs, Modularity and More

Git Tip of the Week: Autocompletion in Shells

Gtotw 2011 Tip Git

This week's Git Tip of the Week is about autocompleting shell environments. You can subscribe to the feed if you want to receive new instalments automatically.


Although many GUI clients exist for Git, sometimes it's useful to drop down to the shell in order to work with repositories. The examples used in this blog all show the shell equivalents, since it's more to do with concepts than specific implementations.

This post is different, however, in that it's explicitly about working with Git in shells. If you never use shells for developing with Git repositories – or you're using Windows – then you can safely come back next week for something more general.

Most shells are fully featured programming environments whose talent is frequently underused as a means of inspecting the file system. You can build functions, loops, file processing and so on. And most shells have a means of displaying a prompt which in the examples I'm using has just been $. But this can be changed to any other value by suppling a different value for a variable called PS1:


$ export PS1='+++ Redo From Start +++ '
+++ Redo From Start +++ echo Hello World
Hello World
+++ Redo From Start +++

This isn't particularly exciting; it's just changed the $ for +++ Redo From Start +++. Note that it was specified in quotes to ensure that the trailing space is present in PS1, as otherwise it would blur into the command given.

It turns out that PS1 is evaluated each time, which means we can use a function instead of static text each time we want to use it:


$ d() { date; }
$ d
Thu 14 Jul 2011 08:34:31 BST
$ PS1='$(d) \$ '
Thu 14 Jul 2011 08:34:50 BST $ echo Hello
Hello
Thu 14 Jul 2011 08:34:51 BST $ echo World
World
Thu 14 Jul 2011 08:34:52 BST $

Note that each time we hit enter after a command, the function is called again, and you get a different time. (If you see the same time, check you remembered the quotes around the variable as otherwise the function will be evaluated at PS1 assignment time; check it's what you expect with echo $PS1.)

How does that fit in with Git? Well, it turns out there's a great script called git-completion.bash in the contrib directory of your Git installation; if it's not there, you can look take a copy from contrib/completion/git-completion.bash instead.

You can make a copy of the script in your local directory (or just symlink it to the contrib location) – in the examples below, I've copied it as .git-completion.sh in my home directory. You need to source it when your shell starts; the common way (for bash shells) is to add this to your .bashrc script:


$ echo source ~/.git-completion.sh >> .bashrc

The .bashrc is read upon each (non-login) bash shell, though there's a .bash_profile which is used for login shells. To ensure a consistent experience, ensure that your .bash_profile sources your .bashrc and then put changes solely in the .bashrc files:


$ echo source ~/.bashrc >> .bash_profile

Note that on Mac OS X, new Terminal windows and tabs are always considered login shells so this is a necessary step for OS X users.

Showing Git status in the prompt

Having covered the basics, how can we use this to our advantage in Git repositories? Well, the git-completion.sh script defines a function, __git_ps1, which gives you information about what branch you're on, if you're in a Git repository. (Note that you have to start a new shell to ensure you source the completion script):


$ __git_ps1
 (master)
$ cd /tmp
$ __git_ps1
$

This gives us the branch that we're on if we're in a Git repository, and nothing if we're not in a repository. We can use this to set up a prompt accordingly:


$ export PS1='$(__git_ps1) \$ '
 (master) $ cd /tmp
 $

This shows us when we're on the master branch, and doesn't when we're not, but it gives us extra spaces. We can format that by passing a %s (standard printf formatting for a string) to add a space only when necessary:


$ export PS1='$(__git_ps1 "%s ")\$ '
master $ git checkout -b example
example $

As well as showing the branch, you can also show whether there are uncommitted changes or whether you are behind/ahead of a remote branch. There are various variables you can set:

  • GIT_PS1_SHOWDIRTYSTATE=true will show a * if there are unstaged and + if there are staged (but uncommitted) changes
  • GIT_PS1_SHOWSTASHSTATE=true will show a $ if there are stashed files
  • GIT_PS1_SHOWTRACKEDFILES=true will show a % if there are tracked files
  • GIT_PS1_SHOWUPSTREAM=auto will show a = if you are at the same commit, < if you are behind, > if you are ahead and <> if you have diverged from the upstream branch.

Autocompletion

So far, we've talked about the PS1 environment variable and tracking the branch name (along with the shells). However, the completion script also adds in the ability to auto-complete git commands (including arguments). Simply by typing a few characters, you can complete argument names and branch/tag names by hitting the TAB key. This can be useful if you don't know the full options that a command can take (type -- and then hit TAB) or when you're referring to a branch/tag frequently and don't want to type it in all the time.

There's a lot more that you can do to customise your shell prompt; since Git runs quickly, and you can set up a function to do what you want, it's possible to print out the current commit id or even use colours to denote different stages. Similar extensions exist for different shells, such as Steve Losh's extravagant ZSH prompt.


Come back next week for another instalment in the Git Tip of the Week series.