<aside> 🔀

Module 10 — Rewriting History (git rebase, cherry-pick). Merges record history as it happened; rebase and friends record it as it should read. This module completes your undo/redo arsenal: rebase for linear histories, interactive rebase for editing commits before sharing, cherry-pick for copying single commits — and the golden rule that keeps all of it safe. The merge-vs-rebase question promised since Module 6 gets its full answer here.

🧠 concept → 🧩 analogy → 🧪 exercise → ✅ expected result (hidden) → 🎯 interview questions (hidden)

</aside>

<aside> 🚦

Before you start. You need Module 6 (merging and conflicts — rebase reuses the machinery), Module 7 (reflog — your safety net while practicing), and Module 8 (shared vs local history). Exercises continue the kitchen repository. One habit for this whole module: rebase output includes Rebasing (n/m) progress chatter on interactive terminals — the transcripts here show only the meaningful lines.

</aside>

<aside> 🖼️

This page contains Mermaid diagram blocks. Notion shows them as code by default — click the block and switch it to Preview to see the diagram. You only need to do this once per block.

</aside>

Part A — Rebase: replaying commits on a new base

A1. What rebase actually does

<aside> 📖

Official docs: Git Book §3.6 — Rebasing · git-rebase manual

</aside>

git rebase main (run on a feature branch) says: take my branch's commits — everything since the merge base — and replay them, one by one, on top of main's current tip. For each original commit, Git computes its patch and applies it to the new base, minting a new commit. New parent, new content-context, new timestamp → new hash, necessarily (Module 3's arithmetic — a rebase cannot preserve IDs even in principle). The branch pointer then moves to the last replayed commit; the original commits still exist, unreferenced, reachable through the reflog (Module 7) until GC.

The result: your branch sits directly on top of main, as if you had started work this morning instead of last week. The divergence is gone — so the eventual merge into main is a clean fast-forward, and history reads as one straight line.

flowchart TB
    subgraph B["after: git rebase main"]
        M2["main"] --> F2["focaccia note"] --> BA2["merge base"]
        FB2["feature"] --> N2["naan' (new hash)"] --> P2["pita' (new hash)"] --> F2
    end
    subgraph A["before: diverged"]
        M1["main"] --> F1["focaccia note"] --> BA1["merge base"]
        FB1["feature"] --> N1["naan notes"] --> P1["pita notes"] --> BA1
    end

🧪 Exercise 10.1 — diverge, rebase, fast-forward: linear history end to end

cd ~/git-course/kitchen              # on main, clean
git switch -c feature/flatbread
echo "pita: pocket forms from steam" > pita.txt
git add pita.txt && git commit -q -m "Add pita notes"
echo "naan: cook in dry skillet" > naan.txt
git add naan.txt && git commit -q -m "Add naan notes"
git switch main                      # meanwhile, main moves
echo "focaccia: dimple with fingers" > focaccia.txt
git add focaccia.txt && git commit -q -m "Add focaccia note"
git switch feature/flatbread
git rev-parse --short HEAD           # note the tip hash BEFORE
git rebase main
git rev-parse --short HEAD           # ...and AFTER
git log --oneline --graph --all | head -4
git switch main && git merge feature/flatbread | head -2
git branch -d feature/flatbread

<aside> 🎬

Real-world analogy — reshooting scenes vs splicing footage

A merge is a documentary edit: two cameras filmed in parallel, and the editor splices both reels with a caption saying "these happened simultaneously" (the merge commit). A rebase is a reshoot: the director looks at today's set (new main) and refilms your scenes on it, in order, so the final cut plays as one continuous take. The reshot scenes are genuinely new footage — same script, new film.

Where the analogy stops working. Reshoots cost what the original shoot cost; rebasing is nearly free, because each "scene" is a patch mechanically re-applied (conflicts are the exceptions where the new set no longer fits the script — C2). And film cuts destroy the outtakes; Git keeps them — the original commits linger in the reflog, so the documentary version remains reconstructable for weeks. The real cost of the reshoot is different in kind: anyone who already watched the original footage now holds obsolete hashes — which is exactly the golden rule's territory (Part B).

</aside>

A2. Merge vs rebase — the actual answer

<aside> 📖

Official docs: Git Book §3.6 — Rebasing

</aside>

Both integrate the same changes; they differ in what history claims happened. Merge preserves truth: work happened in parallel, and the merge commit records the join — at the cost of a graph full of bubbles and cross-lanes. Rebase curates a story: work happened sequentially — at the cost of rewriting commits (new hashes) and flattening the record of parallelism. Neither is "correct"; they optimize for different readers. Merge optimizes for provenance (what really happened, when, revertable per-feature via -m 1). Rebase optimizes for readability (bisectable straight lines — Module 13 will thank you — and no merge-commit noise).

Axis Merge Rebase
Commit hashes Preserved Rewritten (new commits)
History shape True parallel graph, merge commits Linear, as-if-sequential
Safe on shared branches Always No — the golden rule (Part B)
Conflict handling Once, at the merge Possibly per replayed commit (rerere helps — Module 15)
Typical team policy Merging into main (PR button) Updating your feature branch from main; tidying before review

The pragmatic synthesis most teams land on: rebase your own unshared work (keep feature branches current with git rebase main or git pull --rebase; tidy commits before review with Part C), merge everything shared (PRs land via merge or squash on the host). You get readable branches and truthful mainline joins.

🎯 Interview questions — Part A