Saturday, February 28, 2009

Organizing Commits with Mercurial Queues and Attic

I've been using distributed version control for a few years. Although I've only collaborated with smaller groups of people, these tools have helped out immensely. However, I've only recently started using Mercurial to organize changes into logical commits. I use the Mercurial Queues and attic (or shelve) extensions. Note:

EDIT: A few people have mentioned that the workflow outline below is possible using only the attic extension. For the example outlined below, the attic extension would be adequate. However, using Mercurial Queues allows you to work on patches that depend on each other, which gives you quite a bit of flexibility, almost like a "queue" of patches ;).

If you're using Git, check out StGit and the stash command, or with Bazaar, the Loom and shelve commands for similar functionality. Similar workflows are also possible with the help of the Git Index.

My workflow has traditionally gone something like this:

...work on some parts of features...
hg commit -m "Added feature1, part of feature2..."
...some more random work...
hg commit


While my commits contain generally related items, the implementation of features is often spread out among commits. Below is an example of an alternative workflow resulting in more organized commits.

Say we're working on a multithreaded application, and have written the skeleton code for the worker thread function. Without committing, we began working on the code for the main thread.
We could always commit with the record extension or something similar, but there is a more natural way.

Store away the changes related to the main function interactively (similar to record) in a patch called main.p. It will be stored in .hg/attic.

hg shelve -i main.p


Initialize a Mercurial Queues repository:

hg qinit -c


Create a new patch to track the changes to the worker thread (use the -f flag to include local changes.) The patch will be stored as .hg/patches/workerthread.p

hg qnew -f workerthread.p


Refresh the patch, and create a new patch for the main thread.

hg qrefresh
hg qnew mainthread.p


Restore the changes we saved earlier, and delete the temporary patch from the attic directory.

hg unshelve --delete main.p


The working directory is now updated with those changes. Refresh the mainthread.p patch.

hg qrefresh


We can use the qapplied command at any time to look at the applied patches that Mercurial Queues is managing:

hg qapplied

mainthread.p
workerthread.p



We are now free to work on the patches separately, using qpush and qpop to switch to the patch we want to edit, then running qrefresh to save our changes. Mercurial will warn you when you try to switch patches without saving local changes. It's also possible to change the order that the patches are applied by editing the .hg/patches/series file. Just make sure that none of them are applied! When we feel that one of our patches is ready, we can turn it into a "real" commit using qfinish.

I highly recommend reviewing the Mercurial Queues documentation if you use Mercurial. It opens the door to workflows of which you may not have been aware. Organized commits are especially important when collaborating on projects that depend on patches. When each commit makes one logical change or a group of logically related changes, the process of tracking down the source of a bug is greatly simplified.