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 to run, by what you are undoing and whether it has been pushed.
What you are undoingNot pushedAlready pushedKeeps your work?
Edits to a file in the working treegit restore <file>n/a - nothing was committedNo. Permanent
A file you staged by mistakegit restore --staged <file>n/aYes. Edits are untouched
The last commit's messagegit commit --amendDo not. Use a new commitYes
The last commit, keeping it stagedgit reset --soft HEAD~1Do not. Use revertYes, staged
The last commit, keeping the editsgit reset HEAD~1Do not. Use revertYes, unstaged
The last commit and the work in itgit reset --hard HEAD~1NeverNo
A commit in the middle of historygit revert <commit>git revert <commit> and pushYes - adds a new commit
Untracked filesgit clean -f -dn/aNo. Permanent
A commit you destroyed with reset --hardgit 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>.

Unstage a File · Git
git restore --staged <file>

Unstage a File in Git · Undoing Changes

Destructive. The file is overwritten from the index, or from HEAD if nothing is staged, and the previous content is gone.

Discard Working Directory Changes · Git
git restore <file>
git restore .

Discard Working Directory Changes in Git · Undoing Changes

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.

Undo Commit, Keep Changes Staged · Git
git reset --soft HEAD~<n>

Undo Commit, Keep Changes Staged in Git · Undoing Changes

Re-splitting the last n commits: the content comes back unstaged.

Undo Commit, Keep Changes Unstaged · Git
git reset HEAD~<n>
git reset --mixed HEAD~<n>

Undo Commit, Keep Changes Unstaged in Git · Undoing Changes

The one to be careful with. Uncommitted work in those commits is not recoverable.

Undo Commit and Discard All Changes · Git
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.

Revert a Commit (Safe Undo) · Git
git revert <commit>

Revert a Commit (Safe Undo) in Git · Undoing Changes

If a force push is unavoidable, this is the only form that can refuse to clobber someone else's work.

Force Push Dangers · Git
git push --force-with-lease origin <branch>

Force Push Dangers in Git · Common Mistakes & Recovery

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.

Amend the Last Commit · Git
git commit --amend [-m "new message"]

Amend the Last Commit in Git · Undoing Changes

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 -n first to preview.
  • Stashed: still there. git stash list shows 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.

Recover Lost Commits · Git
git reflog
git reset --hard <hash-from-reflog>
git cherry-pick <hash>

Recover Lost Commits in Git · Common Mistakes & Recovery

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.

.gitignore Not Ignoring Files · Git
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.

Accidentally Committed a Secret · Git
git filter-repo --invert-paths --path <secret-file>
# Then: rotate the credential immediately

Accidentally 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.

Keep going

More guides