Undoing Changes
Safely undo mistakes at every level — from working-directory edits to published commits
One of Git's greatest gifts is that almost nothing is truly permanent. But "undo" means different things depending on where the change is — uncommitted, committed locally, or already pushed. Using the right tool for each keeps you safe.
A Map of Undo
Match the tool to where the change lives:
| Situation | Command |
|---|---|
| Discard unstaged file edits | git restore <file> |
| Unstage a staged file | git restore --staged <file> |
| Fix the last commit's message or contents | git commit --amend |
| Undo local commits (not pushed) | git reset |
| Undo a pushed commit safely | git revert |
| Recover a commit you thought was gone | git reflog |
Amending the Last Commit
Forgot a file, or made a typo in the message? --amend rewrites the most recent commit:
git add forgotten-file.js
git commit --amend -m "feat: add search with debounce"
--amend replaces the last commit with a new one (new SHA). That's fine for
a commit you haven't pushed. Avoid amending commits others may already have
— you'd force them to reconcile diverged history.
reset: Rewrite Local History
git reset moves the current branch pointer to an earlier commit. Its three modes differ in what they do to your files:
git reset --soft HEAD~1 # undo commit, KEEP changes staged
git reset --mixed HEAD~1 # undo commit, keep changes unstaged (default)
git reset --hard HEAD~1 # undo commit AND discard the changes (destructive!)
--soft → commit undone, work back in staging
--mixed → commit undone, work back in working dir
--hard → commit undone, work gone
--soft is great for "I committed too early" — it puts your work back in staging so you can recommit. --hard throws the work away, so use it only when you're sure.
revert: Undo Safely in Shared History
reset rewrites history, which is dangerous for commits others already have. For anything pushed, use git revert instead. It doesn't erase the commit — it creates a new commit that undoes the changes:
git revert a1b2c3d
before: A ─ B ─ C(bug)
after: A ─ B ─ C(bug) ─ D(undoes C)
History stays intact and honest; everyone can pull the revert cleanly. Rule of thumb: reset for local, revert for pushed.
reflog: Your Safety Net
Think you lost commits after a bad reset? The reflog records where HEAD has been, even for commits no longer on any branch:
git reflog
a1b2c3d HEAD@{0}: reset: moving to HEAD~1
f9e8d7c HEAD@{1}: commit: the work I thought I lost
Recover by pointing a branch back at that SHA:
git reset --hard f9e8d7c # or: git switch -c recovered f9e8d7c
Commits you "lose" usually linger in the reflog for weeks before garbage
collection. Before panicking over a bad reset or rebase, check git reflog
— the commit is often still there.