DevOps
Part 3 of 6 · GitRebase vs Merge vs Squash — When, Why & Tradeoffs
Merge records parallel work with a join commit. Rebase replays commits onto a new base and mints new ids. Squash collapses a branch into one commit. The wrong choice on a shared branch hurts the team. The wrong choice on a private branch mostly wastes review time.
- 1Gist
- 2Maps
- 3Q&A
- 4Sandbox
Voice readout needs Web Speech Synthesis in this browser.
Question ladder
L1
Does rebase delete the old commits?
Answer
No. It writes new commits. The old ids can stay reachable from the reflog until they expire.
L2
Why did a merge after a rebase duplicate changes?
Answer
The pre-rebase commits and the replayed copies were both merged. Pick one history and reset the other branch onto it.
L3
When is a merge commit on main valuable?
Answer
Long-lived release branches, an explicit integration record, or a policy that wants both parents preserved.
L4
How do squash and bisect interact?
Answer
Bisect works well when each squash on main builds. It works badly when one squash mixes a fix, a feature, and a refactor.
L5
What does --force-with-lease check?
Answer
That the remote ref is still the commit you expect. If someone else moved it, the push stops.
L6
How does pull --rebase differ from pull?
Answer
pull --rebase replays your local commits on top of the fetched tip. A default pull may create a merge commit.
L7
How do you port a hotfix that already landed on main as a squash?
Answer
Cherry-pick the squash commit, or the original pull-request commits if they still exist. The recovery lesson owns the command.
Failure modes
Rebase plus force on a shared branch
Teammates diverge. A later merge can apply the same changes twice under different ids.
Squash with no pull-request link
main is one commit and the review trail is gone.
Merge-from-main on every private update
The pull request graph fills with join commits that hide the feature.
Rebasing a pile of merge commits by habit
Replay order and parent choice get subtle. Prefer a fresh branch unless you know --rebase-merges.
Misconceptions
Rebase rewrites the original objects in place.
It writes new objects and moves your branch ref. The old objects remain until they are unreachable.
Squash and rebase are the same button.
Squash lands one new commit. Rebase-and-merge replays each commit and fast-forwards.
A merge commit means the history is dirty.
It means two lines of work joined. That record is the point on a shared branch.
Interviewer traps
Saying you always rebase, including main.
Private branch, rebase or squash. Shared branch, merge. main, revert.
Drawing only the happy rebase and skipping duplicate commits.
Mention the old ids. If both copies get merged, the changes show up twice.
Design scenario
Same prompt for every reader.
Requirements
Update the login branch without merge noise. Update the shared branch without new commit ids for existing commits. Land login as one commit or as a replay, per team policy.
Traffic / scale
Several pulls a day on main. The shared release branch moves a few times a week.
Latency
Conflict resolution happens before review, commit by commit on rebase, or once on merge.
Consistency
After a rebase, only the new ids are what you push. The remote tip must still match your lease.
Availability
If the rebase goes wrong, rebase --abort restores the pre-rebase tip.
Failure assumptions
- The shared branch is rebased without an announcement.
- A squash on main mixes unrelated changes.
Constraints
- Do not force-push main.
- Use --force-with-lease on the private branch after rebase.
Prompt
A private login branch is three commits behind main. A shared release branch has two authors. main must stay linear.
API
Which forge button matches merge --no-ff, squash, and rebase-and-merge?
Data
Which commit ids change, and which parents does the merge commit record?
Architecture
Where does the private branch tip move relative to origin/main and the shared release branch?
main moved while your three-commit feature sat in review
Prefer
Rebase if you are the only author
Replay F1 and F2 onto the new main tip. The pull request reads top to bottom. Bisect on main stays linear after you land.
- Your commits keep their messages, with new ids.
- Conflicts show up one commit at a time.
- The push is a lease, because the remote still has the old tip.
Alternative
Merge main into a branch nobody else uses
You get a join commit in the pull request for every update. Reviewers read the merge instead of the feature.
- The old commit ids survive, which you did not need.
- main history gets noisier if you merge the merge.
- The shared-branch case is the one that actually wants this join.
Refresh a private branch, then a shared one
Same upstream. Different publish rules.
- 1
Fetch
origin/main is the base you mean. Do not rebase onto a stale local main. - 2
Private: rebase
Replay your commits. Fix, add, and rebase --continue, or rebase --abort. - 3
Private: lease push
force-with-lease updates the remote tip only if nobody else moved it. - 4
Shared: merge
Merge origin/main and push a fast-forward of that branch. No new ids for the old commits.
Overview
Three styles, one question: who else has these commit ids, and how small must a later revert be?
Rebase beats repeated merge-from-main when the pull request is short and private. Merge beats rebase when several people push the same branch. Squash beats both when the pull request is the unit of review and main should carry one buildable commit.
Comparison
| Dimension | Merge commit | Rebase | Squash |
|---|---|---|---|
| History shape | A join with two parents | A linear replay | One commit on the target |
| Per-commit story | Kept on both sides | Kept as rewritten copies | Lost. One commit remains |
| New ids for your commits | No | Yes | Yes, one new id |
| Safe on a shared tip | Yes | Only if everyone coordinates | Usually fine as a forge strategy onto main |
| Bisect | Good when merges are clean | Excellent | Excellent on main, blind inside the pull request |
| Typical noise | High if you merge main often | Low when you rebase | Low on main |
| Undo after it is on main | revert -m 1 for a merge | Revert the single commits | Revert one SHA |
Failure if you choose wrong: rebase and force a branch coworkers use, and you get diverged histories plus duplicate commits on the next merge. Squash without a pull-request link, and archaeology gets hard. Merge everything, and main is noisy.
Before and after
Merge
Flow
- 1
M1
- nextM2
- 2
M2
- nextM3 on main
- nextF1 on feature
- 3
M3 on main
- nextMerge commit
- 4
F1 on feature
- nextF2
- 5
F2
- nextMerge commit
- 6
Merge commit
Lesson map
Rebase vs Merge vs Squash — When, Why & Tradeoffs
Merge records parallel work with a join commit. Rebase replays commits onto a new base and mints new ids. Squash collapses a branch into one commit. The wrong choice on a shared branch hurts the team. The wrong choice on a private branch mostly wastes review time.
Architecture. Architecture
Select a node to see why it exists, or an edge to see the protocol, direction, effect, and consequence.
Mermaid export
flowchart TB m1["M1"] m2["M2"] m3["M3 on main"] f1["F1 on feature"] m1 -->|M1 to M2| m2 m2 -->|M2 to M3 on main| m3 m2 -->|M2 to F1 on feature| f1
M1 -- M2 -- M3 ---- Merge
\ /
F1 -- F2Rebase onto main
Before, F1 and F2 sat on M2. After, the copies sit on M3. The copies are new objects.
Flow
- 1
M1
- nextM2
- 2
M2
- nextM3 new base
- 3
M3 new base
- nextF1 replayed
- 4
F1 replayed
- nextF2 replayed
- 5
F2 replayed
M1 -- M2 -- M3 -- F1' -- F2'Squash onto main
M1 -- M2 -- M3 -- SS contains the F1 plus F2 diff and has a single parent on main.
Public versus private
| Branch type | Update from main | Publish a rewritten tip |
|---|---|---|
| Private, only you | git fetch then git rebase origin/main | git push --force-with-lease |
| Shared feature | git merge origin/main | Avoid. If you must, announce it and use a lease |
| main or a release | Others merge in | Forbidden without an incident protocol |
git fetch origin
git switch feature/login
git rebase origin/main
git push --force-with-lease origin feature/login
git fetch origin
git switch feature/shared
git merge origin/main
git push origin feature/sharedForge buttons and local analogues
| Forge option | Local analogue | Notes |
|---|---|---|
| Create a merge commit | git merge --no-ff | Keeps the branch topology on main |
| Squash and merge | merge --squash, then commit | The pull request becomes one commit. Delete the branch |
| Rebase and merge | Rebase onto main, then fast-forward | Linear. The landed ids are still new objects |
Know the org default and the reason: audit, bisect, or a quieter main.
Conflicts
Rebase and merge use the same conflict markers. Rebase stops on each commit, so the same region can conflict more than once.
git rebase origin/main
git status
git add PATHS
git rebase --continue
git rebase --abort--abort restores the pre-rebase tip. That is the local escape hatch. The reflog is the second one, covered on the recovery page.
Rebasing a merge commit is possible with --rebase-merges. It is advanced. Do not start there when a clean replay of ordinary commits will do.
Which command
Press Run. Snippets must be self-contained — no network, files, or native modules.
Press Run. Snippets must be self-contained — no network, files, or native modules.
Interview Q&A
Does rebase delete old commits?
Answer
It creates new commits. The old ids can remain reachable from the reflog until the entries expire and garbage collection runs.
Why did a merge after a rebase duplicate changes?
Answer
The old commits and the new copies were both in history. Someone merged the pre-rebase tip as well as the replay. Prefer one workflow, or reset the stray branch onto the rewritten tip.
When is a merge commit on main worth keeping?
Answer
Long-lived release branches, a policy that wants an explicit integration record, or a join you will revert with -m 1.
Squash and bisect?
Answer
Strong if every squash on main builds and does one thing. Weak if a huge squash mixes a fix, a feature, and a refactor. Bisect can only land on that one commit.
--force-with-lease versus --force?
Answer
Lease checks that the remote ref is still the object id you expect. Force does not look.
pull --rebase versus pull?
Answer
pull --rebase replays your local commits on the fetched tip. A default pull may merge and add a join you did not want on a private branch.
Can you rebase a merge commit?
Answer
Yes, with --rebase-merges, and it is easy to get the topology wrong. Avoid rebasing complicated merge histories unless that flag is the task.
A hotfix is already on main as a squash. How do you port it to a release branch?
Answer
Cherry-pick the squash commit. If the original pull-request commits still exist, you can cherry-pick those instead. The next lesson after surgery is the recovery page, which owns cherry-pick.
Pitfalls
- Rebase of
mainbecause the graph "looked messy." - Plain
--forceas muscle memory after every rebase. - Squash that cannot be reverted as one logical change.
- Continuing a rebase after a bad conflict resolution and discovering it three commits later. Abort is cheaper early.
Sketch M1, M2, M3 and a two-commit feature. Show the merge join, then the replay with new ids. Say which picture you would push to a branch your teammate already fetched.