Learn the theory
Git Merge vs Rebase: how it works
4 min read · Updated
Merge, rebase and squash all do the same job — get the work from one branch into another — but they leave very different history. Merge preserves what happened, rebase rewrites it into a straight line, and squash condenses it into one commit. The right choice depends on whether the branch is shared and how much you value a tidy log. This lab draws the commit graph so you can see exactly what Git does to it.
In short
- Git history is a graph of commits; a branch is just a movable label pointing at one commit.
- Merge adds a commit with two parents and keeps every original commit untouched.
- Rebase replays your commits on a new base, creating new commits with new IDs and a linear history.
- Squash merge turns the whole branch into a single commit on the target branch.
- Golden rule: never rebase commits that others have already pulled — it rewrites shared history.
How Git models history
Each commit stores a snapshot plus a pointer to its parent commit(s). Together they form a directed acyclic graph. A branch such as main or feature is nothing more than a name pointing at one commit; HEAD points at the branch (or commit) you currently have checked out. Creating a commit moves the current branch's label forward.
A branch diverges when both it and its base get new commits after the point they split. That's when integrating them needs a real strategy.
Merge
git merge feature (run on main) combines both lines of work in a new merge commit with two parents. Nothing is rewritten: every original commit keeps its ID, and the graph shows exactly when the branch diverged and when it came back.
git checkout main
git merge feature # creates a merge commit
git merge --no-ff feature # force a merge commit even if a fast-forward is possibleRebase
git rebase main (run on feature) takes your commits, sets them aside, moves the branch to the tip of main and replays each commit on top of it. The changes are the same, but each replayed commit has a different parent, so it gets a new commit ID. The old commits become orphaned (still reachable via git reflog for a while).
git checkout feature
git rebase main # replay feature's commits onto main
git checkout main
git merge feature # now a pure fast-forward: linear historyBecause rebase replays commits one at a time, a conflict may appear once per commit — you resolve it, run git rebase --continue, and Git moves on. With a merge you resolve conflicts once, for the combined result.
Squash merge
git merge --squash feature applies the final result of all feature commits to your working tree without creating a merge commit; you then commit it once. main gets one tidy commit per feature, but Git no longer knows the branch was merged — which is why git branch -d feature refuses to delete it and you use -D (or let the pull-request UI delete it).
- Pro: a clean, one-commit-per-feature history that's easy to revert.
- Con: the individual commits and their messages are lost from
main, and long-lived branches that squash repeatedly can re-conflict.
Which one should you use?
| Situation | Good choice | Why |
|---|---|---|
Updating your private feature branch with the latest main | Rebase | Linear history, no noisy “Merge main into feature” commits. |
Integrating a finished feature into main | Merge or squash | Safe on shared branches; squash keeps main tidy. |
| Messy WIP commits (“fix”, “fix again”) | Squash | Collapse them into one meaningful commit. |
| Public or shared branch | Merge | Never rewrite history others depend on. |
| You want to see when work was integrated | Merge (--no-ff) | The merge commit marks the integration point. |
Undoing mistakes
Rebased the wrong thing? Git rarely loses commits immediately. git reflog lists every position HEAD has been in; find the commit from before the rebase and git reset --hard <id> (or create a branch there) to get it back. For a merge, git merge --abort cancels a conflicted merge and git revert -m 1 <merge-commit> undoes a completed one.
Frequently asked questions
- What is the difference between git merge and git rebase?
- Merge joins two branches with a merge commit and preserves history as it happened. Rebase replays your commits on top of another branch, rewriting them into new commits and producing a linear history.
- Why does rebase change commit hashes?
- A commit's ID is computed from its content, its metadata and its parent. Replaying a commit onto a new parent changes the parent, so the result is a different commit with a different hash.
- When should I not use git rebase?
- Don't rebase commits that have been pushed and pulled by others. Rewriting shared history forces everyone else to reconcile diverged branches.
- What is a fast-forward merge?
- When the target branch hasn't diverged, Git simply moves its pointer to the tip of the other branch. No merge commit is created.
- What does git push --force-with-lease do?
- It force-pushes only if the remote branch is still where you last saw it. Unlike plain
--force, it refuses to overwrite commits a teammate pushed in the meantime. - Is squash merging better than merge commits?
- It's a trade-off. Squashing gives a clean, one-commit-per-feature history but discards the branch's individual commits; merge commits preserve everything at the cost of a busier graph.