Using git commit fixup to simplifying commit squashing

Published: February 3, 2026
Updated: February 4, 2026
1 min read

🎯

Learn how to use git commit --fixup with interactive rebase to automatically squash commits and clean up your commit history with real-world examples.

Introduction

We've all been there. You're working on a feature branch, making small incremental commits with messages like "wip", "fix typo", "update imports". Before you know it, you have 15 commits for what should logically be one clean PR. Squashing these commits manually is tedious and error-prone.

Enter git commit --fixup—a powerful tool that lets you amend commits in place and use interactive rebase to automatically squash them. In this guide, you'll learn how to use --fixup with rebase to efficiently clean up your commit history and produce professional, easy-to-review PRs.

What is git commit --fixup?

git commit --fixup is a flag that allows you to amend the most recent commit without creating a new commit. Unlike a traditional amend, --fixup doesn't create a replacement commit but instead modifies the current commit directly in the git history.

Key difference:

  • git commit --amend: Creates a new commit, invalidates the old one
  • git commit --fixup: Modifies the commit in-place, preserves history

When combined with git rebase -i, --fixup becomes incredibly powerful because you can:

  1. Fix typos in commit messages
  2. Add forgotten files to commits
  3. Squash multiple related commits into one
  4. Reorder commits logically
  5. All while maintaining a clean, linear history

The Traditional Problem

Consider this common scenario:

# You're working on a feature and make these commits:
git commit -m "wip: add authentication"
git commit -m "fix: typo in auth"
git commit -m "add tests for auth"
git commit -m "update auth logic"
git commit -m "fix off-by-one error"
git commit -m "wip: add authorization"
git commit -m "refactor auth module"

Seven commits for what should be one cohesive "Add authentication" feature. This is where --fixup with interactive rebase shines.

Another common example: You're debugging an issue and make rapid commits:

git commit -m "wip: trying solution A"
git commit -m "didn't work, revert"
git commit -m "wip: trying solution B"
git commit -m "close but need tweak"
git commit -m "fix: final adjustment"

Five commits that should be one "Fix authentication bug" commit. Without --fixup, you'd need to manually interactively rebase each time.

Using git commit --fixup

Basic Fixup Workflow

When you need to clean up commits:

  1. Identify the commit range you want to rebase
  2. Use interactive rebase to start the process
  3. Mark commits to squash with fixup
  4. Let rebase auto-squash them

Creating Fixup Commits

First, create fixup commits as you work:

# Original commit
git commit -m "feat: add user authentication"

# Oops, forgot to add the password reset
# Add the file and create a fixup commit
git add password-reset.ts
git commit --fixup=HEAD

This creates a new commit marked as a fixup for HEAD. When you run git rebase --autosquash, Git will automatically combine them.

Step-by-Step Example

Let's say you have this commit history:

$ git log --oneline
a1b2c3d (HEAD) refactor auth module
e4f5g6h add tests for auth
h7i8j9k fix typo in auth
1a2b3c4 add authorization

You want to squash the last three commits into one. Here's how:

# Start interactive rebase
git rebase -i HEAD~3

# The editor opens showing:
pick a1b2c3d refactor auth module
fixup e4f5g6h add tests for auth
fixup h7i8j9k fix typo in auth

