It was one of those days — I was mid-way through fixing a payment feature, about 8 files in various states of change. My manager messaged: “fix the login bug ASAP, customers can’t get in.” I did a git stash, jumped to main, fixed the bug, came back — and realized the stash included local test config files and some temporary debug console.log statements that had nothing to do with payment. Applying it caused conflicts in all the wrong places.
Basic git stash is something everyone knows. But after 6 months of real-world use on a team, I found 3 techniques that, without knowing them, will get you into trouble: partial stashing with -p, naming stashes so you don’t mix them up, and recovering a stash after accidentally dropping it.
How Git stores stashes — it’s not “hiding” anything
Understanding this mechanism is what explains why you can recover a dropped stash — and why you have to act fast. Git doesn’t “hide” your changes anywhere — every time you run git stash, Git creates 2–3 hidden commits under refs/stash:
- One commit for staged changes
- One commit for unstaged changes
- One commit for untracked files (if you used
-u)
# List all stashes
git stash list
# stash@{0}: WIP on feature/payment: a3f2b1c Add payment form
# stash@{1}: WIP on main: 9d8c4e5 Fix login bug
# Preview the changes in a stash before applying
git stash show -p stash@{0}
These orphaned commits live in Git’s object database until garbage collection runs. That’s why recovery is possible — and also why you need to act quickly.
git stash -p — Stash only part of your changes
That unnecessary conflict I mentioned is what sent me looking for -p. This flag (short for --patch) lets you choose individual “hunks” — small chunks of changes — to either stash or keep in the working tree.
git stash -p
Git walks you through each hunk and asks:
diff --git a/src/payment.js b/src/payment.js
@@ -45,7 +45,12 @@ function validateCard(card) {
+ if (!card.cvv) {
+ throw new Error('CVV required');
+ }
Stash this hunk [y,n,q,a,d,s,?]?
The most useful options:
y— stash this hunkn— skip it, keep it in the working trees— split the hunk into smaller pieces (very useful for large hunks)q— quit and apply everything selected so far?— show full help
A real-world example: I was working on a payment feature while also sprinkling in some debug console.log statements. With git stash -p, I stashed just the debug lines and kept the feature code — no need to stash everything.
Naming stashes and stashing specific files
Name your stashes to avoid confusion
After a few weeks of unnamed stashes, my list looked like this:
stash@{0}: WIP on feature/payment: a3f2b1c ...
stash@{1}: WIP on feature/payment: a3f2b1c ...
stash@{2}: WIP on feature/payment: a3f2b1c ...
No way to tell them apart. Once I started using -m:
git stash push -m "payment: validate CVV logic"
git stash push -m "debug: temp console.log for card parser"
Much clearer:
stash@{0}: On feature/payment: debug: temp console.log for card parser
stash@{1}: On feature/payment: payment: validate CVV logic
Stash specific files
If you only want to stash 1–2 files instead of walking through hunks with -p:
git stash push -m "only payment validator" -- src/payment.js src/validators/card.js
Include untracked files
By default, git stash ignores newly created (untracked) files. Add -u to include them:
git stash push -u -m "include new files too"
Recovering a dropped stash with git fsck
I once accidentally ran git stash drop on the wrong stash — nearly 2 hours of work, gone. Or so I thought. That’s the day I learned this technique.
A stash is a hidden commit. As long as Git hasn’t run garbage collection, the dangling commit is still in the object database:
# Find all "orphaned" commits (no ref pointing to them)
git fsck --lost-found | grep commit
Output:
dangling commit a1b2c3d4e5f67890...
dangling commit f9e8d7c6b5a43210...
Inspect each commit to find the right one:
git show a1b2c3d4e5f67890
Once you’ve found it, apply it:
git stash apply a1b2c3d4e5f67890
Important caveat: The recovery window isn’t unlimited. Git runs garbage collection periodically — by default after 2 weeks or when the number of loose objects exceeds ~6,700 — after which orphaned commits are permanently gone. I recovered mine because I caught it within a few hours.
Prevention tip: before dropping, save the hash:
# Get the hash of a stash before dropping it
git rev-parse stash@{0}
# a1b2c3d4e5f67890... ← save this
git stash drop stash@{0}
# If you regret it within the next few hours:
git stash apply a1b2c3d4e5f67890
Real-world team workflow
On an 8-person team, before we adopted these practices, I counted 3–5 “inexplicable” conflicts every week — the kind caused by stashing the wrong things or applying stashes in the wrong context. After agreeing on the rules: always name your stashes, use -p for mixed changes, copy the hash before dropping — that number dropped to 0–1.
Two patterns I use most often:
Pattern 1: Stash + create a quick branch to try something out
# Mid-feature, want to quickly test an idea
git stash push -m "feature/payment in progress"
git checkout -b experiment/try-new-approach
# Experiment didn't pan out, go back
git checkout feature/payment
git stash pop
Pattern 2: Move changes to the right branch
# Accidentally coded on main instead of a feature branch
git stash push -m "oops, this should be on a feature branch"
git checkout -b feature/new-feature
git stash pop
Accidentally coded on main instead of a feature branch — I use this one at least once a week.
A few more useful commands
# Apply a stash but KEEP it in the list (unlike pop, which removes it)
git stash apply stash@{1}
# Create a new branch directly from a stash
git stash branch new-branch-name stash@{0}
# Clear the entire stash list
git stash clear
Wrapping up
I used Git for over a year before I knew -p existed. It’s in the documentation, but basic tutorials never mention it — so most people just walk right past it. I stumbled onto git fsck by accident while frantically searching Stack Overflow after dropping the wrong stash.
Give git stash -p a try — once you get used to it, you’ll find it uncomfortable to stash an entire file just because of a few debug lines you don’t want to commit.
