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:
s2 d2 m0 / \ s1 d0 s0 d0 \ / b
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: Condor, Development, Git
August 8, 2010 at 6:26 pm |
Lots of Fantastic information in your posting, I favorited your blog so I can visit again in the future, All the Best, Keith Lehnen