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.

What a single stash entry storesA stash entry is a commit whose first parent is HEAD, whose second parent holds the staged state, and whose optional third parent holds untracked files. Dropping the entry removes the reference, not the commits, which is why recovery is possible.one entry, two or three commitsHEADCindex stateIstash entryWuntrackedUW is the working-tree state; its parents are C, I and optionally U

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.

Four ways to put work downA stash is quick and invisible to everyone else. A scratch commit on a branch is durable and pushable. A worktree keeps the work checked out somewhere else entirely. Each suits a different length of interruption.Stashsecondsno branchlocal onlyScratch commitdurablepushableamendableWorktreestays checked outno context switchBranch + pushsurvives a lost laptopvisible to othersthe first column is the only one with no backup anywhere

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=now removes 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.

How long work should stay in a stashFor a few minutes the stash is ideal: fast, invisible and easy to restore. Past a day it is storage with no backup on one machine, and past a week the entry is almost always dropped without anyone checking what was in it.Idealfast, low riskminutesFinestill rememberedsame dayMove to a branchno backup existsa day or moreUsually lostdropped in a tidy-upa weekthe transition at the third point costs one command and removes every failure mode

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 optionEffectWhen to use
git stash push -mLabels the entryAlways
git stash push -uIncludes untracked filesWhenever new files are part of the work
git stash push -aIncludes ignored files tooAlmost never β€” sweeps up build output
git stash push -- <path>Stashes only the given pathsSplitting work in progress
git stash applyApplies without droppingWhenever the result is uncertain
git stash branch <name>Creates a branch from the entryRecovering a stash onto a clean base
git reflog stashHistory of the stash refRecovering a dropped entry
git fsck --unreachableFinds orphaned commitsWhen the reflog entry is gone too

Troubleshooting Jump to heading

SymptomLikely causeFix
Untracked files missing after a stash-u was not usedRecover from the working tree; use -u next time
pop conflicted and the entry vanishedVersion-dependent behaviourRecover from git reflog stash
Stash applies to the wrong branchApplied after switching branchesUse git stash branch to land it cleanly
stash clear removed wanted entriesNo labels, so the stack looked staleRecover via fsck; always label entries
Applying a stash reintroduces old codeThe entry predates several mergesCreate a branch from it and rebase
Stash contains build output-a was usedUse -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.