Clean Git Commit History with Interactive Rebase
Squash, reorder, edit, and split commits with git rebase interactive. Covers pick, squash, fixup, reword, drop, and conflict resolution.
Note: This guide follows English-language naming conventions and terminology standards common in international development teams. Examples use English identifiers and comments to maximize compatibility across codebases and tooling.
Overview
Interactive rebase lets you rewrite commit history before merging. You can squash related commits, reorder them, edit commit messages, split commits, and drop unwanted ones. This keeps your branch history clean and readable. This recipe covers all interactive rebase actions with practical examples.
When to Use
- You want to clean up commits before merging a feature branch
- You have WIP commits that should be squashed into one
- You need to reword or fix typos in commit messages
- You want to reorder or split commits for clarity
- You need to remove a commit that should not be in the branch
Solution
Start an interactive rebase
# Rebase last 5 commits
git rebase -i HEAD~5
# Rebase onto a specific commit
git rebase -i <commit-hash>
# Rebase onto a branch (common before merge)
git rebase -i main
This opens your editor with a list of commits and available actions:
pick 1a2b3c4 Add user model
pick 5d6e7f8 WIP: fix validation
pick 9a0b1c2 WIP: tests
pick 3d4e5f6 Add user API endpoints
pick 7a8b9c0 Fix typo in endpoint
# Rebase commands:
# pick = use commit as-is
# reword = use commit, edit message
# edit = use commit, pause to amend
# squash = combine with previous commit
# fixup = like squash, discard message
# drop = remove commit
Squash commits
pick 1a2b3c4 Add user model
squash 5d6e7f8 WIP: fix validation
squash 9a0b1c2 WIP: tests
pick 3d4e5f6 Add user API endpoints
pick 7a8b9c0 Fix typo in endpoint
After saving, Git opens an editor to combine commit messages:
# This is a combination of 3 commits.
# This is the 1st commit message:
Add user model
# The commit messages of commits being squashed:
WIP: fix validation
WIP: tests
Edit to a single clean message:
Add user model with validation and tests
Fixup — squash and discard message
pick 1a2b3c4 Add user model
fixup 5d6e7f8 WIP: fix validation
fixup 9a0b1c2 WIP: tests
pick 3d4e5f6 Add user API endpoints
Fixup combines the commit into the previous one and discards its message. No second editor prompt.
Reword a commit message
pick 1a2b3c4 Add user model
reword 3d4e5f6 Add user API endpoints
pick 7a8b9c0 Fix typo in endpoint
Git pauses and opens an editor for the reworded commit. Type the new message and save.
Edit a commit (pause to modify)
pick 1a2b3c4 Add user model
edit 3d4e5f6 Add user API endpoints
pick 7a8b9c0 Fix typo in endpoint
Git pauses at the edit commit. You can modify files, stage changes, and amend:
# Make changes
vim src/api/users.py
git add src/api/users.py
# Amend the commit
git commit --amend
# Continue rebase
git rebase --continue
Drop a commit
pick 1a2b3c4 Add user model
drop 5d6e7f8 Add debug logging
pick 3d4e5f6 Add user API endpoints
Or simply delete the line to drop the commit.
Reorder commits
pick 3d4e5f6 Add user API endpoints
pick 1a2b3c4 Add user model
pick 7a8b9c0 Fix typo in endpoint
Git replays commits in the new order. Conflicts may occur if commits depend on each other.
Split a commit
pick 1a2b3c4 Add user model and API endpoints
edit 3d4e5f6 Add tests
pick 7a8b9c0 Fix typo
When Git pauses at the edit commit:
# Unstage all changes from the commit
git reset HEAD^
# Stage and commit the first part
git add src/models/user.py
git commit -m "Add user model"
# Stage and commit the second part
git add src/api/users.py
git commit -m "Add user API endpoints"
# Continue rebase
git rebase --continue
Resolve conflicts during rebase
git rebase -i main
# CONFLICT (content): Merge conflict in src/models/user.py
Resolve the conflict:
# 1. Edit the file to resolve conflicts
vim src/models/user.py
# 2. Stage the resolved file
git add src/models/user.py
# 3. Continue the rebase
git rebase --continue
If you want to abort:
git rebase --abort
If you want to skip the conflicting commit:
git rebase --skip
Autosquash with fixup commits
# Create a fixup commit targeting a specific commit
git commit --fixup 1a2b3c4
# Rebase with autosquash — automatically reorders fixups
git rebase -i --autosquash HEAD~5
Git automatically places fixup commits after their target and marks them as fixup:
pick 1a2b3c4 Add user model
fixup 9a0b1c2 fixup! Add user model
pick 3d4e5f6 Add user API endpoints
Force push after rebase
# Safe force push — checks that no one else pushed
git push --force-with-lease origin feature-branch
# Never use plain force push on shared branches
# git push --force # DANGEROUS
Explanation
Interactive rebase replays commits one by one, applying your chosen action to each:
- pick: Keep the commit as-is. Default action.
- reword: Keep the commit, but open an editor to change the message.
- edit: Pause at the commit. Modify files, stage, and
git commit --amend. Continue withgit rebase --continue. - squash: Combine the commit into the previous one. Opens an editor to merge messages.
- fixup: Combine into the previous commit and discard the commit message. No editor prompt.
- drop: Remove the commit entirely. Same as deleting the line.
- exec: Run a shell command at that point in the rebase.
- break: Stop the rebase at that point. Resume with
git rebase --continue.
Key concepts:
- Rebase rewrites history. Old commits are replaced with new ones. This changes commit hashes.
- Never rebase commits that have been pushed to shared branches. Other developers’ history will break.
--force-with-leaseis safer than--force. It checks that no one else pushed before overwriting.--autosquashautomatically reorders fixup and squash commits next to their targets.- Conflicts occur when commits depend on each other. Resolve, stage, and continue.
Variants
| Action | Effect | Use When |
|---|---|---|
| squash | Combine + edit message | Related commits, want one message |
| fixup | Combine + discard message | WIP commits, message not needed |
| reword | Edit message only | Fix typo, improve clarity |
| edit | Pause and modify | Split commit, add forgotten file |
| drop | Remove commit | Revert unwanted change |
| exec | Run shell command | Run tests at specific commit |
Guidelines
- Rebase feature branches before merging to keep history clean.
- Use
squashfor related commits where the individual messages add value. Usefixupfor WIP commits where the message is noise. - Use
--force-with-leaseinstead of--forceto avoid overwriting others’ work. - Use
--autosquashwithgit commit --fixupfor a streamlined workflow. - Keep commits small and focused. Smaller commits are easier to reorder and split.
- Write clear commit messages. The squashed message should describe the full change.
- Test after rebase. Replaying commits can introduce subtle issues.
- Never rebase commits on shared branches (main, develop). Only rebase your own feature branch.
- Use
git reflogto recover if a rebase goes wrong. Reflog tracks all head movements.
Common Mistakes
- Rebasing commits that are already pushed to shared branches. This breaks other developers’ history.
- Using
git push --forceinstead of--force-with-lease. The latter is safer and prevents data loss. - Squashing too many commits into one. Large squashed commits are hard to review and revert.
- Not testing after rebase. Replaying commits can introduce conflicts or subtle bugs.
- Dropping a commit that other commits depend on. This causes conflicts in subsequent commits.
- Forgetting
git rebase --continueafter resolving a conflict. The rebase stays paused. - Panicking during a rebase gone wrong. Use
git rebase --abortto cancel and return to the original state. - Not using
git reflogfor recovery. Reflog has all head movements, even after a bad rebase.
Frequently Asked Questions
Is interactive rebase safe?
Yes, on your own feature branch that no one else has pulled. Never rebase commits on shared branches like main or develop. If a rebase goes wrong, git rebase --abort cancels it and git reflog recovers previous states.
What is the difference between squash and fixup?
Squash combines the commit into the previous one and opens an editor to merge commit messages. Fixup does the same but discards the commit message entirely. Use squash when the message has useful information. Use fixup for WIP or typo-fix commits.
How do I recover from a bad rebase?
Use git reflog to find the commit hash before the rebase, then git reset --hard <hash> to restore. Reflog tracks every head movement, so nothing is truly lost.
Should I rebase before merging a PR?
Yes. Rebasing your feature branch onto the latest main before merging keeps the history linear and clean. Squash WIP commits, reword unclear messages, and drop unnecessary commits before the final merge.