Understanding git fast forward merges

Greg Foster
Greg Foster
Graphite software engineer

Understanding git fast forward merges

One common type of merge operation is the "fast forward" merge. This guide will explain what a fast forward merge is, how it works, and when it's applicable, including detailed examples and explanations of relevant Git terminology.

A fast forward in Git occurs during a merge operation when the base branch that's being merged into has no new commits since the feature branch (the branch being merged) was created or last updated. Instead of creating a new "merge commit," Git simply moves the pointer forward, hence the term "fast forward."

Fast-forward merges occur when there is a linear path from the base branch to the target branch, allowing the head of the base branch to be simply moved forward to point to the target branch. The target branch's pointer is moved forward to the head of the source branch if no divergent work has been done on the target branch. This effectively brings the history of the target branch in line with the source branch.

  1. Simpler history: Fast-forward merges keep the repository history linear and clean, which can simplify the understanding and navigation of the history.
  2. Less overhead: No extra merge commits are created, which can be preferable in small projects or projects with fewer parallel developments.
  1. Loss of context: The major downside is the loss of context regarding the feature's development process. Without a dedicated merge commit, it's harder to see the boundary between different features or to identify when a feature was merged.
  2. Complicated reverts: Reverting a specific feature becomes more difficult as there's no single merge commit to revert. You would need to individually revert all commits pertaining to that feature.

Using the --no-ff flag forces a merge commit even when a fast-forward merge would be possible. This approach is preferred in the git-flow mode introduced by Vincent Driessen, for merging feature, release, and hotfix branches back to develop or main branches.

  1. Preserves history: It keeps a clear record of project changes, including the context of feature development and integration.
  2. Easier to manage features: Features can be collaboratively developed and then cleanly merged as a unit, which also simplifies potential reverts to a single merge commit.
  3. Facilitates code review and collaboration: The merge commits provide a natural place for code review and discussions about specific features or fixes.
  1. Complex history: This method introduces additional merge commits that can clutter the project history, potentially making it harder to follow.
  2. More merge conflicts: There is a slightly increased likelihood of encountering merge conflicts that need to be resolved manually, as changes from the base branch are not automatically incorporated into the feature branch during its development.

Let's go through some common scenarios and commands to understand this better.

Assume you have two branches: main and feature. The feature branch was created from main and has new commits, but main has not changed since feature was created.

  1. Check out the main branch:

    Terminal
    git checkout main
  2. Merge the feature branch:

    Terminal
    git merge feature

If a fast forward is possible, you'll see output like:

Terminal
Updating 4a5b6c7..9d8e7f8
Fast-forward
file.txt | 2 ++
1 file changed, 2 insertions(+)

This output shows that the main branch pointer has moved to the point of the feature branch.

Sometimes, you might want to preserve the exact history of changes, including the fact that a feature branch was created and merged. For this, you can disable the fast forward merge:

Terminal
git merge --no-ff feature

This forces Git to create a new merge commit even if a fast forward merge is possible, thus preserving the history of a separate branch and merge.

When you pull changes from a remote repository, you can explicitly ask for a fast forward merge:

Terminal
git pull --ff-only

This command updates your current branch only if the pull can be resolved as a fast forward.

If you want to fast forward your local branch to catch up with changes in another branch (like main), you can use:

Terminal
git checkout feature
git merge main

This will fast forward the feature branch if no divergent changes have been made on feature.

Before attempting a fast forward merge, you might want to check if it's possible:

Terminal
git merge-base --is-ancestor main feature

This command returns 0 (true) if main can be fast forwarded to feature, and 1 (false) otherwise.

The decision between using fast-forward and non-fast-forward merges largely depends on the project's requirements for history visibility and management. For larger, collaborative projects where tracking the development and integration of features separately is crucial, the non-fast-forward approach recommended by the git-flow model is beneficial. It provides explicit markers in the project history that denote the start and end of feature integrations, facilitating easier troubleshooting and version management.

Conversely, for smaller projects or those with a more streamlined development process, fast-forward merges might be preferred for their simplicity and cleaner history. In any case, it's important to choose a strategy that aligns with the team's workflow and project requirements.

Fore more detail on fast forward merging see the official Git documentation.

Give your PR workflow
an upgrade today

Stack easier | Ship smaller | Review quicker

Or install our CLI.
Product Screenshot 1
Product Screenshot 2