How do you manage multiple stashes, apply them selectively, and handle stash conflicts in complex workflows?
Quick Answer
git stash saves uncommitted changes to a stack, letting you switch context without committing half-done work. Use git stash list to see all stashes, git stash apply stash@{N} to apply a specific one, git stash pop to apply and remove, and git stash branch to create a branch from a stash. Stash conflicts are resolved like merge conflicts.
Detailed Answer
Think of git stash like a set of labeled drawers beside your workbench. When a colleague interrupts you with an urgent bug, you sweep your current work-in-progress into a drawer, label it with a description, and start fresh on the bug. When the bug is fixed, you pull your work back from the drawer and continue where you left off. You can have multiple drawers with different projects, and you can peek into any drawer without pulling everything out.
Git stash saves both staged and unstaged modifications (and optionally untracked files) to a stack of saved states, then reverts your working directory to a clean state matching HEAD. Each stash entry is stored as a special commit object with two or three parents: one for the HEAD commit, one for the index state, and optionally one for untracked files. The stash list operates as a LIFO stack: stash@{0} is the most recent, stash@{1} the previous, and so on. You apply a stash with git stash apply (keeps the stash in the list) or git stash pop (removes it after successful application).
Internally, git stash creates actual commit objects that are referenced by the refs/stash ref. Running git stash is equivalent to committing the index state, then committing the working tree state with the HEAD and index commits as parents. With --include-untracked, a third commit captures untracked files. These commits are not on any branch but are reachable through the stash reflog. The reflog at .git/logs/refs/stash tracks all stash entries. Because stashes are real commits, they can be cherry-picked, diffed against branches, and even pushed to a remote (though this is unusual). The git stash branch command creates a new branch from the commit where the stash was created and applies the stash there, which is useful when the working tree has changed too much for a clean apply.
In production workflows, stash management becomes important during rapid context-switching. A platform engineer might stash payment-retry changes to investigate a production alert, stash the investigation notes to review a colleague's PR, then need to return to the original work. Best practices include: always use git stash push -m "descriptive message" so you can identify stashes later, use git stash list and git stash show -p stash@{N} to inspect contents before applying, prefer git stash branch over apply when significant time has passed (the codebase may have diverged), and clean up old stashes with git stash drop stash@{N} or git stash clear. Some developers avoid stash entirely, preferring WIP commits on feature branches (git commit -m 'WIP: save context') that they later amend or interactive-rebase, arguing that WIP commits are more visible and less likely to be forgotten.
A critical gotcha: stash conflicts happen when the code has changed since the stash was created. Git applies the stash using merge machinery, and conflicting hunks get conflict markers just like a merge. You must resolve them manually, then git add the resolved files (stash pop will not auto-remove the stash entry if conflicts occurred). Another trap: git stash by default does not stash untracked files or ignored files. Use --include-untracked (-u) or --all (-a) to capture those. Also, old stashes can become nearly impossible to apply cleanly if weeks pass and the codebase evolves significantly. Treat stash as a short-term parking lot, not long-term storage.