A journal to record my notes and ideas related to software development and computing

Thursday, October 16, 2008

Home directory version control notification

I spent some time in admin mode recently, seeing if I could improve the way my home directory is versioned. Previously, I had a few Mercurial repositories scattered around the place to track my documents, scripts, config and miscellaneous files.

Most of my work-related directories have their own Subversion repositories attached to them, but there were also some scratch projects that weren't under any VCS. My config files (eg, .bashrc) were sym-linked to a separate directory that had its own repository.

The main problems I had with the different repositories is that I didn't have an easy way to determine if I had any outstanding changes that need to be committed (my documents directory was shockingly out of date), and there were things that I wanted versioned (for example, application preferences) that weren't being versioned.

My first thought was to add a single Mercurial repository at the root of my home directory so I only had one repository to remember, and could easily track anything that gets added over time (for example, config files for new programs). Once I started thinking about all of the entries in my ignore file though, and how I wanted to share some stuff between work and home but not others, this option became less and less appealing.

So, back to the multiple repositories it was. What I ended up was repositories for the following directories:


The ~/Library directory is the standard directory on OSX for storing application support files, preferences, etc, and while I'm not particularly interested in the day-to-day happenings of this directory, it is helpful to have snap-shots that I can revert to at a later date.

I'm using the home repository to track utility scripts and dot files that live directly under ~ (mostly run config and history files). Almost all dot files in my ~ directory are sym-linked to somewhere in my home repository, and I have a bootstrap script to take care of this if I need to setup another home directory on a different machine in the future.

Finding out how to merge my old scripts and config repositories (whilst retaining their histories) was not a simple exercise. I thought I might be able to use Mercurial's import and export commands to merge one into the other, but there doesn't appear to be a way of specifying a relative path to import a repository at, so when I tried, I ended up with everything in the root of the repository. In hindsight, I realise I could have created a new repository and imported both repositories there, making sure to move the contents straight after importing, but there was another similar solution that I eventually found and got to work.

Now that I was happy with my repositories layout, I wanted a way to make sure they were kept up to date. At first I thought about adding a cron job that adds, removes, and commits changes automatically at given periods. Then I thought that might lead to stuff being committed that I didn't want (for example, if I generated a large amount of data that slipped through my ignore file), and having a repository log full of static messages. So instead, I decided to go with a notification approach instead.

Finding uncommitted changes was pretty straight forward with the following command:

find . -name .hg -not -path "./Library/.hg" -exec /opt/local/bin/hg st {}/.. \;

(I took the Library repository out of the results because I didn't want to be bugged about those changes throughout the day; instead, I just take a snapshot of that at the end of each day).

This has the added benefit that if I add more Mercurial repositories in the future, they'll automatically be included in the notification without me having to change anything.

Next up, notification. I find Growl on the Mac to be useful for receiving reasonably unobtrusive notifications throughout the day. It ships with a command-line interface (that you need to install separately), which was perfect for what I needed:

/usr/local/bin/growlnotify "Uncommitted Changes" <$CHANGES_FILE

$CHANGES_FILE holds the output of the previous find command. I wanted it stored so I could also copy the changes to my desktop, giving me a secondary visual cue that I have changes outstanding in case I missed the Growl notification.

I ended up with the following bit of script (after adding a few finishing touches, such as using a temp file for the intermediate results, only notifying if the size of the changes file is greater than zero, and displaying the notification with a nice Mercurial icon):

TMP_FILE=`mktemp /tmp/uncommitted_changes.XXXXXX` || exit 1

cd $HOME
find . -name .hg -not -path "./Library/.hg" \
-exec /opt/local/bin/hg st {}/.. \; >$TMP_FILE

if [[ -s $TMP_FILE ]]; then
/usr/local/bin/growlnotify \
--image $HOME/bin/img/hg-icon-50.png \
"Uncommitted Changes" <$CHANGES_FILE > /dev/null 2>&1

And here it is in action:

One thing to note is that the output of growlnotify is being ignored. This is because of a known issue of invoking growlnotify from cron, whereby the notification still works but the following warning is mailed to you:

growlnotify[1592:10b] could not find local GrowlApplicationBridgePathway, falling back to NSDNC

I tried using the network settings described here, but this resulted in my notifications not showing up at all. For now, I'm just ignoring the output of growlnotify. I figure if something else goes wrong with it, it'll most likely result in notifications not showing up, which I think I will notice once I start getting used to them.

One final thing to mention is that I had intermittent trouble with using the --image flag when explicitly running the script. It sounds related to this problem, in that the notification would only be displayed intermittently but no other error or warning was emitted. Interestingly, I don't seem to get the problem when the script runs from cron, so I've left the flag in for now.

So there you go; notifications for uncommitted changes under my home directory, and a great reminder for me to go back and look at whether I want to keep configuration changes that are usually made when my mind is on other things. I've currently got it set to run every 15 minutes, which seems to be a reasonable balance between being too annoying and having too many outstanding changes.

No comments:

About Me

Computer programmer with and interest in music, and a passion for brewing beer, which I'm working at developing into a career!