Using git merge --no-ff

Reading time: 2 minutes

When working with git, I like linear history because otherwise merging concurrent feature branches makes the history graph look like spaghetti.

Two common ways to get a linear history are squashing and rebasing.

Squashing applies all the changes on the feature branch as a single linear commit on the target branch.

# Before squashing:

A---B---C main
     \
      D---E---F feature

# After squashing:

A---B---C---G(=D+E+F) main
     \
      D---E---F feature

Rebasing moves D-E-F to the tip of main. A normal merge will fast-forward, keeping the history linear.

# After rebase:

A---B---C main
         \
          D---E---F feature

# After fast-forward merge:

A---B---C---D---E---F main,feature

The downside of squashing is that it can easily lead to large commits that do too many things. It’s much better to have single-purpose commits. And if any small part needs to be reverted, you have to do that by hand.

Rebasing keeps your commits properly sized. Later on, though, it’s hard to pick out all the changes associated with a feature from the commit graph. And if you have to revert a feature, you have to figure out what the starting commit is.

A no-fast-forward merge after rebasing makes an empty merge commit, even though a feature branch could have fast-forwarded. You can do that with git merge --no-ff:

# After rebase:

A---B---C main
         \
          D---E---F feature

# After merge --no-ff:

A---B---C-----------G main
         \         /
          D---E---F feature

Now the history is effectively linear – there’s no spaghetti of other merges between C and G, but the commits associated with the feature are clearly delineated.

I wouldn’t recommend this for small features with only one or two commits, but for large changes spanning multiple commits, I think it’s very useful. Large changes are also the riskier ones that benefit most from the context of seeing commits together and from easy reverts, which no-fast-forward merges give you.

GitHub’s default “merge button” behavior uses --no-ff merges, so if you manually rebase first, the button will do this for you.

•      •      •

If you enjoyed this or have feedback, please let me know by or