Git Rebase: The Complete Guide for 2026

Published February 12, 2026 · 28 min read

⚙ Decision guide: Not sure whether to preserve branch context or keep history linear? Read Git Merge vs Rebase.
⚙ Decision guide: Not sure whether to copy a fix or undo a bad change? Read Git Cherry-Pick vs Revert.

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?

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

When to Use Merge

Quick Comparison

Aspect Rebase Merge
HistoryLinearBranch topology preserved
Commit hashesRewrittenPreserved
Merge commitsNoneOne per merge
Safe for shared branchesNoYes
Conflict resolutionPer commitOnce
Bisect / revert friendlinessExcellentGood

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:

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:

Related Tools

Git Diff Viewer
Visualize and compare diffs in the browser
JSON Formatter
Format and validate JSON data instantly
Regex Tester
Test regular expressions with live matching
Git Cheat Sheet
One-page quick reference for all Git commands