git prompts for bash

I’m trying to learn more about git. I thought it’d help me to modify my bash prompt to include git status. Turns out theres lots of ways to do that. I want something simple and minimal, but maybe using color. For years my prompt has been a simple “(hostname) = “; don’t want to go crazy. (Why =? Because it doesn’t look like $ or # or %, the standard characters. I know it’s mine.) Here’s some options.

  1. Here’s a blog post with some minimal git additions to the prompt. It just invokes git symbolic-ref to figure out the branch name. Simple, easy to understand, there’s a zillion hacks like this. Here’s another.
  2. git-prompt.sh is a bash addon distributed with git that adds a shell function __git_ps1 that can be invoked from inside the shell to print state. This is as close as it gets to a “standard”. This blog post does a good job explaining it. The configuration is a mess. The code is quite complex and handles a lot of strange git situations I wouldn’t have realized were important.
  3. bash-git-prompt is a different approach that has a bit more info on the state of the repo. As nice as it looks, I’m not going to use something that invokes a Python script every time bash needs to print a prompt.
  4. GIT Prompt uses colors to convey repo state, sets the terminal title, etc. It’s a very fancy prompt, but it’s overkill for my purposes. I do like how it displays non-zero exit codes though, and it’s got a nice configuration mode.

Screen Shot 2013-04-06 at 4.34.05 PM

I settled on option #2, the “stock” git-prompt.sh. It seems to work pretty well. The biggest problem is that Ubuntu 12.04 has an old version that doesn’t support working via PROMPT_COMMAND, so there’s an ugly workaround. Also gotta love the quoting. Anyway, screenshot and configuration.

GIT_PROMPT_VERSION=`sha1sum /etc/bash_completion.d/git`
if [ "$GIT_PROMPT_VERSION" = 'c52dd44066a21660c8830f8e6bd700893e2f7569  /etc/bash_completion.d/git' ]; then
    BAD_GIT_PROMPT=1
fi

if [ -n "$PS1" ]; then
  # Figure out what prompt character to use
  P='='
  if [ $UID == 0 ]; then
    P='#'
  fi

  # Set traditional prompt
  PS1="(\h) $P ";
  PS2="> "

  # Set up git prompting if available
  declare -f -F __git_ps1 > /dev/null
  if [ $? -a \! $BAD_GIT_PROMPT ]; then
    PROMPT_COMMAND='__git_ps1 "(\h" \)\ '$P'\  " %s"'
    GIT_PS1_SHOWCOLORHINTS=1
    GIT_PS1_SHOWDIRTYSTATE=1
    GIT_PS1_SHOWUNTRACKEDFILES=1
  fi
fi

I have some concerns about running any commands at all from inside a bash prompt. First it’s notably slow, particularly on a cold cache. Even warm it’s a few hundred milliseconds, and every extra GIT_ status thing I enable makes it slower. Worse, prompt commands do bad things if the command fails. Particularly a problem if you’re inside a remotely mounted filesystem that’s hung. I don’t know if bash is smart enough to timeout commands called when printing the prompt.

Update: this prompt setup is intolerably slow for big repos like libgit2 on a network filesystem mounted via SMB from a Linux server to a mac client. I’m talking 1000-1300ms per prompt, a noticeable pause. With no GIT_PS1 environment variables (just showing branch), the git prompt takes 25ms. Adding GIT_PS1_SHOWDIRTYSTATE adds an extra 100ms. GIT_PS1_SHOWUNTRACKEDFILES is the big culprit, adding an extra 1200ms or so. No surprise, the underlying command git ls-files --others --exclude-standard is making calls to getdirentries64() for every directory, also processing .gitignore files all up and down the tree. Not sure there’s any way to make that fast on a network file system. Maybe that’s why they went through the trouble to let you override the environment variable in the local git config.

Unrelated, but also useful; git-completion.bash which adds smart completion support for the git command. Seems to rely on the bash-completion addon to bash, although the script itself is distributed with git. In Mac Homebrew it’s a separate file; on Ubuntu it’s rolled together with git-prompt.sh. Apparently that’s an upstream change from a year ago.