git rebase takes the commits on your branch and replays them, one by one, on top of a different base commit. The result is a linear history that looks as if you had branched from the latest tip of the target instead of where you originally branched. It's how teams keep main history readable and how individuals tidy up their work before opening a PR.
Rebase is powerful but rewrites commit SHAs. Use it on local branches and feature branches you alone work on; do not rebase commits other people have already based work on.
Basic rebase: bring your branch up to date
You're on feature/x, branched from main. main has moved on. Bring your branch on top of the new main:
git switch feature/x
git fetch origin
git rebase origin/main
What happens:
- Git temporarily sets aside your
feature/xcommits. - It moves your branch to point at
origin/main. - It replays your commits on top, one at a time.
- Each commit gets a new SHA but the same author, message, and changes.
If there are no conflicts, you end up with a clean linear history. Push:
git push --force-with-lease
The --force-with-lease is needed because the SHAs changed; a regular git push is rejected. Never use plain --force — --force-with-lease refuses if someone else has pushed in the meantime.
Interactive rebase: squash, edit, reorder, drop
git rebase -i HEAD~5
Git opens an editor with the last 5 commits listed in chronological order:
pick a1b2c3d feat: search ui
pick d4e5f6g fix typo
pick 7h8i9j0 wip
pick k1l2m3n add tests
pick o4p5q6r polish styling
Change pick on each line to one of:
p, pick— keep as-is.r, reword— keep changes, edit the commit message.e, edit— pause after this commit so you can amend it.s, squash— combine with the previous commit; keep the message of both.f, fixup— same as squash but discards this commit's message.d, drop— remove the commit entirely.
Reorder lines to reorder commits. Save and quit the editor — git replays the commits according to your edits, pausing whenever it needs input.
A common pre-PR cleanup pass:
pick a1b2c3d feat: search ui
fixup d4e5f6g fix typo
fixup 7h8i9j0 wip
pick k1l2m3n add tests
pick o4p5q6r polish styling
Squash mistakes into the feature commit, keep the test and polish commits separate.
Pause to edit a commit
If you set a line to edit, git stops after applying that commit:
# git is paused here. Make your changes, then:
git add path/to/file
git commit --amend
git rebase --continue
Or, to drop in a new commit at this point:
git commit -m "missed change"
git rebase --continue
Resolving conflicts during rebase
A rebase can produce one conflict per replayed commit:
git rebase main
# CONFLICT (content): Merge conflict in src/config.ts
# resolve markers in src/config.ts
git add src/config.ts
git rebase --continue
If you don't want this commit at all:
git rebase --skip
To bail out completely:
git rebase --abort
--abort is safe — it puts your branch back exactly where it was before the rebase started.
Tip: enable git rerere once and forget about it:
git config --global rerere.enabled true
Git remembers your conflict resolutions and replays them automatically the next time the same conflict comes up — invaluable on long rebases.
--onto: rebase a slice of commits onto a different base
--onto is the answer to "I branched from the wrong place" or "I want to lift the last 3 commits onto a different branch":
git rebase --onto <new-base> <old-base> <branch>
Example: you branched feature/x from feature/y, but feature/y is being abandoned. Move feature/x to be based on main instead:
git rebase --onto main feature/y feature/x
This replays the commits that are on feature/x but not on feature/y onto main.
Pull with rebase instead of merge
To avoid merge commits when pulling:
git pull --rebase
Or set it as the default:
git config --global pull.rebase true
Now git pull rebases your local commits on top of the fetched remote instead of merging.
Autosquash: pre-mark fixup commits
If you make a fix commit while still working on a feature, label it for a future squash:
git commit --fixup <sha-of-the-target-commit>
Later, run:
git rebase -i --autosquash main
The fixup commits are pre-arranged in the right places with the right keywords. You just save and quit.
To make autosquash always implied:
git config --global rebase.autosquash true
The golden rule
Do not rebase commits that exist outside your local repository and that other people may have based work on.
Rebasing rewrites SHAs. If a teammate based their branch on your old SHAs and you force-push the new ones, their next pull will be a mess. Safe candidates for rebase:
- Local branches you haven't pushed.
- Feature branches that only you work on, even if pushed (force-push needed afterwards).
- Branches where the team has explicitly agreed rebase is the workflow.
Unsafe:
main,master, release branches.- Any branch other people have checked out and based work on.
- Pushed branches with open PRs that already have review comments tied to specific SHAs (you'll lose the comment anchors).
When in doubt, use git merge instead. It produces a merge commit but doesn't rewrite anything.
Practical recipes
Clean up before a PR
git fetch origin
git rebase -i origin/main
# squash WIPs, reword messages, drop dead-end commits
git push --force-with-lease
Pull in latest main without a merge commit
git fetch origin
git rebase origin/main
Move the last commit to a different branch
git switch other-branch
git cherry-pick <sha>
git switch original-branch
git reset --hard HEAD~1
(Yes, the simpler cherry-pick + reset is often cleaner than --onto for a single commit.)
Abort and pretend it never happened
git rebase --abort
Recover after a botched rebase
git reflog show <branch>
# find the entry just before the rebase ("rebase: checkout ..." line)
git reset --hard <branch>@{N}
Or, before risky rebases, make a backup branch first:
git branch backup/feature-x
git rebase -i main
# if it goes wrong:
git reset --hard backup/feature-x
Pitfalls
- Force-pushing to a shared branch. Coordinate, and use
--force-with-lease, never plain--force. - Rebase makes line-by-line review harder. PR review tools sometimes can't compare across a rebase. Push small fixup commits during review and squash only at the end.
- Long rebases conflict on every commit. If a feature branch is wildly out of date, sometimes a clean merge from
mainfollowed by a single squash is less work than a 40-commit rebase. - Tags and signed commits. Rebase rewrites commits, breaking signatures. Re-sign with
git rebase --exec 'git commit --amend --no-edit -S'or sign at the end.
Summary
git rebase <base>replays your commits on top of<base>, producing a linear history.git rebase -i HEAD~Nis your tool for squashing, reordering, and rewording before a PR.- Conflicts: resolve,
git add,git rebase --continue. Bail with--abort. - Push rewritten branches with
--force-with-lease, never plain--force. - Don't rebase shared history. Use
mergethere. git reflogis the safety net if a rebase goes wrong.