Part 8 of 11 · 3 min read
Undoing things safely
Which command undoes which mistake, and which commands destroy work for good.
Everyone makes mistakes with Git. The good news is that, once something is committed, Git makes it very hard to lose. The danger lies in work that was never committed, and in a few commands that discard things on purpose. Choose the undo by asking one question: how far did the mistake get?
| Where the mistake is | What you want | Command |
|---|---|---|
| Edited a file, not staged | Throw the edits away | git restore app.py |
| Staged a file by mistake | Unstage it, keep the edits | git restore --staged app.py |
| Committed, not pushed | Change the last commit | git commit --amend |
| Committed and shared | Cancel it with a new commit | git revert <commit> |
| Think a commit is "lost" | Find where HEAD has been |
git reflog |
Throw away edits, or unstage
$ echo x > a.txt
$ git add a.txt
$ git status -s
A a.txt
$ git restore --staged a.txt
$ git status -s
?? a.txt
The short status shows A (added, staged) and then ?? (untracked) after unstaging. The file and its content are untouched. git restore <file> does the opposite job for an edited file: it puts the last committed content back and discards your edits for good, because uncommitted work is not stored anywhere else.
Cancel a shared commit with revert
If a bad commit has already been pushed, never erase it. Add a new commit that does the opposite:
$ git revert --no-edit HEAD
[main 81bdfb7] Revert "Add app v2"
1 file changed, 1 deletion(-)
delete mode 100644 app.py
$ git log --oneline
81bdfb7 Revert "Add app v2"
1b8cd2a Add app v2
9506b91 Merge feature/retry, use 15s
History keeps both the mistake and its correction, which is honest and safe for people who already have the old commit.
reset --hard and how reflog rescues you
git reset --hard <commit> moves your branch back to that commit and throws away the changes after it from your working folder:
$ git reset --hard HEAD~2
HEAD is now at 9506b91 Merge feature/retry, use 15s
Two commits seem to be gone. They are not. Git keeps a private log of everywhere HEAD has been:
$ git reflog
9506b91 HEAD@{0}: reset: moving to HEAD~2
81bdfb7 HEAD@{1}: revert: Revert "Add app v2"
1b8cd2a HEAD@{2}: commit: Add app v2
$ git reset --hard HEAD@{1}
HEAD is now at 81bdfb7 Revert "Add app v2"
Going back to HEAD@{1} brought the commits back. The reflog lives only on your computer and old entries are eventually deleted, so it is a safety net for recent mistakes, not a backup.
The two commands that can lose work for good
git reset --hard and git restore <file> discard uncommitted edits, and nothing can bring them back, not even the reflog. Commit (or git stash) first if you are not sure.
For the curious Going deeper: reset has three modes, and --amend rewrites
git reset --soft <commit> moves the branch but keeps your changes staged. --mixed (the default) keeps them but unstaged. --hard also resets your files. git commit --amend replaces the last commit with a new one (new ID), so, like rebase, use it only before you have pushed.