Git Fixup: The Cleanest Way to Update Commits Before a PR
How to use git commit --fixup to fold changes into earlier commits without losing your history or your mind.
You asked an AI to implement a feature. It broke the work into five well-scoped commits. You review the code before opening the PR, and you spot things you want to change. A missing edge case in commit 2. A better variable name in commit 4.
Now what?
Most people either:
- Squash everything into one commit (lose history)
- Add a "fix stuff" commit (noisy history)
- Manually edit commits in interactive rebase (error-prone, easy to drop changes)
None of these are great.
git commit --fixup solves this cleanly. You make your fix, tell git which commit it belongs to, and then rebase. Git moves each fix into its original commit automatically. Your history stays clean like nothing happened.
Think of it like attaching a sticky note to an old commit that says: "change this part".
Later, autosquash folds that note into the original.
The Workflow in 30 Seconds#
- You have a branch with multiple commits
- You spot something to fix in an earlier commit
- You make the fix and run
git commit --fixup <commit-hash> - When you're done with all fixes, run
git rebase -i --autosquash - Save and close. Git handles the squash automatically
Let's walk through a real example.
Step by Step#
Say you have three commits on your branch:
$ git log --oneline
c3c3c3c Add input validation
b2b2b2b Add user registration endpoint
a1a1a1a Add user model and migrationYou're reviewing the AI's work before opening the PR. You notice two things:
- The migration in
a1a1a1ais missing an index onemail - The validation in
c3c3c3cshould reject emails missing a domain
Fix 1: Add the missing index#
Make your change (add the index to the migration file), then stage and fixup:
git add db/migrate/20260325_create_users.rb
git commit --fixup a1a1a1aThis creates a commit with the message fixup! Add user model and migration. The fixup! prefix is what git uses later to know where this belongs.
Fix 2: Tighten the validation#
Edit the validation, stage it, and fixup against the right commit:
git add app/models/user.rb
git commit --fixup c3c3c3cNow your log looks like this:
$ git log --oneline
e5e5e5e fixup! Add input validation
d4d4d4d fixup! Add user model and migration
c3c3c3c Add input validation
b2b2b2b Add user registration endpoint
a1a1a1a Add user model and migrationSquash them in#
git rebase -i --autosquashGit opens your editor with the commits reordered. Each fixup commit is placed right below the commit it targets, with the fixup action already set:
pick a1a1a1a Add user model and migration
fixup d4d4d4d fixup! Add user model and migration
pick b2b2b2b Add user registration endpoint
pick c3c3c3c Add input validation
fixup e5e5e5e fixup! Add input validationSave and close the editor. Git does the rest:
Successfully rebased and updated refs/heads/add-user-registration.Your log is now:
$ git log --oneline
h8h8h8h Add input validation
g7g7g7g Add user registration endpoint
f6f6f6f Add user model and migrationThree clean commits with new hashes (rebase rewrites history). The fixes are folded in. No trace of the fixups.
Tips#
You don't need the hash#
Looking up commit hashes is annoying. Use the commit message instead:
git commit --fixup :/validationThe :/ prefix searches for the most recent commit whose message matches the pattern. I almost never copy hashes anymore.
Set autosquash as the default#
Add this once and forget about it:
git config --global rebase.autoSquash trueNow git rebase -i handles fixup commits automatically. No extra flags.
If you hit a conflict, don't panic#
Sometimes a fixup touches a line that was changed in a later commit. Git will pause the rebase and ask you to resolve it. Fix the conflict, git add the file, and git rebase --continue. If it gets messy, git rebase --abort puts everything back exactly where it was.
Don't rebase after humans start reviewing#
This is important. This workflow is for before you open the PR. Once teammates are reviewing, rebasing rewrites history. They lose track of what changed between reviews, and now they're re-reading code they already approved. After the PR is open, use regular commits. Squash when merging if you want a clean main branch.
Why This Matters Now#
AI tools are good at breaking work into logical commits. But they're not perfect. You'll always want to review and tweak before opening a PR. --fixup lets you make those tweaks without flattening the commit structure. Reviewers can still follow the implementation step by step. Your fixes are invisible.
Most developers I know either skip the cleanup (noisy PRs) or squash everything into one commit (unreadable PRs). --fixup is the middle path. Clean history, minimal effort, no risk of messing things up.
