CHAPTER 09

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.

The following Monday

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.

BEFORE — a rebase-worthy situation: no conflicts yet, just messy commits
                         D — E   ← feature/past-events
                        /
A ── B ── C ── F ── G   ← main (two commits ahead of where feature branched off)
Vidha, keeping her own branch current
$ git switch feature/past-events
$ git rebase main
Successfully rebased and updated refs/heads/feature/past-events.
AFTER — same changes, replayed on top of main's latest work
A ── B ── C ── F ── G ── D' ── E'   ← feature/past-events
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

NEVER REBASE COMMITS SOMEONE ELSE ALREADY HAS
Imagine, hypothetically, that Anvesha had pulled Rachit's 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":

tidying before anyone else sees it
$ git rebase -i HEAD~5

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:

  1. Fix the conflict in the file, same as Chapter 8.
  2. git add the resolved file.
  3. git rebase --continue — moves on to replaying the next commit.
  4. Or git rebase --abort to 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:

the polite force-push
$ git push --force-with-lease
# overwrites the remote branch with your rebased version —
# but refuses if someone else has pushed to it since you last fetched

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."

CHECK
Vidha's branch is entirely local — nobody has fetched or pulled it. Is it safe for her to rebase it onto the latest main?
The danger in rebasing is entirely about other people already holding copies of the commits you're about to replace. An unshared local branch has no such audience — replaying it onto a new base is exactly what rebase is for, and it's the routine, safe case.

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.