Undoing changes: restore, reset & revert
Everyone commits the wrong file or breaks something eventually. Git almost never loses committed work, but the right "undo" command depends on where the change is: working directory, staging area, a local commit, or a commit already pushed and shared.
The three places a change can live
| Where the mistake is | Command | Destroys work? |
|---|---|---|
| Edited file, not staged | git restore file.py |
Yes, the edits are gone |
| Staged but not committed | git restore --staged file.py |
No, just unstages |
| Last commit message wrong | git commit --amend |
No (rewrites the local commit) |
| Local commits you want to drop or redo | git reset |
Depends on the mode |
| Commit already pushed to a shared branch | git revert <sha> |
No, adds a new commit |
git restore
restore (Git 2.23+) replaced the confusing uses of checkout for files.
git restore settings.py # throw away unstaged edits to one file
git restore --staged .env # unstage, keep the file as it is
git restore --source=HEAD~2 app.py # bring back app.py as it was two commits ago
git reset: move the branch pointer
reset moves your current branch to another commit. Its mode decides what happens to the changes in between:
git reset --soft HEAD~1 # undo last commit, keep changes staged (great for re-committing)
git reset --mixed HEAD~1 # default: undo commit, keep changes unstaged
git reset --hard HEAD~1 # undo commit AND discard its changes
Use reset only on commits that exist only on your machine. Resetting commits that teammates already pulled
rewrites history and forces them to untangle it.
git revert: the safe undo for shared history
revert creates a new commit that applies the opposite of an earlier commit. History stays intact, so it is the
right tool for main or any pushed branch.
git log --oneline -5
# 7c1e2d9 Change GST rate calculation <- this broke billing
git revert 7c1e2d9 # opens the editor for a message, then commits
git push
Reverting a merge commit needs -m 1 to say which parent is the mainline: git revert -m 1 <merge-sha>.
Your safety net: reflog
Git records every movement of HEAD for a while (by default around 90 days for reachable entries). Even after a
bad reset --hard, you can usually recover:
git reflog
# a41f0b3 HEAD@{0}: reset: moving to HEAD~3
# 9d8e7f6 HEAD@{1}: commit: Add invoice PDF export
git reset --hard 9d8e7f6 # back to where you were
Uncommitted changes are not in the reflog, which is why restore and reset --hard on unsaved work are the only
truly dangerous undo operations.
Common mistakes
- Running
git reset --hardwith uncommitted work you wanted to keep. Stash or commit first. - Resetting and force-pushing a shared branch instead of reverting.
- Using
git commit --amendafter pushing, then being surprised the push is rejected. - Committing a secret and thinking
revertremoves it. The secret is still in history: rotate the key immediately.
Practice
In a scratch repo make five commits. Then: amend the last message, reset --soft two commits and re-commit
them as one, revert the first commit, and finally do a reset --hard HEAD~2 and recover it using git reflog.