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
The last section ended on a promise: that real fluency in Git is fluency in recovery — and that Git’s undo is not one command but a small family, each matching a different mess. Here is the whole family, and the good news is that it maps cleanly onto one idea you already hold.
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.
Which tree does each command touch?
In one breath
Every undo is a targeted operation on the three trees, so first locate the mistake. Uncommitted edits: git restore <file> discards working-dir changes; git restore --staged <file> unstages but keeps them. The last commit, not yet pushed: git commit --amend rewrites it. Whole commits, locally: git reset moves HEAD back, and the mode sets how much it drags along — --soft keeps everything staged (great for squashing), --mixed (default) unstages but keeps your files, --hard discards everything. The one question that overrides all of this: has it been pushed? If yes, every history-rewriting option is off the table — use git revert, which appends a new commit that inverts the bad one, safe on any shared branch.
Practice
Before the quiz, match the tool to the mess, three times: (a) you staged secrets.env by accident but want to keep editing it — which command? (b) you made three tiny local commits for one feature and want them as one before pushing — which reset mode, and why not --hard? (c) a commit you pushed two hours ago, already pulled by teammates, turns out to be buggy — which command, and what makes the other options dangerous here?
Quick check
A question to carry forward
Read this lesson’s warnings again and you will notice a phrase repeated like a drumbeat: reset --hard has “no recycle bin,” the work is “gone.” That is almost true — and the gap in that “almost” is the most reassuring secret in all of Git. Behind your back, Git has been quietly writing down every single place HEAD has ever pointed, including the commits you thought you had obliterated. A reset that seemed to vaporize an afternoon’s work usually left that work sitting right there, reachable, for weeks. There is a command that shows you this hidden ledger and lets you walk back to any moment in it — the undo for your undos, the thing that turns “I destroyed it” into “I recovered it in ten seconds.” It is git reflog, and it closes the course in the final lesson.