Section 4 · Professional Habits
Undoing Safely: Revert, Reset and the Reflog
18 min3 min readChoose wisely
Lesson at a glance
Choose the right undo for any mistake (restore, amend, revert or reset in its three modes) and use the reflog to recover commits you thought were gone.
- 1Pick the tool by location
- 2Amend: fix the last commit
- 3Revert: undo in public
- 4Reset: move the branch, three ways
- 5The reflog: Git’s flight recorder
No video here — just the real thing: a written lesson, runnable examples, a quiz and practice.
Everyone breaks something in Git eventually. You commit to the wrong branch, push a bug, reset away a morning’s work. The good news: Git almost never throws data away, and nearly every mistake has a clean undo. The trap is reaching for whichever undo command you half-remember. Pick by asking one question: where does the mistake live?
Pick the tool by location
| Where the mistake is | Command | Rewrites history? |
|---|---|---|
| Unstaged edits in a file | git restore <file> |
No (but discards the edits) |
| Staged, not committed | git restore --staged <file> |
No |
| Last commit, not pushed | git commit --amend |
Yes, your local commit only |
| Local commits, not pushed | git reset |
Yes, local only |
| Commits already pushed and shared | git revert <commit> |
No |
The table follows a single rule: once other people have a commit, you add new commits on top instead of changing it.
Amend: fix the last commit
Forgot a file or wrote a bad message, and haven’t pushed?
git add contact.css
git commit --amend --no-edit # same message, file included
git commit --amend -m "Add contact form" # or a new messageAmend doesn’t edit the commit; it replaces it with a new one with a new ID. That’s fine locally and a problem if you’ve pushed, for the same reason rebase is.
Revert: undo in public
You merged a commit to main that broke the mobile nav. It’s pushed, and teammates have pulled. Don’t rewrite anything. Add a commit that reverses it:
git revert HEAD # if it's the latest commit
git revert 8f1e2aa # or any commit by IDGit creates a new commit, Revert "Move nav into header", that applies the exact opposite changes. History still shows the mistake and its fix, which is honest and safe: everyone gets the revert with a normal pull. To revert a merge commit, tell Git which parent to keep as the mainline, usually the first: git revert -m 1 <merge-commit>.
Sometimes only part of a commit was wrong. If the nav move was fine except for styles.css, you don’t need to revert the whole thing. Bring that one file back to how it was before the commit, check it, and commit the result as an ordinary fix:
git restore --source=8f1e2aa~1 styles.css
git diff
git commit -am "Restore mobile nav styles"That’s still “adding history, not rewriting it”, just with a smaller scope.
Reset: move the branch, three ways
git reset <commit> moves the current branch to another commit. What it does to the index and working tree depends on the mode, and this is where people get hurt.
Three situations, three modes:
git reset --soft HEAD~2 # squash: the last two commits' changes become staged, ready for one commit
git reset HEAD~1 # un-commit (--mixed): changes return as unstaged edits to rework
git reset --hard HEAD~1 # discard: the commit and its changes are gone from the branch and filesA classic use: you committed to main when you meant to be on a branch. Commit or stash any uncommitted edits first (--hard would wipe them), create the branch where you are, then move main back:
git branch contact-form # the branch keeps your commit
git reset --hard origin/main # main goes back to match GitHub
git switch contact-formThe reflog: Git’s flight recorder
Here’s the safety net that makes all of this less frightening. Every time HEAD moves (commit, switch, reset, rebase, merge), Git writes a line in the reflog:
git reflog2b7f0c1 (HEAD -> main) HEAD@{0}: reset: moving to HEAD~3
9e4a1d5 HEAD@{1}: commit: Add contact form validation
4c8b2e7 HEAD@{2}: commit: Add contact form
a71d3f0 HEAD@{3}: checkout: moving from footer to mainAfter that accidental git reset --hard HEAD~3, the three commits aren’t on any branch, but they still exist and HEAD@{1} points at the newest. Bring them back:
git reset --hard HEAD@{1} # put main back where it was
# or, more cautiously, keep them on a new branch to inspect first
git switch -c rescue 9e4a1d5The reflog is local (it isn’t pushed or cloned) and entries expire: by default after 90 days, or 30 days for commits no branch can reach. That’s plenty of time to notice a mistake.
What the reflog can’t save is work that was never committed. git restore and git reset --hard overwrite uncommitted edits in your files, and no log knows about them. So the real safety habit is simple: commit early and often on your branch. You can always squash commits before review; you can’t recover edits you never committed.
Some work-in-progress isn’t ready for a commit, though. Next you’ll park it with git stash, and stop Git from tracking files it never should.
Key takeaways
- Pick the undo by where the mistake lives (working tree, index, local commit or shared commit), not by which command you remember.
git revert <commit>adds a new commit that cancels an old one, which is the safe way to undo anything already pushed.git resetmoves the current branch:--softkeeps changes staged,--mixedkeeps them unstaged,--harddiscards them.git commit --amendreplaces the last commit, so use it only before pushing.git refloglists where HEAD has been, so commits lost to a reset or rebase can be found and restored for weeks afterwards.