Git Stash: Complete Guide to Saving Work in Progress
You are halfway through implementing a feature when a critical bug report comes in. Your working directory is full of uncommitted changes, and you need a clean slate to start the hotfix. This is exactly where git stash saves the day. It shelves your work-in-progress changes, gives you a clean working tree, and lets you come back to your work later as if you never left.
This guide covers every aspect of git stash: the basic workflow, advanced options like partial stashing and branch creation, how stash works internally, and the real-world workflows that make it indispensable for day-to-day development. If you’re specifically trying to undo file changes or unstage a file (undo git add), also see Git Restore.
Table of Contents
- What Is Git Stash and When to Use It
- Basic Stash Workflow
- git stash push with Messages
- git stash list and Navigating Stashes
- git stash pop vs apply
- git stash show: Viewing Stashed Changes
- Stashing Specific Files
- Stashing Untracked and Ignored Files
- Partial/Interactive Stash (-p)
- Creating Branches from Stash
- Stash Conflicts and Resolution
- git stash drop and clear
- Advanced: Stash Internals
- Real-World Workflows
- Common Pitfalls and Best Practices
- FAQ
What Is Git Stash and When to Use It
Git stash takes your uncommitted changes — both staged (in the index) and unstaged (in the working directory) — saves them on a stack, and reverts your working directory to match the HEAD commit. Think of it as a clipboard for your in-progress work.
Common scenarios where stash is essential:
- Switching branches — Git refuses to switch if your uncommitted changes would conflict with the target branch. Stash first, switch, and come back later.
- Pulling remote changes — You need a clean working tree to pull, but you are not ready to commit your current work.
- Urgent hotfixes — A production bug needs your attention right now, but you are midway through a feature.
- Experimenting safely — Try something risky, stash the result, and decide later whether to keep it.
- Code review prep — Clean up your working directory before reviewing someone else's branch.
Stash is not a substitute for commits. It is a temporary holding area. If you stash something and forget about it for weeks, you are doing it wrong. Commit early and often; use stash for short-lived context switches.
Basic Stash Workflow
The simplest stash workflow is three commands: stash your changes, do something else, and pop them back.
# You're working on a feature and have uncommitted changes
$ git status
On branch feature/login
Changes not staged for commit:
modified: src/auth.js
modified: src/api.js
# Stash everything
$ git stash
Saved working directory and index state WIP on feature/login: a1b2c3d Add login form
# Working directory is now clean
$ git status
On branch feature/login
nothing to commit, working tree clean
# Switch to another branch, fix a bug, whatever you need
$ git checkout main
$ git checkout feature/login
# Bring your changes back
$ git stash pop
On branch feature/login
Changes not staged for commit:
modified: src/auth.js
modified: src/api.js
Dropped refs/stash@{0}
When you run git stash with no arguments, Git saves both staged and unstaged tracked changes. After popping, all changes come back as unstaged, regardless of whether they were staged before. To preserve the staging state, use the --index flag:
# Preserve staged vs unstaged distinction
$ git stash pop --index
git stash push with Messages
The bare git stash command generates a default message like WIP on branch: hash message. When you have multiple stashes, those messages are useless. Always add a descriptive message:
# Stash with a meaningful message
$ git stash push -m "login form validation - half done"
Saved working directory and index state On feature/login: login form validation - half done
# Another stash with context
$ git stash push -m "API rate limiting experiment"
The push subcommand is the modern form (Git 2.13+). The older git stash save "message" syntax still works but is deprecated. Always use push for new work.
# Deprecated (still works, but avoid)
$ git stash save "my message"
# Modern equivalent
$ git stash push -m "my message"
You can also stash only staged changes with --staged (Git 2.35+):
# Stash only what's in the index (staged)
$ git stash push --staged -m "staged changes only"
git stash list and Navigating Stashes
Stashes are stored on a stack (LIFO). Each stash gets a reference like stash@{0}, stash@{1}, etc. The most recent stash is always stash@{0}.
$ git stash list
stash@{0}: On feature/login: API rate limiting experiment
stash@{1}: On feature/login: login form validation - half done
stash@{2}: WIP on main: e5f6a7b Update README
You can reference any stash by its index when running pop, apply, show, or drop:
# Apply the second stash (index 1) without removing it
$ git stash apply stash@{1}
# Pop a specific stash
$ git stash pop stash@{2}
# Show what's in a specific stash
$ git stash show stash@{1}
The stash list uses the reflog format, so you can also use time-based references:
# Stashes from the last hour
$ git stash list --since="1 hour ago"
# Format stash list with date
$ git stash list --date=relative
git stash pop vs apply
This is one of the most common questions about stash. Both commands reapply stashed changes, but they differ in one critical way:
| Command | Applies Changes | Removes Stash Entry | On Conflict |
|---|---|---|---|
git stash pop |
Yes | Yes (if clean apply) | Keeps the stash |
git stash apply |
Yes | No | Keeps the stash |
Use pop when you are done with the stash and want it removed from the stack. This is the most common case: you stashed to switch branches, now you are back and want your changes.
Use apply when you want to keep the stash around. This is useful when:
- You want to apply the same changes to multiple branches
- You are not sure the apply will work cleanly and want a safety net
- You are using the stash as a reusable patch
# Safe approach: apply first, drop manually after verifying
$ git stash apply
# ... verify everything looks good ...
$ git stash drop stash@{0}
git stash show: Viewing Stashed Changes
Before applying a stash, you often want to see what it contains. git stash show gives you a summary by default and a full diff with -p:
# Summary of changes (default: diffstat)
$ git stash show
src/auth.js | 24 ++++++++++++++++--------
src/api.js | 8 ++++++--
2 files changed, 22 insertions(+), 10 deletions(-)
# Full diff (patch format)
$ git stash show -p
diff --git a/src/auth.js b/src/auth.js
index 1a2b3c4..5d6e7f8 100644
--- a/src/auth.js
+++ b/src/auth.js
@@ -10,6 +10,12 @@ function validateLogin(email, password) {
+ if (!email.includes('@')) {
+ return { valid: false, error: 'Invalid email format' };
+ }
...
# Show a specific stash
$ git stash show -p stash@{2}
# Show only file names
$ git stash show --name-only stash@{0}
src/auth.js
src/api.js
# Show with stat and patch combined
$ git stash show --stat -p stash@{0}
You can also use any git diff flag with stash show, including --color-words for inline diffs or --name-status to see which files were added, modified, or deleted.
Stashing Specific Files
Sometimes you do not want to stash everything. Maybe you have changes in five files but only need to set aside two of them. Use git stash push with pathspecs:
# Stash only specific files
$ git stash push -m "auth changes" src/auth.js src/middleware/auth.js
# Stash everything in a directory
$ git stash push -m "all API changes" src/api/
# Stash using glob patterns
$ git stash push -m "test files" -- "*.test.js"
The files you specify are stashed and reverted. All other uncommitted changes remain in your working directory untouched. The -- separator before glob patterns prevents Git from misinterpreting them as flags.
This is one of the most useful but least known features of stash. Before pathspec support was added in Git 2.13, you had to manually stage files and use workarounds. Now it is straightforward.
# Verify: only specified files were stashed
$ git stash show --name-only
src/auth.js
src/middleware/auth.js
# Your other changes are still in the working directory
$ git status
Changes not staged for commit:
modified: src/dashboard.js
modified: src/utils.js
Stashing Untracked and Ignored Files
By default, git stash only saves tracked files (files that Git already knows about). New files that have never been added are ignored. This catches people off guard regularly.
# Default stash: ONLY tracked files
$ git stash
# New files are left behind!
# Include untracked files with -u (or --include-untracked)
$ git stash push -u -m "including new files"
# Include EVERYTHING: untracked AND ignored files with -a (or --all)
$ git stash push -a -m "full clean slate"
The -u flag is what you want most of the time. It picks up new files you have created but not yet added to Git. The -a flag also includes files matched by .gitignore (like node_modules/, build artifacts, and .env files). Use -a sparingly — stashing node_modules can create an enormous stash entry.
When to use each flag
- No flag — You only have changes to existing tracked files
-u— You created new source files that are not yet tracked-a— You need an absolutely clean directory (rare; usually for build system debugging)
Partial/Interactive Stash (-p)
The -p (patch) flag lets you interactively choose which hunks within files to stash. This is the most granular control you can get:
$ git stash push -p -m "just the validation logic"
diff --git a/src/auth.js b/src/auth.js
--- a/src/auth.js
+++ b/src/auth.js
@@ -10,6 +10,12 @@ function validateLogin(email, password) {
+ if (!email.includes('@')) {
+ return { valid: false, error: 'Invalid email' };
+ }
(1/3) Stash this hunk [y,n,q,a,d,j,J,g,/,e,?]?
The interactive prompt options:
| Key | Action |
|---|---|
y | Stash this hunk |
n | Skip this hunk |
q | Quit (do not stash remaining hunks) |
a | Stash this hunk and all remaining hunks in this file |
d | Skip this hunk and all remaining hunks in this file |
s | Split this hunk into smaller hunks |
e | Manually edit this hunk |
Partial stash is powerful when you have interleaved changes in a single file — some ready for a commit and some that need to be set aside. Combined with descriptive messages, it lets you surgically separate concerns.
Creating Branches from Stash
Sometimes you stash changes, and by the time you come back, the branch has moved so far ahead that applying the stash causes conflicts. Or maybe you realize the stashed work deserves its own feature branch. git stash branch solves both cases:
# Create a new branch from the stash, starting from the commit
# where the stash was originally created
$ git stash branch feature/validation stash@{0}
Switched to a new branch 'feature/validation'
On branch feature/validation
Changes not staged for commit:
modified: src/auth.js
Dropped stash@{0}
This command does three things in one step:
- Creates a new branch starting from the commit where the stash was originally recorded
- Applies the stashed changes to the new branch
- Drops the stash entry (like pop)
Because the branch starts from the exact commit where you stashed, there is zero chance of conflicts. This is the safest way to recover stashed work that has become difficult to apply.
# If you don't specify a stash, it uses stash@{0}
$ git stash branch my-new-branch
# You can also specify any stash
$ git stash branch rescue-work stash@{3}
Stash Conflicts and Resolution
When you apply or pop a stash, Git performs a three-way merge between the stash, the commit where the stash was created, and your current HEAD. If the same lines changed in both the stash and the commits since, you get a conflict:
$ git stash pop
Auto-merging src/auth.js
CONFLICT (content): Merge conflict in src/auth.js
The stash entry is kept in case you need it again.
Notice the last line: when pop encounters conflicts, the stash is not dropped. This is a safety measure. You need to resolve the conflict manually.
# 1. Open the conflicted file and resolve the markers
<<<<<<< Updated upstream
const token = generateJWT(user.id);
=======
const token = createToken(user);
>>>>>>> Stashed changes
# 2. After resolving, stage the file
$ git add src/auth.js
# 3. The stash is still there - drop it manually
$ git stash drop stash@{0}
If the conflict is too messy and you want to abort, reset your working directory:
# Undo the stash apply and go back to clean state
$ git checkout -- .
# Or use git restore (Git 2.23+)
$ git restore .
# Your stash is still safe on the stack
$ git stash list
git stash drop and clear
Stashes accumulate over time. Clean them up to avoid confusion.
# Drop a specific stash
$ git stash drop stash@{0}
Dropped stash@{0} (a1b2c3d4e5f6...)
# Drop a stash by index (same thing, shorter)
$ git stash drop stash@{2}
# WARNING: Nuclear option - delete ALL stashes
$ git stash clear
git stash clear is irreversible. There is no confirmation prompt. All stash entries are permanently deleted. Before running it, list your stashes and make sure nothing important is there:
# Review before clearing
$ git stash list
stash@{0}: On main: temp experiment
stash@{1}: On feature/auth: validation WIP
stash@{2}: On main: debug logging
# If stash@{1} is important, apply or branch it first
$ git stash branch save-validation stash@{1}
# Now safe to clear the rest
$ git stash clear
Dropped stashes can sometimes be recovered using git fsck --unreachable if Git has not yet garbage-collected them, but do not rely on this. Treat drop and clear as permanent.
Advanced: Stash Internals
Understanding how stash works under the hood helps you debug edge cases and appreciate why certain flags exist.
A stash entry is not a special data structure — it is a regular commit object. Specifically, each stash creates two or three commit objects:
# Stash commit structure:
#
# stash@{0} (merge commit W)
# |-- Parent 1: HEAD commit at time of stash
# |-- Parent 2: Index state (commit I)
# +-- Parent 3: Untracked files (commit U, only with -u or -a)
#
# W = Working directory state
# I = Index (staged) state
# U = Untracked files
You can inspect these directly:
# Show the stash commit
$ git log --oneline --graph stash@{0}
# The stash ref is stored in .git/refs/stash
$ cat .git/refs/stash
# The full stash reflog
$ git reflog show stash
# Treat stash as a commit - diff against anything
$ git diff stash@{0} HEAD
$ git diff stash@{0}^1 stash@{0} # Changes in the stash
Because stashes are commits, you can cherry-pick them, reference them in diffs, and even push them to a remote (though you should not). The --index flag on apply/pop works because Git stores the index state in a separate parent commit, allowing it to reconstruct which changes were staged.
Real-World Workflows
Quick Context Switching
The most common workflow. You are on a feature branch and need to fix something on main:
# Stash current work
$ git stash push -u -m "feature/payments: stripe integration WIP"
# Switch and fix
$ git checkout main
$ git pull
# ... make the fix, commit, push ...
# Come back
$ git checkout feature/payments
$ git stash pop
Code Review Preparation
Before reviewing a colleague's pull request, clean up your working directory so you do not accidentally include your changes in test runs:
# Stash everything including new files
$ git stash push -u -m "my WIP before reviewing PR #42"
# Check out the PR branch
$ git fetch origin
$ git checkout origin/feature/new-dashboard
# ... review, test, comment ...
# Go back to your branch and restore
$ git checkout feature/my-work
$ git stash pop
Experimental Changes
Try a risky refactor without committing to it:
# Make your experimental changes...
# Save the experiment
$ git stash push -m "experiment: replace axios with fetch"
# Try a different approach...
# Save that too
$ git stash push -m "experiment: replace axios with ky"
# Compare both experiments
$ git stash show -p stash@{0} # ky approach
$ git stash show -p stash@{1} # fetch approach
# Keep the one you prefer
$ git stash pop stash@{0}
# Clean up the other
$ git stash drop stash@{0} # was stash@{1}, shifted after pop
Pulling with a Dirty Working Tree
You cannot pull if you have local changes that would conflict with incoming changes. Stash provides a clean workaround:
# This pattern is so common it's worth memorizing
$ git stash push -u -m "before pull"
$ git pull --rebase
$ git stash pop
If conflicts arise during the stash pop, resolve them as described in the conflicts section.
Common Pitfalls and Best Practices
Pitfall: Forgetting untracked files
The most common stash mistake. You create new files, run git stash, and the new files are still there. Your "clean" working directory is not actually clean.
# Wrong: new files left behind
$ git stash
# Right: include untracked files
$ git stash push -u
Pitfall: Stash stack confusion
When you pop or drop a stash, all entries above it shift down. stash@{2} becomes stash@{1}, and so on. If you are dropping multiple stashes, work from the highest index down:
# Correct order: drop from highest to lowest
$ git stash drop stash@{3}
$ git stash drop stash@{2}
# Wrong: indices shift after each drop
$ git stash drop stash@{1} # drops what was originally stash@{1}
$ git stash drop stash@{2} # NOT what you think - indices shifted!
Pitfall: Long-lived stashes
Stashes that sit for days or weeks become increasingly difficult to apply. The codebase moves on, and conflicts pile up. If work is worth keeping for more than a day, commit it on a branch instead.
Pitfall: Losing the staging distinction
By default, git stash pop restores everything as unstaged, even if some changes were staged before stashing. Use --index to preserve the original staging state:
$ git stash pop --index
Best Practices
- Always use messages —
git stash push -m "description". Your future self will thank you. - Use
-uby default — Include untracked files unless you specifically want to leave them. - Keep the stash stack shallow — If you have more than 3-4 stashes, you should be using branches instead.
- Prefer apply over pop for risky restores — Use
apply, verify, thendrop. Safer than pop when you are not sure it will apply cleanly. - Review before clearing — Always run
git stash listbeforegit stash clear. - Use
stash branchfor old stashes — If a stash is more than a day old, create a branch from it instead of trying to pop it onto a changed codebase.
FAQ
What does git stash do and when should I use it?
Git stash temporarily saves your uncommitted changes (both staged and unstaged) and reverts your working directory to the last commit. Use it when you need to switch branches quickly, pull remote changes on a dirty working tree, pause current work to fix an urgent bug, or set aside experimental changes you are not ready to commit. Your stashed changes are stored on a stack and can be reapplied later with git stash pop or git stash apply.
What is the difference between git stash pop and git stash apply?
Both commands reapply stashed changes to your working directory, but they differ in what happens to the stash entry afterward. git stash pop applies the changes and then removes the stash entry from the stack (if no conflicts occur). git stash apply reapplies the changes but keeps the stash entry intact, so you can apply the same stash to multiple branches or keep it as a backup. If pop encounters a merge conflict, the stash is not dropped, behaving like apply.
How do I stash only specific files in Git?
Use git stash push followed by the file paths: git stash push -m "description" path/to/file1 path/to/file2. This stashes only the specified files while leaving all other changes in your working directory. You can also use git stash push -p to interactively select individual hunks within files to stash. Both approaches give you fine-grained control over exactly which changes get stashed.
Continue Learning
This guide is part of our Git deep-dive series. Explore these related guides to master your Git workflow:
- 📖 Git Log: The Complete Guide — Pretty graphs, filters, ranges, and searching history
- 🔀 Git Rebase: The Complete Guide — Interactive rebase, squash commits, and rebase workflows
- 🌿 Git Branching Strategies Guide — Git Flow, GitHub Flow, and Trunk-Based Development
- ⚡ Git Merge Conflicts Guide — Resolve merge conflicts with confidence