Translating my Git workflow with local branches to Mercurial

It took me a while to really settle in to a Git workflow I like to use on a daily basis.  It’s a pretty common workflow, and is centered around local topic branches and rebasing.  It’s not actually much different than the workflow I used with SVN, except that I prefer rebase over merge.  My typical Git workflow starts out with:

  • git checkout –b “SomeTopic”
  • <work work>
  • git add .
  • git commit –am “Commit message”
  • <repeat last 2 steps as necessary>
  • git checkout master
  • git pull –rebase origin master
  • git checkout SomeTopic
  • git rebase master
  • git checkout master
  • git rebase SomeTopic (or merge, same thing)
  • git push origin master
  • git branch –d SomeTopic

In a nutshell, all work, and I mean ALL WORK, is done in a local branch.  Every time.  This is because I can never predict when other, unrelated work might come up.  I do work in a local branch.  When I want to bring that work back to master (basically, trunk), I first do a pull from origin back to master, rebasing my existing commits.

At this point, I should note that I rarely, rarely ever need to rebase upstream changes.  If I pull work back to master, that means that I’m about to push.  Otherwise, it can just stay in a local topic branch.

Anyway, I make sure that my master reflects the absolute latest upstream changes.  When I’m ready to bring the local branch back in, INSTEAD OF MERGING, I rebase the branch on to master.  All this means in practice is that the branch’s commits are re-played onto the master branch.  Merging instead squashes all the branch’s work in to one single merge commit, which I’d rather avoid.

Side note – The git “rebase considered harmful” article is 3 years old.  A lot of opinions have changed since then, so while its core arguments do apply (never rebase a pushed commit), rebase is a sharp, useful tool that does great things when used right.

Finally, I do a merge/rebase from master to the branch, which really just does a fast-forward merge.  Because the SomeTopic pointer is a descendant of the master pointer, a merge/rebase is really just moving the master pointer up to the SomeTopic pointer.  Once this is done, I push and I’m finished.

Translating this to Hg has been a little more difficult, however.

Combining Rebase and Bookmarks

Right now, I’m trying to use Bookmarks and Rebase to achieve this same workflow.  The basic workflow I want is:

  • All work is always isolated from any other work
  • Pulling latest does not affect isolated work
  • Isolated work, when rolled back in, has linear history preserved

I don’t really care how this is accomplished.  So, I’m going from this article on the different ways of doing branching in Mercurial.

So my first try was using the Hg extensions Rebase and Bookmarks, which have both been included with Mercurial for a while now.  On the surface, Rebase and Bookmarks seem very similar to Git’s rebase and branching model.

Mimicking git, I try:

  • hg bookmark SomeTopic
  • <work work>
  • hg commit –Am “Some message”

Now I want to pull those changes back to master…but wait, there is no master!  I don’t have a pointer to when I first made the branch, and even worse, the “default” branch is now sitting on my SomeTopic branch.

What I’d really like to do is do “hg checkout default”, then do an update, so that “default” always represents the upstream current state.  But “default” has moved!

My next attempt was to create a “master” bookmark right before I created the “SomeTopic” bookmark, so that “master” mimics the Git master branch – that is, it’s merely a named pointer, nothing special.

I now want to do some more, unrelated work, so I:

  • hg update master
  • hg bookmark SomeOtherTopic
  • <work work>
  • hg commit –Am “Some other message”

At this point, here’s my tree:

image

As we can see here, I have my master bookmark marking where I diverged my bookmarks.  This now more or less matches what I see in Git, with the exception of that “default” branch.

Let’s say that we now want to bring “SomeTopic” back to “master”.  Really, I want to rebase “SomeTopic” on to “master”.  Because “master” is a parent of SomeTopic, this should really just be a fast-forward merge.  The master should just be moved up to SomeTopic.

If there were upstream changes, that would change the story a bit.  Master would move up to those upstream changes, and all changes from where SomeTopic and master diverged would be replayed on top of master.  One thing to note is that Git is very smart about fast-forward merges.  If I rebase SomeTopic on to master, and master is still where it was, nothing would happen.  I could then rebase master on to SomeTopic (or merge), and master would just move up.

In the picture above, I really just want “master” to move up to “SomeTopic”, then I can push “master” up.  So let’s try to do an hg rebase:

  • hg rebase –b SomeTopic –d master

I want to rebase SomeTopic on top of master.  I get a message “nothing to rebase”.  That’s fine, as I have nothing to do here anyway.  “master” is in the direct ancestry of “SomeTopic”.

