■ space · prose lane

■ the answer first

Undo the last commit, keep the changes: git reset --soft HEAD~1 (staged) or git reset HEAD~1 (unstaged).

Already pushed? git revert HEAD — a new commit that undoes it, safe on shared branches.

reset --hard is the only line that deletes work — and git reflog still knows, for ~90 days.

THE THREE LANES

One wrong commit, three exits. The flag decides where your changes land — follow the lanes:

fig — the only lane that deletes work is --hard; the reflog is the gutter running beneath all three

KEEP THE CHANGES: THE TWO COMMANDS

The most common situation: you committed, then spotted a typo, a missing file, or realised the change belongs in a different commit. Nothing is pushed yet. Undo the commit and put everything back:

# commit gone — changes go back to the staging area
git reset --soft HEAD~1

# changes go back to the working tree (unstaged) instead
git reset HEAD~1

HEAD~1 means "one commit before where HEAD points now". After either command your files are untouched on disk — only the branch pointer moved. Check with git status: with --soft the changes are staged, ready to re-commit with fixes folded in; without a flag they are unstaged, so you can edit freely first.

verify: git status → staged (--soft) or unstaged (default) · git log --oneline -1 → the commit before

A safer habit when you just want to add files to the last commit is not to undo it at all:

git add forgotten-file.txt
git commit --amend --no-edit

--amend replaces the last commit in place (no message change here). Note that amending a pushed commit rewrites history — more below.

THE THREE FLAGS, SIDE BY SIDE

This is the part worth memorising, because the flag decides where your changes end up — and whether they can be lost:

FlagCommit undone?Changes on diskStaged?Typical use
--softYesKeptYesRe-commit with small fixes
--mixed (default)YesKeptNoRe-group changes, re-stage selectively
--hardYesDeleted—Throw the work away entirely
git revertNo (new commit)Kept—Undo a commit that was already pushed

--hard is the only dangerous one: it discards the commit and the changes. Before any hard reset, stash or branch what you have:

git branch backup-before-reset   # cheap insurance
git reset --hard HEAD~1          # commit + changes gone

Already ran the dangerous version? All is not lost — git reflog remembers where HEAD has been. The reflog keeps dangling commits for around 90 days by default, so "I hard-reset by mistake" is almost always recoverable if you act before garbage collection:

git reflog                       # find the commit hash you lost
git reset --hard HEAD@{1}        # or: git reset --hard <hash>

verify: git reflog → the dropped commit sits at HEAD@{1}

ALREADY PUSHED?

Shared history gets a new commit, never a rewrite. If the commit is on a shared branch — anything other than your own feature branch — do not rewrite history. git revert computes the opposite change and commits it. Everyone who pulls simply gets a new commit that removes the old one's changes. No force-push, no broken colleagues:

git revert HEAD
git push

Undoing a run of commits is the same move over a range: git revert --no-commit HEAD~3..HEAD && git commit. The alternative — git reset --hard HEAD~1 && git push --force-with-lease — is only acceptable on a branch nobody else builds on: --force-with-lease refuses the push if the remote moved since your last fetch; plain --force overwrites whatever appeared. Force-pushing shared branches is how teams lose commits and CI logs mysteriously stop matching anyone's checkout.

verify: git log --oneline -2 → Revert "…" sits on top of the undone commit

GOOD WORK, WRONG ADDRESS

Sometimes the commit is fine — wrong branch, or too early. Instead of undoing it, move it. Park the whole commit on a new branch, then clean your current branch — or take just that commit to another branch without touching the current one:

git branch stash-commit          # park the commit on a new branch
git reset --hard HEAD~1          # then clean your current branch

git cherry-pick <hash>           # while on the target branch

Between reset --soft, cherry-pick and revert, there is no commit you cannot relocate or nullify — the trick is choosing "move" versus "undo" before reaching for a flag.

THE WORD IS THE COMMAND

Half the confusion is vocabulary: six verbs people type into the search box, three commands that actually exist. Match the word in your head to the flag that does it:

The word you typedWhat you wantThe commandChanges kept?
undo / uncommitcommit gone, work backgit reset --soft HEAD~1yes — staged
undo, unstagedcommit gone, work back loosegit reset HEAD~1yes — mixed
remove / dropcommit and edits gonegit reset --hard HEAD~1no — reflog ~90 days
revert / cancel, pushedpublic undo, history intactgit revert HEADyes — new commit

The verbs are not synonyms in Git: revert never touches the past, reset rewrites it. Say the wrong word to a teammate and you get the wrong command. The whole decision, as a checklist:

  1. Not pushed, want to fix and re-commit → git reset --soft HEAD~1
  2. Not pushed, want to re-stage selectively → git reset HEAD~1 (mixed)
  3. Want the changes gone completely → git reset --hard HEAD~1 (reflog knows, if you regret it)
  4. Already pushed to a shared branch → git revert HEAD
  5. Commit belongs on another branch → cherry-pick, don't undo

Want the decision made for you? The git-undo generator asks these questions and prints the command. And one last operational tip: if a bad commit did make it to a server — a botched deploy hook, a CI runner misbehaving after a force-push — the next place to look is the machine's logs, not Git: on any systemd box, journalctl -u <service> -n 100 shows you exactly what ran and when. And when Git refuses to run at all — fatal: not a git repository — that is a different problem from undoing anything.

■ honest stamp Tested on: git 2.4x · Linux · macOS · Windows — every command runs as-is. First published 2026-09-03, record refreshed 2026-10-09. The flag table, the ~90-day reflog window and the force-with-lease behaviour are git's documented defaults, typed from use; nothing on this page is hypothetical syntax.

end of the undo page · nothing fetched · everything local