git rebase is one of Git's most powerful — and most misunderstood — tools. It rewrites commit history to produce a clean, linear timeline. This guide covers everything from basic rebasing to interactive rebase, squashing, and conflict resolution.
Quick reference
| Command | What it does |
|---|---|
git rebase main |
Rebase current branch onto main |
git rebase -i HEAD~3 |
Interactively edit last 3 commits |
git rebase --continue |
Continue after resolving a conflict |
git rebase --skip |
Skip the current conflicting commit |
git rebase --abort |
Cancel the rebase, restore original branch |
git rebase -i --root |
Interactively rebase from the very first commit |
git pull --rebase |
Fetch + rebase instead of fetch + merge |
git push --force-with-lease |
Push rebased branch safely |
What rebase actually does
When you rebase branch feature onto main, Git:
- Finds the common ancestor of
featureandmain - Detaches the commits unique to
feature - Reapplies them, one by one, on top of
main's latest commit
Before rebase:
A---B---C feature
/
D---E---F---G main
After: git rebase main (from feature branch)
A'--B'--C' feature
/
D---E---F---G main
The commits A', B', C' have new SHA-1 hashes even if the file changes are identical. That's why rebasing rewrites history.
Basic rebase: update a feature branch
The most common use: bring your feature branch up to date with main without a merge commit.
# Start on your feature branch
git checkout feature/my-work
# Rebase onto main
git rebase main
This is equivalent to "replaying my work on top of the latest main". The result is a linear history — no ugly merge commits.
Compare with merge:
# Merge approach — creates a merge commit
git checkout feature/my-work
git merge main
| Approach | History shape | Extra commits | Good for |
|---|---|---|---|
rebase |
Linear | None | Feature branches, cleaner PRs |
merge |
Non-linear | One merge commit | Shared/public branches |
merge --no-ff |
Non-linear | Forced merge commit | Preserving branch context |
Interactive rebase: rewrite history
git rebase -i (interactive) opens an editor where you choose what to do with each commit.
# Edit the last 3 commits
git rebase -i HEAD~3
The editor shows:
pick a1b2c3d Add login form
pick d4e5f6g Fix typo in form label
pick h7i8j9k Add form validation
# Commands:
# p, pick = use commit as-is
# r, reword = use commit, but edit the message
# e, edit = use commit, but stop for amending
# s, squash = meld into previous commit
# f, fixup = like squash, but discard this commit's message
# d, drop = remove commit entirely
Squash commits
Combine multiple commits into one before merging a PR:
pick a1b2c3d Add login form
s d4e5f6g Fix typo in form label
s h7i8j9k Add form validation
Save and close. Git opens another editor to write the combined commit message.
Fixup (squash silently)
Like squash but discards the later commit messages — useful for "fix typo" commits:
pick a1b2c3d Add login form
f d4e5f6g Fix typo in form label
pick h7i8j9k Add form validation
Reword a commit message
pick a1b2c3d Add login form
r d4e5f6g Fix typo in form label ← will open editor for new message
pick h7i8j9k Add form validation
Drop a commit
pick a1b2c3d Add login form
d d4e5f6g Fix typo in form label ← deleted from history
pick h7i8j9k Add form validation
Reorder commits
Simply reorder the lines in the editor. Git replays them in the new order.
Resolving conflicts during rebase
Unlike merge (one conflict resolution), rebase may pause at each conflicting commit and ask you to resolve it.
git rebase main
# CONFLICT (content): Merge conflict in src/auth.ts
# error: could not apply a1b2c3d... Add login form
Resolution workflow:
# 1. Open the conflicting file and fix it
# (look for <<<<, ====, >>>> markers)
# 2. Stage the fixed file
git add src/auth.ts
# 3. Continue the rebase
git rebase --continue
# Git will pause again if the next commit also conflicts
If you want to abandon the entire rebase:
git rebase --abort
# Returns your branch to its exact state before you started
If one commit is impossible to apply cleanly and you want to skip it:
git rebase --skip
# Discards the conflicting commit and continues
git pull --rebase
By default, git pull does a fetch + merge, creating a merge commit even for trivial upstream updates. Use --rebase for a linear history:
git pull --rebase origin main
Set it as the default:
git config --global pull.rebase true
Push after rebasing
Because rebase rewrites history, the remote branch will reject a normal git push:
git push origin feature/my-work
# error: Updates were rejected because the tip of your current branch is behind
You must force-push. Always prefer --force-with-lease over --force:
# Safe: fails if someone else pushed in the meantime
git push --force-with-lease origin feature/my-work
# Unsafe: overwrites without checking
git push --force origin feature/my-work # avoid unless necessary
Rebase vs merge — when to use which
| Situation | Use |
|---|---|
| Update feature branch with latest main | rebase |
| Integrating a feature into main | merge (or merge --no-ff) |
| Cleaning up commits before a PR | rebase -i (squash) |
| Shared/public branch (others have pulled it) | merge — never rebase |
| Long-lived branch with frequent upstream changes | rebase + resolve conflicts per commit |
| Preserving the exact sequence of events | merge |
The golden rule: never rebase shared history
Never rebase commits that exist on a remote branch other people have pulled.
When you rebase, the old commits still exist on teammates' machines. After you force-push, their history diverges. When they try to push or pull, Git gets confused and they end up with duplicate commits.
Safe to rebase: branches only you have pushed (feature/your-name branches).
Never rebase: main, develop, or any branch others actively use.
Practical patterns
Pattern 1: Clean up a messy feature branch before PR
git checkout feature/auth
git log --oneline
# 3f9a2c1 wip
# 2b8d0e4 fix lint
# 1a7c9f3 more wip
# 0d6b8e2 Add JWT authentication
git rebase -i HEAD~4
# squash first 3 into the bottom one
# write a clean commit message: "Add JWT authentication with refresh tokens"
git push --force-with-lease origin feature/auth
Pattern 2: Sync a long-running branch with main
git checkout feature/big-refactor
git fetch origin
git rebase origin/main
# resolve conflicts commit by commit
git push --force-with-lease origin feature/big-refactor
Pattern 3: Split a commit (using edit)
git rebase -i HEAD~1
# change "pick" to "edit"
# Git pauses at that commit
git reset HEAD~1 # unstage the commit, keep changes in working tree
git add -p # stage only the first logical change
git commit -m "Add user model"
git add -p # stage second change
git commit -m "Add user service"
git rebase --continue
Common mistakes
| Mistake | Why it's a problem | Fix |
|---|---|---|
Rebasing main or develop |
Rewrites shared history, breaks teammates' repos | Only rebase private branches |
git push --force without --lease |
Overwrites someone else's push silently | Always use --force-with-lease |
Forgetting git rebase --continue after resolving |
Leaves rebase in a stuck state | Always git add then --continue |
| Squashing a commit that shouldn't be squashed | Can mix unrelated changes | Review the diff before squashing |
| Rebasing with uncommitted changes | Git refuses to start | git stash first, then rebase |
Using git rebase --skip carelessly |
Silently loses work | Only skip if you're sure the commit is redundant |
FAQ
What's the difference between git rebase and git merge?
Both integrate changes from one branch into another. Merge preserves the full branching history with a merge commit. Rebase rewrites history to look as if you branched off the latest tip, producing a linear history.
Does rebase lose my commits?
No — it rewrites their SHA hashes and re-applies the same changes. Your code changes are preserved. The old commits still exist in Git's reflog for ~30 days, so you can recover them with git reflog + git reset.
Why does my rebase have more conflicts than a merge would?
Rebase applies commits one at a time, so each commit can conflict separately. A merge collects everything into one big conflict. More conflicts isn't necessarily worse — each one is smaller and easier to reason about.
Can I rebase onto a different remote branch?
Yes. git rebase origin/staging rebases your current branch onto the remote staging branch (after git fetch).
What is --autosquash?
If you name a commit fixup! <original message> or squash! <original message>, git rebase -i --autosquash automatically places and marks those commits as fixup or squash in the interactive editor.
git commit -m "fixup! Add login form"
git rebase -i --autosquash HEAD~5
Is it safe to rebase a PR branch after the PR is open?
Yes — force-push after rebasing and GitHub/GitLab will update the PR automatically. Just communicate with reviewers so they're not in the middle of leaving comments.