Git undo cheat sheet: fix mistakes safely

This is the sheet you keep open in a tab. Git records three levels of state — the working tree, the index (staged) and commits — and each mistake lives at one of those levels, so each has a matching undo. The single rule that keeps you safe: read git status and prefer the least destructive fix first.

Almost nothing is truly lost. Even a hard reset leaves the old commits in the reflog for about 90 days, so a 'deleted' commit can usually be recovered.

Published

Pick the right undo

Match your mistake to its safe fix — from least to most destructive
MistakeSafe fixWhat happens
Edited a file, want it backgit restore <file>discards unstaged working-tree changes, restoring the file to the index/HEAD
Staged the wrong filegit restore --staged <file>un-stages the file, keeping your changes
Wrong commit messagegit commit --amendrewrites the last commit's message (local commits only)
Forgot a file in the last commitgit add <file> && git commit --amend --no-editadds the file to the last commit
Last commit is bad (pushed)git revert <sha>creates a NEW commit that reverses it — safe for shared history
Last commit is bad (local)git reset --soft HEAD~1moves HEAD back one, keeping all changes staged
Throw away the last commit entirelygit reset --hard HEAD~1moves HEAD back and discards the changes — destructive
Recover a 'lost' commitgit reflogshows every recent HEAD move so you can reset back to the old hash

Reset, explained

The three resets — the only difference is what happens to your changes
# --soft: move HEAD back, keep everything staged (changes survive, ready to recommit)
git reset --soft HEAD~1

# --mixed (default): move HEAD back, unstage but keep changes in the working tree
git reset HEAD~1

# --hard: move HEAD back AND discard changes from working tree and index
git reset --hard HEAD~1

What each flag touches

--soft
moves only the branch pointer — commits, index and working tree are all left alone
--mixed
moves the pointer and resets the index — changes become unstaged but remain on disk (the default)
--hard
moves the pointer and resets index and working tree — changes are discarded
HEAD~1
the parent of the current commit; HEAD~2 is two back — use a hash to jump anywhere
git reset --hard permanently discards uncommitted work in the files it resets. If you might want it back, snapshot it first with git stash or a quick git add . && git commit. The commits themselves are still recoverable from the reflog, but uncommitted working-tree changes are not.

Revert vs reset

The rule of thumb: once a commit has been pushed and someone else might have pulled it, use git revert — it writes a new commit that undoes the old one, so history is appended, never rewritten. Keep git reset for local, unpushed commits, because reset moves the branch pointer backwards and rewriting pushed history will force a conflict for collaborators.
Undo a pushed commit without rewriting history
# Reverse the effects of commit a1b2c3d and commit it as a new change
git revert a1b2c3d

# Revert a whole range (older..newer), committing each reversal
git revert HEAD~3..HEAD

# Revert but don't commit yet — stage the reversal for review
git revert --no-commit a1b2c3d

Stash: park changes without committing

Switch context without committing
# Save working changes and clean the tree
git stash

# Bring the most recent stash back and remove it from the list
git stash pop

# Bring it back but keep it in the stash list
git stash apply

# See what is stashed
git stash list

# Drop one stash (stash@{0} is the newest)
git stash drop stash@{0}

# Stash everything including untracked files
git stash -u
A stash is a stack, not a single slot — git stash list shows stash@{0}, stash@{1} and so on. pop applies and removes the top entry; apply applies it and leaves it in the list, which is the safer choice when you are unsure whether the merge back will succeed.

The reflog: your undo button for undo

Recover anything you reset, rebased or amended
# Every place HEAD has been, newest first
git reflog

# Output looks like:  a1b2c3d HEAD@{0}: reset: moving to HEAD~1
# Restore the state from before the reset:
git reset --hard HEAD@{1}

# Or restore a specific commit by its hash:
git reset --hard a1b2c3d
The reflog is local to your clone and records every HEAD movement — commits, resets, rebases, amends and checkouts — for roughly 90 days (configurable via gc.reflogExpire). Because it is local, it survives the mistakes that rewrite history, which is exactly why it is the standard recovery path after a bad --hard or rebase.

References

FAQ

How do I undo my last commit but keep the changes?

Use git reset --soft HEAD~1 to move the branch pointer back one commit while leaving the changes staged, or git reset HEAD~1 (mixed) to keep the changes but unstage them. Both keep your work; only git reset --hard HEAD~1 deletes it. If you already pushed, prefer git revert instead so you do not rewrite shared history.

What is the difference between git reset and git revert?

git reset moves the current branch pointer backwards, rewriting history, and can discard changes; it is for local, unpushed work. git revert computes the inverse of an existing commit and records it as a new commit on top of history, leaving the past intact — the safe choice for commits that have already been pushed and shared.

How do I undo a pushed commit?

Run git revert and push the result. That adds a new commit whose changes cancel the bad one, which everyone can pull without conflict. Avoid git reset on a pushed branch: it rewrites history, and any collaborator who already pulled the old commit will have to force-pull to reconcile.

Can I recover a commit I deleted with git reset --hard?

Usually yes. Run git reflog to find the hash of the state before the reset (look for the reset entry and the commit that came before it), then git reset --hard to move the branch back. The reflog keeps these entries for about 90 days, but uncommitted working-tree changes are gone for good, so stash or commit before any destructive reset.

How do I unstage a file?

git restore --staged (Git 2.23+) or git reset HEAD (the classic spelling). Both remove the file from the index so the next commit ignores it, while leaving your working-tree edits intact.