Git Commit Squashing: How to Combine Multiple Commits into One
Master the art of combining multiple commits into a single, meaningful commit using interactive rebase. Learn how to clean up your Git history and prepare professional pull requests.
Git Commit Squashing: How to Combine Multiple Commits into One
Ever found yourself with a messy commit history full of "fix typo," "update comment," and "oops, forgot this"? You're not alone. Most developers make multiple small commits while working on a feature, but before sharing your work with the team, it's often better to combine them into a single, meaningful commit. This process, called "squashing," creates a cleaner, more professional Git history that's easier to understand and review.
Why Squash Commits?
The Problem: Messy Commit History
Imagine you're working on a login feature and your commit history looks like this:
git log --oneline
abc123 Fix typo in error message
def456 Add input validation
ghi789 Update error message
jkl012 Initial work on login form
mno345 Fix linting errors
pqr678 Add password strength check
Issues with this approach:
- Hard to review - Reviewers see 6 separate commits for one feature
- Confusing history - Future developers can't easily understand what was done
- Noise in logs - Important changes get lost among minor fixes
- Poor atomicity - Changes are scattered across multiple commits
The Solution: Squashed Commits
After squashing, your history becomes:
git log --oneline
xyz789 Add login form with validation and error handling
Benefits of squashing:
- Clean history - One commit per feature
- Easier reviews - Reviewers see the complete feature at once
- Better atomicity - Each commit represents a complete, working feature
- Professional appearance - Clean, organized commit history
How to Squash Commits: Interactive Rebase
Step 1: Start Interactive Rebase
# Squash the last 4 commits
git rebase -i HEAD~4
# Or specify a commit hash
git rebase -i abc123^
Step 2: Choose Your Strategy
The interactive rebase opens an editor with your commits:
pick jkl012 Initial work on login form
pick ghi789 Update error message
pick def456 Add input validation
pick abc123 Fix typo in error message
Available commands:
pick
- Use the commit as-issquash
(ors
) - Combine with the previous commitfixup
(orf
) - Combine and discard the commit messagedrop
(ord
) - Remove the commit entirelyreword
(orr
) - Change the commit messageedit
(ore
) - Stop to amend the commit
Step 3: Configure the Squash
Change the commands to squash commits:
pick jkl012 Initial work on login form
squash ghi789 Update error message
squash def456 Add input validation
squash abc123 Fix typo in error message
Step 4: Write the New Commit Message
Git opens another editor for you to write the combined commit message:
# This is a combination of 4 commits.
# This is the 1st commit message:
Initial work on login form
# This is the commit message #2:
Update error message
# This is the commit message #3:
Add input validation
# This is the commit message #4:
Fix typo in error message
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
Write a clear, descriptive message:
Add login form with validation and error handling
- Implement user authentication form
- Add input validation for email and password
- Include comprehensive error messaging
- Ensure proper form submission handling
Step 5: Complete the Rebase
Save and exit the editor. Git will combine the commits and create a new commit with your message.
Advanced Squashing Techniques
Technique 1: Partial Squashing
Sometimes you want to keep some commits separate:
pick jkl012 Initial work on login form
squash ghi789 Update error message
squash def456 Add input validation
pick abc123 Fix typo in error message
This keeps the typo fix as a separate commit while squashing the main feature work.
Technique 2: Using Fixup
For commits with poor messages, use fixup
to combine them without keeping their messages:
pick jkl012 Initial work on login form
fixup ghi789 Update error message
fixup def456 Add input validation
fixup abc123 Fix typo in error message
Technique 3: Dropping Commits
Remove commits you don't want to keep:
pick jkl012 Initial work on login form
squash ghi789 Update error message
squash def456 Add input validation
drop abc123 Fix typo in error message
Real-World Examples
Example 1: Feature Development
Before squashing:
git log --oneline
a1b2c3 Fix linting errors
d4e5f6 Add unit tests
g7h8i9 Refactor validation logic
j0k1l2 Add form validation
m3n4o5 Create login form
After squashing:
git log --oneline
p5q6r7 Add login form with validation and testing
Example 2: Bug Fix
Before squashing:
git log --oneline
x9y8z7 Fix typo in error message
a1b2c3 Update error handling
d4e5f6 Fix null pointer exception
After squashing:
git log --oneline
g7h8i9 Fix null pointer exception in user validation
Example 3: Code Review Feedback
Before squashing:
git log --oneline
m3n4o5 Address review feedback
p5q6r7 Fix formatting issues
s7t8u9 Update documentation
v0w1x2 Implement requested changes
After squashing:
git log --oneline
y3z4a5 Address code review feedback and improve documentation
Best Practices for Commit Squashing
1. Squash Before Pushing
# Squash local commits before pushing
git rebase -i HEAD~3
git push origin feature-branch
2. Use Descriptive Commit Messages
# Good: Clear and descriptive
Add user authentication with JWT tokens
- Implement login and registration endpoints
- Add JWT token generation and validation
- Include password hashing with bcrypt
- Add middleware for protected routes
# Bad: Vague and unhelpful
Fix stuff
3. Keep Related Changes Together
# Good: Related changes in one commit
Add payment processing integration
# Bad: Unrelated changes mixed together
Add payment processing and fix typo in README
4. Don't Squash Everything
# Good: Keep logical separations
Add user authentication
Add payment processing
Fix security vulnerability
# Bad: Everything in one massive commit
Add entire application
When to Use Force Push
Safe Force Push
# Only if commits were already pushed
git push origin feature-branch --force-with-lease
Why Use --force-with-lease
?
- Safer than
--force
- Fails if someone else has pushed changes - Prevents data loss - Won't overwrite others' work
- Better for collaboration - Safer in team environments
Common Pitfalls and Solutions
Pitfall 1: Squashing Already Merged Commits
Problem: Trying to squash commits that are already in the main branch Solution: Only squash commits on your feature branch before merging
Pitfall 2: Losing Important Context
Problem: Squashing commits that contain important debugging information Solution: Keep commits that provide valuable context separate
Pitfall 3: Over-squashing
Problem: Combining unrelated changes into one commit Solution: Maintain logical separation between different features or fixes
Pitfall 4: Forgetting to Test
Problem: Squashing without testing the combined result Solution: Always test your code after squashing to ensure it still works
Alternative: GitHub's Squash and Merge
Many teams use GitHub's "Squash and Merge" feature instead of manual squashing:
- Create a pull request with your feature branch
- Use "Squash and Merge" when merging
- GitHub automatically squashes all commits into one
- You write a single commit message for the combined changes
Benefits:
- No force push needed - GitHub handles the squashing
- Clean main branch - Only meaningful commits in the main history
- Easy to use - No need to learn interactive rebase
Conclusion
Commit squashing is an essential skill for maintaining clean Git history and creating professional pull requests. By combining multiple related commits into single, meaningful commits, you make your code easier to review, understand, and maintain.
Remember:
- Squash before pushing to avoid force push issues
- Use descriptive commit messages that explain what was done
- Keep related changes together but maintain logical separation
- Test after squashing to ensure everything still works
- Consider GitHub's squash and merge for simpler workflows