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.

Know More Team
January 27, 2025
4 min read
GitInteractive RebaseCommit SquashingClean HistoryPull 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-is
  • squash (or s) - Combine with the previous commit
  • fixup (or f) - Combine and discard the commit message
  • drop (or d) - Remove the commit entirely
  • reword (or r) - Change the commit message
  • edit (or e) - 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
# 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:

  1. Create a pull request with your feature branch
  2. Use "Squash and Merge" when merging
  3. GitHub automatically squashes all commits into one
  4. 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

Table of Contents

Navigate the scroll
Reading Progress