Using git fast-forward merging to keep branches in sync

Just to be clear, my last post was not prompted by any particular event or person.  The transition to git (from CVS) is new to pretty much everyone as evidenced by the numerous git related threads on fedora-devel.  I thought I’d contribute to the discussion by giving a simple outline of what to do and what not to do.  I’ll expand on my previous post here by walking through an example.  But first lets go through a bit of git theory.

Git supports two types of merges between branches: fast-forward merge and true merge.  One of the downsides of distributed revision control systems is that the relationships between branches can become quite complicated.  Fast-forward merges were created to avoid this difficult relationship where possible.  When doing a git merge, if only one of the two branches in the merge has changed, a fast-forward merge will occur.  If both sides have changes, than a true merge will occur.  Lets look at what this looks like in gitg:

First, we have a new package with two branches: master and f14 (for rawhide and Fedora 14 respectively).

> fedpkg clone foo
> cd foo
> gitg

Next, we add a line in the master branch and commit it:

> fedpkg switch-branch master
> echo “added a line” > x
> git add x
> fedpkg commit -m “added a line” # You may also push your changes at this point with -p
> gitg

Let’s make another change in master:

> echo “added another line” >> x
> fedpkg commit -a -m “added another line” # Note that -a replaces the git add step above (see git docs for the implications of this)
> gitg

Now, we want our changes made in master to show up in the f14 branch. We simply do:

> fedpkg switch-branch f14
> git merge master # Note that NO commit was necessary because there were no conflicts
> gitg

So far so good!  Our changes were merged and our history looks clean.  Lets back up, however, and do it the wrong way.  Rather than doing the merge, we will just manually add our changes to the f14 branch.  So instead of the previous step we do:

> fedpkg switch-branch f14
> echo “added another line” >> x
> fedpkg commit -a -m “added another line”
> gitg

You should notice that we have now two disparate commits, one in each branch.  We have now just broken the ability to do a fast-forward merge.  You should also notice that master has a change that is not in f14 (‘added another line’).  Let’s merge this change into f14 to see what happens:

> git merge master
Auto-merging x
CONFLICT (content): Merge conflict in x
Automatic merge failed; fix conflicts and then commit the result.

Yikes!  What before was a simple merge is now a conflict!  Ok, so lets resolve the conflict:

> vi x # Edit to resolve the conflict
> git add x # Mark the conflict as resolved
> fedpkg commit -m “merge from master”
> gitg

So now, with a bit more effort (that could have easily been avoided had we just used a fast-forward merge) we have the changes from master merge into f14 by using a true merge.  All set, right!? Not by a long shot.  To show what I mean by this, lets add a new commit to master:

> fedpkg switch-branch master
> echo “added a third line” >> x
> fedpkg commit -a -m “added a third line”
> gitg

Notice that master and f14 again have disparate commits.  This is because the true merge that occurred in the previous step involved a commit. So even though we have merged changes from master into f14, we have permanently broken the ability do do fast forward merges.  Let me prove my point:

> fedpkg switch-branch f14
> git merge master
> gitg

Again, because of the disparate commits in f14 and master, the git merge performed a true merge, resulting in a new commit on f14.  All is not lost however.  To repair this situation and bring back the ability to do a fast-forward merge we need to notice only one thing: master does not yet have a disparate commit.  So this time, lets do a merge from f14 into master rather than vice versa:

> fedpkg switch-branch master
> git merge f14
> gitg

Yay! f14 and master are in sync again!  Let’s add a change to master and see if we can get the fast-forward into f14 again:

> echo “added a forth line” >> x
> fedpkg commit -a -m “added a forth line”
> fedpkg switch-branch f14
> git merge master
> gitg

The merge was performed as a fast-forward merge and our faith in humanity has been restored!

Now, this example has been a fairly simple one, however, with real-world uses this gets complicated fast.  This is all the more reason to make sure that you don’t break the ability to do fast-forward merges.  Let me show you a real world example (names removed to protect the innocent; click to enlarge):

  1. Notice that the packager imported as disparate commits.  This meant that merging was broken from the get-go.
  2. The result of #1 is that every time a change had to be made to a package it had to be merged by hand.  Further, there is no correlation between the commits, even though the contents of those commits were the same.
  3. The problem is resolved by merging every branch together.  Keep in mind that this was a time-consuming process since nearly every merge would have had a conflict which had to be manually resolved.
  4. The end result is that all branches are at the same HEAD and now fast-forwarding merging will work as normal.

In short, gitg (or other visualization tools) are your friend. If you have a graph in gitg that looks complicated, its probably because you’ve broken fast-forward merging and you can simplify your life by fixing it and working to make sure that disparate commits don’t happen again. The best development practice is to commit *all* changes into master, test them in rawhide and then merge them via fast-forward to the other branches.

8 thoughts on “Using git fast-forward merging to keep branches in sync

  1. Thanks for the write-up! gitg really helped visualize what was going on. There’s no way I would have been able to sync everything back up without it!

  2. Previously I was scratching my head over why some merges were causing separate commits and other were not – I couldn’t see the logic. Now I know better thanks to your clearly presented article!

  3. Pingback: My Git Snippets

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>