Switched to Mercurial
During this month I've written just a few new code, focusing instead on the conversion of my projects from Subversion to Mercurial (and also converting almost everything from Ant to Maven, and working on a much improved build workflow with Hudson, but these are matters for another post).
So I'll blog a bit about Mercurial in the following month - the target of these posts will be people with no or small knowledge about Mercurial, that might find interesting to know why a conversion is worth while and how the tool can be used in a more advanced workflow.
There were no doubts from the beginning that Mercurial is a superior tool than Subversion, since - like similar stuff as Git - it's a next-generation Distributed SCM (DSCM). This means that there's no more the concept of a "central" repository where everybody commits to and update from, but a sort of peer-based mechanism where everybody owns a complete repository in his local disk, that can kept in sync with others by means of new operations (push, pull, merge ...). Of course, among all the peers there's still a single repository that you designate as the primary, if not because it's reliably managed and backed up (e.g. by a forge such as Kenai).
But being superior doesn't necessarily means that it's the best tool for you. The best tool is always the smaller and simpler tool which is enough for your needs, not necessarily the most complex. The advantages of a DSCM are obvious for very large projects such as OpenJDK, NetBeans or OpenSolaris, with a huge number of committers; my projects are by far smaller, and I have just a handful of committers. So, until a few time ago, I still believed that Subversion was still the best fit. Chris Adamson seemed to be in a similar position about one year ago, and Tom Ball gave some initial answers to that.
The first reason that start floating in my mind is exactly the last given by Tom: asynchronous mode support. This might not be the most important thing, but I love the fact that Mercurial commits locally, so you can work without being connected. You should know that I love to be disconnected, if possible, and other times I can't be connected if I'm traveling (and with the incoming August I'm going to move for a month to the countryside). The requirement of synchronous operations by a centralized SCM such as Subversion is really annoying if you like the "commit often" approach, like me: if you are not connected, either you can't work, or you are forced to change commit style, which is no good. If you have a mobile connection, which is often slow and intermittent, "commit often" works but you loose lots of time while waiting for commits to be completed. With Mercurial, commits are immediate, so you can work as usual. Then, a few times per day, you do a "push" to synchronize the primary repository - this might take a few time, but you can run it while you're doing something else (another job, you're eating or having a bath).
A definitely more important reason is better support for branching. Ok, this is more controversial: of course, even CVS and Subversion supported branches. The point is that managing a branch wasn't easy at all, at the minimum it required a small amount of "bureaucracy" (I mean, coordination between developers), with the result that it was mostly used at "gross grain"; or not used at all (I've personally avoided it as much as possible). Indeed, if you think carefully, branching can be useful at fine grain: every change that you do e.g. to fix a bug might be seen as a micro-branch - usually, a short-lived one; but for most complex cases, the branch might live longer. Mercurial promises to make things easier and I've decided to believe in it - thus, I'll start working putting more branches in my workflow, and I'll tell you how the thing works. Just as a starter, let's talk of a long-lived branch I've just created in jrawio for started working on features for 2.0 - a pack of stuff that needs extensive changes even in the APIs. I started with a command to see the current status:
[Mistral:Projects/jrawio/src] fritz% hg tip changeset: 289:8c9b3c199622 tag: tip user: fabriziogiudici
date: Sat Jul 25 14:37:36 2009 +0200 summary: SimpleLogFormatter dependency: 1.0.1-SNAPSHOT -> 1.0.2
The tip of a Mercurial repo is the most recent stuff that you added in the repo. In this case, it's a small change in made in the Maven pom. Then I created the branch with:
[Mistral:Projects/jrawio/src] fritz% hg branch 2.0 [Mistral:Projects/jrawio/src] fritz% hg commit -m "Created branch 2.0" [Mistral:Projects/jrawio/src] fritz% hg tip changeset: 290:6f5c11c29a31 branch: 2.0 tag: tip user: fabriziogiudici
date: Sat Jul 25 22:42:28 2009 +0200 summary: Created branch 2.0
Note how I didn't have to move into another working directory: everything stays in Projects/jrawio/src. I first changed the poms to reflect the new version - I can sum up the changes with hg diff:
[Mistral:Projects/jrawio/src] fritz% hg diff diff -r 6f5c11c29a31 jrawio/pom.xml --- a/jrawio/pom.xml Sat Jul 25 22:42:28 2009 +0200 +++ b/jrawio/pom.xml Sun Jul 26 13:59:31 2009 +0200 @@ -6,7 +6,7 @@
it.tidalwave.imageio jrawio-all- 1.5.1-SNAPSHOT+ 2.0.0.ALPHA-SNAPSHOT jrawiodiff -r 6f5c11c29a31 pom.xml --- a/pom.xml Sat Jul 25 22:42:28 2009 +0200 +++ b/pom.xml Sun Jul 26 13:59:31 2009 +0200 @@ -11,7 +11,7 @@ it.tidalwave.imageio jrawio-all pom- 1.5.1-SNAPSHOT+ 2.0.0.ALPHA-SNAPSHOT jrawio-all http://jrawio.tidalwave.it
diff -r 6f5c11c29a31 reajent/pom.xml --- a/reajent/pom.xml Sat Jul 25 22:42:28 2009 +0200 +++ b/reajent/pom.xml Sun Jul 26 13:59:31 2009 +0200 @@ -6,7 +6,7 @@
it.tidalwave.imageio jrawio-all- 1.5.1-SNAPSHOT+ 2.0.0.ALPHA-SNAPSHOT
Then I committed:
[Mistral:Projects/jrawio/src] fritz% hg commit -m "Changed version in poms." [Mistral:Projects/jrawio/src] fritz% grep "^
Not that everything is still on my laptop. I can confirm that with this command:
[Mistral:Projects/jrawio/src] fritz% hg outgoing comparing with https://kenai.com/hg/jrawio~src searching for changes changeset: 291:a9bd843b7d86 branch: 2.0 tag: tip user: fabriziogiudici
date: Sun Jul 26 13:59:58 2009 +0200 summary: Changed version in poms. changeset: 290:6f5c11c29a31 branch: 2.0 user: fabriziogiudici date: Sat Jul 25 22:42:28 2009 +0200 summary: Created branch 2.0
To sync the primary repo I ran:
[Mistral:Projects/jrawio/src] fritz% hg push pushing to https://kenai.com/hg/jrawio~src searching for changes adding changesets adding manifests adding file changesadded 2 changesets with 3 changes to 3 files [Mistral:Projects/jrawio/src] fritz% hg outgoing comparing with https://kenai.com/hg/jrawio~src searching for changes no changes found
Now, I can switch back and forth between the new branch and the default one (it's called default in Mercurial jargon):
[Mistral:Projects/jrawio/src] fritz% hg update -c default 3 files updated, 0 files merged, 0 files removed, 0 files unresolved [Mistral:Projects/jrawio/src] fritz% grep "^
1.5.1-SNAPSHOT[Mistral:Projects/jrawio/src] fritz% hg update -c 2.0 3 files updated, 0 files merged, 0 files removed, 0 files unresolved [Mistral:Projects/jrawio/src] fritz% grep "^ 2.0.0.ALPHA-SNAPSHOT
Last but not least, Mercurial gives you a very high level of control of the repository. For instance, sometimes it happens that you're going to do a large refactoring, so complex that you have to split it into parts because you want to check every step - and eventually make a "savepoint" out of it by committing frequently. The pitfall of this approach is that you get multiple commits for a single logical operation; furthermore, it's possible that during the intermediate commits the code is not compilable at all. Taking advantage of the fact that nothing goes to the primary repository before a "hg push", you can even tweak the local repository so all the commits are merged into a bigger one, comprehensive of all. In the history of the project, thus, your refactoring will appear as a single, atomic operation. Martin Fowler described this technique a few days ago - note how, among other things, the capability of easily creating clones of the local repository makes it possible to experiment and try things before applying changes to the real stuff.