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 --autosquashSave 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:
You're reviewing the AI's work before opening the PR. You notice two things:
The migration in
a1a1a1ais missing an index onemailThe 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:
This 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:
Now your log looks like this:
Squash them in
Git opens your editor with the commits reordered. Each fixup commit is placed right below the commit it targets, with the fixup action already set:
Save and close the editor. Git does the rest:
Your log is now:
Three 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:
The :/ 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:
Now 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.