Why we chose not to git fast forward merge

Matthieu - 14 Feb 2014

This post comes from an interesting email thread we had. In the thread, Stephen was answering various questions from the team about why it is so important to NOT use fast-forward merges.

Question: Can you give a quick explanation of what --no-ff and --log do and why we want them?



Out of the box, if you call "git merge", it is not guaranteed to create a merge commit.

Technically, merge commits are only required when both branches have new commits, e.g.:

  • master: C1 -> C2
  • feature: C1 -> C3

If you do:

git checkout master
git merge feature

It will be forced to make a new merge commit, C4 that ties together C2 and C3:

  • master: C1 -> C2 -> C4
  • feature C1 -> C3 /

(If the ASCII art lines up, C4 has both C2 and C3 pointing at it.)

However, if feature had been created off of C2 instead of C1, and so it looked like this:

  • master: C1 -> C2
  • feature: C2 -> C3

And you do:

git checkout master
git merge feature

Git will say "huh, technically I could make master the same as feature by just moving master to now be C3", and so master would look like:

  • master: C1 -> C2 -> C3

Which makes sense, git has incorporated the work of feature, and without rewriting history. In git parlance, this is called a fast forward.

The downside is that you've lost the notion that the feature branch ever existed. Which is not necessarily crucial, but if you want your DAG to always look a certain way, aesthetically it's nice to force the merge commit anyway, so:

git merge --no-ff feature

Will force a new merge commit, C4, who parents are C2 and C3.

The general assertion is that, if you're running "git merge" by hand in the first place, you are probably merging across "real" branches and so forcing an actual merge commit is probably a good idea.

The other flag, --log, just means that the merge commit's generated commit message will include a brief description of what was merged, e.g. instead of just:

  Merge feature-a into master.
It'll be:
  Merge feature-a into master.
Commits: * Second commit on feature-a * First commit on feature-a

Which is handy for when you're scanning merge commits in git log/etc.

Try it yourself!

To see by yourself, below instructions will help you setting up a dummy repo.

Pre-requisite: git is installed. Should work on any shell or Windows command prompt.

Enter the following to create the repo and add the first commit:

git init test ; cd test ; echo "content" > file.txt ; git add * ; git commit -m "init"

Create a branch, modify your file and commit (done twice here to get a better gitk view later):

git checkout -b branch1 ; echo "modif" >> file.txt ; git commit -am "branch1 modif" ; echo "modif" >> file.txt ; git commit -am "branch1 modif"

Merge your branch without fast-forward and optionally --log:

git checkout master ; git merge --no-ff --log --no-edit branch1

Again, create a branch, modify your file and commit (again x2 for the gitk view):

git checkout -b branch2 ; echo "modif" >> file.txt ; git commit -am "branch2 modif" ; echo "modif" >> file.txt ; git commit -am "branch2 modif"

Merge your branch with fast-forward:

git checkout master ; git merge --ff-only branch2

Running 'gitk' in your repository path should something like:

We can see on the gitk screenshot that “branch2” becomes invisible in the history.

If for example you have a one branch per feature policy, this means you lost the ability to tell what are all the commits that correspond to that feature.

Question: This seems to be similar in spirit but opposite in effect to "git pull --rebase", which prevents git pull from creating merge commits and use rebase instead.


Yes, that is insightful.

When pulling, we need to tell git "no, really, don't make merges".

And when merging, we need to tell git "no, really, make a merge".

I don't have a good explanation for why this is, other than it's just the whole "the git CLI is unintuitive/not well designed" thing.

Well, and, to give them a little credit, git is not opinionated in saying "your workflow must be this", which has the unfortunate side effect that the default behavior does not itself adhere to one or another workflow.

Question: Is there a community convention over this, or do most projects just accept whatever git does by default?


I don't know for sure, but my feeling is that there is a lot of community convention around both "pull --rebase" and "merge --no-ff".

In particular, "git pull --rebase" seems really common, given the number of blog posts about it, and the fact that it eventually got its own config setting ("pull.rebase=true") for the user to change their default "git pull" behavior.

Same thing with "git --no-ff --log"--they both also got config settings.

To me, a flag graduating to a config setting says a lot of people are using it as convention. (I would go further and assert that getting a config setting almost means the behavior is the correct/preferred way, but the default can't be changed for backwards compatibility purposes.)

comments powered by Disqus