Git: undo anything
Everyone makes git mistakes: a typo in a commit message, a file left out, a change that broke the site, a reset that seemed to delete a week of work. The good news is that git almost never forgets anything. This lesson is the map: which undo to use for which mistake, and the one rule that keeps your teammates happy.
You will learn
- Throw away changes, and unstage files, with
restore - Fix your last commit with
commit --amend - Undo a commit that's already pushed, safely, with
revert - The three kinds of
reset: soft, mixed and hard - Rescue “lost” commits with
reflog
Which undo do I need?
Find your mistake in the left column. Git works the same on Rocky and Ubuntu, so every command here is the same on both.
| The mistake | The fix | Safe after a push? |
|---|---|---|
| I changed a file and want the last committed version back | git restore FILE | Yes (nothing was committed) |
| I staged a file by mistake | git restore --staged FILE | Yes |
| Typo in my last commit message, or I forgot a file | git add FILE, then git commit --amend | No |
| My last commits were a bad idea, and I haven't pushed them | git reset HEAD~1 (soft, mixed or hard: see below) | No |
| A commit that's already pushed broke something | git revert HASH | Yes: that's what it's for |
| I need an old version of one file | git restore --source=HASH FILE | Yes |
| I reset or deleted something and my commits are gone! | git reflog, then git reset --hard HEAD@{N} | Yes |
The golden rule: don't rewrite pushed history
--amend and reset don't edit commits. They make new commits and throw the old ones away. That's fine while the commits only exist on your computer. Once you've pushed, your teammates have the old commits too, and rewriting them makes their next pull a mess (and git will refuse your push anyway).
For anything already pushed, use git revert. It adds a new commit that does the exact opposite of the old one. History stays honest: anyone can see the mistake and the fix.
$ git revert 4e1f0c2 [main 9b3d7aa] Revert "Add a big red banner" 2 files changed, 6 deletions(-)
On a real computer, git first opens your editor with the message Revert "…" ready to go. Save and close it (in nano: Ctrl+O, Enter, Ctrl+X), or add --no-edit to skip the editor.
Fixing your last commit: --amend
Forgot a file, or spelled the message wrong? If you haven't pushed yet, fold the fix into the last commit instead of adding a “fix typo” commit:
git add contact.css # the file you forgot (if any) git commit --amend -m "Add contact page" # replaces the last commit git commit --amend --no-edit # same, but keep the old message
The three kinds of reset
git reset moves your branch back to an earlier commit. The difference is what happens to the work in the commits you're stepping back over:
| Command | Branch moves back | Staging area | Your files | Use it when |
|---|---|---|---|---|
git reset --soft HEAD~1 | ✓ | kept, staged | kept | “I want to redo that commit” |
git reset HEAD~1 (mixed, the default) | ✓ | emptied | kept | “Uncommit it, I'll pick what to add again” |
git reset --hard HEAD~1 | ✓ | emptied | thrown away | “That was a dead end, get rid of it” |
HEAD means “the commit I'm on”, HEAD~1 is the one before, HEAD~3 is three back. You can use a hash from git log --oneline instead.
reset --hard also throws away changes you haven't committed, and those are gone for good. Committed work can still be rescued (next section). Run git status first, and commit or git stash anything you want to keep.
The safety net: git reflog
Git keeps a diary of everywhere your HEAD has been: every commit, reset, switch and merge, for about 90 days. A commit you “lost” with reset is still there, it just has no branch pointing at it.
$ git reflog
2b81c4e (HEAD -> main) HEAD@{0}: reset: moving to HEAD~3
9b3d7aa HEAD@{1}: revert: Revert "Add a big red banner"
5d0e6f1 HEAD@{2}: commit (amend): Add contact page
…
Read it from the top: HEAD@{1} is where you were one move ago. Go back with git reset --hard HEAD@{1}, or keep both with git switch -c rescue HEAD@{1}.
“I committed a password!”
If you haven't pushed: remove it from the file, add the file to .gitignore if needed, and git commit --amend (or reset) so the secret never leaves your computer.
If you have pushed, treat it as leaked, because bots scan public repositories for keys within minutes. First change the password or revoke the key. That's the real fix. Then remove it from the code with a normal commit. Scrubbing it out of the history (with a tool like git filter-repo) is a later clean-up, and it rewrites history for everyone, so it's a team decision.
Try it: a messy morning on the club website 🔧
Your site/ repository is shared with the club through /srv/git/site.git. Yesterday you pushed a banner that everyone hates. This morning you committed a contact page (with a typo in the message, and you forgot its CSS file), and then you broke index.html while testing something.
Quick check
1. A commit you pushed an hour ago broke the website. Your teammates have already pulled it. What's the right undo?
✓ revert adds a new commit that undoes the old one, so nobody's history gets rewritten.
2. You want to undo your last (unpushed) commit but keep all its changes staged, ready to commit again. Which one?
✓ --soft only moves the branch. --hard would throw the changes away.
3. After a git reset --hard, three commits seem to have vanished. Where do you look?
✓ git log only shows what your branch can reach. The reflog remembers where HEAD has been.