Friday, June 25, 2010

watch out for a rebase of a merge

So, a while back I tried to publish some detailed foolproof instructions for using named branches and hgsubversion. I've been using Mercurial as a Subversion client for a little while now, and I figured that if I posted my discoveries and practices then others with an Internet connection could benefit as well. After revising the post multiple times, I finally decided to pull it (HA!) from the blog because it clearly wasn't as foolproof as anticipated - as proven by the mishaps of this fool, anyway. At this point I redirect you to the Stack Overflow question "How to handle merges with hgsubversion?" and parrot the advice there: don't bother trying to reconcile named branches to Subversion!  To fork development, clone the repository instead.

(Normally, my personal inclination is toward branches since clones feel like a hack solution. Given that I'm doing the right thing by using a VCS, why should I need to have separate directories for separate lines of development? It's true that environment variables or symbolic links make switching between the two directories fairly easy, but having to do those manipulations outside the VCS then feels like a hack of a hack. More and more frequently I wonder whether I'll settle on git, which has its own well-known set of warts, in the end.)

One of the primary shatter-points of my confidence was a rebase of a merge. This is the situation in which the default branch was merged into the other branch in the past, and the time arrives to rebase the other branch onto the default branch. (During work on a branch that will be ended by a rebase, the better course of action would have been to update it by a rebase onto default with --keepbranches rather than merging in the default branch.) The root problem is that a merge duplicates changes on the default branch by re-applying to the other branch, so to rebase this merge is to attempt to apply the default branch's past state to its present state, although the default branch may have changed in numerous ways after that merge!

As you might expect, a rebase of a merge often produces conflicts, and the likely desired resolution is to choose the version in the tip of the destination branch, "local" or "parent1". I can only write "likely" because, as usual with conflict resolution, only the developer's judgment can determine the right mixture whenever significant feature changes are involved in the conflict too.

While all these conflicts can be irritating, hair-pulling may be a more proper response to the instances of no conflict; Mercurial dutifully sends one or more files in the default branch backward in time without comment. I know of two causes of this behavior. 1. After the merge from default into other, a later change in default undoes one of the merged changes, and through the rebase of the merge the undoing is undone (reviving the original mistaken change). 2. After the merge from default into other adds a new file, a later change in default modifies the added file, and through the rebase of the merge the added file's later modifications vanish.

Therefore, after a rebase, it's a good idea to run a diff --stat in the range of the default's tip before the rebase and default's tip after the rebase. And for any of the unexpected changes that have no relevance to the feature just rebased, revert those files to the version of the default tip before the rebase and check in. Constant vigilance!

No comments:

Post a Comment