This guest post is written by Sylvie Davies, the author of the Bit-Booster Commit Graph and More add-on for Bitbucket Server. Sylvie is a mother, poet, painter, pianist, and software developer with over 5 years experience in Java and JavaScript, and 2 years with Git.

First, what is a foxtrot merge?

A foxtrot merge is a specific sequence of git commits. A particularly nefarious sequence. Out in the open, in lush open grasslands, the sequence looks like this:

But foxtrots are rarely seen in the open. They hide up in the canopy, in between the branches. I call them foxtrots because, when caught mid-pounce, they look like the foot sequence for the eponymous ballroom dance:

Others have blogged about foxtrot merges, but they never name them directly. For example, Junio C. Hamano blogs about having Fun With –First-Parent, as well as Fun With Non-Fast-Forward. David Lowe of nestoria.com talks about Maintaining Consistent Linear History. And then there’s a whole pack of people telling you to avoid “git pull” and to always use “git pull –rebase” instead. Why? Mostly to avoid merge commits in general, but also to avoid them darn foxtrot vermin.

But are foxtrot merges really bad? Yes.

They are clearly not as bad as the Portuguese Man o’ War. But foxtrot merges are bad, and you do not want them creeping into your git repositories.

Why are foxtrot merges bad?

Foxtrot merges are bad because they change origin/master’s first-parent history.

The parents of a merge commit are ordered. The 1st parent is HEAD. The 2nd parent is the commit you reference with the “git merge” command.

This means first-parent history is exactly like it sounds. It’s the history you get when you omit all parents except the first one for each commit. For regular commits (non-merges) the first parent is the only parent, and for merges it was the commit you were on when you typed “git merge.” This notion of first-parent is built right into Git, and appears in many of the commands, e.g., “git log –first-parent.”

The problem with foxtrot merges is they cause origin/master to merge as a 2nd parent.

Which would be fine except that Git doesn’t care about parent-order when it evaluates whether a commit is eligible for fast-forward. And you really don’t want that. You don’t want foxtrot merges updating origin/master via fast-forward. It makes the first-parent history unstable.

Look what happens when a foxtrot merge is pushed:

You can calculate the first-parent history yourself by tracing the graph with your finger starting from origin/master and always going left at every fork.

The problem is that initially the first-parent sequence of commits (starting from origin/master) is this:

B, A

But after the foxtrot merge is pushed, the first-parent sequence becomes this:

D, C, A

Commit B has vanished from the origin-master‘s first-parent history. No work is lost, and commit B is still part of origin/master of course.

But first-parent turns out to have all sorts of implications. Did you know that the tilda notation (e.g., <commit>~N) specifies the Nth-commit down the first-parent path from the given commit?

Have you ever wanted to see each commit on your branch as a diff, but “git log -p” is clearly missing diffs, and “git log -p -m” has way too many diffs?

Try “git log -p -m –first-parent” instead.

Have you ever wanted to revert a merge? You need to supply the “-m parent-number” option to “git revert” to do that, and you really don’t want to provide the wrong parent-number.

Most people I work with treat the first-parent sequence as the real “master” branch. Either consciously or subconsciously, people see “git log –first-parent origin/master” as the sequence of the important things. As for any side branches merging in? Well, you know what they say:

But foxtrot merges mess with this. Consider the example below, where a sequence of critical commits hits origin/master in parallel to your own slightly less important work:

At this point you’re finally ready to bring your work into master. You type “git pull,” or maybe you’re on a topic branch and you type “git merge master”. What happens? A foxtrot merge happens.

This wouldn’t really be of any concern. Except when you type “git push” and your remote repo accepts it. Because now your history looks like this:

What should I do about the pre-existing foxtrot merges that have infected my git repo?

Nothing. Leave them. Unless you’re one of those antisocial people that rewrites master. Then go nuts.

There are other ways. You could disable direct pushes to master, and hope that pull-requests never merge with fast-forward. Or train your staff to always do “git pull –rebase” and to never type “git merge master” and once all your staff are trained, never hire anyone else.

If you have direct access to the remote repository, you could setup a pre-receive hook. The following bash script should help you get started:

But please don’t do #3, because the final result is called a “Portuguese man o’ war merge,” and those guys are even worse than foxtrot merges.

Conclusion

At the end of the day a foxtrot merge is just like any other merge. Two (or more) commits come together to reconcile separate development histories. As far as your codebase is concerned, it makes no difference. Whether commit A merges into commit B or vice versa, the end result from a code perspective is identical.

But when it comes to your repository’s history, as well as using the git toolset effectively, foxtrot merges create havoc. By setting up policy to prevent them, you make your history easier to understand, and you reduce the range of git command options you need to memorize.

One Comment

Hey Amber, thanks for the writeup! I’m actively pushing git at my workplace, and I’m taking notes on things exactly like this so we start off with the right “culture” from the get go. The less we have to retrain, the better.

Question for you: how important is it to dodge foxtrot merges? When I got to the end of the post and read your solutions, I immediately thought, “Ah yes! The age-old rebase-vs-merge debate.” You sort of skimmed over the negative effects a foxtrot merge can have, and seem to assume we’re already deep in that apparently bad hole. Could you update or follow this post with what bad things a foxtrot merge does to git’s functionality?