"Undo" in git is two different operations, and choosing the wrong one is how history gets rewritten on a shared branch. git reset moves a branch label and optionally rewrites the working tree; git revert adds a new commit that undoes an old one. One is local archaeology, the other is public honesty.

The three faces of reset #

Reset, in increasing violence
$ git reset --soft HEAD~1     # move branch back; keep changes staged
$ git reset HEAD~1            # (mixed, default) keep changes, unstage them
$ git reset --hard HEAD~1     # move branch AND discard the changes

All three move the branch pointer back one commit. What differs is what happens to your work: soft keeps it staged, mixed keeps it in the working tree, hard deletes it from both. The working-tree version is why --hard deserves respect — uncommitted changes vanish, and only reflog can find them.

GoalCommand
Redo the last commit's messagereset --soft HEAD~1, re-commit
Unstage everythinggit reset (mixed, HEAD implied)
Abandon local commits entirelygit reset --hard origin/main
Unstage one filegit restore --staged file

The modern git restore covers the file-level cases cleanly; reset is the branch-level tool.

revert: undo in public #

Create an anti-commit
$ git revert f3e2d1c
[main 8d9e0f1] Revert "fix: correct cache headers on media"

revert computes the inverse of the commit's diff and commits it. History grows — nothing is rewritten — so it is safe on pushed, shared branches, and the original commit stays visible for the record. It even handles a range:

Undo several commits in one anti-commit
$ git revert --no-commit f3e2d1c..b2c3d4e && git commit -m "revert: toc rollout"

Choosing correctly #

SituationChoice
Local commit, not pushed, message wrongreset --soft + amend
Local work, abandonedreset --hard
Pushed commit is wrongrevert — always
A whole feature came out wrongrevert the merge or range
Working-tree file experimentsgit restore / checkout

The dividing line is shared history: before a push, history is yours to reshape (with rebase and reset); after, it belongs to everyone who fetched, and revert is the only honest move.

File-level undo is git restore; deleted-branch recovery is reflog. The Git series continues.