The next thing I want to do is move master to SomeTopic, which at this point should just be a fast-forward merge.  But nothing I seem to do will allow me to move “master” up to “SomeTopic”.  I try all of these:

  • hg update master/hg merge –r SomeTopic <- “nothing to merge”
  • hg rebase –b master –d SomeTopic <- “nothing to rebase”

Blarg.  Even though I’ve configured my bookmark to automatically move forward, there doesn’t seem to be a way to do this myself.  What I can do is:

  • hg bookmark –d master
  • hg update SomeTopic
  • hg bookmark master
  • hg bookmark –d SomeTopic

This basically fast-foward merges the master bookmark to SomeTopic, by…deleting it and re-creating it.  If Mercurial supported a fast-forward merge here, that would be GREAT, but it doesn’t, so I have to jump through a bunch of hoops here.  All of which I could batch up in to a “fast-forward” alias, but is still annoying, as Git just handles this automatically.

Anyway, this is now the state of things:

image

And now I want to push “master” up.  But this will be interesting, I don’t want “SomeOtherTopic” to go out.  So, I use:

  • hg push –b master

Now only the “master” piece got pushed up.  Let’s say we now want to get the SomeOtherTopic back in to the fold, AND, that there were upstream changes.  In this case, we want to update our master to be include new changes, but without affecting “SomeOtherTopic”.  We do this to update our master:

  • hg update master
  • hg pull –rebase –b default

This makes our local repository tree now:

image

Exactly what we wanted! The “master” bookmark got moved up, past “SomeOtherTopic”.  Now, we just need to rebase SomeOtherTopic onto master:

  • hg rebase –b SomeOtherTopic –d master

This means I’m replaying the SomeOtherTopic on top of master, resulting in the following tree:

image

Looking good!  I now just follow the FF-merge-master-to-branch routine:

  • hg update SomeOtherTopic
  • hg bookmark –f master <- basically moves master to current location, better than delete/add
  • hg bookmark –d SomeOtherTopic <- delete the bookmark (the work is integrated now)
  • hg push –b master

Well, that’s it.  It’s not completely like git branching, there are some caveats here and there, as git handles remotes different than Hg.  Git also automatically handles fast-forward merges, but in practice, I don’t think that it’ll be a big deal.

I need to run with this with a team to really make sure it doesn’t corrupt things, but it seems to work so far.

Related Articles:

Post Footer automatically generated by Add Post Footer Plugin for wordpress.

About Jimmy Bogard

