■ space · prose lane

■ the answer first

Has anyone else pulled the commit? Yes → git revert <sha>. No → git reset, any mode.

--soft keeps edits staged, --mixed (default) unstaged, --hard deletes them — the only shredder.

One mangled file? git restore <file> — leave the branch alone.

THE THROWAWAY-REPO DEMO

Two minutes in a scratch repo, and the difference is not a definition any more — it's a log you've seen:

build a two-commit repo — 2 commits · the mistake is committed, the tree is clean

git init /tmp/revert-vs-reset && cd /tmp/revert-vs-reset
echo one > file.txt && git add . && git commit -m "one"
echo two > file.txt && git add . && git commit -m "two"

revert it — history grows

git revert --no-edit HEAD
git log --oneline
# 1a2b3c4 (HEAD -> main) Revert "two"   ← the cancellation
# 5d6e7f8 two                          ← still in the log
# 9f8e7d6 one

reset it — history shrinks

git reset --hard HEAD~1
git log --oneline
# 9f8e7d6 one                          ← "two" is unreachable

verify: after the revert git log --oneline → 3 commits · after the reset → 1 commit — the asymmetry is the whole answer

THE THREE RESET MODES

The modes control where your changes survive after the pointer moves — and only one row of this table deletes work:

ModeBranch pointerStaging areaWorking treeYour edits
--softmoves backkept stageduntouchedfully kept
--mixed (default)moves backunstageduntouchedkept, unstaged
--hardmoves backresetresetdeleted

verify: git reset --soft HEAD~1 → git status shows the change "to be committed" · --mixed → "changes not staged"

revert has no modes because it never destroys anything — it only ever adds a compensating commit.

THE ONE QUESTION

Has anyone else pulled this commit? If yes, reset is off the table. Resetting a branch others have based work on rewrites shared history: their next pull meets divergent branches, and the "fix" is usually a force-push that turns your mistake into everyone's problem. Revert appends a normal commit, so a plain git pull merges cleanly for everyone — that's why revert is the default answer on main and on GitHub pull requests, where the "Revert" button exists precisely because rewriting a merged PR's history is not an option.

Reset is for the window before sharing: the commit from thirty seconds ago, the staging mistake, the local experiment branch. If the only copy of the work lives on your machine, you are free to rearrange history — that is the entire point of the pre-push window. Middle case: you pushed to your own feature branch and nobody builds on it — a force-push after reset is socially acceptable there, but revert still costs less coordination. Reserve force-pushes for branches you own alone.

Revert is also safer structurally, not just by convention: the revert produces an ordinary commit — CI runs on it, the diff is reviewable, and git log explains to future-you why the change disappeared. Reset silently discards the intermediate state; nobody reviewing later can see what was undone or when.

TWO SHARP EDGES ON REVERT

WHERE RESTORE FITS

git restore (and git switch, its pair) arrived in git 2.23 to take over jobs reset was doing badly by name overload:

TaskOld wayClear way
Discard working-tree edits in a filegit checkout -- file / git reset --hardgit restore file
Unstage a filegit reset HEAD filegit restore --staged file
Move branch to another commitgit reset <sha>git reset <sha> (no replacement — this stays)

The modern split: restore fixes files, reset moves branches, revert undoes published commits. They are one mental model split across three commands. The old git checkout forms still work everywhere; the new forms just stop you from sending a whole branch to the shredder when you meant to unstage one file.

THE RECAP

  1. Commit is public (pushed, others pulled) → git revert <sha>.
  2. Commit is local-only, want to redo it → git reset --soft or --mixed and recommit.
  3. Local-only, want it gone with the uncommitted mess → git reset --hard, after one honest look at what else the working tree holds.
  4. One file got mangled → git restore <file> and leave the branch alone.

verify: git reflog → HEAD@{n} lists the pre-reset position — git reset --hard HEAD@{1} walks it back

The only genuinely dangerous entry in that list is --hard — everything else can be talked back with git reflog — which remembers the detached commits for ~90 days. The permanent losses in git are narrow and mostly require you to type it explicitly; the habit worth building is pausing half a second before --hard, not avoiding git's sharp tools altogether. And if the whole undo menu feels like ceremony, that's a fair criticism — jujutsu, the git-compatible VCS, redesigns exactly this part.

■ honest stamp Tested on: git 2.4x · Linux · macOS · Windows. First published 2026-09-15, record refreshed 2026-10-10. The demo above is a runnable throwaway repo — the log shapes shown are what git actually prints. Mode behaviour, merge-revert semantics and the reflog window are git's documented behaviour, typed from use.

end of the undo pair · nothing fetched · everything local