Undoing changes: restore, reset, revert
A precise map of every Git undo command — which tree each one touches, when to use each, and which ones can destroy work permanently.
What you'll learn
- Which undo command targets which of the three trees (working dir, staging, repo)
- When to reach for restore, reset, amend, or revert — and why the answer depends on whether you have pushed
- "reset --hard and restore are destructive: what that means and how to avoid surprises"
Before you start
You already know the three trees: the working directory (files on disk), the staging area (the proposed next commit), and the repository (the chain of committed snapshots, with HEAD pointing at the tip). Every Git undo command is just a targeted operation on one or more of those trees. Once you know which tree holds your mistake, the right command becomes obvious.
The undo toolbox at a glance
| Intent | Command | Touches |
|---|---|---|
| Discard unsaved edits to a file | git restore <file> | Working dir only |
| Unstage a file (keep edits) | git restore --staged <file> | Staging only |
| Fix the last commit message or add a forgotten file | git commit --amend | Repo (rewrites last commit) |
| Move HEAD back, keep everything staged | git reset --soft <ref> | HEAD / repo only |
| Move HEAD back, unstage changes, keep edits | git reset --mixed <ref> | HEAD + staging |
| Move HEAD back and discard all changes | git reset --hard <ref> | HEAD + staging + working dir |
| Undo a commit on shared history | git revert <sha> | Repo (adds new commit) |
git restore — undo edits you have not committed yet
git restore talks to the working directory and the staging area. It does not touch commits.
Discard working-directory changes (reset a file back to what HEAD has):
git restore README.md
# No output. README.md now matches the last commit.
Unstage a file without losing your edits (move staging back to match HEAD, leave the file on disk untouched):
git restore --staged README.md
# No output. README.md is now unstaged; your edits are still in the working dir.
You can also combine both flags to unstage and discard in one step:
git restore --staged --worktree README.md
git commit —amend — rewrite the last commit
Sometimes you commit and immediately realize the message has a typo, or you forgot to include one file. --amend replaces the tip commit with a new one that has your corrections.
# Stage the forgotten file first
git add forgot-this.py
# Rewrite the last commit
git commit --amend -m "Add login endpoint and missing helper"
[main 9c3f21a] Add login endpoint and missing helper
2 files changed, 47 insertions(+)
Notice that the SHA changed from whatever it was before — --amend creates a new commit object. The old commit is discarded. This is fine as long as that commit was never pushed. If teammates have already pulled it, amending produces a diverging history and forces a conflict.
git reset — move HEAD (and optionally the trees)
git reset moves the HEAD pointer (and the branch it points to) back to an earlier commit. Three modes control how much collateral change happens.
—soft: move HEAD only
git reset --soft HEAD~1
HEAD moves back one commit. The changes from that commit reappear in the staging area, ready to be recommitted differently. Your working directory is untouched.
Use this when: you want to squash several local commits into one, or when your commit was correct but needs a better message before you push.
—mixed (the default): move HEAD and unstage
git reset HEAD~1
# same as:
git reset --mixed HEAD~1
HEAD moves back, and the staging area is cleared to match that earlier commit. Your edits stay in the working directory as unstaged changes. Nothing in your files is lost.
Use this when: you want to undo a commit and re-stage only some of the changes.
—hard: move HEAD and discard everything
git reset --hard HEAD~1
HEAD moves back, staging is cleared, and the working directory is overwritten to match that earlier commit. Any uncommitted work is gone.
Use this when: you are absolutely certain you want to throw away that entire commit’s worth of changes and start fresh from the previous state.
HEAD~1 means “one commit before HEAD”. You can also pass a specific SHA: git reset --hard a1b2c3f.
git revert — the safe undo for shared history
git revert does not move HEAD backward. It reads a past commit, figures out what it changed, and creates a new commit that does the opposite. The history stays intact; a correction is simply appended.
git revert a1b2c3f
[main 7d4f11e] Revert "Add experimental cache layer"
1 file changed, 12 deletions(-)
Because revert only adds commits and never rewrites them, it is safe on any branch that others are using — pushed main, shared feature branches, release branches. When in doubt, prefer revert.
The decision: local cleanup vs. shared history
The central question is always: has this commit been pushed?
- Not pushed yet — you have full freedom.
restore,amend, andresetare all on the table. Pick based on which tree holds the mistake. - Already pushed — use
git revert. Every other option rewrites history and will cause problems for anyone who has pulled.