Rebase & Conflict Resolution
Rewriting history safely, and getting out of a conflict without panic-deleting the repo.
35 minDifficulty 3/5toolingAI-writtenWritten by a model on 16 August 2026 and not yet read by a person. Checked automatically: schema, the pedagogical rules the hand-written material is held to, and every diagram parsed for real.
Before this
Why this exists
Your feature branch is three days old and main has moved on twenty commits. Merging now means one large, tangled diff to resolve at once. Rebasing replays your commits one at a time on top of the new main, so each conflict — if any — arrives small, in context, tied to the one commit that caused it, instead of arriving all at once as an undifferentiated mess.
The mental model
Rebase is literally "take these commits off, and re-apply them somewhere else." Git computes the diff each of your commits introduced, then replays those diffs one by one on top of a new base — usually the current tip of main — producing brand-new commits with the same content but different parents, and therefore different hashes.
How it works
Rebasing rewrites commits, it doesn't move them
A rebased commit is a new object with a new hash — same message, same diff content, different parent. The old commits still exist in git's object database until garbage collected, which is why `git reflog` can rescue a rebase gone wrong: nothing is destroyed, only your branch pointer moves to point at the new chain.
Never rebase a branch someone else has pulled
If a teammate already has your original commits, rebasing gives you new hashes for the same logical work and their history and yours now disagree about what happened. Reconciling requires a force-push and everyone downstream re-syncing — the golden rule is: rebase private branches freely, never rebase shared ones.
A conflict during rebase is per-commit, not per-branch
Rebase pauses at the FIRST commit whose replay conflicts, shows you exactly that commit's diff against the new base, and waits. You resolve, `git add` the fixed files, and `git rebase --continue` — then it moves to the next commit and may pause again. This is why rebase conflicts feel smaller: you're never resolving twenty commits' worth of drift at once.
Escape hatches exist, and they're not embarrassing to use
`git rebase --abort` returns everything to exactly how it was before you started — as if the rebase never happened. If you're mid-conflict and genuinely stuck, aborting and asking for help costs nothing; the panic move of deleting the repo and re-cloning does not, because your uncommitted resolution work is gone.
The mechanism
For each commit on your branch, rebase computes its diff and tries to apply that diff to the current state of the new base. When a diff can't apply cleanly — because the new base already changed the same lines — git inserts conflict markers into the file and pauses. You edit the file to the correct final state, stage it, and continue; rebase moves to the next commit and repeats.
sequenceDiagram participant Y as Your branch participant M as Rebase engine participant N as New base (main) Y->>M: commit C1 (diff) M->>N: apply C1's diff N-->>M: conflict on file.js M-->>Y: pause, show conflict markers Y->>M: resolve #59; git add #59; continue M->>N: apply C2's diff N-->>M: applies cleanly M-->>Y: rebase complete, new hashes
What people get wrong
- Rebasing loses your original commits if something goes wrong.
- The original commits stay in the object database, reachable via `git reflog`, until git eventually garbage-collects unreferenced objects — typically weeks later. The fear of permanent loss is why people avoid rebase entirely, when in practice `git reflog` plus `git reset --hard <old-hash>` recovers almost any rebase mistake within the same session.
- Resolving a rebase conflict means picking git's version or your version.
- A conflict means BOTH sides made changes git can't reconcile automatically; the correct resolution is usually neither side verbatim but a manual edit combining what both changes intended. Blindly accepting one side of a conflict marker silently discards real work from the other side, which is a worse outcome than a slower, careful manual merge of the two changes.
- Force-pushing after a rebase is always dangerous.
- Force-pushing your OWN private feature branch that nobody else has pulled is routine and expected after a rebase; it is only dangerous on branches other people are also committing to. The danger is entirely about shared state, not about the force-push command itself — conflating the two makes people avoid rebase workflows that are actually completely safe.
When not to use it
- The branch is shared — more than one person is committing to it.
- Merge instead of rebase, or coordinate explicitly before any rebase and force-push so nobody's local history silently diverges from the remote.
- You want to preserve exactly what happened, including when work interleaved in real time.
- Merge, which keeps the true chronological structure — rebase presents a fictional linear history where your commits look like they happened after everyone else's, even if they didn't.
Terms
- Rebase
- — Replaying a sequence of commits on top of a different base commit, producing new commits with the same content but different hashes and parents.
- Conflict markers
- — The <<<<<<<, =======, >>>>>>> text git inserts into a file to show both versions of a line range it could not automatically reconcile.
- Reflog
- — A local log of every position HEAD has pointed to, including commits no longer reachable from any branch — the safety net for undoing a bad rebase or reset.
- Force-push
- — Pushing a branch whose history has been rewritten, overwriting the remote's version rather than requiring it to be an ancestor of the new history.
In an interview
Why does a rebased commit have a different hash even though the code change is identical?
- a commit hash is derived from its content plus its parent commit
- rebase changes the parent to the new base
- same diff, different parent, therefore different hash
Can you recall it?
What does rebase actually do to your commits, and why must it never be done on a branch other people have already pulled?