Rebasing is one of Git’s most powerful features for rewriting history. It allows you to transplant a series of commits onto a different starting point, creating a cleaner, more linear history.
Rebasing rewrites commit history. Never rebase commits that have been pushed to a shared repository and that others may have based work on.
What is Rebasing?
Rebasing takes a series of commits and “replays” them on top of a different base commit. Unlike merging, which creates a new commit to join two branches, rebasing rewrites the commit history to make it appear as if the work was done sequentially.
Before rebase: After rebase:
A---B---C topic A'--B'--C' topic
/ /
D---E---F---G master D---E---F---G master
Basic Rebasing
To rebase your current branch onto master:
git checkout topic
git rebase master
Resolve conflicts if they occur
If Git encounters conflicts during the rebase:
Fix the conflicts in the affected files
Stage the resolved files: git add <filename>
Continue the rebase: git rebase --continue
Skip the current commit: git rebase --skip
Abort the entire rebase: git rebase --abort
Interactive Rebase
Interactive rebasing gives you complete control over your commit history, allowing you to reorder, edit, squash, or drop commits.
This opens an editor with a list of commits:
pick deadbee Implement feature XXX
pick fa1afe1 Fix typo in feature XXX
pick c0ffeee Add tests for feature XXX
Available Commands
pick - Use the commit as-is
reword - Use the commit, but edit the commit message
edit - Use the commit, but stop for amending
squash - Combine this commit with the previous one, keeping both messages
fixup - Like squash, but discard this commit’s message
drop - Remove the commit entirely
exec - Run a shell command after this line
Example: Cleaning Up Commits
pick deadbee Implement feature XXX
fixup fa1afe1 Fix typo in feature XXX
pick c0ffeee Add tests for feature XXX
exec make test
This squashes the typo fix into the original commit and runs tests after applying the test commit.
Advanced: Rebase with —onto
The --onto option allows you to transplant a branch from one base to another.
Transplanting a Topic Branch
Suppose topic is based on next, but you want to move it to master:
o---o---o---o---o master
\
o---o---o---o---o next
\
o---o---o topic
To rebase topic onto master:
git rebase --onto master next topic
Result:
o---o---o---o---o master
| \
| o'--o'--o' topic
\
o---o---o---o---o next
Removing Commits from History
To remove commits F and G from a branch:
E---F---G---H---I---J topicA
git rebase --onto topicA~5 topicA~3 topicA
Result:
Handling Merge Commits
By default, rebase drops merge commits. To preserve the branch structure, use --rebase-merges:
git rebase --rebase-merges master
This recreates merge commits during the rebase, maintaining your branch topology.
Autosquash Workflow
Use special commit messages to automatically mark commits for squashing:
# Make a commit
git commit -m "Add feature"
# Later, create a fixup commit
git commit --fixup=<commit-hash>
# Rebase with autosquash
git rebase -i --autosquash main
Git will automatically arrange the fixup commits to be squashed into their targets.
Recovering from Upstream Rebase
If someone rebases commits you’ve based work on, you’ll need to rebase your work onto the new commits.
After upstream rebases, find where the old branch tip was:
# Using reflog
git reflog show subsystem
# The old tip is at subsystem@{1}
git rebase --onto subsystem subsystem@{1}
Rebase vs. Merge
| Aspect | Rebase | Merge |
|---|
| History | Linear, cleaner | Shows actual development history |
| Conflicts | Resolved per commit | Resolved once |
| Safety | Rewrites history | Preserves history |
| Use case | Local cleanup, feature branches | Integrating shared work |
Best Practices
-
Never rebase public commits - Once commits are pushed and shared, rebasing them forces everyone downstream to fix their history
-
Rebase before pushing - Clean up your local commits before sharing them
-
Use interactive rebase for cleanup - Squash “fix typo” and “oops” commits before pushing
-
Test after rebasing - Each rebased commit should still compile and pass tests. Use
git rebase -i --exec "make test"
-
Keep ORIG_HEAD in mind - Git saves your pre-rebase position in
ORIG_HEAD, so you can recover with git reset --hard ORIG_HEAD
Common Scenarios
Updating a Feature Branch
# Update main and rebase your feature
git fetch origin
git rebase origin/main
Splitting a Commit
- Start interactive rebase:
git rebase -i <commit>^
- Mark the commit for editing: change
pick to edit
- Reset to before the commit:
git reset HEAD^
- Stage and commit changes separately
- Continue:
git rebase --continue
Cleaning Up Before Merging
# Squash all commits since branching from main
git rebase -i main
# In the editor, keep the first commit as 'pick'
# Change others to 'squash' or 'fixup'
Configuration Options
Useful rebase configurations in your .gitconfig:
[rebase]
# Automatically stash before rebasing
autoStash = true
# Automatically apply fixup and squash commits
autoSquash = true
# Show a diffstat of changes after rebase
stat = true
Troubleshooting
Rebase Stopped with Conflicts
Check which files have conflicts:
See the current patch being applied:
git rebase --show-current-patch
Accidentally Started a Rebase
Abort and return to the original state:
Rebase Completed but Something’s Wrong
Reset to the state before rebasing:
git reset --hard ORIG_HEAD