Git stash, reset and revert without losing work
Save your code before you panic
Every developer knows the sinking feeling in their chest right after hitting Enter on a destructive Git command. You wanted to clean up your workspace or undo a broken commit, but your changes vanished.
Git’s undo commands—stash, reset, and revert—are among the most powerful tools in version control. They are also the fastest way to accidentally hide, overwrite, or detach your work if you run them on autopilot.
This tutorial is for developers who know basic Git commands (add, commit, push) but feel anxious whenever they need to change history, set code aside, or unbreak a branch. You will learn what each command actually touches under the hood, how to choose the right one, and how to rescue your files when things go sideways.
Part 1: Git Stash (The temporary shelf)
git stash takes uncommitted modifications in your working directory and staging index, stores them on an internal stack, and returns your working tree to match the clean HEAD commit.
Use git stash when you are in the middle of a feature and need to switch branches immediately to fix a production bug, but your code is not ready for a commit.
# Stash your changes with an explicit label
git stash push -m "WIP: payment webhook listener"
# Include untracked files (new files you haven't run git add on)
git stash push -u -m "WIP: payment listener and config file"
Applying vs. Popping
Once you return to your branch, you have two ways to retrieve that work:
git stash pop: Applies the most recent stash and immediately deletes it from the stash list if there are no conflicts.git stash apply: Applies the stash but leaves the copy in your stash list.
git stash apply is safer. If applying your stash causes massive merge conflicts, the stash remains untouched in your list as a backup until you drop it manually.
# View all stashed items
git stash list
# Apply the most recent stash without deleting it
git stash apply stash@{0}
# Once you verify everything works, remove it manually
git stash drop stash@{0}
Part 2: Git Reset (Rewinding local history)
git reset moves your current branch pointer backward to a specific commit in history. What happens to your files during that rewind depends entirely on the flag you pass.
Git tracks your files across three trees:
- Working Directory: The actual files on your hard drive that you edit.
- Staging Index (
Index): The snapshot prepared for the next commit (git add). - Commit History (
HEAD): The committed snapshots in your.gitrepository.
Here is how the three main flags handle these areas:
| Flag | Moves HEAD? |
Touches Staging Index? | Touches Working Directory? | Safe to use? |
|---|---|---|---|---|
--soft |
Yes | No (files stay staged) | No (files left intact) | Very safe |
--mixed (default) |
Yes | Yes (files become unstaged) | No (files left intact) | Safe |
--hard |
Yes | Yes (wipes staging) | Yes (overwrites modified files) | Dangerous |
When to use each reset mode
Use git reset --soft HEAD~1 when you committed too early, forgot a file, or made a typo in your commit message. It rewinds the commit, but leaves all your changes staged and waiting.
# Undo the last commit, but keep everything staged
git reset --soft HEAD~1
# Make your fix, then commit again cleanly
git commit -m "feat: complete user authentication flow"
Use git reset --mixed HEAD~1 (or just git reset HEAD~1) when you want to split a large commit into smaller, focused commits. It rewinds the commit and unstages everything, but leaves your files intact in your editor.
Use git reset --hard HEAD~1 only when you deliberately want to throw away the last commit and all local changes associated with it.
Warning: Never use
git reseton commits you have already pushed to a shared remote branch (likemainordevelop). Reset rewires history, which causes conflicts for everyone else collaborating on that branch.
Part 3: Git Revert (The safe, collaborative undo)
If you have already pushed a bad commit to a shared GitHub repository, git reset is out of the question. You need git revert.
Instead of deleting the bad commit from history, git revert inspects the commit you point to, figures out the exact opposite diff, and creates a brand-new commit that cancels it out.
# Revert the most recent commit
git revert HEAD
# Revert a specific commit from earlier history
git revert a1b2c3d
Because git revert only moves history forward, it is completely safe for team workflows.
Summary: Stash vs. Reset vs. Revert
| Command | Primary Use Case | Modifies Existing History? | Safe for Shared Branches? |
|---|---|---|---|
git stash |
Shelve uncommitted work temporarily | No | Yes |
git reset |
Rewrite or undo local commits | Yes | No (Local branches only) |
git revert |
Reverse the effects of an old commit | No (adds new commit) | Yes |
Hands-on example: Recovering from a “Disaster”
Let’s walk through an actual disaster scenario: you run git reset --hard by mistake and think your work is gone forever. Then, we will rescue it using git reflog.
Run these commands in an empty test directory:
# 1. Initialize a throwaway repo
mkdir git-recovery-demo
cd git-recovery-demo
git init -b main
# 2. Make an initial stable commit
echo "console.log('App running');" > app.js
git add app.js
git commit -m "feat: initial stable app"
# 3. Add critical new work and commit it
echo "console.log('Important billing code');" >> app.js
git commit -am "feat: add payment processing logic"
# 4. Check log to confirm both commits exist
git log --oneline
Now, simulate the mistake: you intended to run --soft, but accidentally executed --hard:
# ACCIDENTAL DESTRUCTIVE ACTION
git reset --hard HEAD~1
# Check the file: your payment processing code is gone!
cat app.js
git log --oneline
Your branch pointer moved back, and app.js is back to its original state. Here is how to recover it:
# 5. Open the reflog (Git's audit journal for HEAD movements)
git reflog
You will see output resembling this:
7c841a2 (HEAD -> main) HEAD@{0}: reset: moving to HEAD~1
e41a9bf HEAD@{1}: commit: feat: add payment processing logic
7c841a2 (HEAD -> main) HEAD@{2}: commit (initial): feat: initial stable app
Git never immediately deleted that second commit. It only disconnected main from it. Notice HEAD@{1} points to feat: add payment processing logic at commit e41a9bf.
Run the rescue command:
# 6. Hard reset back to the commit hash identified in the reflog
git reset --hard e41a9bf
# 7. Verify your files are fully restored
cat app.js
git log --oneline
Your lost payment processing logic is back.
Common mistakes and troubleshooting
1. You dropped a stash by accident (git stash drop)
If you accidentally dropped a stash using git stash drop, you can find its lingering object in Git’s loose object database:
# Scan for dangling commits
git fsck --no-reflog | grep commit
Run git show <hash> on each resulting commit until you spot your stash message. Once found, recover it with:
git stash apply <hash>
2. Ran git reset --hard with uncommitted changes
git reflog tracks commits, not uncommitted files. If you had modified files that you never added to the index (git add), Git has no record of them, and git reset --hard deletes them permanently.
The fix: Always run git add . before experimenting with dangerous commands. If a file was added to the index at least once, Git wrote it as a blob object, and it can often be recovered via git fsck --lost-found.
3. Merge conflicts when popping a stash
If your working branch changed significantly while your code was stashed, git stash pop may throw conflict markers.
The fix: Do not panic. Git halts the pop process and keeps the stash safe in your list. Open the conflicted files, resolve the markers (<<<<<<<, =======, >>>>>>>), stage the files with git add, and then manually drop the stash with git stash drop.
Alternatively, turn your stash directly into a new branch:
git stash branch recovery-branch stash@{0}
4. Git revert fails on a merge commit
Running git revert <merge-commit-hash> fails with: error: commit is a merge but no -m option was given.
The fix: A merge commit has two parent histories. You must tell Git which parent branch should remain the “mainline” using the -m flag (usually parent 1 for the branch you merged into):
git revert -m 1 <merge-commit-hash>
Try it yourself
Create a scratch repository and intentionally break it to build muscle memory:
- Create a repository with a file named
notes.txtand commit it. - Stash a second line of text using
git stash push -m "test-stash". - Clear your stash with
git stash clear. - Recover that stashed content using
git fsck --lost-foundandgit mergeorgit show.
Doing this once in a safe sandbox eliminates 90% of the anxiety when a real emergency hits your production codebase.
What’s next
- Practice using
git refloganytime a branch disappears or a commit vanishes. - Look into
git restore, the modern Git command designed to replace confusing legacy uses ofgit checkoutandgit reseton individual files. - In our next tutorial, we will break down interactive rebase (
git rebase -i)—how to squash, reorder, and edit commits cleanly before opening your GitHub pull request.
Bookmark this page so you have the recovery commands handy the next time a Git command does not go as planned.
Diagrams

A horizontal flow showing three zones (Working Directory, Staging Index, Commit History) with arrows for ‘stash’ (sweeping working dir/staging into a stack), ‘reset’ (moving branch pointers backward across history, staging, and working dir based on –soft, –mixed, –hard), and ‘revert’ (appending a brand-new commit that inverts a previous commit’s diff).

A reflog timeline tree displaying HEAD moving from commit C to B via ‘git reset –hard HEAD~1’, leaving commit C detached. A dashed recovery arrow points from ‘git reflog’ output back to commit C to restore it via a temporary branch.
This tutorial was drafted with AI assistance and verified with runnable examples. Found a mistake? Email the author — corrections are welcome.