Rebasing
The tool that makes history readable, and the exact way it can quietly wreck someone else's afternoon if you point it at the wrong commits.
Vidha's just joined the website work — she's picking up a small task, adding a "past events" section below the fold. Before she starts, she looks at git log --oneline to understand how the project's been built so far, hoping to learn the club's conventions from real examples.
What she finds, from the redesign merge two weeks ago, is this:
9a1b2c3 Merge branch 'redesign/homepage'
e5f6a7b wip redesign
d4e5f6a more redesign stuff
c3d4e5f fix typo
b2c3d4e actually get the gradient right this time
a1b2c3d redesign wip 2
...
Not one of those messages tells her anything. "wip redesign," twice, with no explanation, is what Rachit's actual two weeks of thoughtful work looks like from the outside. She can't learn from it, and if something in the redesign ever needs debugging, nobody's going to want to git blame their way through "more redesign stuff."
This is a real cost, and it's exactly what Rachit was uneasy about at the end of Chapter 8. The fix is a tool called rebase — and understanding it properly means understanding both what makes it powerful and what makes it the one Git command with an actual warning label.
What rebase does that merge doesn't
A merge, as you saw last chapter, keeps both histories exactly as they happened and stitches them together with a commit that has two parents. Rebase does something structurally different: it picks up your commits, sets them aside, and replays them one at a time on top of a different starting point — as if you'd started your work later than you actually did.
/
A ── B ── C ── F ── G ← main (two commits ahead of where feature branched off)
D' and E' contain the same content as D and E — but they're brand new commit objects, new hashes, because their parent is different now.
This matters because when Vidha eventually merges her tidy two-commit branch, it fast-forwards cleanly onto the tip of main — no merge commit, no tangle, just a straight readable line. But notice what the diagram is quietly telling you: D and E don't exist anymore. They've been replaced by D' and E', which look the same but have different hashes. That single fact is the entire reason rebase needs a warning label.
The rule Rachit's redesign would have violated
redesign/homepage branch to help debug the gradient, and then Rachit rebased it. Her local copy still has the old D and E. His now has D' and E'. As far as Git can tell, these are two branches that have nothing to do with each other past commit C — even though they contain, in essence, the same work. Her next push or pull turns into a mess neither of them signed up for. This is the one Git mistake that isn't really "undoable" in the simple sense, because it doesn't corrupt anything — it just quietly creates two divergent, confusing histories that a human has to manually reconcile.
The safe version of the rule is short: rebase freely on a branch that's entirely your own and hasn't been shared. The moment anyone else has fetched or pulled your branch, treat it as shared — from then on, prefer merge (or the safe, force-with-lease push described below) instead of a plain rebase.
Interactive rebase: fixing Rachit's "wip" mess after the fact
Here's the tool that would have saved Vidha's confusion, if Rachit had run it on his own branch before merging — and the one he uses on his next feature branch, having learned the lesson. Say he's five commits into another piece of work, several of them genuinely just "wip" and "oops typo":
An editor opens showing all five commits, oldest first, each prefixed pick:
pick 8f2a1b3 events section: basic markup
pick c9d4e5f wip
pick 3f8a1b2 events section: styling
pick 7d0c2e5 oops typo
pick 1a2b3c4 fix mobile layout for events section
He changes the plan — fixup folds a commit into the one above it silently, keeping only the earlier message:
pick 8f2a1b3 events section: basic markup
fixup c9d4e5f wip
pick 3f8a1b2 events section: styling
fixup 7d0c2e5 oops typo
pick 1a2b3c4 fix mobile layout for events section
Saves, and Git replays the plan. Five commits collapse into three clean ones, each with a message that actually describes a real, complete step. Nobody has seen this branch yet — Vidha included — so nothing anywhere gets confused by the rewrite.
When the replay hits a conflict
Rebase can conflict for the same reason merge can — two changes touched the same lines. The difference is it happens per commit, potentially more than once during one rebase:
- Fix the conflict in the file, same as Chapter 8.
git addthe resolved file.git rebase --continue— moves on to replaying the next commit.- Or
git rebase --abortto cancel entirely and land back where you started.
Pushing after you've rewritten your own history
If a branch already exists on GitHub (Chapter 11) and you rebase it locally, a plain git push will be rejected — the remote still has D and E; you're offering D' and E', and Git won't silently overwrite someone's history on request:
That refusal is the safety check that plain --force doesn't have — it's the difference between "I'm confidently replacing my own recent work" and "I might be about to erase someone else's."
main?Vidha's "past events" branch is clean, rebased, and ready. But there's a genuinely different situation waiting for her a few weeks later: a bug shows up on the live site that used to work, and needs a fix backported specifically to last month's snapshot without touching anything that's happened since. That's not a job for merge or rebase — it needs something more surgical.