I'm a technical architect with Headspring in Austin, TX. I focus on DDD, distributed systems, and any other acronym-centric design/architecture/methodology. I created AutoMapper and am a co-author of the ASP.NET MVC in Action books.
This entry was posted in git, Mercurial. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • http://stevenharman.net Steven Harman

    re: Git Rebase… I’m solidly in the “all for it” camp, which seems to be a growing constituency. speaking of which, check out The Case for Git Rebase: http://darwinweb.net/articles/86

  • http://www.tavaresstudios.com Chris Tavares

    I’ve been using Mercurial for a while, and have a similar workflow, but I do it very differently. The biggest difference is that I don’t futz around with the single repository. Instead, I:

    hg clone master working
    cd working
    .. checkin checkin checkin …
    cd ..\master
    hg pull (get any upstream changes)
    cd ..\working
    hg push (back tp master)
    cd ..\master
    hg rebase

    And that’s it. I have completely separate workspaces. Any time I want to start doing something else, I’m just an hg clone away (which is usually plenty fast).

    Just curious – what do you see as the advantage of keeping everything in a single directory?

    • http://www.johntron.com Johntron

      Wow, I love this. I’m still new to mercurial (git veteran), and hadn’t thought about doing it this way. I’m curious to see what others think about this approach.

  • http://www.sullivansoftdev.com/blog Brian Sullivan

    @Chris

    I concur. Keeping the branches in separate directories seems much more straightforward to me than doing all those gymnastics just to stay in the same directory the whole time.

  • http://jorudolph.wordpress.com Johannes Rudolph

    Nice real-life comparison. Having worked with both, git and mercurial, I personally find the hg way to do local branching via anonymous branches and maybe a branch tip bookmark for additional information (which I don’t need very often since my commit messages are descriptive and branches short-lived) easier than the overly complicated git model where I have to remember which ref points where and will be moved when etc. It’s just about abstractions, which git does not have but instead exposes it’s internal model to the user.

    Have you tried anonymous branching or have you just tried to replicate your git workflow to mercurial? From my personal experience, it’s worth it trying to understand the differences between the git and hg phillosophy and use an appropriate workflow.

  • http://www.lostechies.com/members/bogardj/default.aspx bogardj

    @Chris

    Single directory = single repository. It’s really not hard to switch items in a local repo, that’s what hg update is for. Git does it very easily, I don’t see why Hg should be any different. With a single repo, I can now view ALL the work going on in my repository, instead of looking across all my separate, local forks. Coming from the Git side, this is extremely useful.

  • http://www.lostechies.com/members/bogardj/default.aspx bogardj

    @Brian

    It’s not just to stay in the same directory. It’s to stay in the same repository. For me, it’s important to visualize the history to understand what came where, so I keep the repo browser open 100% of the time. When I have three branches open, but in separate folders, I don’t really have one view of where they all came from.

  • http://www.lostechies.com/members/bogardj/default.aspx bogardj

    @Johannes

    I think this is one of those “to each their own” things. Git branching is about as simple as can be, as a Git branch is simply a named pointer to a commit. Hg branches are much more intrusive, as that metadata lives with each commit. I’ve never personally had to remember where things go, I just look at my repository browser to visualize it. Anyway, Git is super smart about that kind of thing.

    I’m not necessarily trying to replicate my Git workflow, but rather, the workflow I use which Git supports. I’ll take a look at anonymous branching….but one thing about Git branches is that I don’t have to decide up front the nature of work I’m doing.

  • Brent

    Jimmy,

    Nice writeup. I always enjoy seeing other workflows like this. Any particular reason you’re staying away from named branches? Is it due to the fact they live in your revision history?

  • http://www.lostechies.com/members/bogardj/default.aspx bogardj

    @Brent

    I’m still very new to Hg, so I think my next attempt will be to do named branches. Named branches + rebase might also do the trick as well.

  • http://jorudolph.wordpress.com Johannes Rudolph

    @Jimmy: As as I can tell you should be fine with anonymous branching plus bookmarks for branch tips. The only thing you would
    miss compared to git is the pointer to the branch origin (the fork rev), but since you’ve got your repository browser open all the time anyway you don’t really need that. All mercurial commands will handle anonymous branches transparently.

    I dont see any scenario git supports that hg doesnt (in terms of branching).

    >.but one thing about Git branches is that I don’t have to decide up front the nature of work I’m doing.

    Thats actually a case for anonymous branches as hg has them. You can name a branch anytime via bookmark.

    Named branches aren’t for short but rather for long lived branches (such as trunk and stable).

  • http://www.lostechies.com/members/bogardj/default.aspx bogardj

    @Johannes

    In the anonymous branch case, suppose I do 5 commits, then a bug comes in.

    In Git, I would now switch back to “master”, do a pull –rebase, then start THAT work on another local branch. How would I switch back to “master” in the anonymous branching case?

    I couldn’t find a way, which is why I went for the bookmark of “master” that tracks the local “trunk” (mirroring the remote default branch). I don’t want to go just based on ref #’s to “figure out” where I should rewind time to.

  • http://jorudolph.wordpress.com Johannes Rudolph

    I don’t find juggling around with ref#’s inconvenient, so I’m happy with what mercurial brings to the table. Especially when I have a graphical repo-browser.

    The git workflow is appealing though, however it requires me to know about the behavior (when are they moved etc.) of all those revision bookmarks.

    However, writing a mercurial extension that keeps track of you bookmarks shouldn’t be all too difficult. Did I ever mention I wanted to learn python anyway ;-)

  • venizio krups

    you can also do the same with mq, and keep refreshing the patches as you go along. when it’s time for a “changeset” just queue up another patch on top of that one and keep working. when you’re done, decide where on the tree these commits should go, and hg qfinalize.

    let’s say now that you have been basing your queue on changeset x, and by commit time you realize that changeset y should be the parent. so you just hg up y, and apply the queue. if there are conflicts, you can go back to x, apply, and with the queue in place go back to y. mercurial will do the merging onto the patches for you! and you can just apply. very flexible.

    hope this makes sense.

  • http://www.lostechies.com/members/bogardj/default.aspx bogardj

    @venizio

    I’d heard of patches, but I haven’t found a real good source of information on how it compares to local branches + rebasing. For example, with Git, I always work in a local branch. I don’t decide on what queue to put it in, I’m just working on a certain topic. Looks like I need to investigate more.