Rebasing
By the end of this lesson
Replay commits onto a new base, and know when not to.
Rebasing answers the same question as merging — how do I combine my work with everyone else's — and answers it differently.
A merge joins two lines of history and records the join. A rebase takes your commits off your branch, moves the starting point to the current tip of the other branch, and applies your changes again on top. The result looks as though you had started your work from that newer point all along.
Before
origin/main A - B - C
feature A - B - D - E D and E are your commits
After git rebase origin/main
origin/main A - B - C
feature A - B - C - D2 - E2 D2 and E2 are NEW commits
D2 contains the same change as D. It is not the same commit:
its parent is different, so its identifier is different too.
D and E still exist, but nothing points at them any more.- Rebase does not move commits. It creates new ones with the same changes and different parents, then moves the branch pointer to the new chain.
- That is why every identifier on the rebased branch changes. Even a commit whose content is untouched gets a new identifier, because a commit's identifier depends on its parent.
- The originals are not deleted immediately — they are reachable through the reflog, covered later in this course. But your branch no longer points at them, and they will eventually be cleaned up.
# Get the latest state of the remote without changing your files
git fetch origin
# Replay your commits on top of the current origin/main
git switch add-expense-export
git rebase origin/main
# If a conflict stops it: fix the files, then stage and continue
git add src/ExpenseExporter.cs
git rebase --continue
# Or change your mind and put everything back as it was
git rebase --abort- git fetch updates your knowledge of the remote. It does not touch your working files, so it is safe to run at any time.
- git rebase replays your commits one at a time. If one of them conflicts, the rebase pauses at that commit rather than failing outright.
- git rebase --continue resumes after you have resolved and staged the conflict. Do not commit during a rebase — staging is what Git is waiting for.
- git rebase --abort returns your branch to exactly where it was before you started. Remember this one. It is the reason a rebase gone wrong is recoverable.
The honest comparison. Both are legitimate; they optimise for different things:
| Merge | Rebase | |
|---|---|---|
| Existing commits | Untouched | Replaced by new commits with new identifiers |
| Resulting history | Shows the true shape of parallel work | A straight line, easy to read top to bottom |
| Accuracy of the record | Accurate — it happened this way | Tidied — it did not actually happen this way |
| Conflicts | Resolved once, in the merge | Possibly once per replayed commit |
| Safe on a shared branch? | Yes | No — it changes commits others may already have |
| Recovering if it goes wrong | git merge --abort, or revert the merge commit | git rebase --abort, or the reflog once it has finished |
Summary
- Rebasing replays your commits onto a new starting point, producing new commits with new identifiers
- The result is a linear history that reads well but no longer records how the work actually happened
- Never rebase commits others have pulled; rebasing your own unpushed branch onto an updated main is the safe, common case
- git rebase --abort restores your branch exactly as it was, which makes rebasing something you can try
- Merge and rebase are both defensible — decide as a team what the history is for
Practice
Attempt each one before opening the solution. Getting it wrong first is how the idea sticks.
Think about it
Why the identifiers change
You rebase a branch and the content of your files is byte-for-byte identical afterwards. Every commit identifier is different. Why?
Show solution
A commit's identifier is derived from its contents, and its contents include a reference to its parent. Change the parent and you have changed the commit, even if the files did not change.
This is why rebasing is described as rewriting history rather than moving it. The new commits are genuinely new objects that happen to carry the same changes.
It also explains why a rebased branch cannot be pushed normally after it has been pushed once. From the remote's point of view your branch no longer contains the commits it had, which is the situation a force push overrides.
Try it yourself
Rebase, then abort
In a practice repository, create a branch and make two commits. Switch to main, make a commit there, then switch back and run git rebase main.
Compare git log --oneline before and after. Then repeat the exercise, and this time run git rebase --abort while it is paused on a conflict.
Show solution
After the rebase your two commits sit on top of main's new commit, with different identifiers. The history is a straight line.
The abort run is the more valuable half. Knowing that --abort returns you exactly to where you started is what makes rebasing something you can try rather than something you dread.
git switch -c add-csv-export
git commit -am "Add CSV export for the expense report"
git commit -am "Escape commas in exported description fields"
git switch main
git commit -am "Rename ExpenseReport to ExpenseSummary"
git switch add-csv-export
git log --oneline
git rebase main
git log --onelineKnowledge check
Nothing is recorded and there is no score. The explanation appears either way.
Saved in this browser only.