I’ve been a sworn fan of version control for a good few years now. After a brief flirtation with Subversion I am currently in a long term and very committed relationship with the Git version control system. I use Git to store all my code and writing and to keep everything in sync between my machines. Almost everything I do goes into a repository.
When I’m working I spend most of my time in three applications: a text editor
(generally Emacs), a terminal (either iTerm2 or Gnome Terminal) and a browser
(Firefox or Safari). When in Emacs I use the excellent Magit mode to keep track
of the status of my current project repository. However my interaction with git
is generally split between Emacs and the terminal. There’s no real pattern, just
what’s easiest and open at the moment. Unfortunately when I’m in the terminal
there’s no visible cue as to what the status of the repo is. I have to be
careful to run git status
regularly to see what’s going. I need to manually
make sure that I’ve committed everything and pushed to the remote server. Though
this isn’t usually a problem, every now and then I’ll forget to commit and push
something on one of my machines, go to another and then realized I’ve left
behind all my work. It’s annoying and kills productivity.
Over the last few days I decided to sit down and give my terminal a regular indicator of the state of the current repository. So without further ado, here’s how I altered my Bash prompt to show relevant Git information.
There are generally three things I’m concerned about when it comes the Git repo I’m currently working on:
- What is the current branch I’m on?
- Are there any changes that haven’t been committed?
- Are there local commits that haven’t been pushed upstream?
Git provides a number of tools that gives you a lot of very detailed information about the state of the repo. Those tools are just a few commands away and I don’t want to be seeing everything there is to be seen at every step. I just want the minimum information to answer the above question.
Since the bash prompt is always visible (and updated after each command) I can put a small amount of text in the prompt to give me the information I want. In particular my prompt should show:
- The name of the current branch
- A “dirty” indicator if there are files that have been changed but not committed
- The number of local commits that haven’t been pushed
The symbolic-ref
command shows the branch that the given reference points
to. Since HEAD is the symbolic reference for the current state of the working
tree, we can use git symbolic-ref HEAD
to get the full branch. If we were on
the master
branch we would get back something like refs/heads/master
. We use
a little Awk magic to get rid of everything but the part after the last
/. Wrapping this into a litte function we get:
function git-branch-name
{
echo $(git symbolic-ref HEAD 2>/dev/null | awk -F/ {'print $NF'})
}
Next we want to know if the branch is dirty, i.e. if there are uncommitted
changes. The git status
command gives us a detailed listing of the state of
the repo. For our purposes is the very last line of the output. If there are no
outstanding changes it says “nothing to commit (working directory clean)”. We
can isolate the last line using the Unix tail
utility and if it doesn’t match
the above message we print a small asterisk (*). This is just enough to tell us
that there is something we need to know about the repo and should run the full
git status
command.
Again, wrapping this all up into a little function we have:
function git-dirty {
st=$(git status 2>/dev/null | tail -n 1)
if [[ $st != "nothing to commit (working directory clean)" ]]
then
echo "*"
fi
}
Finally we want to know if all commits to the respective remote branch. We can
use the git branch -v
command to get a verbose listing of all the local
branches. Since we already know the name of the branch we’re on, we use grep
to isolate the line that tells us about our branch of interest. If we have local
commits that haven’t been pushed the status line will say something like "[ahead
X]"
, where X is the number of commits not pushed. We want to get that number.
Since what we’re looking for is a very well-defined pattern I decided to use BASH’s built-in regular expressions. I provide a pattern that matches =”[ahead X]” where X is a number. The matching number is stored in the BASH_REMATCH array. I can then print the number or nothing if no such match is present in the status line. The function we get is this:
function git-unpushed {
brinfo=$(git branch -v | grep git-branch-name)
if [[ $brinfo =~ ("[ahead "([[:digit:]]*)]) ]]
then
echo "(${BASH_REMATCH[2]})"
fi
}
The =~ is the BASH regex match operator and the pattern used follows it.
All that’s left is to tie together the functions and have them show up in the
BASH prompt. I used a little function to check if the current directory is
actually part of a repo. If the git status
command only returns an error and
nothing else then I’m not in a git repo and the functions I made would only give
nonsense results. This functions checks the git status
and then calls the
other functions or does nothing.
function gitify {
status=$(git status 2>/dev/null | tail -n 1)
if [[ $status == "" ]]
then
echo ""
else
echo $(git-branch-name)$(git-dirty)$(git-unpushed)
fi
}
Finally we could put together prompt. BASH allows for some common system
information to be displayed in the prompt. I like to see the current hostname
(to know which machine I’m on if I’m working over SSH) and the path to the
directory I’m in. That’s what the \h
and the \w
are for. The Git information
comes after that (if there is any) followed by a >. I also like to make use of
BASH’s color support.
function make-prompt
{
local RED="\[\033[0;31m\]"
local GREEN="\[\033[0;32m\]"
local LIGHT_GRAY="\[\033[0;37m\]"
local CYAN="\[\033[36m\]"
PS1="${CYAN}\h\
${GREEN} \w\
${RED} \$(gitify)\
${GREEN} >\
${LIGHT_GRAY} "
}
I like this prompt because it gives me just enough information at a glance. I know where I am, if any changes have been made and how much I’ve diverged from the remote copy of my work. When I’m not in a Git repo the git information is gone. It’s clean simple and informative.
I’ve borrowed heavily from both Jon Maddox and Zach Holman for some of the functionality. I didn’t come across anyone showing the commit count, but I wouldn’t be surprised if lots of other people have it too. There are probably other ways to get the same effect, this is just what I’ve found and settled on. The whole setup is available as a gist so feel free to use or fork it.