Git Rebase
git rebase takes a series of commits and replays them on top of a different base. Where a merge ties two histories together with a merge commit, a rebase rewrites your commits so they look as if you'd started from the latest code all along. The result is a clean, linear history that's easier to read, bisect, and review.
Rebase is also Git's main history-editing tool. Interactive rebase lets you squash noisy "fix typo" commits, reword messages, reorder, and split commits before anyone reviews them. With that power comes one rule you must respect: never rewrite commits other people have already based work on.
TL;DR
git rebase mainreplays your branch's commits on top ofmain: linear history, no merge commit.- Rebasing rewrites commits (new hashes). Only rebase your own, unshared commits, or shared ones by explicit team agreement.
git rebase -i HEAD~5opens an editor to pick, reword, squash, fixup, drop, or reorder commits.git commit --fixup <sha>plusgit rebase -i --autosquashfolds fixes into the right commit automatically.- On conflicts: fix files,
git add, thengit rebase --continue; or--abortto go back. - After rebasing a pushed branch, push with
--force-with-lease, never plain--force.
Quick Example
Update a feature branch with the latest main, clean up its commits, and push:
The interactive todo list:
The branch now has three clean commits sitting directly on top of main.
Core Concepts
Rebase vs Merge
Many teams use both: rebase locally to keep feature branches current and tidy, then merge (often squash) into main via pull request.
Interactive Rebase Commands
Reorder commits by reordering lines. git rebase -i --exec "npm test" main runs tests after every replayed commit, verifying each commit builds on its own.
Fixup Commits and Autosquash
During review, instead of amending in place:
Set git config --global rebase.autoSquash true to make this the default.
--onto: Transplanting Commits
git rebase --onto <newbase> <oldbase> <branch> moves the commits after oldbase onto newbase. It's useful when a branch was accidentally based on another feature branch:
It's also how you update stacked branches after the bottom branch merges. git rebase --update-refs updates every branch in a stack in one go.
Resolving Conflicts During a Rebase
Rebase applies commits one at a time, so conflicts are reported per commit:
- Git stops and lists conflicted files.
- Edit them to the correct result; remember you're resolving this commit's change against the new base.
git add <files>, thengit rebase --continue.git rebase --skipdrops the current commit;git rebase --abortrestores everything to before the rebase.
Enable rerere ("reuse recorded resolution") with git config --global rerere.enabled true, and Git will remember how you resolved a conflict and reapply it automatically if it recurs.
Best Practices
Follow the Golden Rule
Don't rebase commits that exist outside your repository and that others may have built on. Rebasing a shared branch forces every collaborator to reconcile duplicated, rewritten history. For your own pushed feature branch, rebasing is fine, since nobody else is building on it.
Use --force-with-lease
After rewriting a pushed branch, git push --force-with-lease refuses to overwrite the remote if someone else pushed in the meantime. Plain --force silently destroys their commits.
Make Pull the Rebasing Kind
git config --global pull.rebase true makes git pull rebase your local commits on top of fetched ones instead of creating throwaway "Merge branch 'main' of ..." commits.
Tidy Before Review, Not After Approval
Clean up commits before requesting review, and use fixup commits during review so reviewers can see what changed. Autosquash just before merging. Rewriting heavily mid-review makes it hard for reviewers to track changes.
Common Mistakes
Rebasing a Shared Branch
Integrate into shared branches with merges or PRs; rebase only your branch onto them.
Panicking Mid-Rebase
A conflict-heavy rebase can feel like lost work, but nothing is lost. git rebase --abort returns to the starting point, and even after a finished rebase, git reflog shows the old branch tip, so git reset --hard <old-sha> restores it. See undoing things in Git.
Squashing Everything Into One Commit by Default
One giant commit hides the structure of a large change. Squash noise (typo fixes, "wip"), but keep logically separate steps (refactor, feature, tests) as separate commits when they aid review and bisecting.
FAQ
Should I use rebase or merge?
Use rebase to keep your own feature branch current and its commits clean. Use merge, or squash-merge through a PR, to integrate into shared branches like main. The combination gives a readable history without rewriting anything other people depend on.
What does "rewriting history" actually mean?
Commits are identified by a hash of their content and their parent. Rebasing changes a commit's parent, so Git creates new commits with new hashes and moves the branch to them. The originals still exist, unreferenced, until garbage collected, which is why reflog can recover them.
Why do I get the same conflict repeatedly during a rebase?
Each replayed commit can touch the same lines, so a conflict can recur commit after commit. Squashing related commits first, or enabling rerere, reduces the repetition. For branches with many commits touching the same area, a single merge can be less work.
How do I undo a rebase?
Find the pre-rebase commit in git reflog (an entry like rebase (start), or the branch tip before it), then git reset --hard <sha>. ORIG_HEAD also points to the pre-rebase tip immediately afterwards: git reset --hard ORIG_HEAD.
Related Topics
- Git — The version control system overview
- Git Branching Strategies — Where rebase fits in team workflows
- Undoing Things in Git — reflog, reset, and recovery
- Git Internals — Why rebased commits get new hashes
- Code Review — Preparing commits for reviewers