Change fixup commits to squash (or leave them as fixup—they'll be auto-squashed):

pick a1b2c3d refactor auth module
squash e4f5g6h add tests for auth
squash h7i8j9k fix typo in auth

When you save and close, git automatically squashes the marked commits into the first commit.

The Auto-Squash Behavior

Here's the key insight: any commit marked with fixup (or squash) after the first pick will be automatically combined with the pick commit.

For example, with this configuration:

pick 1a2b3c4 add authorization
fixup h7i8j9k fix typo in auth
fixup e4f5g6h add tests for auth
pick a1b2c3d refactor auth module

Git produces:

  • Commit 1: Contains changes from 1a2b3c4, h7i8j9k, and e4f5g6h (squashed)
  • Commit 2: Contains changes from a1b2c3d

Example with fixup workflow:

# While working on a feature
git commit -m "feat: implement login"

# Later, you find a bug
vim login.ts
git add login.ts
git commit --fixup=HEAD

# Even later, you remember to add tests
git add login.test.ts
git commit --fixup=HEAD

# Now clean up with autosquash
git rebase -i --autosquash HEAD~3

# Git automatically arranges:
pick abc1234 feat: implement login
fixup def5678 Fix login bug
fixup ghi9012 Add login tests

# All combined into one clean commit!

Real-World Example: Adding Authentication

Let's walk through a practical scenario. You're adding user authentication to your app and have these commits:

$ git log --oneline
f47ac8 (HEAD) refactor: clean up auth flow
ba3d92 wip: add logout
8c7e1a wip: add token refresh
4a5b6c fix: handle edge case
9c8d7e wip: add login form
2a3b4c wip: setup auth context

You want the final history to look like:

  • "setup auth context" with everything
  • "refactor: clean up auth flow"

Step 1: Start Rebase

git rebase -i HEAD~5

Step 2: Mark Commits

Change the configuration to:

squash 2a3b4c wip: setup auth context
squash 9c8d7e wip: add login form
fixup 4a5b6c fix: handle edge case
fixup 8c7e1a wip: add token refresh
squash ba3d92 wip: add logout
pick f47ac8 refactor: clean up auth flow

Step 3: Save and Continue

Git will:

  1. Squash 2a3b4c with 9c8d7e, 4a5b6c, 8c7e1a, and ba3d92
  2. Keep f47ac8 as a separate commit
  3. Open your text editor to refine the combined commit message

Step 4: Refine the Message

The editor opens with the combined commit message:

# This is a combination of 5 commits.
# This is the 1st commit message:
wip: setup auth context

# This is the 2nd commit message:
wip: add login form
...

Edit it to:

feat: add authentication with login form, token refresh, and logout functionality

Real-World Example: Fixing a Bug Rapidly

# Initial bug fix
git commit -m "fix: handle null pointer in user service"

# Test and find another case
vim user.service.ts
git add user.service.ts
git commit --fixup=HEAD

# Add test for the fix
git add user.test.ts
git commit --fixup=HEAD

# Clean up
git log --oneline
# abc1234 fix: handle null pointer in user service
# def5678 fixup! fix: handle null pointer...
# ghi9012 fixup! fix: handle null pointer...

git rebase -i --autosquash HEAD~3
# Git automatically combines them into one commit!

Best Practices and Common Pitfalls

DO: Plan Your Rebase Strategy

Before starting, think about your desired outcome:

  • Single feature? Squash all related commits into one
  • Multiple features? Group commits logically, then squash within each group
  • Bug fixes? Keep bug fixes separate from feature commits
# Plan: 3 logical commits
# 1. Setup (commits 1-5)
# 2. Bug fix (commit 6)
# 3. Refactor (commit 7)

git rebase -i HEAD~7

Example: Good planning prevents mess:

# Bad: Everything in one commit
git rebase -i HEAD~10  # Squash all into one monolithic commit

# Good: Logical groupings
git rebase -i HEAD~10  # Create 3-4 focused commits
# - Feature implementation (commits 1-6)
# - Bug fix (commit 7-8)
# - Refactoring (commit 9-10)

DO: Use Descriptive Commit Messages

Since you're combining commits, write comprehensive messages:

# Bad
git commit -m "fix"

# Good
git commit -m "fix: handle null pointer in auth service when user is not found"

This makes the --fixup workflow more efficient because you won't need to edit messages later.

Example: Good messages save time:

# While working
git commit -m "feat(auth): add login component with form validation"

# Oops, missed something
git commit --fixup=HEAD -m "forgot to include error handling"

# When squashing, the message is already clear!

DO: Use git rebase --autosquash

Git has a powerful --autosquash flag that automatically organizes fixup commits:

# Create fixup commits as you work
git commit -m "feat: add authentication"
git add forgotten-file.ts
git commit --fixup=HEAD  # Marks it as a fixup

# Later, clean up everything automatically
git rebase -i --autosquash HEAD~5

Git will automatically arrange fixup commits right after their target commits, saving you from manually reordering them.

Example: Autosquash in action:

# Your history looks messy
git log --oneline
abc1234 feat: add authentication
def5678 fixup! feat: add authentication
ghi9012 wip: update docs
jkl3456 fixup! feat: add authentication

# Run autosquash
git rebase -i --autosquash HEAD~4

# Git automatically produces:
pick abc1234 feat: add authentication
fixup def5678 fixup! feat: add authentication
fixup jkl3456 fixup! feat: add authentication
pick ghi9012 wip: update docs

This is especially useful when you have multiple fixup commits scattered throughout your history.

DO: Test Between Rebases

After squashing, verify your code still works:

# After rebase, run tests
npm test

# If tests fail, you can still use git reflog to recover
git reflog
git reset --hard HEAD@{1}

Example: Catching bugs early:

# Rebase and squash
git rebase -i HEAD~5

# Immediately test
npm test
# ✅ All passing - safe to continue
# ❌ Failing - use reflog to recover

# If tests fail
git reflog
# abc1234 HEAD@{0}: rebase -i: Fast-forward
# def5678 HEAD@{1}: commit: Fix typo

git reset --hard HEAD@{1}  # Back to safety

DON'T: Rebase Public History

Never rebase commits that have been pushed to a shared repository. This creates duplicate commits and confuses your team.

# Check if commits are pushed
git log origin/main..HEAD

# If output is empty, safe to rebase
# If commits show up, don't rebase—use merge instead

Example: Checking before rebasing:

# Before rebasing, check if pushed
git log origin/main..HEAD --oneline
# abc1234 New feature
# def5678 Fix typo

# These commits haven't been pushed - safe to rebase

# If you see:
git log origin/main..HEAD --oneline
# (empty)

# Already pushed - DO NOT rebase!
# Use merge instead or coordinate with team

Common Mistakes and How to Avoid Them

Mistake 1: Forgetting to Force Push

After rebasing, you need to force push:

git push --force-with-lease

Use --force-with-lease instead of --force to prevent overwriting others' work.

Example: Safe force push:

# Rebase complete
git rebase -i HEAD~5

# Push with lease (safer)
git push --force-with-lease origin feature-branch

# If someone else pushed, this will fail and warn you
# Instead of overwriting their work

Mistake 2: Conflicts During Rebase

When squashing commits with merge conflicts:

git rebase -i HEAD~5

# Conflict arises in commit 3
Auto-merging failed for "src/auth.ts"
Fix conflicts and mark as resolved:

git add src/auth.ts
git rebase --continue

Example: Handling conflicts gracefully:

# Conflict during rebase
git rebase -i HEAD~5
# CONFLICT: content merge conflict in auth.ts

# Edit file, resolve conflict
vim auth.ts
# <<<<<<< HEAD
# Your changes
# =======
# Their changes
# >>>>>>> fixup-combined

# Save and stage
git add auth.ts
git rebase --continue

# If too complex, abort and try different strategy
git rebase --abort

Mistake 3: Losing Work in Fixup Commits

Remember: fixup modifies commits in-place. If you need the original:

# Before rebasing, create a backup branch
git branch backup-branch

# If something goes wrong, recover
git reset --hard backup-branch

Example: Safety first:

# Before major rebase
git branch backup-before-cleanup

# Attempt the rebase
git rebase -i HEAD~10

# Oops, something went wrong
git reset --hard backup-before-cleanup

# Safe to try again

Mistake 4: Squashing Too Much

Resist the urge to squash everything into one giant commit. Good granularity:

# Good: Logical grouping
- "setup authentication" (commits 1-5)
- "fix authentication bug" (commit 6)
- "refactor authentication module" (commit 7)

# Bad: Monolithic commit
- "everything related to auth" (all commits combined)

Example: Finding the right balance:

# Too granular (10 commits for one feature)
# Hard to review, noisy history

# Too coarse (1 giant commit)
# Impossible to code review, loses context

# Just right (3-4 commits)
# feat: add authentication system
# fix: handle edge cases in auth
# refactor: improve auth code structure

# Each commit is focused and reviewable

Troubleshooting Common Issues

Issue: Git Doesn't Recognize --fixup

Ensure you're using Git 2.24 or later:

git --version
# Should be 2.24.0 or higher

Issue: Rebase Shows Confusing Merge Conflicts

Sometimes rebase produces unexpected conflicts:

# Strategy: Reset and start fresh
git rebase --abort

# Check what went wrong
git status

# Try again with clearer planning
git rebase -i HEAD~5

Issue: Squashed Commit Has Wrong Message

You can edit the message during the squash, but if you need to change it later:

# Amend the squashed commit
git commit --amend

# If it's not the most recent commit
git rebase -i HEAD~2  # interactively change the second commit

Comparison: Manual vs. Fixup Workflow

| Aspect | Fixup Approach | Manual Interactive Rebase | |