Undoing Things in Git
Almost every mistake in Git is recoverable. Git rarely deletes data: commits you "lost" to a bad reset or rebase usually still exist, reachable through the reflog for weeks. The trick is knowing which tool fits which situation. restore handles files, reset moves a branch, revert safely undoes commits others already have, and reflog finds your way back.
The single most important question before undoing anything: has this been pushed and possibly pulled by others? Local-only history can be rewritten freely. Shared history should be undone with new commits.
TL;DR
- Discard working-tree changes to a file:
git restore <file>. Unstage:git restore --staged <file>. - Fix the last commit (message or content):
git commit --amend, but only if it isn't pushed. - Move a branch back:
git reset --soft(keep changes staged),--mixed(keep them unstaged),--hard(discard them). - Undo a pushed commit:
git revert <sha>, which creates a new commit applying the inverse change. - Lost a commit or a branch?
git reflog, thengit switch -c rescue <sha>orgit reset --hard <sha>. - A pushed secret must be rotated, whatever you do to history.
Quick Example
Common "oops" moments and their fixes:
Core Concepts
Three Places Changes Live
Undo commands differ in which of these they touch:
See Git internals for how these fit together.
restore: Files, Not History
git restore (Git 2.23+) replaces the file-level uses of the overloaded git checkout:
Discarding unstaged changes with restore is one of the few truly destructive operations: those edits were never committed, so the reflog can't bring them back.
reset: Move the Branch
git reset <commit> moves the current branch to <commit>. The mode decides what happens to the changes in between:
reset rewrites the branch, so use it on unpushed work.
revert: Undo With a New Commit
git revert <sha> creates a commit applying the inverse of <sha>. History is preserved and nobody's clone breaks, which makes it the right tool for shared branches. Reverting a merge commit needs -m 1 to say which parent is the mainline. Be aware that re-merging the same branch later won't reintroduce the reverted changes unless you revert the revert.
amend: Fix the Last Commit
git commit --amend replaces the last commit with a new one that includes the current index and, optionally, a new message. It's perfect for "forgot a file" or "typo in message", but it rewrites the commit, so avoid it once the commit is pushed and shared.
reflog: The Safety Net
Git records every movement of HEAD and each branch in the reflog:
Any commit that appears there can be restored. Entries last about 90 days (30 for unreachable commits) by default, so recovery works long after a mistake. The reflog is local: it records your clone's history, not the remote's.
stash: Shelve Work Temporarily
git stash push -m "wip export" saves uncommitted changes and cleans the working tree. git stash pop restores them. Stashes are commits under the hood, so even a dropped stash can often be recovered with git fsck --lost-found.
Recovery Recipes
Removing Secrets From History
If you commit a password, key, or token:
- Rotate the secret immediately. Assume it's compromised the moment it was pushed, since clones, forks, CI logs, and caches may already hold it.
- If it isn't pushed yet:
git resetor--amendit away, and you're done. - If it was pushed: rewrite history with
git filter-repo(the successor tofilter-branch) or BFG, force-push, and ask collaborators to re-clone. Contact your Git host to purge cached views and pull request refs. - Prevent recurrences with secret scanning in Git hooks and on the server. See secrets management.
Best Practices
Commit Early to Make Everything Undoable
Committed work is recoverable through the reflog; uncommitted edits aren't. Frequent small commits on a local branch (squashed later if you like) make mistakes cheap.
Prefer Revert on Shared Branches
For anything others have pulled, undo with git revert. Rewriting shared history forces every collaborator to repair their clone and risks resurrecting the bad commit by accident.
Check Before Running --hard
Run git status and git stash before reset --hard or restore. Uncommitted work they discard can't be recovered with Git.
Configure Safer Defaults
git config --global push.default simple plus branch protection on the server, and the habit of --force-with-lease instead of --force, prevent most catastrophic remote mistakes.
Common Mistakes
Using reset --hard to Undo Pushed Commits
Confusing revert With "Go Back to a Version"
git revert <sha> undoes that one commit's changes; it doesn't restore the repository to the state at <sha>. To return files to an old state while keeping history, use git restore --source=<sha> -- . and commit the result.
Deleting a File in a New Commit to Remove a Leaked Secret
The secret is still in the previous commit and in every clone. Rotate it, then purge history if necessary.
FAQ
What's the difference between reset, revert, and restore?
restore changes files in the working tree or index and doesn't touch history. reset moves the branch pointer, rewriting local history, and optionally resets the index and working tree. revert adds a new commit that undoes an earlier one, leaving history intact, which is safe for shared branches.
Can I recover a commit after git reset --hard?
Yes, if it was committed. Find it with git reflog and reset or branch back to it. Uncommitted changes discarded by --hard generally can't be recovered, although staged ones might be found as dangling blobs with git fsck --lost-found.
How do I undo a merge?
If it isn't pushed: git reset --hard ORIG_HEAD (immediately after the merge) or reset to the pre-merge commit from the reflog. If it's pushed: git revert -m 1 <merge-sha>, and remember to revert that revert before re-merging the branch later.
How long does Git keep "lost" commits?
Unreachable commits stay recoverable through the reflog for 30 days by default (90 days for reachable reflog entries), and garbage collection won't prune them before those entries expire. Don't rely on that indefinitely: create a branch as soon as you find the commit.
Related Topics
- Git — The version control system overview
- Git Internals — Refs, the index, and the reflog explained
- Git Rebase — Rewriting history deliberately
- Git Hooks — Preventing mistakes like leaked secrets
- Secrets Management — Rotating credentials after a leak