git stash
git stash is Git’s tool for temporarily shelving uncommitted work — both staged and unstaged changes in your working tree — so you can switch context with a clean slate and bring the changes back later, exactly as they were. It doesn’t create a commit on your branch and doesn’t touch your commit history; instead it saves a snapshot to a separate, stack-like storage area that sits alongside your regular commits. This makes it ideal for the “I need to drop what I’m doing right now” moment — switching branches to fix an urgent bug, pulling in a teammate’s changes, or just tidying your working tree before running a build — without polluting your history with half-finished work.
Overview / How it works
Under the hood, git stash doesn’t invent new machinery — it reuses Git’s ordinary commit objects, the same blob-tree-commit structure behind every commit you make. When you run git stash, Git builds a commit that represents the state of your index (what’s staged) and a second commit that represents your full working tree (staged plus unstaged changes), both parented on the commit you currently have checked out. If you include untracked files with -u, a third commit captures those too. Git then points a special reference, refs/stash, at the newest of these stash commits and records the previous value of that reference in its own reflog. That reflog is exactly why stashes are numbered stash@{0}, stash@{1}, and so on, most recent first — a stash entry is simply a line in the refs/stash reflog. Because it’s built from real commits and a real ref, a stash is ordinary, hashable Git data: you can run git show on it, diff it, cherry-pick a piece of it, or recover a dropped stash for as long as Git’s garbage collector hasn’t pruned the now-unreachable commit.
After building those commits, Git resets your index and working tree to match HEAD, so git status reports a clean tree (untracked files are left alone unless you stashed them with -u or -a). Crucially, nothing is written to your branch — the branch pointer and HEAD don’t move, and the stash never shows up in git log. That’s the core difference between stashing and committing: a stash sits off to the side, needs no commit-message discipline, and is meant to be short-lived, whereas a commit is a permanent, shareable part of history.
git stash pop and git stash apply reverse the process: they take the diff recorded in the stash’s commits and replay it on top of whatever you currently have checked out — which may not be the branch the stash was taken from. apply leaves the stash entry in refs/stash so you can reuse it elsewhere; pop additionally removes it, but only if the apply succeeds cleanly. If reapplying the stash conflicts with your current working tree, Git leaves conflict markers in the affected files and does not drop the stash — even with pop — so your work is never silently lost while you sort out the conflict.
Syntax
git stash has several subcommands; running git stash alone is shorthand for git stash push with no arguments.
git stash [push] [-m <message>] [-u] [-a] [--] [<pathspec>...]
git stash list
git stash show [-p] [<stash>]
git stash apply [<stash>]
git stash pop [<stash>]
git stash drop [<stash>]
git stash branch <branchname> [<stash>]
git stash clear
| Flag / subcommand | Meaning |
|---|---|
push |
Explicit form of stashing; lets you combine -m and a pathspec. Default when the subcommand is omitted. |
-m, --message <message> |
Give the stash a human-readable label instead of the default “WIP on <branch>”. |
-u, --include-untracked |
Also stash new files that aren’t tracked by Git yet. |
-a, --all |
Stash untracked and ignored files too. |
-p, --patch |
Interactively choose which hunks to stash, hunk by hunk. |
-- <pathspec> |
Stash only the changes to the given file(s) or path(s). |
list |
Show every stash entry, newest first. |
show [-p] [<stash>] |
Show a diffstat (or full diff with -p) for a stash without applying it. |
apply [<stash>] |
Reapply a stash’s changes but keep the entry in the stash list. |
pop [<stash>] |
Reapply a stash’s changes and remove the entry if it applies cleanly. |
drop [<stash>] |
Delete a single stash entry without applying it. |
branch <name> [<stash>] |
Create a new branch from the commit the stash was based on, apply the stash there, and drop it. |
clear |
Delete every stash entry at once. No confirmation, no undo once garbage-collected. |
Examples
Example 1: Stashing to switch branches quickly
You’re partway through editing the login page when an urgent request comes in to check something on main. Your tree isn’t ready to commit, so you stash it.
cd ~/projects/website
git status
Output:
On branch feature/login-page
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: src/login.js
modified: src/styles.css
no changes added to commit (use "git add" and/or "git commit -a")
git stash
Output:
Saved working directory and index state WIP on feature/login-page: a1b2c3d Add login form skeleton
Git swept both files’ changes into a new stash entry and reset the working tree to match the last commit, so you’re now free to git switch main with nothing in the way. Later, back on feature/login-page, you restore the work:
git status
git stash pop
Output:
On branch feature/login-page
Changes not staged for commit:
modified: src/login.js
modified: src/styles.css
no changes added to commit (use "git add" and/or "git commit -a")
Dropped refs/stash@{0} (e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4)
pop reapplied the exact same edits and, because there were no conflicts, immediately deleted the stash entry — the last line confirms refs/stash@{0} was dropped.
Example 2: Naming and managing multiple stashes
Stashes stack, so it’s easy to end up with several at once. Always label them — an unnamed stash@{3} from last week is a mystery.
git stash push -m "wip: login validation logic"
git stash push -m "wip: navbar css tweaks"
git stash list
Output:
stash@{0}: On feature/login-page: wip: navbar css tweaks
stash@{1}: On feature/login-page: wip: login validation logic
The most recently created stash is always stash@{0}. To bring back the older one, the validation work, without touching the navbar stash still sitting at stash@{0}, reference it explicitly and use apply instead of pop so it stays in the list in case you need it again:
git stash apply stash@{1}
Output:
On branch feature/login-page
Changes not staged for commit:
modified: src/login.js
no changes added to commit (use "git add" and/or "git commit -a")
The login-validation edits are back in your working tree, but git stash list would still show both entries, since apply never removes anything.
Example 3: Stashing untracked files and reviewing a stash before applying
You’ve added a new icon file alongside a modified script, and you want both shelved together — and you want to double-check exactly what’s in the stash before you touch it again.
git status
git stash push -u -m "wip: search widget plus new icon asset"
Output:
On branch feature/login-page
Untracked files:
assets/icons/search.svg
Changes not staged for commit:
modified: src/search.js
Saved working directory and index state On feature/login-page: wip: search widget plus new icon asset
Without -u, the new search.svg file would have been left sitting untouched in your working tree instead of being shelved. Before applying this stash somewhere else, inspect it in full:
git stash show -p stash@{0}
Output:
diff --git a/src/search.js b/src/search.js
index 3b1f7a2..9c4d8e1 100644
--- a/src/search.js
+++ b/src/search.js
@@ -12,6 +12,9 @@ function initSearch() {
const input = document.querySelector('#search-input');
+ input.addEventListener('input', debounce(handleSearchInput, 200));
return input;
}
diff --git a/assets/icons/search.svg b/assets/icons/search.svg
new file mode 100644
index 0000000..e69de29
show -p prints a full patch without touching your working tree at all, which is the safest way to check what a stash contains before deciding to apply or drop it. Once you’re confident it’s been merged into your work another way, clean it up:
git stash drop stash@{0}
Output:
Dropped stash@{0} (f7e6d5c4b3a2918070605040302010f0e0d0c0b)
How it works step by step
- Git reads the current index (staging area) and the full working tree.
- It writes a new commit whose tree matches the index — the “index commit,” parented on the commit you have checked out.
- It writes a second commit, parented on the index commit, whose tree matches the full working tree (staged plus unstaged edits). This is the commit
stash@{0}actually points to. - If
-uor-awas given, a third commit captures the untracked (and ignored) files. - Git updates
refs/stashto point at the new stash commit, appending the reference’s previous value to its own reflog — this reflog is the stash list. - Git resets the index and working tree to match
HEAD, removing any files that were stashed as untracked, leaving a clean tree. - On
applyorpop, Git computes the diff between each stash commit and its parent, then applies that diff against your currentHEAD, index, and working tree, restoring the original staged/unstaged split as closely as possible. popadditionally removes therefs/stashentry once step 7 finishes without conflicts — under the hood this is the same as runninggit stash dropright after a successfulapply.
Because it’s all just commits and a ref, you can inspect the raw mechanism directly:
git reflog show stash
Output:
c3d4e5f (refs/stash) stash@{0}: On feature/login-page: wip: navbar css tweaks
b2c3d4e stash@{1}: On feature/login-page: wip: login validation logic
That’s the exact same information git stash list shows you, confirming that a “stash” is nothing more mysterious than a reflog entry pointing at a pair of ordinary commits.
Common Mistakes
Mistake 1: Assuming new files get stashed automatically
git status
git stash
git switch main
By default, git stash only shelves changes to files Git already tracks. A brand-new, untracked file — say a new asset you just added — is left sitting in your working tree. Switching branches with it still there can leave it dangling on main, or even collide with a same-named file that main already tracks. The fix is to include untracked files explicitly:
git stash -u
Use -a instead if ignored files (build output you’ve deliberately excluded via .gitignore but still want shelved) need to come along too.
Mistake 2: Treating the stash list as disposable and running clear carelessly
git stash clear
git stash clear deletes every stash entry in one shot, with no confirmation prompt. It’s tempting to run as a quick “clean slate” command, but if you haven’t reviewed what’s actually stashed, you can permanently lose days of shelved work the moment Git’s garbage collector runs. Always check what you’re about to discard first, and prefer dropping entries one at a time until you’re certain:
git stash list
git stash drop stash@{2}
Mistake 3: Re-running pop after a conflict instead of resolving it
git stash pop
git stash pop
If the first pop conflicts with your current working tree, Git leaves conflict markers in the affected file and deliberately does not drop the stash, precisely so the work isn’t lost. Running pop a second time doesn’t retry cleanly — it tries to reapply the same diff on top of a tree that already has unresolved conflict markers in it, which usually produces a second, messier round of conflicts. The correct fix is to resolve the markers in the file by hand, stage the result, and only then remove the stash entry yourself, since a conflicted pop never drops it automatically:
git add src/login.js
git stash drop stash@{0}
Best Practices
- Always label a stash with
git stash push -m "..."— an unlabeledstash@{2}from three weeks ago tells you nothing. - Pass
-u(or-afor ignored files too) whenever new files are part of the work you’re shelving; the plaingit stashsilently skips them. - Run
git stash show -p stash@{0}before applying, especially if you’re unsure what’s in it or you’re applying it somewhere other than where it was created. - Prefer
git stash push -- <pathspec>to shelve only the files relevant to the interruption, leaving unrelated in-progress edits untouched. - After a conflicted
pop, resolve the markers, stage the file, and manually rungit stash drop—popwon’t do it for you. - Use
git stash branch <name>when you expect a stash to conflict with whatever’s currently checked out; it recreates the stash’s original branch point first, so the reapply is far more likely to be clean. - Don’t let stashes pile up. Run
git stash listperiodically and either drop stale entries or turn anything you actually want to keep into a real commit.
Practice Exercises
- On
feature/login-pageyou’ve modified bothsrc/login.jsandsrc/styles.css. Stash only the CSS change sosrc/login.jsstays modified and staged for further testing. Hint: use a pathspec after--withgit stash push, then confirm withgit status. - Create two stashes from two unrelated sets of changes, each with a descriptive
-mmessage. Confirm both appear withgit stash list, restore only the older one (stash@{1}) without removing the newer entry, then clean up both stashes once you’ve reviewed each withgit stash show -p. - On
feature/login-page, edit a line insrc/login.jsand stash it. Without switching branches, edit the same line differently and commit that change. Rungit stash pop, observe the conflict, resolve the markers, stage the file, and remove the stash entry pop left behind.
Summary
git stashshelves staged and unstaged changes as ordinary commit objects referenced byrefs/stash, without touching your branch orHEAD.- Untracked and ignored files are excluded by default — use
-uto include untracked files and-ato include ignored ones too. - Stashes are numbered
stash@{0}upward, most recent first, because they’re literally entries in therefs/stashreflog. git stash applyreapplies without removing the entry;git stash popreapplies and removes it, but only on a clean, conflict-free apply.- Always label stashes with
-msogit stash listis still useful weeks later. - A stash isn’t tied to the branch it came from — you can apply it onto any branch, which is powerful but can produce confusing conflicts if you’re not careful.
- Treat stash as short-term scratch space, not long-term storage; for anything you want to keep safely, make a real commit or push a draft branch instead.
