Stashing & Work-in-Progress Recovery Jump to heading
git stash is the most-used command people least understand. It looks like a clipboard and is actually a stack of commit objects hanging off a ref with no branch pointing at it β which explains both its convenience and every way it loses work. Understanding what it stores makes the failure modes predictable, and knowing the alternatives means reaching for it only where it is genuinely the right tool. This part of Conflict Resolution & Safe Merge Operations covers the mechanism, the alternatives and the recovery routes.
Prerequisites Jump to heading
What a Stash Actually Is Jump to heading
Each stash entry is a merge commit with two or three parents: the commit you were on, a commit holding the index state, and β when untracked files are included β a third holding those. The entries live on refs/stash, and only the most recent is the refβs tip; the rest exist solely as reflog entries on that ref.
Two consequences follow immediately. Untracked files are not included unless you ask, because they are not part of any of the first two parents. And a dropped stash is recoverable, because the commits remain in the object database until garbage collection removes them.
Step 1 β Stash Deliberately, With a Message Jump to heading
The default entry is labelled with a branch name and a commit subject, which is indistinguishable from every other entry a week later.
git stash push -m 'refund window: half-done clamp, needs the config lookup' # Include untracked files, which the default silently leaves behind
git stash push -u -m 'new adapter files plus wiring' # Stash only some paths, leaving the rest in the working tree
git stash push -m 'just the migration' -- db/migrations/ # Verification: what is in the stack, and what does each entry contain?
git stash list
git stash show --stat stash@{0} The -u flag is the one that prevents most surprises: a stash taken without it, followed by a git clean, deletes the untracked files permanently and they were never in the stash to begin with.
Step 2 β Prefer apply Over pop While Anything Is Uncertain Jump to heading
pop applies and then drops. If the apply conflicts, some versions leave the entry in place and some do not, and the difference matters when the apply went badly.
git stash apply stash@{0} # leaves the entry on the stack
# ... verify the result, run the tests ...
git stash drop stash@{0} # only once you are satisfied # Verification: the entry still exists after apply
git stash list | head -1 Step 3 β Know the Three Alternatives and When Each Is Better Jump to heading
The stash is one of four ways to set work aside, and it is the least durable.
For an interruption of minutes, the stash is right. For anything that will outlast the day, a commit on a branch is strictly better: it is durable, it can be pushed, and it can be amended into its final shape later. The comparison is worked through in stash vs worktree vs WIP commit.
Step 4 β Recover From the Two Common Losses Jump to heading
Both have the same underlying answer: the commits still exist.
# A dropped stash: the reflog of refs/stash holds the entry
git reflog stash
git stash apply "$(git reflog stash --format='%H' | sed -n '2p')" # A stash dropped after the reflog entry is gone: search dangling commits
git fsck --unreachable --no-reflogs | awk '$2=="commit" {print $3}' | while read -r c; do
git log -1 --format='%h %ad %s' --date=short "$c"
done | head -20 # Verification: the recovered commit's tree contains what you expect
git show --stat "$recovered" SAFETY WARNING β recovery depends on garbage collection not having run.
git gc --prune=nowremoves unreachable objects immediately, and an aggressive automatic collection can do the same. If you have just lost a stash, do not run maintenance commands, do not clone over the repository, and recover before doing anything else; the detailed procedure is in recovering a dropped stash.
Step 5 β Handle a Conflicting Pop Without Losing the Entry Jump to heading
A pop that conflicts leaves conflict markers in the working tree and a decision to make.
git stash pop
# CONFLICT (content): Merge conflict in src/payments/refund.ts # The entry usually survives a conflicted pop β check before doing anything
git stash list # Abort cleanly, restoring the pre-pop state
git checkout --merge -- . # or resolve, then: git stash drop # Verification: the working tree is back to where it was
git status --short | head The full procedure, including the case where the entry was dropped despite the conflict, is in resolving conflicts when popping a stash.
Habits That Make the Stash Safe Jump to heading
Three habits remove most of the ways the stash loses work, and none of them cost anything.
The first is always passing -m. A stack of six entries labelled WIP on main: 4f2a1b fix tests is a stack nobody will unpick, and the usual outcome is that all six are dropped together during a tidy-up. A one-line description turns the stack into something with a reviewable inventory.
The second is treating the stash as same-day storage. Anything still stashed tomorrow should become a commit on a branch, because the stash has no backup, does not push, and is invisible to every tool that might otherwise protect it. A branch named wip/refund-window pushed to the remote survives a lost laptop, a repository reclone and an accidental git stash clear, and it costs one command more.
The third is checking git stash list before any destructive operation. git stash clear and git reset --hard are both routine and both remove things people did not intend to remove. A glance at the list first is the whole safeguard.
Finally, be aware of what the stash does not capture even with -u: ignored files. git stash push -a includes them, which is occasionally what you want and frequently catastrophic, because it sweeps up build output and dependency directories into a stash entry that then takes a minute to apply. Prefer -u and keep ignored files out of it.
Configuration Reference Jump to heading
| Command or option | Effect | When to use |
|---|---|---|
git stash push -m | Labels the entry | Always |
git stash push -u | Includes untracked files | Whenever new files are part of the work |
git stash push -a | Includes ignored files too | Almost never β sweeps up build output |
git stash push -- <path> | Stashes only the given paths | Splitting work in progress |
git stash apply | Applies without dropping | Whenever the result is uncertain |
git stash branch <name> | Creates a branch from the entry | Recovering a stash onto a clean base |
git reflog stash | History of the stash ref | Recovering a dropped entry |
git fsck --unreachable | Finds orphaned commits | When the reflog entry is gone too |
Troubleshooting Jump to heading
| Symptom | Likely cause | Fix |
|---|---|---|
| Untracked files missing after a stash | -u was not used | Recover from the working tree; use -u next time |
pop conflicted and the entry vanished | Version-dependent behaviour | Recover from git reflog stash |
| Stash applies to the wrong branch | Applied after switching branches | Use git stash branch to land it cleanly |
stash clear removed wanted entries | No labels, so the stack looked stale | Recover via fsck; always label entries |
| Applying a stash reintroduces old code | The entry predates several merges | Create a branch from it and rebase |
| Stash contains build output | -a was used | Use -u; keep ignored files out |
Frequently Asked Questions Jump to heading
Why does the stash not include untracked files by default? Jump to heading
Because the entry is built from HEAD and the index, and an untracked file is in neither. The -u flag adds a third parent commit specifically to carry them, which is why it is a separate option rather than the default β the default behaviour predates that mechanism.
Is a stash pushed to the remote? Jump to heading
No. refs/stash is local and is not included in a normal push, so a stash exists on exactly one machine with no backup. That is the single strongest argument for using a branch for anything you would be upset to lose.
Can several stashes be applied at once? Jump to heading
Only in sequence, and each may conflict with the result of the last. If you find yourself doing this routinely, the work wants to be branches rather than stash entries β the stack is not designed as a queue of parallel work.
What happens to a stash when I switch branches? Jump to heading
Nothing β it stays on the stack and can be applied to any branch. That is convenient and is also how work gets applied to the wrong branch; git stash branch avoids it by creating a branch from the entryβs original base.
Should hooks run when stashing? Jump to heading
They do not, and that is worth knowing in both directions. git stash creates commits without running pre-commit or commit-msg, so a stash can capture work that your hooks would have rejected β unformatted code, a secret, a message that violates the convention. That is the right behaviour for a temporary snapshot, and it becomes a problem only if the stash is later turned into a commit without the hooks getting a chance to run.
The practical consequence is that applying a stash and committing immediately with --no-verify, which people do when in a hurry, bypasses every check twice over. Apply the stash, let the ordinary commit path run, and let the hooks do their job on the result. If a hook then rejects the work, that rejection is information about the code rather than an obstacle created by the stash.
There is a related subtlety with git stash branch. It creates a branch from the entryβs original base and applies the stash there, which is the cleanest way to turn an old stash into real work β the base is right, so the apply rarely conflicts, and what you end up with is an ordinary branch that behaves like any other.
Related Jump to heading
- Recovering a Dropped Stash β the reflog and fsck routes, step by step.
- Moving Uncommitted Work to the Right Branch β when you started on the wrong one.
- Stash vs Worktree vs WIP Commit β choosing the right tool for the length of the interruption.
- Resolving Conflicts When Popping a Stash β what to do when the apply does not go cleanly.