Monday, February 6, 2012

Git: Rebasing Merge Commits

There are many articles related to rebasing and some of them also describe rebasing merge commits. Long story short: You just completed a merge and somebody has pushed a commit before you were able to push yours. The solution is to make git aware of the merge you did.

git rebase --preserve-merges <upstream>
or
git rebase -p <upstream>

This way git will be aware of your merge commit and it will try to redo it when rebasing. Here's what man git-rebase has to say about this:

-p, --preserve-merges
           Instead of ignoring merges, try to recreate them.

           This uses the --interactive machinery internally but
           combining it with the --interactive option explicitly
           is generally not a good idea unless you know what you
           are doing (see BUGS below).

But there's a problem, if your merge had conflicts that you solved they won't be picked up by the rebase machinery. And you will end up resolving the conflicts again ... at least this is the case with git version 1.7.5.4

But there's hope. We can make our lives easier by using another git cool feature aka rerere (Reuse recorded resolution of conflicted merges).  Here's what man git-rerere sais:

In a workflow employing relatively long lived topic branches, the developer sometimes needs to resolve the same conflicts over and over again until the topic branches are done (either merged to the "release" branch, or sent out and accepted upstream).

This command assists the developer in this process by recording conflicted automerge results and corresponding hand resolve results on the initial manual merge, and applying previously recorded hand resolutions to their corresponding automerge results.

OK, sounds cool, but how can we use rerere now? Let's see... , assuming your last commit was the merge commit, do a git log -n1 and "remember" the sha1 of the commit. And then:

  1. Enable rerere mechanism.

    git config --global rerere.enabled 1

  2. Go back one commit 

    git reset --hard HEAD^

  3. Redo the merge.  Rerere will record the conflicts.

    git merge --no-ff <topic>

  4. "resolve" conflicts

    git checkout <commit> -- ./
    git add -u

  5. Complete the merge. Rerere will record conflict resolution.

    git commit

  6. Go back where we started.

    git reset --hard <commit>

  7. Do the rebase again. 

    git rebase -p <upstream>

where <commit> is the sha1 for the merge commit and <upstream> is the remote branch. This will automatically reuse the manual resolution previously recorded by rerere mechanism.

Now relax, there's no hurry in doing the push ;)

1 comment:

  1. Many thanks. Well explained! I run into this problem today and couldn't imagine that git can't do that automatic. I hope git rebase -p will do it, someday, automatic. For now rerere does the job.

    ReplyDelete