linuxlab.io
Tutorials▾
  • Linux & networking
    File system, processes, TCP/IP, BGP and OSPF
    →
  • Terraform & IaC
    HCL, state, plan/apply on a LocalStack sandbox
    →
  • Git & GitHub
    Object model, plumbing, branching, GitHub Actions
    →
All tutorials →
PricingAboutSign inCreate account
/
Intro
Lessons
Footer
linuxlab-TutorialsPricingAboutPrivacy & cookies
Copyright © 2026 LinuxLab. All rights reserved.
linuxlab.io
Tutorials▾
  • Linux & networking
    File system, processes, TCP/IP, BGP and OSPF
    →
  • Terraform & IaC
    HCL, state, plan/apply on a LocalStack sandbox
    →
  • Git & GitHub
    Object model, plumbing, branching, GitHub Actions
    →
All tutorials →
PricingAboutSign inCreate account
/
  • Introduction
  • Chapters
  • How it works
  • Lessons
  • Knowledge base
  • Interview prep
home/git/how/fast-forward-vs-merge

how/history

Fast-forward vs merge commit

You "merged the branch" but how? Without a merge commit or with one? What does "branches diverged" mean, and why does --no-ff exist? The commit graph shows it all in a second.

git merge feat can end in two completely different ways, and which one happens decides how the history looks afterward.

  • Fast-forward (ff): the branch pointer just moves forward. No new commits are created. Possible when the branches have not diverged: the target branch has no commits made after the branching point.
  • Merge commit: a separate commit with two parents is created. Always possible, but required only when the branches have diverged.

Press ▶ to see five scenarios. The same git merge feat behaves differently depending on what was on main.

step 1/5·00 · feat moved ahead, main fell behind
MAINFEATABCmain →feat →main отстал от feat: B и C сидят на feat, у main только A

§ steps

  1. Starting state:

    main:  A
                ↘
    feat:  A → B → C

    main has only commit A. feat has A → B → C. The key point: main has no new commits after the branching point. This means the tip of main (A) is an ancestor of the tip of feat (C).

    In Git this is exactly the condition for fast-forward: the target branch lags on a straight line behind the source.

recap

What to remember:

  • FF is rewriting refs/heads/main to the SHA of feat's tip. No new commits appear, the history stays linear, and the fact of branching disappears.
  • A merge commit is required when the branches have diverged (both have commits after the branching point). It has two parents, which is exactly what sets it apart from a regular commit.
  • git merge --no-ff feat forces a merge commit even when FF is possible. Useful when you want the log to show "there was a feat branch here." In GitHub-flow, PRs are merged exactly this way.
  • git merge --ff-only feat is the opposite: "if FF is not possible, do nothing." It protects you from accidental merge commits in git pull (see pull.ff = only).
  • git rebase feat onto main is a third path: move feat's commits on top of main so that FF becomes possible again. The history stays linear, but the commit SHAs change (same diff, new parent = new hash). So you can only rebase what nobody has pulled yet.

What to choose in practice:

  • PR branches → --no-ff (the standard for PR forges).
  • Local sync with main → rebase (or git pull --rebase).
  • Quick direct merges (for example, a personal feature → a personal integration) → ff is fine, the history is cleaner.

§ dig into the knowledge base

  • mergemerge - three ways to combine branches
  • fast-forwardfast-forward - how Git "winds" a branch forward
  • branchbranch - a pointer to a commit
  • rebaserebase - rewrite history with different parents
  • merge-strategiesmerge-strategies - what forges choose
Footer
linuxlab-
Copyright © 2026 LinuxLab. All rights reserved.
Tutorials
Pricing
About
Privacy & cookies