Undoing Changes & Recovery
Git almost never loses committed work if you know the tools: reset moves branch pointers (soft/mixed/hard), revert creates an inverse commit that is safe for shared branches, restore and stash handle uncommitted changes, and reflog records every pointer move for recovery.
What is this?
Undoing in Git has two axes: uncommitted vs committed, and local vs shared. git restore discards or unstages working-tree changes; git reset moves a branch pointer while optionally unstaging or discarding; git revert adds a new commit that undoes another (safe on shared branches); git stash shelves uncommitted work; git reflog records every HEAD movement for recovery.
Why do you need it?
- Committed work is almost always recoverable — reflog and fsck keep snapshots of moved pointers.
- git revert is safe on shared branches because it adds a commit instead of rewriting history.
- git stash lets you switch context without losing or committing work in progress.
- Understanding reset's three modes gives you precise control over staged and committed state.
Mental model
A word processor's undo stack
Git's reflog
Undo steps you back through every edit. Git goes further: reflog keeps a log of every place HEAD has been, so even deep mistakes are reversible.
How it works
- git reset --soft HEAD~1 moves the branch pointer back but keeps working tree and index intact.
- git reset (mixed, the default) also unstages changes, leaving them in the working directory.
- git reset --hard additionally discards working-tree changes — permanent unless you have the commit hash from reflog.
- git revert <commit> computes the inverse patch and creates a new commit, preserving history.
- git reflog lists every HEAD movement; you can reset to an old reflog entry to resurrect lost commits.
Reset modes vs revert on a commit history.
Key commands
git restore <file>Discard uncommitted changes in a file, restoring the last committed version.
git restore --staged <file>Unstage a file, keeping the working-directory changes.
git reset --soft HEAD~1Move the branch back one commit, keeping changes staged.
git reset --hard HEAD~1Move the branch back and discard working-tree changes.
git revert <commit>Create a new commit that undoes the given commit.
git stash push -m 'wip'Shelve uncommitted changes with a label.
git reflogList every HEAD movement for recovery.
Examples
Undo a mistaken commit before pushing
# You accidentally committed a file with a typo:git reset --soft HEAD~1 # uncommit, keep everything stagedgit restore --staged app.js # unstage the file# fix the typogit add app.jsgit commit -m "Fix typo in app.js" # On a shared branch, use revert instead of reset:git revert HEAD # adds a commit that undoes the last onereset --soft uncommits without touching the working tree, letting you restage and recommit cleanly. On branches others depend on, revert is the safe choice because it adds a commit instead of rewriting the history they may have cloned.
In production
- git revert is the standard way to roll back a bad commit on main without rewriting shared history.
- git stash is used to pause work when an urgent fix or hotfix interrupts the current task.
- Reset before pushing cleans up local-only mistakes without affecting anyone else.
- Reflog recovery rescues work lost to a botched rebase or reset --hard.
Best practices
- Never reset --hard on a shared branch — use revert there.
- Prefer revert for anything already pushed; reset is for local-only mistakes.
- Commit early and often so you always have something to revert or stash.
- If you must rewrite pushed history, use --force-with-lease and coordinate with the team.
- Check git status before every destructive command to confirm what will be discarded.
Common mistakes
- Running reset --hard with uncommitted work you wanted to keep.
- Rewriting shared history with reset/rebase instead of using revert.
- Forgetting that a stashed change still exists — then losing it in a clean-up.
- Panicking after a reset --hard and not checking reflog, where the commit still exists.
Common issues
You are not currently on a branchHEAD is detached because you checked out a raw commit.
Create a branch to keep the work with git switch -c rescue-branch, or return with git switch main.
CONFLICT while applying stashYour working tree changed since the stash was created.
Resolve the conflict like a merge, then git stash drop once it is safely applied.
Changes 'lost' after git reset --hardThe branch pointer moved, orphaning the commit.
Find it in git reflog (or git fsck --lost-found) and restore with git reset --hard <hash> or a new branch.
Debugging tips
- git reflog is the first place to look after any 'lost' commit.
- git stash list shows shelved changes and their labels.
- git fsck --lost-found finds orphaned commits unreachable from any branch.
- git status before a destructive command reveals exactly what would change.
Interview checkpoint
Quick quiz
Which command is safe to use on a shared branch that others have pulled?
git reflog can help you recover commits lost with git reset --hard.
What does the default git reset (mixed) do?
Cheat sheet
git restore <file>Discard working-tree changesgit restore --staged <file>Unstage onlygit reset --soft HEAD~1Uncommit, keep stagedgit reset --hard HEAD~1Uncommit + discardgit revert <commit>Safe inverse commitgit stashShelve WIPgit reflogRecover lost commits