Rebase
Rebase rewrites the current Git branch by replaying its unique commits, oldest first, on a different base. The replacements keep each patch and message, but new parent links give them new object IDs; the target branch never moves. Use it to refresh private feature work, not history teammates already use.
Think of two corrections penciled into an old printing of a book. A new printing arrives with its own edits, so you copy your corrections onto that edition in order. You did not move the old pages. You made replacement pages whose lineage starts from the new printing.
How rebase works
Suppose feature and main share a fork point. Git walks from the current feature tip back toward that fork point and collects the commits that main cannot reach. Walking backward finds the newest commit first, so Git reverses that list before replay. A later patch may depend on a file state created by an earlier one; oldest-first order preserves that dependency.
The replay starts from the target tip. For each original commit, Git computes the change from that commit's parent snapshot to its own snapshot, applies that patch to the temporary replay snapshot, writes a new tree, and writes a new commit. The new commit retains the original author information and message, but its parent is different and its committer information is new. Since parent headers are part of the serialized commit body, the object ID must change even when the patch is byte-for-byte identical.
During this work, HEAD is temporarily detached at the target or the latest replayed commit. The named feature ref still points to the original tip. Only after every patch succeeds does Git move feature once to the rewritten tip and reattach HEAD. That delayed ref write is what makes abort sane: the old branch tip remains a clean anchor throughout the attempt.
The original commit objects are not edited or immediately deleted. Once feature moves, those objects may become unreachable by branch names, but they can remain in the object database until Git's normal retention and garbage-collection rules remove them.
One real-Git detail matters when the target already contains an equivalent patch. Git may recognize a clean cherry-pick and leave that commit out of the replay queue. --reapply-cherry-picks asks it to replay those commits instead. This lesson's scoped engine does not scan upstream history for patch equivalence, so it always queues every current-branch commit after the merge base.
Rebase step by step
The default trace uses three one-line files so each patch's required before-state and resulting snapshot stay visible: app.py, README.md, and config.json.
commit basewrites the initial snapshot:app.pyisprint("v1"),README.mdishello, andconfig.jsonis{"port":3000}. The newmainref advances only after the commit object exists.branch featurecreatesfeatureat the same base commit.HEADremains attached tomain.switch featureattachesHEADtofeature.commit feature-appchangesapp.pyfromprint("v1")toprint("feature").commit feature-v2changes that same line again, fromprint("feature")toprint("feature v2"). This patch expects the file state created byfeature-app, so replaying it first would fail immediately.switch mainreattachesHEADtomain.commit main-configchanges onlyconfig.jsonto{"port":8080}. Now the histories have diverged:mainowns the config edit, whilefeatureowns two other edits.switch featuremakesfeaturethe current branch again.rebase mainfinds the shared base, collects the twofeaturecommits, and orders them asfeature-appthenfeature-v2. A temporary detachedHEADstarts atmain-config.
The first replay applies the app.py patch to the snapshot under main-config. The result keeps main's port 8080, adds print("feature"), and writes a new feature-app commit. The second patch can now find the print("feature") state it expects, replace it with print("feature v2"), and write a new feature-v2 commit. The order is not bookkeeping: reverse these two corrections and the second one has no matching page to edit.
That is the book analogy doing real work. You copy the first penciled correction onto the new edition before copying a later correction that edits the result of the first.
After the second replay succeeds, feature moves once to the rewritten tip. main stays on main-config, HEAD reattaches to feature, and the two original feature commits remain stored but no longer branch-reachable. In the visualizer, the old chain fades while the primed chain stays on the new base; the mapping ledger makes each replacement explicit.
What rebase costs
Let k be the number of commits replayed and let D be the total amount of file content examined while computing and applying their patches. The history walk costs time proportional to the ancestry traversed, and replay costs roughly O(D) for this ordinary single-parent case. Rebase also writes up to k new commit objects plus the tree and blob objects required by their resulting snapshots.
The practical cost is rarely the hash calculation. It is conflict work. A clean patch is mechanical; a patch that overlaps a target-side edit stops the sequence and asks you to decide what the file should mean now. Replaying a long branch can therefore turn one large merge conflict into several smaller conflicts spread across the original commits. Whether that is pleasant depends on the quality of those commits. Git cannot rescue a commit titled stuff from its author.
The working rule is simple: rebase a private feature branch when you want to update it before review or present a linear series of commits. Prefer a merge, or coordinate explicitly, once teammates have based work on those commit IDs. A tidy graph is not worth ambushing everyone else's clones.
This lesson models a clean local repository, one unique merge base, ordinary non-interactive rebase, and single-parent commits. It uses Git's normal merge-style patch application but omits merge commits, interactive editing, --onto, fork-point heuristics, submodules, rename-heavy cases, and conflict continuation. Those features change the planning surface, not the core rule that replacement commits are new objects.
Rebase edge cases
- Already based: if the target tip is already an ancestor of the current tip, there are no commits to transplant. The operation creates zero commits and moves zero refs.
- Fast-forward boundary: if the current tip is an ancestor of the target tip, the current branch can move directly to the target. No replacement commit is needed.
- Conflict on the first patch: temporary
HEADstays at the new base,REBASE_HEADnames the failing original commit, and the current branch still names its original tip. - Conflict after a successful replay: temporary
HEADpoints to the last replacement commit. The current branch still has not moved, sogit rebase --abortcan restore the attached original state. - Abort and object retention: abort makes temporary replay commits unreachable; it does not promise to erase their object bytes immediately.
- Missing or unborn refs: a target name that does not resolve to a commit, or a current branch with no commit, cannot supply the two tips rebase needs.
- Clean cherry-pick already upstream: real Git may drop the equivalent commit from the replay queue;
--reapply-cherry-pickschanges that choice. The visualizer does not model this scan.
Common Mistakes
- Saying rebase "moves commits." Commit objects are immutable. Rebase reads old commits and writes replacements.
- Expecting the same commit ID because the patch and message match. The parent header changed, so the commit body and hash changed.
- Rebasing the wrong direction. While on
feature,git rebase mainrewritesfeature; it does not movemainontofeature. - Treating the target branch as an output. A normal branch rebase moves the current branch after success and leaves the target ref alone.
- Assuming a conflict means the branch already points to a partial rewrite. During the stopped state, detached
HEADcarries partial progress while the branch ref remains at the original tip. - Rebasing commits that other people already use as shared history, then pushing as if their IDs had not changed. The replacement IDs force everyone else to reconcile two histories that describe similar changes with different objects.
Keep exploring
A few lessons that share the same technique or lead into the next idea.
Found a mistake or something unclear? Leave feedback.