Ad – 728×90
🌿 Branching & Merging

git rebase – Rewriting Commit History Cleanly

Rebase is one of Git's most powerful — and most debated — features. It lets you take commits from one branch and re-apply them on top of a different base, producing a clean linear history. Used carefully, it makes your project history readable. Used recklessly on shared branches, it breaks your teammates' repos. This lesson teaches you both the power and the rules.

⏱️ 25 min read 🎯 Intermediate 📅 Updated 2026

What Is Rebase?

Rebase re-applies your commits on top of a different base commit. Instead of creating a merge commit that joins two branches, rebase takes each commit from your branch, lifts it off its current base, and replays it starting from a new base commit.

The result: your branch looks like it was created from the latest state of main — no divergence, no merge commit, clean linear history.

ℹ️
Rebase rewrites history

Even though the content of your commits stays the same, rebase creates new commits with new SHA hashes. The original commits still exist temporarily but become unreachable. This is why rebase on shared branches is dangerous — your teammates have the old SHAs and Git can't reconcile them.

Rebase vs Merge

AspectMergeRebase
History shape Preserves branch topology (forked graph) Creates linear history (straight line)
Merge commit Creates one (with --no-ff or when diverged) No merge commit
Commit SHAs Original commits unchanged New commits created (new SHAs)
Safety on shared branches Safe DANGEROUS — never rebase shared commits
Best for Preserving full branch history, team merges Cleaning up local feature branch before PR

Basic Rebase

Before rebase (diverged branches)

text
          C4 ← C5
         /         ↑
C1 ← C2 ← C3    feature/login
                  ↑
                 main (new commits C6, C7 added after branching)

         C6 ← C7
        /
C1 ← C2 ← C3
                ↑
               main

After: git rebase main (from feature/login)

text
C1 ← C2 ← C3 ← C6 ← C7 ← C4' ← C5'
                        ↑           ↑
                       main   feature/login (HEAD)

C4' and C5' are NEW commits (same content as C4/C5, but new SHAs).
The branch now starts from the tip of main — clean linear history.
Shell
# Standard rebase workflow
git switch feature/login          # make sure you're on the feature branch
git rebase main                   # rebase onto main

# If there are no conflicts, it completes automatically.
# If there ARE conflicts, resolve them then:
git add <resolved-file>
git rebase --continue             # continue to next commit

# After rebase, merge into main (will be a fast-forward now)
git switch main
git merge feature/login           # fast-forward — clean!
Ad – 336×280

Interactive Rebase

Interactive rebase (git rebase -i) opens a text editor listing your recent commits. You can reorder, rename, squash, or drop individual commits before they get replayed. This is the most powerful way to clean up your commit history before submitting a pull request.

Shell
# Interactively edit the last 3 commits
git rebase -i HEAD~3

# OR: interactively rebase everything since branching off main
git rebase -i main

Git opens your editor with a list like this:

text
pick a1b2c3d Add login form HTML
pick 9f8e7d6 fix typo
pick 5c4b3a2 Add login form validation
pick 2d1e0f9 WIP: more validation

# Commands:
# p, pick   = use commit as-is
# r, reword = use commit but edit the message
# s, squash = combine into previous commit (keep both messages)
# f, fixup  = combine into previous commit (discard this message)
# d, drop   = remove this commit entirely
# (lines can be reordered to reorder commits)

Example: squash the messy commits into one clean commit

text
pick a1b2c3d Add login form HTML
f    9f8e7d6 fix typo            ← fixup: fold into previous, discard message
s    5c4b3a2 Add login form validation  ← squash: fold in, keep message option
f    2d1e0f9 WIP: more validation ← fixup: fold in, discard message

# Save and close the editor.
# Git opens editor again to write the final combined commit message.
# Result: ONE clean commit instead of four messy ones.
CommandShorthandWhat it does
pickpUse the commit as-is — no change
rewordrUse commit but open editor to rewrite the message
squashsCombine into previous commit; opens editor to merge messages
fixupfCombine into previous commit; silently discard this commit's message
dropdDelete this commit entirely from history

