Replaying Commits Somewhere Else
Rebase does not move your commits; it copies them and abandons the originals, which is exactly why it is both tidy and dangerous. Compare the two histories side by side, then meet the one rule that keeps it safe.
Worth reading first: Bringing Two Histories Back Together
Rebase does not move your commits; it copies them and abandons the originals, which is exactly why it is both tidy and dangerous. Compare the two histories side by side, then meet the one rule that keeps it safe.
Rebase copies commits, it does not move them
A rebase takes each of your commits in turn, computes the change it made, and applies that change on top of a different commit. The result is a new commit with the same message, the same author, the same content — and a different hash, because the parent is different and the hash covers the parent.
Two branches that diverged at B. Your work is C and D; main has moved on to E. Nothing is wrong here — this is what every branch that took more than an afternoon looks like.
Step through those three panels and watch what happens to C and D. They do not slide along the line. They stay exactly where they were, unreferenced, while C' and D' appear somewhere else. Nothing points at the originals any more, so Git will eventually collect them.
$ git switch fix/login$ git rebase main# Successfully rebased and updated refs/heads/fix/login.The result is a straight line, and a small lie
The output is a linear history with no merge commits. Reading it is genuinely easier; bisecting it is simpler; the log tells a clean story from top to bottom.
The story is not quite true. It reads as though you started your work from the latest main, finished it, and pushed. In fact you started three days earlier, from a different commit, and this arrangement was constructed afterwards.
Merge preserves what happened
The history records that you branched at B, worked in parallel, and joined at M. Every commit keeps its hash, so anybody who already fetched your branch still has exactly the same commits. The graph is two-dimensional and needs --graph to read.
Rebase preserves what you meant
The history reads as a clean sequence of changes to main. Easier to follow and to bisect, and every commit has a new hash — so what lands is not literally what was tested on the branch.
Neither is correct in general. The genuinely useful compromise, and what a great many teams do, is: rebase your own branch while it is still yours to keep it tidy and up to date, then merge it into main so the landing is recorded.
Never rebase commits somebody else already has
This is the golden rule, and now the reason for it is obvious rather than mystical.
Rebasing gives every commit a new hash. If a colleague has already pulled the originals, their Git still has C and D. Yours now has C' and D'. The two histories contain the same code under different names, and Git cannot tell they are the same work.
* 8f2a1c4 (origin/fix/login) Handle the empty next param <- yours, rebased* 3d9b7e0 Fix the redirect <- yours, rebased* 4b8e0d7 (HEAD -> fix/login) Handle the empty next param <- theirs, original* 2c7a91f Fix the redirect <- theirs, original* 1a5e8c3 Set up the routerEvery commit is now duplicated. If they merge, both copies land and the diff applies twice. Untangling this is genuinely unpleasant and it is always somebody else's afternoon, not yours.
Your own branch, that only you have pushed, or have not pushed at all. Rebase freely.
A shared feature branch two people are working on. Agree, and both re-sync afterwards.
main, or any branch other people build on. Use revert; that is what it is for.
Interactive rebase is where you tidy up
git rebase -iis the same machinery pointed at your own recent commits, and it is how twelve commits called "wip" become three commits somebody can review.
$ git rebase -i HEAD~4 # the last four commits$ git rebase -i main # every commit on this branch since mainpick 2c7a91f Fix the redirect when next is emptysquash 4b8e0d7 fix typoreword 7d1f6b4 wipdrop 9a1c4e8 add debug logging # Commands:# p, pick = use commit as is# r, reword = use commit, but edit the message# s, squash = merge into the previous commit, combining the messages# f, fixup = merge into the previous commit, discarding this message# e, edit = stop here so you can amend the commit# d, drop = remove the commit entirely## These lines are in OLDEST-FIRST order. Reordering them reorders history.Change the word at the start of each line, save, close. Git replays the commits according to your instructions. If a step conflicts it pauses exactly as a merge does, and git rebase --continue carries on once you have resolved and added.
Fold this commit into the one above and let you write a combined message. For assembling a feature from its pieces.
Same, but throw this message away. For the "fix typo" commits that exist only to correct the one before.
Keep the changes, rewrite the message. The most common single use.
Remove it. For debug logging you committed and no longer want anywhere.
force-with-lease is the only force worth typing
After rebasing a branch you have already pushed, a normal push is rejected — correctly, because your history no longer contains the commits the remote has. Publishing the rebase requires overwriting the remote branch.
$ git push --force-with-lease# Not: git push --force--force overwrites the remote branch unconditionally. If a teammate pushed a commit to that branch in the last ten minutes, it is now gone, with no warning and no record on your machine that it existed.
--force-with-lease overwrites only if the remote is still exactly where you last saw it. If somebody has pushed since, the push is rejected and you find out before destroying anything.
--force
"Make the remote match me, whatever is there." There is no situation where this is better than the alternative.
--force-with-lease
"Make the remote match me, but only if nothing has changed since I last fetched." Same result, with a safety check.
Key takeaways
- Rebase replays your changes as new commits with new hashes; the originals are abandoned, not moved.
- git rebase main changes your branch. main is untouched.
- The result is linear and easier to read, and it describes a sequence of events that did not happen.
- A common compromise: rebase your own branch to keep it tidy, then merge it into main so the landing is recorded.
- Never rebase commits other people have — new hashes make the same work look like different commits, and it duplicates.
- In doubt, merge. A messy log costs less than somebody else's lost work.
- git rebase -i squashes, rewords, reorders, and drops commits before review.
- fixup plus --autosquash automates the tidy-up almost entirely.
- After rebasing a pushed branch, use --force-with-lease, never --force.
- The lease is checked against your last fetch, so auto-fetching before pushing silently disables the protection.
Quick check
Answer these to unlock the next chapter — 3 of 4 to pass. You can retake it anytime.
Answer every question to check.
Make a free account to read on
Every chapter is free — an account is how your progress, XP, and streak follow you from your laptop to your phone, and how you show up on the leaderboard. No payment, no trial.