git rebase and merge gotcha (edge case)

Imagine you work on a project that generally has two live branches at a time: master, which holds everything, it’s your development branch; and, stable, which holds your latest stable release, it only ever gets bug fixes. The project tries very hard to maintain stable releases separate from new development, which may break backwards compatibility.

Now imagine a normal workflow for such a project. When a bug is identified in the stable branch, the fix goes into the stable branch. The fix is of course needed on the development branch as well, so the stable branch is merged into the development branch.

All is well and good.

Now imagine a workflow for how the stable branch gets the bug fix. Something like:

$ git checkout stable
$ emacs ...
$ git commit -a
$ git fetch ; git rebase origin stable
$ git push origin stable

The rebase avoids extraneous merge commits that only provide information that two developers were working on the branch at the same time.

So now you’re guessing something went wrong, especially given the title of this post. You’re probably also guessing it has to do with the rebase. You’re right.

If you’re the developer fixing this bug you just made the stable branch == the development branch, and in a rather non-obvious way.

First off, git rebase origin stable has a typo, it should be git rebase origin/stable, subtle. The latter does what you would expect, and is essentially equivalent to git pull --rebase, which is therefore much safer. The former sets the local stable branch equal to the origin branch with the bug fix tacked onto the end. That’s bad because typically origin is going to mean origin/master, and you’ve just set your stable branch equal to your development branch.

Now I didn’t mention it before, but you’re working from a cloned shared repository that disallows pushes that aren’t fast-forward merges. That’s a very good thing, and the default.

The fast-forward only requirement should save you here. git push origin stable should be denied. You would not expect to branch that contains all of your development series commits to be a simple fast-forward merge on top of your stable series. But it is!

Your workflow for bug fixes says you fix the bug then merge to master. After the last bug fix your branches look something like this:

master = d2 m0 (s1 s0) d1 d0 b
stable = s1 s0 b

b is some common base that will always exist
sX are commits to the stable branch
mX are merges of the stable into the edvelopment
dX are development commits

After the latest bug fix your polluted stable branch becomes:

stable = s2 d2 m0 (s1 s0) d1 d0 b

How on earth can this be a fast-forward merge for the push. Well git log may interleave s1 d1 d0 s0 is any number of ways, but in reality the branch is actually a graph:

 /  \
s1  d0
s0  d0
 \  /

And it all becomes clear. To fast-foward this on stable = s1 s0 b just apply m0 then d2 s2.

What can you do? You could not rebase, you could always merge stable^ into master, you can also write an update hook to disallow development commits in the stable branch.


Tags: , ,

One Response to “git rebase and merge gotcha (edge case)”

  1. no religion too Says:

    Lots of Fantastic information in your posting, I favorited your blog so I can visit again in the future, All the Best, Keith Lehnen

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: