"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 #
$ 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.
| Goal | Command |
|---|---|
| Redo the last commit's message | reset --soft HEAD~1, re-commit |
| Unstage everything | git reset (mixed, HEAD implied) |
| Abandon local commits entirely | git reset --hard origin/main |
| Unstage one file | git restore --staged file |
The modern git restore covers the file-level cases cleanly; reset is the branch-level tool.
revert: undo in public #
$ 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:
$ git revert --no-commit f3e2d1c..b2c3d4e && git commit -m "revert: toc rollout"
Choosing correctly #
| Situation | Choice |
|---|---|
| Local commit, not pushed, message wrong | reset --soft + amend |
| Local work, abandoned | reset --hard |
| Pushed commit is wrong | revert — always |
| A whole feature came out wrong | revert the merge or range |
| Working-tree file experiments | git 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.
