Git interview question
How do you undo a commit? Compare git reset, git revert and git restore.
Short answer
git revert creates a new commit that undoes an earlier one, so it is safe on shared branches. git reset moves the branch pointer back, with --soft, --mixed or --hard deciding what happens to staged and working changes; use it only on unpushed commits. git restore discards file changes. git reflog recovers mistakes.
Answer
The right tool depends on whether the commit has been shared. If it has been pushed to a branch others use, I use git revert, which adds a new commit that undoes the change and leaves history intact. If the commit is only local, git reset can move the branch back as if the commit never happened. For uncommitted file changes, git restore is the modern command. And if I get it wrong, git reflog almost always lets me recover.
git revert: undo by adding a commit
git revert a1b2c3d # creates "Revert '<message>'" commit
git revert --no-edit HEAD # undo the latest commit
git revert -m 1 <merge-sha> # revert a merge commit, keeping parent 1 (usually main)
Nothing is rewritten, so everyone can pull it normally. It is the correct way to back out a bad change on main, for example a test change that broke the pipeline.
A subtlety with merges: after reverting a merge, re-merging the same branch later will not bring those changes back, because Git considers them already merged. You must revert the revert.
git reset: move the branch pointer
git reset --soft HEAD~1 # undo commit; changes stay STAGED
git reset --mixed HEAD~1 # (default) undo commit; changes stay in working tree, unstaged
git reset --hard HEAD~1 # undo commit AND discard the changes
| Mode | Branch pointer | Staging area (index) | Working files |
|---|---|---|---|
--soft |
moved | kept | kept |
--mixed |
moved | reset | kept |
--hard |
moved | reset | reset (changes lost) |
Typical uses:
--soft HEAD~3to squash the last three local commits into one.--mixedto unstage everything and re-split changes into better commits.--hard origin/mainto throw away local experiments and match the remote.
Resetting commits that were already pushed requires a force push and causes the same problems as rebasing shared history. Do not do it on shared branches.
git reset --hard also discards uncommitted work, which reflog cannot recover because it was never committed. Run git status or git stash first.
git restore: file-level undo (Git 2.23+)
git restore path/to/file.ts # discard unstaged changes in that file
git restore --staged path/to/file.ts # unstage, keep the changes
git restore --source=HEAD~2 path/to/file # bring back the file as it was two commits ago
These replace the overloaded git checkout -- file and git reset HEAD file forms, which still work.
git reflog: the safety net
Every move of HEAD is recorded locally:
git reflog
# 9f8e7d6 HEAD@{0}: reset: moving to HEAD~1
# a1b2c3d HEAD@{1}: commit: Add checkout tests
git reset --hard a1b2c3d # or: git branch rescue a1b2c3d
Reflog entries expire (by default after 90 days for reachable entries and 30 days for unreachable ones), and they are local to your clone.
Quick decision guide
- Pushed and shared:
git revert. - Local, want to keep the changes:
git reset --softor--mixed. - Local, want to discard:
git reset --hard(check for uncommitted work first). - Just a file:
git restore. - Made a mistake:
git reflog.
What interviewers look for
- The shared versus local distinction driving the choice.
- Correct semantics of
--soft,--mixedand--hard. - Awareness of
git restoreandgit reflog. - Bonus: reverting merges and the “revert the revert” subtlety.
A strong answer covers
- revert adds a new inverse commit and is safe for pushed history
- reset moves the branch pointer and rewrites history
- Explains --soft, --mixed and --hard
- Uses restore for file-level changes
- Uses reflog to recover lost commits