I’ve been using hg the past few weeks to see if I can move off of svn (which I never really got all that deep into anyway) and I have decided that I can. Plus it seems the better solution for the project I am working on.
The distributed capabilities will come in very handy. From a system administration point of view I have been trying to get in the habit of not making .bak files of important configs anymore. They just clutter up the system and sometimes you end up with multiple copies and you forget what each one was for. So instead of making .bak files I vowed to use version control instead. Plus I wanted to version control all of the system configs I touch in general, the dotfiles in my homedir, and my various progamming projects.
Not only does this solve the .bak file organization problem but it protects my code as a form of remote backup, allows me to revert unhealthy changes, and makes it easy to check my preferred environment out into a new home dir on a new machine. My emacs configs have grown especially large and complicated.
This guy has been at this homedir in version control thing longer than I have and may be where I got the inspiration, I don’t recall: http://www.onlamp.com/pub/a/onlamp/2005/01/06/svn_homedir.html With svn I had one main repo on one of my servers which I organized heirarchically by hostname and then path so something like: /hosts/mail.copilotco.com/etc/postfix/main.cf There was also a /projects beside /hosts with a directory for the name of each of my coding projects (not system configs) which held tags, trunks, branches, etc.
The real annoyance with this arrangement is that I have to do a series of svn mkdir’s to create that path, especially for a new host. This turns into a bunch of svn ls and svn mkdir commands until I get what I want. Then I can checkout that empty svn directory into the current working directory where the config files are such as main.cf and then add my files and then commit them. This ends up being just enough work that I often skip it. Especially for the setup of new hosts and directories.
Another thing that bugged me about svn is that I tend to move servers and things around a lot and it always seemed like the svn url of the local working copy was getting out of sync with where the server actually was which caused headaches. Having the actual repository right there where I could commit without having to remember how to do do "svn switch --relocate old-uri new-uri"
to get pointed at the right place is nice.
Another svn annoyance that has been bugging me for a while is: http://subversion.tigris.org/issues/show_bug.cgi?id=2580 which was always causing ssh ControlMaster pipes to be left laying around which would cause ssh to barf and cause the commit to fail after I had already typed up my nice descriptive commit message. Then I had to delete the control file. ssh connection multiplexing is nice because it allows new connections to be set up very quickly by tunneling over the old one. Especially nice if you use tramp with emacs for remote editing of files.
Finally, once I have my configs in svn I would often forget to commit them after I edited them. So eventually I would get a bunch of changes made over the course of days or weeks. I would realize I had messed up and forgotten to commit for a long time. Then I do a commit with a message of “heck if I can remember what all this was about”. So I cooked up a way to be automatically notified if the current dir has uncommitted changes by putting this in my .bashrc:
function cd { builtin cd "$@" if [ -d .svn -a -r .svn ] then DIFFS=`svn status| egrep "^M"` if [ ! -z "$DIFFS" ] then echo "This directory contains uncommitted changes."
So whenever I cd into a directory I get a notification if I have been lazy and not committing things. But eventually I ran into a problem: if the directory has lots of files in it (which my home dir always does) the svn status command can take several seconds to return which is unbearably annoying since this happens with every cd. I end up hitting ctrl-c and not seeing the message saying I have uncommitted changes and the whole thing becomes totally ineffective. While playing with hg I have discovered that hg has the same problem. However, hg also has the ability to use the Linux 2.6 kernel’s inotify functionality. It can subscribe to the kernel to be told when files in the working directory change so it doesn’t have to do a brute force check and stat every single file. So if we put the following in our .hgrc: [extensions] inotify = [inotify] autostart = True we automatically get a little hg daemon running talking to inotify which will communicate with any hg command we invoke. So I changed my .bashrc cd function to read like so:
function cd { builtin cd "$@" if [ -d .hg -a -r .hg ] then DIFFS=`hg status -q` if [ ! -z "$DIFFS" ] then echo "This directory contains uncommitted changes."
and now my cd function works perfectly and instantaneously even on huge directories. However, because I have so many files in my homedir I had to do this to make inotify handle it: echo 32768 > /proc/sys/fs/inotify/max_user_watches So I dumped that in my rc.local.
The inotify functionality requires the most recent version of hg to work halfway decently. And even then occasionally runs into issues. If I do an hg clone with the inotify turned on the clone never finishes, it just hangs. And if I modify and then revert a file hg status -q still says that file is modified and does so until I commit it (but there are no changes so I’m not sure what it’s committing).
So inotify still has a couple bugs. Each hg repo is totally independent and does not rely on any other server. But one of the great things about having a remote server for version control is that if my local box catches fire and is destroyed I have a remote copy of my code. So I need to find a way to easily push my changes to a repository on a remote server.
Mercurial supports this and you don’t have to push your code somewhere else but pretty much everyone does it because that is the whole point of distributed version control: being able to push your changes to another person/place for further work or just another server for storage. E
very time I do an hg init . that creates a new local repository which needs a corresponding remote repository. This is a bit different from svn where each local repository fits into a sort of branch or folder in the big subversion repository on the server. So I set up a remote account with username hg, installed my public key in .ssh/authorized_keys and inside ~hg I have a subdir home.copilotco.com/home/treed inside of which I have done an hg init.
Now if I want to push my home directory from my home workstaion to this server for safe keeping I can say the following: hg push ssh://[email protected]/home.copilotco.com/home/treed but this uri is rather long. I want to make it the default. So in my .hgrc I can say: [paths] default-push = ssh://[email protected]/home.copilotco.com/home/treed So now I can clone my dotfiles and various other settings that I want to carry around with me from that url. When I make a change on some machine somewhere I “hg push” the change back up to the repository. Then I can “hg pull” on any other machine which I want to have the update. Pretty slick.