Undoing Changes in Git
Git gives you many ways to undo work — and that can be paralysing. The trick is to ask one question first: where does the bad state live right now? In your editor (working directory), staged for commit (index), already committed locally, or already pushed to a shared branch? The right command falls out of that answer.
Where can the bad state be?
Working directory — you edited a file but have not staged it.
Staging area (index) — you ran
git addbut have not committed yet.Last local commit — you committed, but have not pushed.
Older local commits — several commits back, still local.
Pushed to a shared branch — other people may already have your bad commit.
Decision flowchart
Pick a command by where the mistake lives
Where is the bad state?
│
├── Working directory only (unstaged edits)
│ └── git restore <file> (discard edits)
│
├── Staged but not committed
│ └── git restore --staged <file> (keep edits, unstage)
│ └── git restore --staged --worktree <file> (throw both away)
│
├── Just committed (last commit, not pushed)
│ ├── Need to fix message/files → git commit --amend
│ ├── Want to uncommit, keep changes → git reset --soft HEAD~1
│ ├── Want to uncommit, unstage them → git reset --mixed HEAD~1
│ └── Want to nuke the commit → git reset --hard HEAD~1 (DANGER)
│
├── Older local commits (not pushed)
│ ├── Throw away history → git reset --hard <sha> (DANGER)
│ └── Interactively edit/squash → git rebase -i <sha>
│
└── Already pushed to a shared branch
└── git revert <sha> (the only safe option)Quick reference table
Scenario | Command | Safe on shared branch? |
|---|---|---|
Discard unstaged edits to a file |
| Yes |
Unstage a file (keep edits) |
| Yes |
Throw away staged + unstaged edits |
| Yes |
Fix the last commit (message or files) |
| No — rewrites history |
Uncommit last commit, keep changes staged |
| No |
Uncommit last commit, keep changes unstaged |
| No |
Throw away last commit and changes |
| No |
Undo an old commit by appending a new one |
| Yes |
Remove untracked files |
| Yes (local-only) |
Recover from a bad undo |
| Local recovery |
Commands you will meet
git restore— move content between trees without touching history. See the git restore page.git reset— move the current branch pointer (and optionally the index/working tree). See git reset.git revert— append a new commit that undoes an old one. Safe everywhere. See git revert.git commit --amend— replace the most recent commit. See Amending Commits.git clean— delete untracked files from the working directory. See Discarding Local Changes.git reflog— your local log of every HEAD movement. See Recovery with git reflog.
The reflog is your safety net
Almost every undo operation that seems destructive can be reversed locally, because Git keeps a private log called the reflog of every position HEAD has held. If you blow away a commit with git reset --hard, the commit itself still exists in the object database for ~90 days — the reflog tells you the SHA so you can get back to it.
The two lines that saved many careers
git reflog
# 9c1a2b3 HEAD@{0}: reset: moving to HEAD~3
# 7f8d9e0 HEAD@{1}: commit: the work I just destroyed
git reset --hard HEAD@{1} # back to safety