Rebasing
git rebase replays your branch's commits on top of another branch's tip, producing a linear history with no merge commit. It is the answer to the messy graph that merges leave behind — at the cost of rewriting commit hashes, which is exactly what makes it dangerous on anything you have already shared.
The whole topic comes down to one tradeoff and one rule. The tradeoff: a clean linear history versus an honest record of how branches actually diverged. The rule: never rebase commits other people have pulled. Get those two right and rebase is a sharp, safe tool.
Replaying Commits
Rebase finds the common ancestor of your branch and the new base, takes each of your commits in turn, and re-creates it on top of the new base. Each replayed commit is a new object with a new hash, because its parent changed and the hash is computed from the content including the parent. The old commits are not edited — they are abandoned, and the branch pointer moves to the new chain.
This is why the result looks like your work was written on top of the latest main all along. It also explains the danger: anyone who had the old commits now has objects that no longer exist on your branch.
Interactive Rebase
git rebase -i <base> opens a to-do list of the commits about to be replayed, oldest first. You can squash or fixup commits together, reword messages, edit a commit to amend it mid-replay, drop a commit, or reorder lines to change the apply order. This is how you turn a working branch's "wip", "fix typo", "actually fix it" history into a handful of clean commits before opening a pull request.
Rebase vs Merge for Integration
Rebasing onto main before merging gives a linear history that reads top to bottom and bisects cleanly, because there are no merge commits to step through. Merging instead preserves the true topology — the branch point and the join are both recorded. Neither is universally correct: linear history is easier to read, branch-preserving history is more faithful to what happened.
The Golden Rule
Never rebase commits that exist on a branch others have pulled. Once a commit is shared, rewriting it creates a divergent copy with a new hash; your collaborators still hold the original, and reconciling the two duplicates or loses work and forces everyone into avoidable conflicts. Rebase freely on local, unpushed commits; on shared branches, merge instead. The rule is not about taste — it is about not invalidating history other people are building on.
Recovering and Continuing
When a replayed commit conflicts, the rebase pauses; resolve the conflict, git add the files, and run git rebase --continue to proceed to the next commit. git rebase --abort backs out cleanly to the pre-rebase state — far better than a manual git reset --hard that can leave the repo half-rebased. And if a rebase truly goes wrong, git reflog still holds the pre-rebase tip, so nothing committed is actually lost.
Merge — preserves the real history, including the branch point and the join, and is non-destructive: no existing commit changes. The cost is merge commits that clutter the graph and make a strictly linear read impossible.
Rebase — produces a clean linear history that bisects well, but rewrites hashes and so must never run on shared or pushed commits. Updating a pushed branch after a rebase requires a force-push, which can clobber a collaborator's work if done carelessly.
- Rebasing a branch teammates have already pulled, then force-pushing — their history diverges and merging your rewritten commits back duplicates or loses work.
- Using
git push --forceinstead ofgit push --force-with-lease, overwriting commits a teammate pushed after your last fetch. - Resolving the same conflict repeatedly across many commits during a long rebase instead of enabling
git rerereto reuse the resolution. - Squashing away meaningful commit boundaries in an interactive rebase, destroying the ability to
git bisectto a precise change. - Aborting a rebase mid-conflict with
git reset --hardinstead ofgit rebase --abort, leaving the repo in a half-rebased state.
- Rebase only local, unpushed commits; for branches others have pulled, merge instead.
- Use
git push --force-with-leaserather than--forcewhen you must update a pushed branch you own. - Use
git rebase -ito squash fixups and reword messages, cleaning up local commits before opening a pull request. - Enable
git config rerere.enabled trueso repeated conflict resolutions during long rebases are reused automatically. - Back out cleanly with
git rebase --abort, and trustgit reflogto recover the pre-rebase state if you need it.
hg rebase plus the hg evolve workflow for safely rewriting shared historyPerforce no native rebaseSubversion no equivalentFossil deliberately omits rebase, treating history as immutableKnowledge Check
Why is rebasing shared commits the cardinal sin?
- Rewriting them creates new hashes, so collaborators holding the originals get divergent copies that duplicate or lose work
- Rebase deletes the entire remote branch on the next push, wiping out every commit teammates have pulled and forcing them to re-clone from scratch
- It permanently disables fast-forward merges everywhere in the repository, forcing every future integration to write a merge commit instead
- Shared commits simply cannot be rebased; Git detects that they have been pushed and silently no-ops the entire operation
What is the core tradeoff between rebase and merge?
- Rebase gives a clean linear history but rewrites hashes; merge keeps the true topology but adds clutter with merge commits
- Rebase is faster but uses more disk space because it copies objects; merge is slower but stays smaller by reusing the existing commits in place
- Rebase works fully offline, while merge requires contacting the remote first to fetch the latest refs before it can join the two branches
- There is no real difference at all in the resulting commit history; both produce the same graph shape and the same set of hashes
Why is --force-with-lease safer than --force?
- It refuses to push if the remote moved since your last fetch, protecting commits a teammate pushed in the meantime
- It compresses the transferred objects more aggressively before sending them, reducing the chance of corruption while they travel over the network
- It pushes to a temporary backup branch on the remote first and then atomically swaps it into place once the upload finishes cleanly
- It only updates your local reflog and never touches the remote at all, so a teammate's pushed commits stay safe simply because nothing is sent
How does squashing in an interactive rebase affect later git bisect?
- Collapsing meaningful boundaries into one commit means bisect can only narrow the bug to that large commit, not a precise change
- It speeds bisect up noticeably because there are far fewer commits left to test, so the search reaches the first bad change in fewer steps
- It has no effect whatsoever, since bisect quietly skips over any squashed commits and tests only the original boundaries underneath them
- It forces bisect to search through the reflog instead of the commit graph, walking your recent ref movements rather than the real ancestry
You got correct