Opinionated Programmer - Jo Liss's musings on enlightened software development.

Colorful bash prompt reflecting Git status

Updated Feb 8, 2011: Handle indescribable HEAD (can happen during conflicts).

Screenshot showing colorful prompt

When I work with Git repositories, I like to have a visual cue as to whether there are modifications in my working copy. Here’s a snippet for your ~/.bashrc to insert info about your git status into your bash prompt in a concise but informative fashion, as in the screenshot on the right:

  • Green: Pristine working copy
  • Yellow: No modifications, but untracked files present
  • Red: Some files are modified (doesn’t matter if staged or not)

On the upside, it’s extremely fast: It does with one single call to git (two on detached heads), and no calls to other binaries (like grep, sed, etc.), so your shell won’t feel sluggish. On the downside, it parses porcelain output, so it might break in some future version of Git. I think that’s a reasonable tradeoff to get maximum speed, though.

If you are on a branch other than master, it also displays the current branch inside the colored box (see the screenshot at the bottom).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function _git_prompt() {
    local git_status="`git status -unormal 2>&1`"
    if ! [[ "$git_status" =~ Not\ a\ git\ repo ]]; then
        if [[ "$git_status" =~ nothing\ to\ commit ]]; then
            local ansi=42
        elif [[ "$git_status" =~ nothing\ added\ to\ commit\ but\ untracked\ files\ present ]]; then
            local ansi=43
        else
            local ansi=45
        fi
        if [[ "$git_status" =~ On\ branch\ ([^[:space:]]+) ]]; then
            branch=${BASH_REMATCH[1]}
            test "$branch" != master || branch=' '
        else
            # Detached HEAD.  (branch=HEAD is a faster alternative.)
            branch="(`git describe --all --contains --abbrev=4 HEAD 2> /dev/null ||
                echo HEAD`)"
        fi
        echo -n '\[\e[0;37;'"$ansi"';1m\]'"$branch"'\[\e[0m\] '
    fi
}
function _prompt_command() {
    PS1="`_git_prompt`"'... your usual prompt goes here, e.g. \[\e[1;34m\]\w \$\[\e[0m\] '
}
PROMPT_COMMAND=_prompt_command

In case anyone cares, the awkward dance of re-setting $PS1 through $PROMPT_COMMAND seems to be necessary, since:

  • Naively using echo -n to dump out the status indicator from inside _prompt_command would make Bash hiccup on the zero width of the ANSI sequences. (That’s what the magic [ and ] pairs in $PS1 are for – they tell Bash that everything in between is non-printing, and hence not to be counted for line editing.)
  • Also, apparently you cannot set PS1='_git_prompt… your prompt …’ (note the single quotes): While _git_prompt does get interpolated every time the prompt is displayed, the special backslash sequences don’t get re-interpreted. (And echoing raw escape characters doesn’t solve our problem, since we still need [ and ].)

Here is a longer screenshot:

Git prompt screenshot