There is no single way to undo work in Git, and confusing git reset revert with git stash is one of the most common sources of trouble. All three feel like "undo," yet they do very different things: one shelves changes for later, one rewinds history, and one creates a new commit that reverses a change without touching the past. Knowing which to reach for is the difference between losing an evening of work and keeping a clean history.
Three commands, three different goals
Let's fix the mental model first. Git has three areas: the working directory, the staging area (index) and the commit history (HEAD). What separates these commands is which of those areas they touch.
git stash— sets aside uncommitted changes in your working directory and index, returning the tree to the last commit. Nothing is destroyed; you can bring it back later.git reset— moves HEAD (and optionally the index and working directory) to another commit. It rewrites history.git revert— creates a new commit that undoes the effect of an earlier one. History stays intact, with an "undo" added on top.
git stash: parking work-in-progress safely
You're halfway through a feature when you suddenly need to switch branches and ship an urgent fix, but your current work isn't ready to commit. That's exactly what git stash is for.
git stash push -m "half-finished filter form"
# tree is clean now, switch branches, do the hotfix...
git stash list
git stash pop # restore the latest stash and drop it from the list
git stash apply # restore but keep it in the list
Use git stash -u to include untracked files too. When juggling several stashes, you can target one by index, e.g. git stash pop stash@{1}. A stash is like a temporary pocket: you set work aside without committing or dirtying the branch. It is not a long-term storage mechanism, though — instead of leaving work stashed for days, opening a WIP commit is safer.
git reset: rewinding HEAD
git reset is the most powerful and most misunderstood command. Its three modes are three levels of severity:
--soft— moves HEAD only. Your changes stay in the index, as if you had just rungit add. Ideal for squashing the last few commits into one.--mixed(the default) — moves HEAD and resets the index. Changes remain in the working directory as "unstaged."--hard— resets HEAD, the index and the working directory at once. Anything uncommitted is gone.
# Squash the last 3 commits into one (without losing the changes)
git reset --soft HEAD~3
git commit -m "filter feature"
# Unstage a file you added by mistake (content untouched)
git reset HEAD file.php
# CAUTION: force the working tree back to the last commit
git reset --hard HEAD
The golden rule: only use git reset on commits you haven't shared (pushed) yet. If you drop commits with reset on a shared branch and push with --force, everyone else's history diverges from yours.
git revert: safely undoing shared history
If a bad commit already landed on main and others have pulled it, you can't erase history. git revert is the right tool here: it adds a new commit that reverses everything the bad commit did.
# Revert a specific commit
git revert a1b2c3d
# Revert a merge commit — specify which mainline to keep
git revert -m 1 <merge-sha>
# Apply the inverse change without creating a commit
git revert --no-commit a1b2c3d
Revert doesn't break history, which makes it the safest choice in teamwork and on production branches. Instead of rewriting the timeline for a mistake everyone can see, you leave an honest record that says "I reverted this commit."
Which one, when?
- Set aside half-done work to switch branches → stash
- Squash or tidy commits you haven't pushed yet → reset --soft or
git commit --amend - Remove a wrongly staged file from the index → reset (mixed)
- Roll everything locally back to the last commit → reset --hard (be careful)
- Undo a shared/pushed commit → revert
If you deleted something by accident: reflog to the rescue
Commits you think you lost to git reset --hard are usually still there. Git records every move of HEAD in the reflog:
git reflog
# find the sha of the target commit in the output, then:
git reset --hard a1b2c3d
# or pull the lost commit into a new branch
git branch recovery a1b2c3d
Reflog entries are kept for 90 days by default, so before you panic, always check the reflog first.
Frequently Asked Questions
Can I recover files lost to git reset --hard?
If the lost change was committed before, yes: find the commit with git reflog and go back to it. But changes that were never committed and only lived in the working directory are permanently removed by --hard; the reflog can't recover those.
Can I reset after pushing?
Technically yes, with --force, but don't do it on shared branches. If others have pulled those commits, history forks. Use git revert instead; it gives the same result safely.
What's the core difference between stash and reset?
Stash is for saving changes and handing them back later without touching history. Reset actually moves HEAD and the index. If you're parking work "to continue later," think stash; if you're editing history, think reset.
Let's clarify your undo strategy. Want a solid setup for your Git flow, release management or CI/CD pipeline? Get in touch and we'll build the safest workflow for your project together.