Clean history: interactive rebase, squash and amend
Reorder, squash, split and reword commits before anyone else sees them, use fixup and autosquash, and know the safety net that makes rewriting recoverable.
Amend and fixup
# last commit, message only
git commit --amend -m "Validate the coupon before applying it"
# last commit, with the file you forgot
git add src/coupons.ts
git commit --amend --no-edit
# a fix for an older commit: create the commit, then mark it
git commit --fixup a1b2c3d
git rebase -i --autosquash main # the fixup! line is moved into place automatically
git log --oneline -1 # note that the commit id changed--amendreplaces the commit rather than editing it: the old commit remains reachable from the reflog, but every commit built on it now has a different id.- Amend only commits that have not been pushed, or coordinate first with everyone who has pulled them.
--fixupplus--autosquashis the fastest route from "I found a bug in my second-to-last commit" to a tidy branch.
Interactive rebase
git rebase -i HEAD~5
# or, better, against the branch you will merge into
git rebase -i main
# the editor opens a plan, oldest commit first
# pick a1b2c3d Add retry logic
# squash 4d5e6f7 Fix typo
# reword 8a9b0c1 Wip
# edit 2b3c4d5 Extract client
# drop 9f8e7d6 Debug logging| Command | Effect |
|---|---|
pick | Keep the commit exactly as it is |
reword | Keep the change, edit the message |
edit | Stop the rebase so you can amend or split the commit |
squash | Combine into the previous commit and merge the two messages |
fixup | Combine into the previous commit, discarding this message |
drop | Remove the commit and its changes |
⚠️
A rebase rewrites every commit from the first change onward, so all of their ids change. Never rewrite a branch other people are working from, and never rewrite anything already merged into a shared branch — use a revert or a merge there instead.
Splitting commits and the safety net
# split one commit into two logical commits
git rebase -i main
# mark the target commit with "edit", then when the rebase stops:
git reset HEAD^ # move its changes back to the working tree
git add -p src/retry.ts # stage only the first logical change
git commit -m "Add retry backoff"
git add -A
git commit -m "Add retry metrics"
git rebase --continue
# the safety net before a big rewrite
git branch backup/pre-rebase
git reflog # every position HEAD has held
git reset --hard HEAD@{5} # return to the state before the rewrite
git rebase --abort # give up mid-flight
git rebase --skip # drop the commit being applied
git rebase --edit-todo # repair the plan while stopped- Start from a clean working tree: a rebase refuses to run with unstaged changes in the way, so stash or commit them first.
- Create a backup branch before a large rewrite. The reflog also works, but a named branch is far easier to find a week later.
git add -pis what makes splitting practical — it stages hunks individually, so two unrelated changes can become two honest commits.- If a conflict resolution goes wrong repeatedly,
git rererereplays your earlier decision instead of asking again.
FAQ
When is rewriting history acceptable?
On any local branch, and on a shared branch only when everyone involved agrees and nobody has based work on the commits you are replacing. Open pull requests are the usual case;
main after a release is not.How do I fix a mistake in the middle of a branch?
Start an interactive rebase from before that commit, mark it
edit, apply the change with git commit --amend, then git rebase --continue. For a purely mechanical follow-up, git commit --fixup plus --autosquash does it without an editor.Related
Reading history: log, diff, blame and bisect Forks, pull requests and review workflows
Last refreshed 2026-09-18.