Rebasing is Git's second major tool for integrating changes from one branch into another — the alternative to merging. While merging preserves the complete history of how work diverged and came back together, rebasing rewrites history to tell a cleaner, more linear story. Understanding when and how to use each is one of the marks of Git fluency.
The Problem Rebasing Solves
When two branches diverge and you want to integrate their work, merging is the straightforward approach — it creates a merge commit that joins both histories. The result is accurate but can leave a complex, non-linear history that is harder to read.
Before merge:
A -- B -- C (main)
\
D -- E (feature)
After merge:
A -- B -- C -- M (main, M = merge commit)
\ /
D ------E
Rebasing offers an alternative: instead of creating a merge commit, it takes the commits from your branch and replays them on top of another branch as if the work had started there all along.
After rebase:
A -- B -- C -- D' -- E' (main, linear history)
The D' and E' are new commits — they contain the same changes as D and E but are built on top of C rather than B. The result is a perfectly linear history with no merge commit.
Basic Rebasing — git rebase
The standard rebase command takes the commits on your current branch and replays them onto a target branch:
git switch feature
git rebase main
What Git does step by step:
- Finds the common ancestor of
featureandmain(commit B in the example above). - Saves the changes introduced by each commit on
featureto temporary patch files. - Resets
featureto point to the tip ofmain. - Replays each saved patch, creating new commits on top of
main.
After the rebase, a fast-forward merge on main brings it up to date:
git switch main
git merge feature # Fast-forward — no merge commit needed
The end result is identical content to a merge, but with a perfectly straight-line history.
Rebase vs. Merge — The Core Trade-off
Both commands integrate changes. The difference is in what they do to history.
| Merge | Rebase | |
|---|---|---|
| History | Preserves the true divergence and joining of branches | Rewrites history to appear linear |
| Merge commit | Creates one | Does not create one |
| Readability | Can become complex in active repositories | Easier to read with git log |
| Safety | Always safe — never rewrites existing commits | Can cause serious problems if misused on shared branches |
| Use case | Integrating completed work into a shared branch | Keeping a feature branch up to date with main |
The end snapshot — the actual state of the code — is identical after either operation. Only the history differs.
Updating a Feature Branch with Rebase
One of the most common uses of rebase is keeping a long-running feature branch current with main as other work lands there. Rather than creating a merge commit every time you pull in upstream changes, you rebase your branch on top of the updated main:
git switch feature
git rebase main
This replays your feature branch's commits on top of the latest main, keeping your branch ahead of main without introducing merge commits into the feature's history. When the feature is eventually merged, the history reads cleanly.
Interactive Rebase — git rebase -i
Interactive rebase is where rebasing becomes genuinely powerful as an editing tool. It allows you to rewrite, reorder, squash, split, or drop commits before sharing them — cleaning up your local history before it becomes permanent.
git rebase -i HEAD~3 # Interactively edit the last 3 commits
This opens an editor showing the commits in order (oldest first):
pick f7f3f6d Add login form component
pick 310154e Add form validation
pick a5f4a0d Fix typo in form label
# Commands:
# p, pick = use commit as-is
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop to amend it
# s, squash = meld into the previous commit
# f, fixup = like squash, but discard this commit's message
# d, drop = remove commit entirely
You edit the list, save, and Git executes your instructions.
Reword a commit message
Change pick to reword (or r):
reword f7f3f6d Add login form component
pick 310154e Add form validation
Git replays the commits and pauses at each reword to let you edit the message.
Squash commits together
Change pick to squash (or s) on the commits you want folded into the one above them:
pick f7f3f6d Add login form component
squash 310154e Add form validation
squash a5f4a0d Fix typo in form label
Git combines all three into a single commit and opens an editor for you to write a combined message. Use fixup instead of squash to silently discard the message of the squashed commit.
Drop a commit entirely
Change pick to drop (or d), or simply delete the line:
pick f7f3f6d Add login form component
drop 310154e Add form validation
The dropped commit is removed from history as if it never existed.
Reorder commits
Rearrange the lines in the editor. Git replays commits in the order they appear — top to bottom:
pick 310154e Add form validation
pick f7f3f6d Add login form component
Rebasing onto a Different Base — --onto
The --onto flag provides precise control when you want to rebase a branch onto a branch other than the one it diverged from. This is useful when a topic branch was created off another topic branch and you want to move just part of the work.
git rebase --onto main server client
This says: take the commits on client that are not on server, and replay them onto main. The server branch is untouched; only client is moved.
The Golden Rule of Rebasing
Never rebase commits that exist outside your local repository and that others may have based work on.
This is the single most important rule in Git.
When you rebase, you are abandoning existing commits and creating new ones that are similar but different — they have different SHA-1 hashes. If you have already pushed those commits to a shared remote and someone else has pulled them and built work on top of them, rebasing and force-pushing replaces the commits they are working from. Their local history no longer matches the remote, creating a confusing and painful situation that is difficult to untangle.
The practical consequence:
- Rebase freely on local commits that have never been pushed. Clean up your history before you share it.
- Never rebase shared branches — branches that others are actively working from.
- If you rebase a branch and need to push it, you must force-push (
git push --force). Force-pushing on a shared branch is a warning sign that you may be breaking collaborators' work.
git pull --rebase
By default, git pull performs a fetch followed by a merge. If you prefer a clean linear history, you can pull with rebase instead:
git pull --rebase
This fetches the remote changes and rebases your local commits on top of them rather than creating a merge commit. To make this the default behaviour globally:
git config --global pull.rebase true
When to Use Rebase vs. Merge
| Situation | Recommended |
|---|---|
Keeping a feature branch current with main | Rebase |
| Cleaning up local commits before pushing | Interactive rebase |
Integrating a completed feature into main | Merge (or rebase + fast-forward merge) |
| Shared branch that others are working from | Always merge — never rebase |
| Preparing a clean series of patches to submit | Rebase |
| Preserving the exact history of how work happened | Merge |
The simplest guideline: rebase local changes before pushing to clean up your work; never rebase anything you have already pushed to a shared branch.
Summary
| Command | What It Does |
|---|---|
git rebase <branch> | Replays current branch commits on top of <branch> |
git rebase -i HEAD~n | Interactively edits the last n commits |
git rebase --onto <base> <upstream> <branch> | Replays <branch> commits (relative to <upstream>) onto <base> |
git pull --rebase | Fetches and rebases instead of fetching and merging |
git rebase --continue | Continues a rebase after resolving conflicts |
git rebase --abort | Cancels a rebase and restores the original state |