Learning Objectives
By the end of this lesson you will be able to:
- State the Golden Rule and explain the mechanism behind it
- Predict what happens to a colleague who pulls a rebased branch
- Recognise the duplicate-commit symptom in
git log - Recover cleanly when someone rebases a branch you had checked out
The Rule
Never rebase commits that exist outside your own machine.
That is the whole rule. Some teams phrase it as "never rebase a public branch" or "never rewrite shared history", but the operational version is the one above, because it is the one you can actually check before you type the command.
main, develop, release/* and any branch someone else is working on are all covered by it. Your own feature branch, pushed only for backup and reviewed by nobody yet, is not — you can rebase that as often as you like.
Why It Breaks Things
You know from Lesson 1 that a rebase replaces commits with copies. Now watch what that does to a second person.
Anna and Boris are both on feature/checkout. It has commits A, B, C. Both have pulled it, so both clones contain those three commits.
Anna: D───A───B───C
Boris: D───A───B───C
Origin: D───A───B───C
Anna rebases onto a newer main and force-pushes:
Anna: D───E───F───A'──B'──C'
Origin: D───E───F───A'──B'──C'
Boris: D───A───B───C ← unchanged, still has the originals
Boris pulls. Git sees his three commits, sees three different commits on the remote, and — because a pull is a fetch plus a merge — dutifully merges them together. The changes in A and A' are identical, so it produces conflicts on every hunk where they overlap, and if it gets through, the branch now contains both copies of every commit:
Boris: D───A───B───C───────M
\ /
E───F───A'──B'──C'
Every change is now in the history twice. If Boris pushes this, it is on the shared branch for good, and the "clean linear history" the rebase was for is gone with interest.
The Symptom
You do not need to guess. The signature is unmistakable in the log:
git log --oneline -8
3f9a1c2 test: add checkout boundary cases
88bd410 test: extract cart fixture
0c5e7d1 test: failing case for empty cart
a11f39e test: add checkout boundary cases ← the same subject, different ID
7d2c084 test: extract cart fixture ← again
b6e0917 test: failing case for empty cart ← again
The same commit messages, in the same order, twice, with different IDs. That is a rebased branch merged with its own pre-rebase copy, every time.
What To Do Instead
When you have pulled a branch and its owner rebases it, you do not merge. You discard your copy and take theirs:
git fetch origin
git status # any local work of your own?
git reset --hard origin/feature/checkout
If you did have local commits of your own on that branch, save them first — one of these two:
git branch backup/my-checkout-work # a label so you can find them again
# or
git log --oneline origin/feature/checkout..HEAD # list them, then cherry-pick after the reset
Cherry-pick is exactly the tool for putting them back on the new base, and it is Module 3.
Pro Tip: If a teammate tells you "I rebased that branch", treat it as an instruction: fetch, then
reset --hardto the remote. Nevergit pull. Getting this reflex right saves an hour of duplicate-commit archaeology.
The Grey Areas, Honestly
A few situations sit between the two extremes, and teams genuinely differ:
A feature branch under review. It is public and someone is reading it, so the rule says no. Many teams still rebase before merging, but announce it — and everyone re-fetches with reset --hard. This is workable precisely because the announcement replaces the guarantee.
A branch nobody but you has ever pulled. Pushed for backup only. Rebase freely. --force-with-lease will tell you if you were wrong about "nobody".
main, ever. No. There is no version of this that ends well. Every clone in the company disagrees with the remote at once, CI reruns from a history that no longer exists, and tags point into nothing.
Key Takeaways
- Never rebase commits that exist outside your own machine
- A rebase replaces commits with copies; anyone holding the originals now disagrees with the remote
- Their
git pullmerges both sets and produces duplicated commits and pointless conflicts - The symptom is identical commit subjects appearing twice with different IDs
- The correct recovery is
git fetchthengit reset --hard origin/<branch>, never a pull - Save your own local commits before that reset — cherry-pick them back afterwards
mainand other shared long-lived branches are never rebased, in any workflow