Don’t Reinvent the Wheel

New York, NY · 2013-01-02

Categories: git, bash, zsh, shell, PS1, PROMPT_COMMAND, yak-shaving

Stop Rolling Your Own

Many people have written or adapted complex scripts to get information from git, munge that data and then put it into their shell’s prompt. (I’ve done it myself.) However, git-prompt.sh – which comes with git – provides a very high-quality way to do that with nearly no effort. We should all stop writing our own and leverage what git already provides.

Disclaimer

Much of what follows only applies to git 1.8.1. Earlier versions of git-prompt.sh have __git_ps1, but they don’t have the extra methods to hook into PROMPT_COMMAND. So you may need to adjust what follows depending on your version of git and its accompanying scripts.

That raises another good point. Whenever you upgrade git, make sure to reread and update the shell-completion and prompt scripts that come with it (git-completion.bash, git-completion.zsh, git-completion.tcsh and git-prompt.sh). They’re frequently updated and improved. I upgraded last night, and I was excited to see some changes to git-prompt.sh. So I’m sharing what I found there. Nothing life-changing, but definitely worth looking at. (Note that I use bash, so most of what follows applies to bash specifically, but you can easily adjust it for other shells.)

Final disclaimer: Although these scripts come with git, they may not be automatically installed or sourced, depending on your operating system and package tools. Check for them, for example, in /usr/local/etc. No matter what you can find them in the source directory for git at contrib/completions. Wherever they are, you probably need to source them manually in your shell’s startup files.

Three Choices

The script git comes with is excellent, but a little complicated. You can now add Git information to your prompt in three ways. I’ll go through each way and give small examples.

For the following, I’m going to assume that I have two helper functions defined already in my .bashrc:

get_dir() {
    printf "%s" $(pwd | sed "s:$HOME:~:")
}

get_sha() {
    git rev-parse --short HEAD 2>/dev/null
}

PS1

The first way to use git-prompt.sh is the simplest. Just place the magic function __git_ps1 into your PS1. For example,

PS1='\u \W$(__git_ps1)\$ '

Most of that is a normal PS1. The call to $(__git_ps1) adds git information to the prompt automatically if you are in a Git repository. The %s expands to name-of-branch in parentheses and adds a space before the addition. The result looks something like this:

achilles ithaca (master)$

If you want more control of the string that git prints, you can add a double-quoted string:

PS1='\u \W$(__git_ps1 " [%s $(get_sha)] ")\$ '

Here I’ve switched to square brackets, added a space before and after the Git information and used the get_sha function to add the SHA1 number of my last commit to the prompt as well. The result might look something like this:

achilles ithaca [master 8348cc8] $.

PROMPT_COMMAND

The second and third ways use PROMPT_COMMAND instead of PS1 directly. In each case, you add __git_ps1 to PROMPT_COMMAND. The difference between the second and third method is how many strings you provide after __git_ps1. You can add either two or three.

If you provide two strings only, the first string will automatically appear before the Git information in the resulting prompt and the second after the Git information. For example:

PROMPT_COMMAND='__git_ps1 "\u@\h:\w" "\\\$ "'

This gives you a fairly traditional listing of your username, your machine’s hostname, your current directory, git information and then $ or #, depending on whether you’re running as a regular user or root. Here’s what it might look like:

achilles@ilium:~/code/ithaca (master)$

You can also add a third string to PROMPT_COMMAND. In this case, the third string formats output in the style of printf. Here’s an example with the third string:

PROMPT_COMMAND='__git_ps1 "\u \W" "\\\$ " "{%s $(get_sha)}"'

Now I’m adding curly brackets and using get_sha. This time, I’ve also removed all spaces to make the prompt more compact. The result looks like this:

achilles ithaca{master 8348cc8}$

In all the cases where you add a string for formatting, the %s will be the branch-name, everything else is up to you. I’m using my little custom function to get the last SHA1 number, but you can do whatever you like there.

Wait: There’s More

Finally, there are a number of variables that you can set in your shell that __git_ps1 will pick up on.

GIT_PS1_SHOWDIRTYSTATE=1
GIT_PS1_SHOWSTASHSTATE=1
GIT_PS1_SHOWUNTRACKEDFILES=1
# Explicitly unset color (default anyhow). Use 1 to set it.
GIT_PS1_SHOWCOLORHINTS=
GIT_PS1_DESCRIBE_STYLE="branch"
GIT_PS1_SHOWUPSTREAM="auto git"

The variables set to 1 can be set to any non-empty value. The others provide choices. (For more details about what each of these does, read through git-prompt.sh.) If these variables are set, __git_ps1 will automatically display information about the state of your repository by adding various symbols to your prompt. SHOWCOLORHINTS also adds color to your prompt, as the name suggests. (This only works if you use one of the two PROMPT_COMMAND methods.)

Let’s assume I’ve set the values as above. The result might look something like this:

achilles dotfiles [master *+= 1a9c53b] $

The * tells me that I have unstaged changes in the repo. The + tells me that I have staged changes in the repo. The = tells me that I’m neither ahead of nor behind the remote branch. (A < means I’m behind, a > means I’m ahead and <> shows that the branches have diverged.)

Using these environment variables in combination with __git_ps1 effectively removes the need for the 50-100 line custom scripts that many people have been using for years to munge Git information into their prompts. (You obviously have slightly less control over what characters show up as hints, but if you really want to control that, you can edit git-prompt.sh itself.) The bottom line is that I trust the maintainers of this script to keep up with changes in how git provides information much more than myself. It also means not everyone on the planet has to create their own munging functions.

One Last Thing

Finally, PROMPT_COMMAND can include more than one command. If you want to use it both for your PS1 and to set your terminal’s title-bar, you can do that too:

 PROMPT_COMMAND='__git_ps1 "\u \W" "\\\$ " " [%s $(get_sha)] "; set_titlebar "$USER@${HOSTNAME%%.*} $(get_dir)"'

By the way, PROMPT_COMMAND appears to be the new hot thing. In the past I only remember seeing it to set a terminal’s title bar. Now in the last month, I’ve seen it in three places. Git is obviously using it here. In addition, both chruby and rvm recently began to use PROMPT_COMMAND rather than overriding cd to provide automatic switching of Ruby interpreters when you enter specific directories. I was initially worried about adding too many commands to PROMPT_COMMAND, but so far I’m not seeing any trouble with it. (Note: I use it only for __git_ps1 and set_titlebar. I don’t need auto-switching of Ruby interpreters.)