Git undo: reset, revert or restore?
Two questions settle almost every git undo. What are you undoing - a file edit, a staged change, a commit, or a push? And has it left your machine? If it has been pushed to a branch other people use, the answer is git revert and nothing else. If it has not, you have the full range, and the only thing left to decide is whether you want to keep the work.
The confusion is historical. git checkout used to do three unrelated jobs, so every tutorial written before 2019 uses it for things git restore and git switch now do properly. This guide maps the question onto the command, using the 19 entries the Undoing Changes and Common Mistakes & Recovery sections of the git sheet already hold.
The decision table
Read down the first column to what you are undoing. The pushed column is the one that actually constrains you: rewriting history that someone else has pulled is the one mistake in this table that damages other people's repositories rather than your own.
| What you are undoing | Not pushed | Already pushed | Keeps your work? |
|---|---|---|---|
| Edits to a file in the working tree | git restore <file> | n/a - nothing was committed | No. Permanent |
| A file you staged by mistake | git restore --staged <file> | n/a | Yes. Edits are untouched |
| The last commit's message | git commit --amend | Do not. Use a new commit | Yes |
| The last commit, keeping it staged | git reset --soft HEAD~1 | Do not. Use revert | Yes, staged |
| The last commit, keeping the edits | git reset HEAD~1 | Do not. Use revert | Yes, unstaged |
| The last commit and the work in it | git reset --hard HEAD~1 | Never | No |
| A commit in the middle of history | git revert <commit> | git revert <commit> and push | Yes - adds a new commit |
| Untracked files | git clean -f -d | n/a | No. Permanent |
| A commit you destroyed with reset --hard | git reflog, then reset to the hash | - | Usually yes, for ~30 days |
restore is for files, reset is for commits
This is the distinction the old git checkout hid. git restore moves file content around; git reset moves the branch pointer. Two entries from the sheet make the pairing clear, and they are deliberately shown side by side here because their names suggest the opposite of what they do.
The important asymmetry: unstaging is safe and discarding is not. git restore --staged moves a file out of the index and leaves your edits alone, so there is nothing to lose. Bare git restore overwrites your edits with whatever is in the index, and the sheet's note on it is blunt about there being no undo.
Safe. The file leaves the index; your edits stay in the working tree. This is the modern replacement for git reset HEAD <file>.
git restore --staged <file>Destructive. The file is overwritten from the index, or from HEAD if nothing is staged, and the previous content is gone.
git restore <file>
git restore .The three resets, in one sentence each
All three move the branch pointer back. They differ only in what they do with the changes those commits contained.
--soft leaves everything staged, which is what you want when you are rewording or combining the last few commits. --mixed, the default, leaves the changes in the working tree unstaged, which is what you want when you intend to re-split them into different commits. --hard throws the changes away along with the commits.
The sheet's note on --hard is worth quoting in substance: it permanently destroys uncommitted work, and while the commits themselves can usually be recovered from the reflog, anything that was never committed cannot.
Rewriting or squashing the last n commits: the content comes back staged.
git reset --soft HEAD~<n>Re-splitting the last n commits: the content comes back unstaged.
git reset HEAD~<n>
git reset --mixed HEAD~<n>The one to be careful with. Uncommitted work in those commits is not recoverable.
git reset --hard HEAD~<n>
git reset --hard <commit>Undo Commit and Discard All Changes in Git · Undoing Changes
Pushed? Then revert, and only revert
git revert does not remove a commit. It creates a new commit that applies the inverse of the one you name, so the history everyone has already pulled stays valid. That is exactly why it is the only safe choice on a shared branch.
The alternative - reset plus a force push - rewrites the remote branch under everyone else. The sheet's entry on undoing a pushed commit says never to do that on a shared branch, and the force-push entry explains why: colleagues who already pulled the original history end up with work that no longer has a parent.
If you genuinely must force push, --force-with-lease is the form to use. It refuses when the remote has moved since you last fetched, which turns a silent overwrite of someone else's push into an error message.
The safe undo. A new commit, no rewritten history, nothing for anyone else to recover from.
git revert <commit>If a force push is unavoidable, this is the only form that can refuse to clobber someone else's work.
git push --force-with-lease origin <branch>Amend is a rewrite, even when it looks harmless
git commit --amend replaces the last commit, which gives it a new hash. Locally that is the cleanest way to fix a message or add a forgotten file. On a pushed commit it is a history rewrite with a friendly name, and the sheet's note says so directly: never amend a commit that has already been pushed to a shared branch.
The practical test before amending: run git log origin/<branch>..HEAD. If the commit you are about to amend is in that list, it has not been pushed and amending is safe.
Fine before the push. A history rewrite after it.
git commit --amend [-m "new message"]When you have already destroyed something
A hard reset past a commit you needed is recoverable far more often than people expect, because the reflog records every position HEAD has held - including ones no branch points at any more.
Two conditions: the work had been committed at least once, and the reflog entry has not expired. For a commit no branch points to any more, the default expiry is 30 days (gc.reflogExpireUnreachable); the 90-day default applies only to entries still reachable from the branch. Within that window, git reflog lists the hashes and you can reset or cherry-pick straight back to one.
The case this does not cover is uncommitted work, which the reflog never saw. That is the real argument for committing early on a local branch rather than carrying a large uncommitted change: it buys you an undo.
- Committed and then lost: usually recoverable with reflog.
- Never committed and then
reset --hard: gone. - Never committed and then
git clean -f: gone, and the sheet recommends-nfirst to preview. - Stashed: still there.
git stash listshows every stash, and stash pop keeps the stash on a conflict rather than dropping it.
The recovery path. Reflog entries for a lost commit expire after 30 days by default.
git reflog
git reset --hard <hash-from-reflog>
git cherry-pick <hash>Two adjacent traps from the same section
Both of these turn up as undo questions and neither is an undo problem.
A file that .gitignore refuses to ignore is almost always already tracked. Ignore rules only apply to untracked files, so the fix is to stop tracking it with git rm --cached and then commit that removal.
A committed secret is not undone by deleting the file in a new commit - it stays in the history, and anyone with the repository can read it. The sheet's entry puts rotating the credential first and rewriting the history second, which is the right order: the rewrite does not un-leak anything that has already been cloned.
The ignore rule was never the problem; the file was already tracked.
git rm --cached <file>
git rm -r --cached <directory>.gitignore Not Ignoring Files in Git · Common Mistakes & Recovery
Rotate the credential first. The history rewrite is the second step, not the fix.
git filter-repo --invert-paths --path <secret-file>
# Then: rotate the credential immediatelyAccidentally Committed a Secret in Git · Common Mistakes & Recovery
Questions people ask
What is the difference between git reset and git revert?
Reset moves the branch pointer backwards and rewrites history; revert adds a new commit that undoes an old one and leaves history intact. On a branch other people pull from, revert is the only safe option - see Undoing Changes.
How do I undo a commit I have already pushed?
git revert <commit> then push. Do not reset and force push a shared branch: anyone who already pulled the original history ends up with a broken copy. The sheet's undo pushed commit entry covers the same ground.
Is git restore the same as git checkout?
It replaces one of git checkout's three jobs. git restore handles file content, git switch handles branches, and git checkout still does both for compatibility. Tutorials written before these commands existed use checkout for everything, which is why the names seem to overlap.
Can I recover work after git reset --hard?
If it was committed at least once, usually yes - git reflog records every position HEAD has held, and entries for a lost commit last 30 days by default. If it was never committed, no. That is the distinction that makes a local commit worth making early.
Why does my .gitignore not work?
Because ignore rules only apply to files git is not already tracking. Run git rm --cached <file> to stop tracking it, then commit. The file stays on disk; it simply leaves the index.
Where this comes from
Every code block above is rendered from this site’s own entry data at build time, with a link to the entry it came from, and every count in the prose is derived from the same file. How this cheat sheet is organized explains the structure and says what the corpus does not cover.