Git stash: park unfinished work without losing your place
Half a feature is on disk, a fix is needed on another branch now, and committing the half would put a broken state into history. git stash exists for exactly that gap. It also has a reputation as the place where work goes missing, and that reputation is earned by three specific behaviours: untracked files are skipped by default, the stack is unnamed unless a message is given, and a dropped entry is not covered by the normal safety nets. All three are avoidable once the shape of a stash entry is clear.
A stash entry is a commit, which is why it survives
The mental model that makes everything else predictable is in the discussion section of the manual. A stash entry is not a patch file or a scratch area. It is a commit.
More precisely, it is a merge commit with two parents. The first parent is the commit that HEAD pointed at when the entry was created. The second parent is a commit whose tree records the state of the index at that moment. The stash commit's own tree records the state of the working directory. Saving therefore captures two snapshots, not one, and that is why the index state can be restored separately later.
The latest entry lives in a ref called refs/stash. Older entries are not separate refs: they are earlier positions in the reflog of that one ref. This explains the naming. stash@{0} is the newest, stash@{1} the one before, and because it is a reflog, time based forms such as stash@{2.hours.ago} work too. A bare integer is accepted as shorthand, so 1 means the same as stash@{1}.
The practical consequence is that a stash entry cannot be corrupted by branch switching, and it is not tied to the branch it was made on. It can be applied on top of a different commit entirely. The cost of that flexibility is that the stack is global to the repository, so entries from three different pieces of work sit in one list with no grouping.
Use push, give it a message, and narrow it with a pathspec
git stash with no arguments is equivalent to git stash push. The manual marks save as deprecated in favour of push, and the difference is not cosmetic: save cannot take a pathspec, because all of its non option arguments are concatenated into the message.
The single highest value habit is passing -m with a real message. Without one, the entry is listed as WIP on the branch name plus the subject of the commit it was based on, which is indistinguishable from every other entry made on that branch. Three unnamed entries later, the only way to tell them apart is to inspect each one.
The pathspec form is the underused half. git stash push -m "config experiment" -- config/ stashes only what matches, and rolls back the index and working tree to HEAD only for those paths, leaving everything else exactly as it was. That turns stash from an all or nothing operation into a way of setting aside one thread of work while continuing another in the same tree. For longer lists of paths, --pathspec-from-file reads them from a file, or from standard input when the filename is a single hyphen.
Non option arguments are rejected when push is omitted, and the manual is explicit that this is deliberate: it stops a misspelled subcommand from silently creating an entry instead of doing what was intended.
Untracked and ignored files are left behind by default
This is where work goes missing, and it is not a bug. A plain stash records modifications to tracked files. A file that Git has never seen is neither in the index nor in HEAD, so there is nothing to record and nothing to roll back. It simply stays on disk.
That is harmless until the branch is switched and a build runs, at which point a new file from the parked work is sitting in the tree and being compiled alongside the emergency fix. The symptom is a failure that belongs to neither piece of work.
-u, spelled --include-untracked, adds untracked files to the entry and then removes them from the working tree using git clean. -a, spelled --all, does the same for ignored files as well. Both are worth understanding before use, because the cleanup step is a real deletion of files from the tree, and only the ones recorded in the entry come back.
On the reading side, git stash show -u includes untracked files in the diff, and --only-untracked shows just those. Since the default for showing them is off, an entry made with -u can look smaller than it is.
pop, apply and branch solve three different problems
Restoring looks like one operation with three names. It is not.
| Command | Removes the entry | Use when |
|---|---|---|
git stash pop |
Yes, on success | Restoring the most recent entry and moving on |
git stash apply |
No | The same changes may be needed on more than one branch, or the result is uncertain |
git stash branch <name> |
Yes, after a clean apply | The branch has moved on and applying now conflicts |
pop requires the working directory to match the index. When applying produces conflicts, the entry is deliberately not removed from the list, and the manual states that the conflicts have to be resolved by hand and the entry dropped manually afterwards. That is the safe behaviour, and it also means a list can accumulate entries that were in fact already restored.
apply is the better default while learning, because nothing is lost by running it twice. It also accepts a wider range of arguments: any commit that looks like one created by stash push or stash create, not only a stash@{n} reference.
git stash branch is the answer to the situation people usually reach for force in. It creates and checks out a new branch starting from the commit at which the entry was originally created, applies the entry there, and drops it if that succeeded. Because the entry is applied on top of the exact commit it was made against, the changes go back with no conflicts, and the merge happens afterwards as a normal branch merge.
--index on pop or apply restores the staging area as well as the working tree. Without it, everything comes back as unstaged, which quietly loses the separation between what was ready and what was not. The manual notes it can fail when conflicts exist, since conflicts are themselves stored in the index.
Keeping the index, and stashing only part of the work
Three options shape what stays behind, and they are the difference between stash as a panic button and stash as a working tool.
-k, or --keep-index, leaves whatever is already staged intact in the working tree while still recording it in the entry. The documented use is testing a partial commit: stage one piece with git add --patch, park everything else with --keep-index, build and test what remains, commit it, then restore the rest.
-p, or --patch, selects hunks interactively from the difference between HEAD and the working tree. The selected hunks are what goes into the entry and what is rolled back from the tree. It implies --keep-index, which can be overridden with --no-keep-index.
-S, or --staged, does the opposite of --keep-index: it stashes only what is currently staged. The manual compares it to committing the staged changes, except the commit ends up in the stash rather than on the branch. This is the right tool for an unrelated fix noticed mid task: stage it, park it with --staged, finish the current work, then restore it on the branch where it belongs. Note that --patch takes priority over --staged when both are given.
Reading an entry before restoring it, and recovering a dropped one
git stash list gives one line per entry and accepts the options of git log, so a date column can be added to tell yesterday's entry from last month's.
git stash show displays the difference between the stashed content and the commit it was based on. By default it shows a diffstat, because stash.showStat defaults to true and stash.showPatch defaults to false. Any format git diff understands can be passed, so git stash show -p stash@{1} prints the second entry as a patch. stash.showIncludeUntracked controls whether untracked files appear, and defaults to false.
Recovery is the part worth knowing before it is needed. git stash drop and git stash clear remove entries, and the manual states plainly that they cannot be recovered through the normal safety mechanisms. Cleared entries become subject to pruning. What the manual does document is a way to find entries that are still in the object database but no longer reachable, by listing unreachable commits with git fsck --unreachable and filtering them through git log for the WIP message. It works because a stash entry is a commit, and it stops working once garbage collection has run.
Two habits make that recovery unnecessary. Prefer apply over pop when the outcome is uncertain, and read git stash list before any drop. For work that is mostly files rather than mostly code, keeping the folder and the shell in one place makes the parked state visible instead of remembered: that is what a file manager with a built in terminal is for, and how it differs from the usual split across windows is set out in the comparison page.
When a stash is the wrong tool for the job
Stash is the right answer to a short interruption. It is a poor answer to three situations that look similar from the outside.
The first is work that will be parked for more than a day or two. A stash entry has no name in the branch namespace, no upstream, and no place in git log, so it is invisible to every habit built around branches. A throwaway branch and an ordinary commit with a message that starts with WIP is easier to find a week later, and the commit can be reshaped with git commit --amend or unwound with git reset --soft HEAD^ when the work resumes. Nothing about a stash entry is safer than that, since both are commits in the same object database.
The second is needing two states on disk at the same time. Stash restores one state and hides the other, so a comparison between the parked work and the fix means restoring, looking, and parking again. git worktree add ../hotfix attaches a second working tree to the same repository and checks out another branch there, which means two folders, two states, and no stashing. The manual describes the main working tree and any number of linked worktrees sharing one repository, removed afterwards with git worktree remove. On a Mac this is often the quieter option, because the second folder can simply be opened in a second window.
The third is scripting. git stash create produces the entry as a commit object and prints its name without touching the ref namespace, and git stash store puts such a commit into the stash ref afterwards. The manual marks both as intended for scripts and says plainly that they are probably not the command wanted interactively. Reaching for them by hand is a sign that a plain commit was the right move.
What to change first
Stop running the bare command. Make it git stash push -u -m "<what this is>", so untracked files travel with the entry and the list stays readable a week later. Then switch from pop to apply followed by an explicit drop, and when an apply conflicts, reach for git stash branch instead of resolving by hand. If the folder, the terminal and the parked work live in three separate windows, Atriens is one way to put them in one.
Frequently asked questions
Why did a new file disappear from the working tree instead of being stashed?
It did not disappear, and it was not stashed. Untracked files are excluded by default, so the file stayed on disk while the tracked changes were rolled back. Add -u to include untracked files in the entry, or -a to include ignored files as well. Both remove the included files from the tree afterwards.
How long does a stash entry last?
Indefinitely, as long as it is not dropped or cleared. Entries are commits reachable from refs/stash and its reflog, so they survive branch switching, checkouts and restarts. What ends them is git stash drop, git stash clear, or a successful pop.
What is the difference between git stash pop and git stash apply?
pop restores the entry and removes it from the list when that succeeds. apply restores it and leaves it in the list. Use apply when the same changes might be needed on another branch or when the result is uncertain, then drop the entry once the outcome is confirmed.
Can a stash dropped by mistake be recovered?
Sometimes, but it is not guaranteed. The documentation states that dropped or cleared entries are not covered by the normal safety mechanisms. The documented last resort is to list unreachable commits with git fsck --unreachable and search them for the WIP message, which only works before garbage collection removes them.
Why are the restored changes unstaged when some of them were staged before?
Because the index is not restored unless asked for. Pass --index to pop or apply to reinstate the staging area along with the working tree. It can fail when conflicts are present, since conflicts are themselves recorded in the index.