Git Rebase: The Complete Guide for 2026
Rebase is the most powerful and most misunderstood command in Git. Used well, it gives you a clean, linear project history that is easy to read, bisect, and revert. Used carelessly, it can rewrite shared history and create chaos for your team. This guide covers everything: how rebase works internally, when to use it instead of merge, interactive rebase for surgical commit editing, conflict handling, autosquash workflows, and the golden rule that keeps your team safe.
Whether you are cleaning up a feature branch before a pull request or trying to understand why your colleague insists on git pull --rebase, you will find practical, copy-paste examples for every scenario here.
Table of Contents
- What Is Rebasing?
- Rebase vs Merge
- Basic Rebase: git rebase main
- Interactive Rebase: git rebase -i
- Rebase Workflow for Feature Branches
- Rebase Onto: Moving Commits Between Branches
- Handling Rebase Conflicts
- git pull --rebase
- Autosquash with Fixup Commits
- The Golden Rule: Never Rebase Published Branches
- Recovering from Bad Rebases with git reflog
- Real-World Team Workflows
- Common Mistakes and How to Avoid Them
- FAQ
What Is Rebasing?
Rebasing means taking a series of commits from one branch and replaying them on top of another base commit. Instead of joining two branches with a merge commit, rebase moves your entire branch so that it starts from the tip of the target branch. The result is a straight, linear history.
Here is what happens visually. Suppose you have a feature branch that diverged from main:
Before rebase:
A---B---C feature
/
D---E---F---G main
After running git rebase main while on the feature branch:
After rebase:
A'--B'--C' feature
/
D---E---F---G main
Commits A, B, and C are replayed as new commits A', B', and C' on top of G. The original commits are abandoned (though still recoverable via reflog). The prime notation (A') indicates these are new commits with different hashes but the same changes.
Internally, Git does this: it finds the common ancestor (E), generates a diff for each commit on your branch, checks out the target branch tip (G), and applies each diff in order, creating a new commit for each one. If any diff cannot be applied cleanly, you get a conflict to resolve before continuing.
Rebase vs Merge
Both commands integrate changes from one branch into another, but they do it differently. Here is a merge of the same scenario:
After merge:
A---B---C
/ \
D---E---F---G---M main (M = merge commit)
Merge preserves the branch topology. Rebase flattens it. Neither approach is universally "better" — they serve different purposes.
When to Use Rebase
- Updating a feature branch — Rebase your feature onto the latest
mainto stay current without cluttering history with merge commits - Cleaning up before a pull request — Squash WIP commits, reword messages, and reorder changes so reviewers see a clean story
- Keeping
git loglinear — A linear history is easier to read,git bisect, andgit revert - Solo branches — When you are the only person working on a branch, there is no risk of rewriting someone else's history
When to Use Merge
- Shared branches — When multiple people commit to the same branch, merge preserves everyone's work without rewriting hashes
- Preserving context — Merge commits record exactly when a feature was integrated, which can be valuable for auditing
- Release branches — You want a clear record of what was merged and when
- Large, long-running integrations — If a branch has been shared for weeks, rebasing it creates too many conflicts
Quick Comparison
| Aspect | Rebase | Merge |
|---|---|---|
| History | Linear | Branch topology preserved |
| Commit hashes | Rewritten | Preserved |
| Merge commits | None | One per merge |
| Safe for shared branches | No | Yes |
| Conflict resolution | Per commit | Once |
| Bisect / revert friendliness | Excellent | Good |
For a deeper dive into branch management, see our Git Branching Strategies Guide.
Basic Rebase: git rebase main
The simplest form of rebase updates your current branch so it starts from the tip of another branch. Here is the complete workflow:
# Start on your feature branch
git checkout feature/user-auth
# Fetch the latest changes from remote
git fetch origin
# Rebase onto the updated main branch
git rebase origin/main
If there are no conflicts, Git replays each of your commits and you are done. Your branch now has a clean, linear history on top of the latest main.
You can also rebase without switching branches first:
# Rebase feature branch onto main without checking it out
git rebase main feature/user-auth
This is equivalent to checking out feature/user-auth and then running git rebase main.
Interactive Rebase: git rebase -i
Interactive rebase is where rebase becomes a precision tool. It lets you rewrite, combine, reorder, or delete commits before they become part of the shared history. This is what separates a messy branch full of "WIP" and "fix typo" commits from a clean, reviewable pull request.
# Interactively rebase the last 5 commits
git rebase -i HEAD~5
Git opens your editor with a list of commits (oldest first):
pick a1b2c3d Add user authentication model
pick e4f5g6h Add login endpoint
pick i7j8k9l Fix typo in login response
pick m0n1o2p Add session middleware
pick q3r4s5t WIP: debug logging
The Commands
Replace the word pick with any of these commands to control what happens to each commit:
pick(p) — Keep the commit as-isreword(r) — Keep the changes but edit the commit messageedit(e) — Pause after applying this commit so you can amend it (add files, split it, etc.)squash(s) — Fold this commit into the previous one and combine both messagesfixup(f) — Fold this commit into the previous one but discard this commit's messagedrop(d) — Delete this commit entirely
Example: Squashing Commits
The most common use of interactive rebase is squashing related commits together. Here we fold the typo fix and WIP debug logging into the commits they belong to:
pick a1b2c3d Add user authentication model
pick e4f5g6h Add login endpoint
fixup i7j8k9l Fix typo in login response
pick m0n1o2p Add session middleware
drop q3r4s5t WIP: debug logging
After saving, the typo fix is silently folded into "Add login endpoint" and the WIP debug commit is deleted. The result is three clean, meaningful commits.
Example: Reordering and Rewording
reword m0n1o2p Add session middleware
pick a1b2c3d Add user authentication model
pick e4f5g6h Add login endpoint
This reorders the commits and prompts you to write a new message for the first one. Use this when the logical order of changes matters to reviewers.
Example: Splitting a Commit with edit
Sometimes a commit does too much. The edit command lets you pause the rebase so you can break it apart:
edit a1b2c3d Add auth model and migration and seed data
Git pauses after applying that commit. Now you can:
# Undo the commit but keep the changes staged
git reset HEAD~1
# Stage and commit the model separately
git add app/models/user.rb
git commit -m "Add user authentication model"
# Stage and commit the migration
git add db/migrate/
git commit -m "Add users table migration"
# Stage and commit the seed data
git add db/seeds.rb
git commit -m "Add default admin user seed"
# Continue the rebase
git rebase --continue
Rebase Workflow for Feature Branches
Here is a complete, real-world workflow for developing a feature using rebase. This is the pattern most teams adopt once they are comfortable with rebase:
# 1. Create a feature branch from the latest main
git checkout main
git pull origin main
git checkout -b feature/payment-processing
# 2. Do your work, making as many commits as you need
git add .
git commit -m "Add Stripe SDK integration"
# ... more work ...
git commit -m "Add payment form component"
# ... more work ...
git commit -m "Fix amount calculation bug"
git commit -m "Add payment confirmation page"
# 3. Before opening a PR, update your branch
git fetch origin
git rebase origin/main
# 4. Clean up your commits with interactive rebase
git rebase -i origin/main
# 5. Force-push your cleaned-up branch (safe because it is your branch)
git push --force-with-lease origin feature/payment-processing
# 6. Open your pull request
The key insight is that you rebase before opening the PR, not after. Once the PR is open and teammates are reviewing, switch to merge-based updates if needed.
For more on team branch workflows, see our Git Workflow Automation Guide.
Rebase Onto: Moving Commits Between Branches
git rebase --onto is the Swiss Army knife of rebase. It lets you transplant a range of commits from one base to another. The syntax is:
git rebase --onto <new-base> <old-base> <branch>
This means: take all commits between <old-base> and <branch> and replay them onto <new-base>.
Scenario 1: Move a Branch to a Different Base
You accidentally branched off develop instead of main:
Before:
D---E---F main
\
G---H develop
\
I---J feature (branched from develop by mistake)
# Move feature's commits (I, J) onto main instead
git rebase --onto main develop feature
After:
D---E---F main
\ \
\ I'--J' feature (now based on main)
\
G---H develop
Scenario 2: Remove Commits from the Middle
Suppose commits B and C introduced a broken experiment and you want to remove them:
Before:
A---B---C---D---E feature
# Remove B and C by rebasing D..E onto A
git rebase --onto A C feature
After:
A---D'--E' feature
Scenario 3: Extract Commits to a New Base
# You have a long branch and want to move just the last 3 commits
# onto a release branch
git rebase --onto release HEAD~3 feature
Handling Rebase Conflicts Step by Step
Rebase applies commits one at a time, so you might hit conflicts at any point during the replay. Here is the exact process for resolving them:
# Start a rebase
git rebase origin/main
# Git stops at a conflict:
# CONFLICT (content): Merge conflict in src/api/auth.js
# error: could not apply a1b2c3d... Add login endpoint
# Resolve all conflicts and then run:
# git rebase --continue
Step 1: See which files have conflicts:
git status
# Unmerged paths:
# both modified: src/api/auth.js
Step 2: Open the file and resolve the conflict markers:
<<<<<<< HEAD
const token = jwt.sign({ userId: user.id }, SECRET, { expiresIn: '24h' });
=======
const token = jwt.sign({ id: user.id, role: user.role }, SECRET);
>>>>>>> a1b2c3d (Add login endpoint)
Edit the file to keep what you need:
const token = jwt.sign({ userId: user.id, role: user.role }, SECRET, { expiresIn: '24h' });
Step 3: Mark the conflict as resolved and continue:
git add src/api/auth.js
git rebase --continue
If there are more commits to replay, Git may stop again with new conflicts. Repeat the process for each one.
Rebase Escape Hatches
# Abort the rebase entirely and go back to where you started
git rebase --abort
# Skip the current commit (discard it) and continue
git rebase --skip
Use --abort generously. There is no shame in aborting a rebase, figuring out the situation, and starting again.
For visualizing diffs during conflict resolution, try our Git Diff Viewer.
git pull --rebase
A regular git pull is actually git fetch + git merge. When your local branch has commits that are not on the remote, this creates a merge commit even though nobody intended to "merge" anything. Over time, these accumulate into a noisy log full of "Merge branch 'main' of github.com/..." messages.
git pull --rebase replaces the merge step with a rebase. Your local commits are replayed on top of the fetched changes:
# Instead of this (creates merge commits):
git pull origin main
# Do this (linear history):
git pull --rebase origin main
Make It the Default
# Set rebase as the default pull strategy globally
git config --global pull.rebase true
# Now every 'git pull' automatically uses rebase
git pull # equivalent to git pull --rebase
Preserve Merge Commits
If your local branch has intentional merge commits you want to keep:
# Rebase but preserve local merge commits
git pull --rebase=merges origin main
# Or set as default
git config --global pull.rebase merges
Autosquash with Fixup Commits
Autosquash is a workflow that lets you mark a commit as a fix for a previous commit at the time you create it. When you later run interactive rebase, Git automatically reorders and marks these commits for squashing.
# You already committed "Add payment form component"
# Now you realize you forgot to handle the error state.
# Instead of making a vague "fix" commit, do this:
git add src/components/PaymentForm.tsx
git commit --fixup=HEAD~2 # targets "Add payment form component"
# The commit message is automatically set to:
# fixup! Add payment form component
Now when you run interactive rebase with --autosquash:
git rebase -i --autosquash origin/main
Git automatically reorders the fixup commit right after its target and marks it with fixup:
pick a1b2c3d Add Stripe SDK integration
pick e4f5g6h Add payment form component
fixup x9y8z7w fixup! Add payment form component
pick m0n1o2p Add payment confirmation page
Save without changing anything and the fixup is folded in automatically.
Make Autosquash the Default
# Always use --autosquash with interactive rebase
git config --global rebase.autoSquash true
With this set, you never need to pass --autosquash manually. Every git rebase -i will automatically reorder fixup and squash commits.
Targeting Specific Commits
# Fix a specific commit by its hash
git commit --fixup=abc1234
# Fix the commit that last touched a specific file
git commit --fixup=$(git log -1 --format="%H" -- src/utils/price.ts)
# Create a squash commit (prompts for message editing)
git commit --squash=abc1234
The Golden Rule: Never Rebase Published Branches
This is the single most important rule for using rebase in a team:
Never rebase commits that exist on a branch someone else might have pulled.
Rebase rewrites commit hashes. If Alice has pulled your branch and based her work on commit abc1234, and you rebase and push, that commit no longer exists in the remote. Alice now has orphaned commits that reference a parent that does not exist. When she pulls, she gets duplicate commits and a tangled mess.
# Safe: rebase your own unpublished feature branch
git rebase main feature/my-private-work
# Safe: rebase before pushing for the first time
git rebase -i main # clean up, then push
# DANGEROUS: rebase main or develop or any shared branch
git checkout main
git rebase feature/something # DO NOT DO THIS
# DANGEROUS: rebase after others have pulled
git checkout feature/shared-work # Alice already pulled this
git rebase main # Rewrites hashes Alice depends on
When You Must Rebase a Pushed Branch
If you are the only person working on a pushed feature branch (a common pattern), rebase is safe as long as you force-push carefully:
# --force-with-lease fails if someone else pushed to the branch
# This is safer than --force which overwrites unconditionally
git push --force-with-lease origin feature/my-branch
Never use --force. Always use --force-with-lease. It checks that the remote ref matches your expectation before overwriting, protecting against accidentally clobbering a colleague's push.
Recovering from Bad Rebases with git reflog
Messed up a rebase? The reflog is your safety net. Git records every movement of HEAD, even through rebases. Nothing is truly lost until the garbage collector runs (usually 90 days later).
# View the reflog
git reflog
# Output:
a1b2c3d (HEAD -> feature) HEAD@{0}: rebase (finish): ...
f9e8d7c HEAD@{1}: rebase (pick): Add payment form
b6a5c4d HEAD@{2}: rebase (start): checkout origin/main
x1y2z3w HEAD@{3}: commit: Add payment confirmation page
m4n5o6p HEAD@{4}: commit: Fix amount calculation bug
...
The entry at HEAD@{3} is where your branch was before the rebase started. Reset to it:
# Hard reset to the pre-rebase state
git reset --hard HEAD@{3}
# Or use the hash directly
git reset --hard x1y2z3w
Your branch is now exactly where it was before the rebase, with all original commits intact.
Recovery During a Rebase
# If you are in the middle of a rebase and want to stop
git rebase --abort
# This restores the branch to its pre-rebase state
# No reflog surgery needed
Creating a Backup Branch
For complex rebases, create a backup first:
# Save current state before rebasing
git branch backup/feature-before-rebase
# Now rebase with confidence
git rebase -i origin/main
# If everything goes wrong
git checkout feature/payment-processing
git reset --hard backup/feature-before-rebase
git branch -d backup/feature-before-rebase
See our Git Commands Guide for more essential recovery techniques.
Real-World Team Workflows Using Rebase
Workflow 1: Rebase + Squash Merge (GitHub/GitLab Style)
This is the most popular workflow for teams that want clean history:
# Developer workflow:
git checkout -b feature/new-api-endpoint
# ... make commits, messy history is fine ...
# Before PR: update and clean up
git fetch origin
git rebase origin/main
git rebase -i origin/main # squash WIP commits
git push --force-with-lease origin feature/new-api-endpoint
# PR reviewer merges with "Squash and merge" button
# Result: one clean commit on main per feature
Workflow 2: Rebase + Fast-Forward Merge (Linear History)
For teams that want every commit on main to be meaningful:
# Enforce linear history on the repository
# (GitHub: Settings -> Require linear history)
# Developer cleans up commits before merging
git rebase -i origin/main
git push --force-with-lease
# Merge is a fast-forward (no merge commit)
git checkout main
git merge --ff-only feature/new-api-endpoint
Workflow 3: Continuous Rebase (Trunk-Based Development)
# Short-lived branches, rebased frequently
git checkout -b fix/login-timeout
# ... make 1-3 commits ...
# Rebase onto main multiple times per day
git fetch origin
git rebase origin/main
# Merge quickly (branch lives hours, not days)
git checkout main
git merge --ff-only fix/login-timeout
git push origin main
git branch -d fix/login-timeout
Recommended Team Configuration
# Every developer should set these
git config --global pull.rebase true
git config --global rebase.autoSquash true
git config --global rebase.autoStash true
# rebase.autoStash: automatically stashes uncommitted changes
# before rebase and pops them after, so you do not have to
# manually stash first
For branch protection and CI integration, see our Git Branching Strategies Guide.
Common Mistakes and How to Avoid Them
Mistake 1: Rebasing a Shared Branch
Problem: You rebase main or develop, rewriting commits that others depend on.
Fix: Only rebase branches that you own. If in doubt, merge instead.
Mistake 2: Using --force Instead of --force-with-lease
Problem: git push --force overwrites the remote unconditionally. If a colleague pushed a commit, you destroy it.
Fix: Always use --force-with-lease. It fails safely if the remote has changed.
# Create a git alias to prevent accidents
git config --global alias.pushf "push --force-with-lease"
# Now use: git pushf origin feature/my-branch
Mistake 3: Resolving the Same Conflict Repeatedly
Problem: You keep rebasing and hitting the same conflict because the same commits clash each time.
Fix: Enable rerere (reuse recorded resolution). Git remembers how you resolved a conflict and applies the same fix automatically next time:
git config --global rerere.enabled true
Mistake 4: Rebasing an Enormous Branch
Problem: Your branch has 50 commits spanning 3 weeks. Rebasing onto main produces conflicts in every third commit.
Fix: Rebase frequently (daily or after every PR merge to main). Short-lived branches with frequent rebases almost never have conflicts. If you are stuck with a large branch, consider doing a merge instead of a rebase.
Mistake 5: Forgetting to Update Before Rebasing
Problem: You run git rebase main but your local main is weeks behind. You rebase onto stale code and still have conflicts when merging.
Fix: Always rebase onto the remote tracking branch:
# Do this:
git fetch origin
git rebase origin/main
# Not this (uses your local, possibly stale, main):
git rebase main
Mistake 6: Losing Track of What Was Rebased
Problem: After a complex interactive rebase, you are not sure what you changed.
Fix: Compare the before and after using the reflog:
# After rebasing, compare with pre-rebase state
git diff HEAD@{1}..HEAD
# Or see the commit log difference
git log --oneline HEAD@{1}..HEAD
For more Git best practices, see our Complete Guide to Git.
Frequently Asked Questions
What is the difference between git rebase and git merge?
Git merge creates a new merge commit that joins two branch histories together, preserving the exact chronological order of commits. Git rebase replays your commits on top of another branch, creating a linear history. Merge is non-destructive and keeps the full context. Rebase produces a cleaner log but rewrites commit hashes. Use rebase for local feature branches; use merge for shared or public branches.
Is it safe to rebase a branch that has been pushed to a remote?
Only if you are the sole person working on that branch. Rebasing rewrites commit hashes, so anyone who has based work on the old commits will face conflicts. If others have pulled your branch, use merge instead. If you must rebase a pushed branch that only you use, follow up with git push --force-with-lease (never --force) to update the remote safely.
How do I squash multiple commits into one using interactive rebase?
Run git rebase -i HEAD~N where N is the number of commits to squash. In the editor, change the word "pick" to "squash" (or "s") for every commit you want to fold into the one above it. Leave the first commit as "pick". Save and close, then write a new combined commit message when prompted. Use "fixup" instead of "squash" if you want to discard the folded commit messages entirely.
How do I undo a rebase that went wrong?
Use git reflog to find the commit hash your branch pointed to before the rebase. The reflog records every HEAD movement, so you will see an entry like "rebase (start)" or the commit message from before the rebase. Then run git reset --hard <hash> to restore your branch. If you are mid-rebase and want to stop, run git rebase --abort to return to the pre-rebase state.
Should I use git pull --rebase instead of git pull?
Yes, for most workflows. A regular git pull creates a merge commit every time your local branch has diverged from the remote, cluttering the history with meaningless merge commits. git pull --rebase replays your local commits on top of the fetched changes, keeping the history linear. Set it as default with git config --global pull.rebase true. Use git pull --rebase=merges if you want to preserve intentional local merge commits.
Continue Learning
This guide is part of our Git deep-dive series. Explore the related guides to build a complete understanding of Git workflows:
- The Complete Guide to Git — Fundamentals through advanced workflows
- Git Commands Every Developer Should Know — Essential command reference
- Git Branching Strategies Guide — Git Flow, GitHub Flow, and Trunk-Based Development
- Git Blame Complete Guide — Find who changed a line (ranges, renames, moved code, ignore-revs)
- Git Log Complete Guide — Pretty graphs, filters, ranges, and searching history
- Git Workflow Automation Guide — Hooks, CI/CD, and automation patterns