← All posts

Git Merge, Rebase & Squash — Part 2: Squash Merge — One Feature, One Commit

Data-Sci & Digital HealthBasic InfoTech & Computing Nexus
Git Merge, Rebase & Squash — Part 2: Squash Merge — One Feature, One Commit
On this page

Abstract

Maintaining a clean version control history is critical for teams seeking to align discrete features with single, understandable changes. While normal merge operations can pollute the main branch timeline with noisy history, a squash merge condenses multiple feature modifications into one cohesive commit. Executed locally via the git merge --squash command followed by git commit, this mechanism stages the combined alterations of an entire branch without automatically generating a commit or establishing an ancestry link. Consequently, the resulting commit is new, possessing a unique hash and a single parent. Because the original branch retains its independent history and remains unrecognized by ancestry-based checks like git branch --merged, developers must forcefully remove it using git branch -D. The operation contrasts with a normal three-way merge that preserves full developmental lineage, as well as an interactive rebase squash that rewrites history within a single branch. This article outlines these fundamental mechanics, equipping developers to intentionally curate project logs while navigating branch ancestry.


Infographic visual summary of Git Merge, Rebase & Squash — Part 2: Squash Merge — One Feature, One Commit
Visual summary · ภาพสรุป

Part 1 mapped every kind of merge onto the commit graph — fast-forward, three-way, --no-ff and --ff-only. This part isolates one more way to bring a branch home: the squash merge, the move behind GitHub's popular Squash and merge button. The idea is deceptively simple — take a feature branch made of many small commits and land it on main as a single, clean commit.

By the end you will be able to run a squash merge locally, explain exactly what Git records — and, just as importantly, what it does not record — tell the two different "squash" operations apart, and see at a glance how a squash merge differs from an ordinary merge commit.

What a squash merge is

A squash merge answers a very human wish: one feature, one commit.

Squash means: take the changes from many commits and combine them into a single new commit.

Suppose your feature/login branch has grown four commits, several of which are just corrections:

D  create login form
E  fix typo
F  fix login again
G  final fix

None of E, F or G is a milestone anyone needs to see on main forever. A squash merge lets you keep all of their changes while discarding the noisy history.

Squash merge in action: four commits become one

Start on the branch you want to merge into, run the squash, then commit:

git switch main
git merge --squash feature/login
git commit -m "Add login feature"

The --squash flag is the key. Unlike a normal git merge, it does not create a merge commit and it does not move main on its own. Instead it stages the combined changes of the whole branch and stops, leaving you to write a single commit message. Here is the graph before:

ABCDEFGfeature/loginmain
Before the squash merge: feature/login carries D–E–F–G, branched off C on main.

and after you run the three commands above:

ABCSmain= D+E+F+G
After the squash merge: a single new commit S carries the combined result of D+E+F+G.

The new commit S carries the combined result of D + E + F + G — the same final files a normal merge would have produced — but as one tidy commit on main.

What a squash merge does not do

This is where squash merges surprise people. S looks like the end of the story, but Git has quietly recorded much less than a normal merge would:

The third and fourth points together have a practical consequence worth spelling out, because it trips up almost everyone the first time.

Why Git doesn't see the branch as "merged"

Because S does not have the feature branch tip as a parent, there is no ancestry link from main back into feature/login.

Git's built-in "is this branch merged?" tools work purely by ancestry — they ask whether the branch tip is reachable from your current commit. After a squash merge it is not. So:

git branch --merged          # feature/login is NOT listed
git branch -d feature/login  # refused: "not fully merged"

To remove the branch you have to delete it forcefully with git branch -D feature/login, confirming that you really are finished with those original commits.

GitHub side-steps this confusion. When you click Squash and merge, GitHub records that the pull request was merged as a first-class fact about the PR — not as a deduction from the commit graph — so the PR shows as Merged and GitHub offers to delete the branch, even though the underlying Git ancestry still looks "unmerged". The lesson: after a squash merge, trust the PR status, not git branch --merged, to tell you whether the work has landed.

Advantages and disadvantages

Why teams like it

What it costs

Running it locally vs GitHub's button

Locally, the full sequence — including a look at what got staged — is:

git switch main
git merge --squash feature/login
git status
git commit -m "feat: add login system"

The git status step is worth keeping: it lets you confirm exactly which changes were staged before you seal them into S.

GitHub's Squash and merge button does the same thing at the server level:

the whole Pull Request → one commit on the target branch.

Everything above — the new hash, the single parent, the branch that looks unmerged by ancestry — applies equally to that button.

The two kinds of "squash" (don't confuse them)

"Squash" is one word doing two different jobs. Keep them apart.

Kind 1 — Squash merge

Used when you bring a whole branch into main:

git merge --squash feature/login
git commit

This is everything described above.

Kind 2 — Interactive rebase squash

Used to tidy commits within the same branch, before it ever reaches main:

git rebase -i HEAD~4

In the editor you mark which commits to fold into the one before them:

pick    Add login
squash  Fix typo
squash  Fix validation

(Part 4 covers interactive rebase in full.) Both operations reduce the number of commits, but the mechanism is different: a squash merge collapses a branch as it lands on another branch, while an interactive-rebase squash rewrites the branch's own history in place.

Merge commit vs squash merge, side by side

Put the two outcomes next to each other. Take a feature branch of three commits:

D---E---F

Merge commit

A normal (three-way) merge keeps every commit and adds a merge commit M that ties the two histories together:

ABCDEFMmain
Merge commit: M ties the two histories together — D, E and F stay reachable from main.

From main you can still walk back into D, E and F. The full development history is preserved.

Squash merge

A squash merge throws that structure away and leaves a single commit:

ABCSmain= D+E+F
Squash merge: main sees only S; the individual commits are gone from this line of history.

From main you see only S. The individual commits D, E and F are gone from this line of history — they survive only on the feature branch until it is deleted.

Same final files, very different history. Choosing between them is really a choice about what you want main's log to remember — a question Part 6 turns into a full decision table.

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