Git95 min total · 17 parts
Git Internals and Workflows: Branching, Merging, and Rebasing
Part 4 of 17 · ~4 min
Merge vs. Rebase: Same Two Branches, Different History
Wednesday morning, and main has moved. While you were building shift swapping, Nadia shipped something the whole app needed: hoursUntil() now respects each site's own timezone instead of assuming UTC, which was quietly wrong for every clinic outside London.
e0b8d31 ← main ("Make hoursUntil respect each site's timezone")
/
a4f1c0e ──────────
\
7c1f4ae ─── 2b9d013 ─── 41e77c6 ← feature/shift-swap
Two branches, one shared ancestor, both moved on. Your feature is built on an hoursUntil() that no longer exists in the form you wrote against, and you need Nadia's work in your branch before you can trust a single test you run.
Git gives you two ways to do that, and they produce genuinely different histories from the identical starting point.
git merge joins the two histories and records that it did. Run it from main, and what comes out is a merge commit carrying two parents at once — one thread leading back into main, the other back into your branch:
git switch main
git merge feature/shift-swap
e0b8d31 ─────────────────────── M ← main (a merge commit, two parents)
/ /
a4f1c0e ────────── /
\ /
7c1f4ae ─── 2b9d013 ─── 41e77c6 ← feature/shift-swap
The fork and the join are both permanently visible. Six months from now, git log --graph will still show that shift swapping was developed in parallel with the timezone fix and landed on Wednesday.
git rebase replays your commits somewhere else and pretends the fork never happened. Run from your branch, it takes each of your commits in order and reapplies it on top of main's current tip:
git switch feature/shift-swap
git rebase main
a4f1c0e ─── e0b8d31 ─── 7c1f4ae' ─── 2b9d013' ─── 41e77c6' ← feature/shift-swap
One straight line, as though you had started the feature this morning, after Nadia's fix, instead of on Monday before it.
Look carefully at those apostrophes, because they are the part that matters — and they are the same notation the diagram at the top of this chapter uses, where C' and D' are the rewritten copies of C and D. 7c1f4ae' is not 7c1f4ae. It contains the same change with the same message by the same author, but its parent is different — and a commit's hash is computed over its content and its parent. Different parent, different hash, different object. The originals still exist for now, unreferenced, exactly like that detached-HEAD commit. Your branch no longer points anywhere near them.
That is the single most consequential fact about rebasing, and every warning in the next two chapters follows from it.
| Merge | Rebase | |
|---|---|---|
| History shape | Branching, with merge commits marking where things joined | Linear — reads as if development happened sequentially |
| Original commit hashes | Preserved | Replaced (new hashes, same content) |
| What it records | What actually happened, including the concurrency | A tidied story of what could have happened |
| Safe on commits others may have pulled | Yes | No — the next chapter is entirely about this |
git log --graph readability | Shows true parallel work; can get noisy on a busy repo | Clean line; the parallel work is invisible |
| Number of conflicts you might resolve | At most once, in one merge commit | Once per replayed commit, potentially |
| Typical use | Landing a finished feature branch on main | Tidying your own unpushed commits; keeping a branch current without a merge commit each time |
Neither is correct in the abstract. What most teams converge on — shiftplan included — is using both for the jobs they suit: rebase your own branch onto main while you are still working on it, so it stays current and its history stays readable; merge it into main when it is done, so the moment it landed is recorded. That gives you a clean feature branch and an honest trunk, which is usually what people mean when they argue about this.
The last row of that table deserves a beat. A merge asks you to reconcile two final states once. A rebase asks you to reconcile each of your commits in turn against the new base, so a branch with six commits can stop and ask you six separate times, sometimes about the same lines repeatedly. That is not a reason to avoid rebasing, but it is why a rebase of a long branch can feel like being asked the same question until you get it wrong.