The Golden Rule of Rebase

⚠️
Never rebase commits that have been pushed to a shared branch

When you rebase, Git creates new commits with new SHA hashes. If your teammates already have the original commits (because they pulled them), their Git history now conflicts with yours. When they try to pull, Git sees two different commits with the same content but different hashes — causing confusing merge conflicts and duplicated commit history. This is the most dangerous mistake in Git. The rule: rebase only on your local branch before pushing, or on a branch only you are working on.

Shell
# SAFE: rebase your LOCAL feature branch before pushing
git switch feature/login
git rebase -i main          # clean up commits
git push -u origin feature/login   # first push — fine

# DANGEROUS: rebasing AFTER you've already pushed
# (teammates or CI have already pulled these commits)
git rebase main             # creates new SHAs
git push --force-with-lease # need force push — red flag!
# This BREAKS anyone who pulled the old branch

# Only OK if you're SURE no one else is on that branch:
git push --force-with-lease origin feature/login  # safer than --force

When to Use Rebase

Good uses of rebase
  • Clean up local feature branch before opening a pull request — squash "WIP" and "fix typo" commits into logical units.
  • Update feature branch with latest main — rebase onto main instead of merging main into your branch (avoids merge commit pollution).
  • Interactive rebase to reorder commits — put related changes together for reviewers.
Shell
# Abort rebase if something goes wrong
git rebase --abort
# Returns everything to pre-rebase state

# If a conflict occurs during rebase, resolve it then:
git add <resolved-file>
git rebase --continue

# Skip a problematic commit (use carefully — loses that commit's changes)
git rebase --skip

📋 Summary

  • Rebase re-applies your commits on top of a new base — creating new commit SHAs and a clean linear history.
  • Rebase vs merge: rebase = linear history (no merge commit); merge = preserves branch topology.
  • git rebase main — rebase current branch onto main (update with latest main changes).
  • git rebase -i HEAD~N — interactive rebase for last N commits: squash, reword, drop, reorder.
  • Interactive commands: pick (keep), squash (combine + merge messages), fixup (combine + discard message), reword (edit message), drop (delete).
  • The Golden Rule: NEVER rebase commits that have been pushed to a shared branch. Rebase only local, unpushed commits.
  • git rebase --abort cancels the rebase safely at any point.

FAQ

If rebase rewrites history, does it change the actual code? +

No — the content of the commits stays the same. The code changes introduced by each commit are preserved exactly. What changes is the commit's metadata: its parent hash (which is now a different base), and therefore its own SHA hash. Think of it like printing the same document on new paper — the text is identical, but the paper (the object in Git's store) is new.

What's the difference between squash in interactive rebase and git merge --squash? +

Both combine multiple commits into one, but they work differently. git merge --squash combines all commits from a feature branch into a single staged change on the target branch — it doesn't touch the feature branch itself. Interactive rebase's squash/fixup combines commits within the same branch's history, rewriting that branch. Merge squash is for when you're done with the feature; interactive rebase squash is for cleaning up before the merge.

I already pushed my branch and want to rebase it. What do I do? +

If you're the only one working on that branch (a personal feature branch, not shared), you can rebase locally and then force-push: git push --force-with-lease origin branch-name. The --force-with-lease flag is safer than --force because it checks that no one else has pushed to the remote branch since your last fetch. If you're collaborating with others on the same branch, communicate before rebasing or use merge instead.

Should I use rebase or merge to keep my feature branch updated with main? +

Both work. git rebase main (from your feature branch) gives a cleaner linear history and makes the eventual merge into main a fast-forward. git merge main (into your feature branch) creates merge commits in your feature branch history, which can clutter it. Most teams that care about clean history prefer rebase here — it's a safe use since you're rebasing your own local branch, not shared commits.