← All posts

Git Merge, Rebase & Squash — Part 1: The Commit Graph and Every Kind of Merge

Data-Sci & Digital HealthBasic InfoTech & Computing Nexus
Git Merge, Rebase & Squash — Part 1: The Commit Graph and Every Kind of Merge
On this page

Abstract

Effective version control requires a rigorous mental model to manage parallel development histories without losing contextual boundaries. In Git, projects are fundamentally stored as a graph of commits rather than discrete file versions, which necessitates precise manipulation commands like merge, rebase, and squash. Focusing on the merge operation, the text delineates how developers can rejoin diverged branches by reshaping this commit graph. A fast-forward merge simply slides the branch pointer forward without generating a new merge commit, preserving a clean linear history while obscuring feature origins. Conversely, a three-way merge utilizes the common ancestor commit alongside both branch tips to construct a new merge commit with two parents, retaining the real history at the expense of a busier log. To control these outcomes, the --no-ff flag forces a merge commit to preserve feature boundaries, whereas --ff-only prevents accidental merges by aborting operations lacking a clean fast-forward. This article ultimately equips teams to predict branching histories and select optimal merge behavior.


Infographic visual summary of Git Merge, Rebase & Squash — Part 1: The Commit Graph and Every Kind of Merge
Visual summary · ภาพสรุป

This is Part 1 of a six-part series on how Git actually reorganises history. Before we can compare merge, rebase, and squash, we need one mental model that everything else hangs on:

Git doesn't store your project as file versions — it stores a graph of commits.

Every history command — merge, rebase, squash — is just a different way of reshaping that commit-history graph. In this part we build the graph model and then work through every kind of merge Git can perform: the fast-forward merge, the three-way merge, and the two flags that force Git's hand, --no-ff and --ff-only. By the end you'll be able to read a branching history, predict whether a given merge will create a merge commit, and choose the merge behaviour your team actually wants.

The mental model: a commit graph

Suppose we start from this history:

A---B---C  main

We create a new branch:

git switch -c feature/login

and then develop on both branches in parallel:

ABCFGDEfeature/loginmain
Diverged history: feature/login (D–E) branches off C while main advances to F–G.

Now we need to bring feature/login back into main. There are three main ways to do it:

  1. Merge — join the two lines of history together.
  2. Rebase — move the feature's commits so they sit on top of main.
  3. Squash — collapse many commits into a single one.

This part focuses on the first option, merge. Rebase and squash get their own parts later in the series (see the navigation above).

Merge

The concept

Merge means:

Take the state and history of two branches and combine them — normally without rewriting the existing commits.

The basic command:

git switch main
git merge feature/login

Merge comes in several forms, and the rest of this part walks through each one.

Fast-forward merge

The situation

Suppose main hasn't moved forward since the feature branch was created:

A---B---C  main
         \
          D---E  feature/login

When you run:

git switch main
git merge feature/login

Git simply slides the main pointer forward to E:

ABCDEmain, feature/login
Fast-forward: main simply slides forward — one linear chain, both branches point to E.

No new merge commit is created. This is called a fast-forward merge.

Advantages

Disadvantages

Looking at the history later, it may not be obvious that commits D–E once formed a separate feature branch.

Forcing a fast-forward-only merge

git merge --ff-only feature/login

If a merge commit would be required, the command stops instead of merging automatically. This is a handy guard against creating an unintended history.

Three-way merge

The situation

Now both main and feature have new commits:

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

When you run:

git switch main
git merge feature/login

Git uses three points to compute the merge:

  1. the common ancestor commit, C;
  2. the tip of main, which is G;
  3. the tip of feature, which is E.

It then creates a new merge commit M:

ABCFGDEMmain
Three-way merge: merge commit M has two parents — G on main and E on the feature branch.

M has two parents:

parent 1 = G
parent 2 = E

This is a three-way merge.

Advantages

Disadvantages

Merge with --no-ff

Even when Git could fast-forward, you can force it to create a merge commit:

git switch main
git merge --no-ff feature/login

Before the merge:

A---B---C  main
         \
          D---E  feature/login

After the merge:

ABCDEMmain
--no-ff: Git records merge commit M even though a fast-forward was possible.

Why use --no-ff

You want the history to record that:

D and E are part of feature/login.

This suits teams following a Git Flow-style approach:

feature/*
release/*
hotfix/*

For example:

git merge --no-ff feature/payment

Merge with --ff-only

git merge --ff-only feature/login

Meaning:

Only merge if the pointer can move straight forward, without creating a merge commit.

If the branches have diverged:

          D---E
         /
A---B---C---F---G

Git refuses:

fatal: Not possible to fast-forward, aborting.

This suits teams that want a linear history and don't want Git to create a merge commit by accident.

Key takeaways

0
Message for International and Thai ReadersUnderstanding My Medical Context in ThailandRead more →Message for International and Thai ReadersUnderstanding My Broader Content Beyond MedicineRead more →

Comments

No comments yet. Be the first to share your thoughts.

Sign in to comment