bling.github.io

This blog has relocated to bling.github.io.

Sunday, December 5, 2010

Working in Git to Working in Mercurial

I took the dive a couple weeks ago and learned how to use Git and fell in love with its simplicity.  I don’t know what it is, but after using Git every day it actually started to make sense that git checkout is used for so many things, which is ironic because prior to using Git, every introductory tutorial I’ve read has always had me thinking, “checkout does what and what and what now??”.

So why did I switch to Mercurial?  I need to push/pull from a Subversion repository, and I work on Windows.  General day to day work is great, but when I needed to git svn rebase or git svn dcommit it would take so long that I simply left to get coffee.  What’s worse, no matter what I set for core.autocrlf I would always get some weird whitespace merging error, when nothing was wrong.  It became a regular part of my workflow to just git rebase –skip everything because that’s what fixed things.  Scary.

The crappy Subversion and whitespace support (at least in Windows) led me to try Mercurial.  After getting accustomed to Mercurial, they’re actually a lot more similar than they are different.

First things first, you need to add the following to your mercurial.ini file in your home directory:

[bookmarks]
track.current = True

Here’s a comparison of my typical Git workflow translated to Mercurial:

  Git Mercurial
Work on a new feature/bug git checkout –b new_feature hg book new_feature
Hack hack hack git add .  
Commit git commit –m “done!” hg commit –m “done!”
Hack more and commit git commit –a –m “more hacking” hg commit –m “more hacking”
Sync with up stream git pull master hg pull
Merge git checkout master hg merge new_feature
  git merge new_feature hg commit –m “merged”
Push git push hg push

Notice that they’re practically exactly the same?  There are some minor differences.  The obvious one being that you need to add into git’s index before committing.  And the other I didn’t have to switch branches in Mercurial (more on that later).  But aside from that, it’s pretty much the same, from a user’s point of view.  Check this fantastic thread on StackOverflow for one of the best comparisons on the net if you want to dive into technical details.

So far, there are only two things that bother me having switched:
  a) No fast-forward merge.  You need to manually rebase and then commit.
  b) No automatic commit after a conflict free merge.

These are fairly minor annoyances, but can be scripted/aliased away.  Or if you don’t like those defaults in Git, you can override them in the command arguments or set them in gitconfig.  Mercurial similarly can change default arguments in its hgrc/mercurial.ini.

One of the biggest advantages of Git is easily switching between all its local lightweight branches at lightning speed.  Tangled source code is no more!  I was confused with Mercurial’s bookmarks when I first used them because all it does is label your head with something.  I don’t know why this is, but it is the same as git checkout –b, but for some reason I visualized Git “branching” the latest commit into two paths.  But how do you do that if they’re the same?  It should only branch after a commit which introduces changes, which is what Mercurial does.  In this scenario, Mercurial is more explicit, whereas Git is implicit.

Using bookmarks, you can mimic Git’s workflow like this:

  Git Mercurial
Create a bunch of ‘working areas’ git branch feature1 hg book feature1
  git branch feature2 hg book feature2
Switch/commit/hack/commit git checkout feature1 hg up feature1
  git commit –a –m “feature1” hg commit –m “feature1”
  git checkout feature2 hg up feature2
  git commit –a –m “feature1” hg commit –m “feature2”
Sync up stream git pull master hg pull
  git checkout master hg up default
Merge in 1 feature git merge feature1 hg merge feature1
    hg commit –m “merged”
Delete branch/bookmark git branch –d feature1 hg book –d feature1
Push git push hg push –r tip
Switch back and hack again git checkout feature2 hg up feature2

The –r tip switch on hg push might have raised an eyebrow.  This is to tell Mercurial to push all changes that lead to the tip.  This will include the changes in feature1 that we just merged in, but exclude all the ones in feature2.  If you issue a hg push it will complain and warn you that you’re going to create multiple heads on the remote repository.  This is not what you want since there may be unfinished features, experimental branches, etc.  Of course, you can force it anyways, but that’s not a good idea.

At first, the tip was most confusing to me because I tried to associate it with Git’s master, which was simply not the case.  Tip refers to the newest change that you know about.  This can be on any branch or bookmark.  Once I understood that, and stopped trying to create a Git master equivalent, everything was straightforward.

So let’s start with an example.  First I create a bookmark feature1 after syncing, and then making a commit looks like this:

image

And then if you switch to feature2 and make a commit, it becomes like this:

image

Here’s where you start thinking, “I don’t have a master which tracks upstream changes, how do I separate my local changes?”  And now here’s a situation where Mercurial does more black magic than Git.  If there are up stream changes, after issuing hg pull, this happens:

image

Mercurial automatically split my local changes into their own separate branches (actually, heads is the accurate term).  After this operation, tip is now tracking the changes that I just pulled in from upstream, instead of my bookmark.

From here, it’s simply hg up tip to switch to the “master” branch.  Then a hg merge feature1; hg commit, and it becomes like this:

image

Then it’s simply hg push –r tip, and you’re good to go.  Basically, if you bookmark every feature/bug you work on, then you should only have one branch that doesn’t have a bookmark, which is the same as the ‘master’ branch from Git.

What about Subversion?

Whoops, forgot why I switched in the first place.  First, install hgsubversion, after that’s set up, simply:

hg clone http://path/to/subversion/repository

And then it’s just hg pull or hg push.  Is it really that simple?  Yes, yes it is.

2 comments:

ms4py said...

I disagree with "no need for a git master equivalent". A typical workflow would be:

1) book and work on "feature-1"
2) go back to "master" (whoops, no reference?!)
3) book and work on "feature-2"

Bailey Ling said...

mercurial bookmarks only move forward with commits, not with pulls.

you are of course welcome to create a master bookmark, but you'd have to manually update the bookmark to the tip after every pull for it to be truly equivalent to git.

the workflow you mentioned is very tedious to do in mercurial, and than rather tracking master yourself it is often easier just to do 'hg book -r 123' if you truly need that branch to be directly off of "master".