Rewriting history

Tidy your own history before anyone reads it — and stop there.

13of 16
≈ 13Min read
6Parts

Why bother

A branch that took two days usually contains "wip", "fix typo", "actually fix it" and a commit that only deletes a stray console.log. None of that helps the person who runs git log next year, and none of it helps a bisect find the commit that broke the build.

Rewriting turns that into two or three commits that each do one thing and each build. It is polish, applied to your own unpushed work.

Squashing with reset --soft

--soft moves the branch pointer and touches nothing else, so all three commits' changes are sitting in the index, ready to be committed as one. It is the least surprising way to squash.

the version worth memorising — no interactive editor needed
git reset --soft HEAD~3        # undo three commits, keep everything staged
git commit -m "Fix the discounted total"

git log --oneline              # one commit where three were

Interactive rebase, for everything else

git rebase -i HEAD~4 opens a list you edit
pick   a1b2c3  Add the discount field
squash d4e5f6  wip
reword 7g8h9i  fix typo
drop   j1k2l3  remove debug logging
# reorder the lines to reorder the commits

# pick   keep as is
# reword change the message
# edit   stop here so you can change the commit
# squash fold into the one above, combining messages
# fixup  fold in, discard this message
# drop   delete the commit entirely
The terminal in this lesson does not do -i

Interactive rebase needs an editor session, which is not something a lab can honestly simulate — so this small git leaves it out rather than faking it. The lab below squashes with reset --soft, which does the same job and is easier to reason about anyway.

cherry-pick: one commit, somewhere else

It applies the change, not the snapshot, so it makes a new commit with a new id. Use it for a hotfix that has to land on both main and a release branch — and not as a substitute for merging, or the same change ends up in history twice with different ids.

take the fix, leave the branch
git switch main
git cherry-pick 832dbe3         # apply that commit's change here, as a new commit
git cherry-pick a1b2c3..d4e5f6  # a range

Force-pushing, carefully

after rewriting a branch that is already pushed
git push --force-with-lease origin fix-total
Only ever your own branch

--force-with-lease refuses if the remote moved since your last fetch, so you cannot silently delete work that arrived while you were rewriting. Plain --force has no such check. Neither belongs anywhere near main — that is what protected branches are for.

The line that must not be crossed

Rewrite freely: commits only you have. Rewrite carefully, with a warning: a pull-request branch other people are reviewing. Never rewrite: main, a release branch, or anything a colleague has pulled. The cost is not yours to pay — it lands on everyone whose history no longer matches.

Practice