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
  • --amend replaces 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.
  • --fixup plus --autosquash is 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
CommandEffect
pickKeep the commit exactly as it is
rewordKeep the change, edit the message
editStop the rebase so you can amend or split the commit
squashCombine into the previous commit and merge the two messages
fixupCombine into the previous commit, discarding this message
dropRemove 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 -p is 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 rerere replays 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.

Reading history: log, diff, blame and bisect Forks, pull requests and review workflows

Last refreshed 2026-09-18.