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

  1. Git reads the current index (staging area) and the full working tree.
  2. It writes a new commit whose tree matches the index — the “index commit,” parented on the commit you have checked out.
  3. 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.
  4. If -u or -a was given, a third commit captures the untracked (and ignored) files.
  5. Git updates refs/stash to point at the new stash commit, appending the reference’s previous value to its own reflog — this reflog is the stash list.
  6. Git resets the index and working tree to match HEAD, removing any files that were stashed as untracked, leaving a clean tree.
  7. On apply or pop, Git computes the diff between each stash commit and its parent, then applies that diff against your current HEAD, index, and working tree, restoring the original staged/unstaged split as closely as possible.
  8. pop additionally removes the refs/stash entry once step 7 finishes without conflicts — under the hood this is the same as running git stash drop right after a successful apply.

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 unlabeled stash@{2} from three weeks ago tells you nothing.
  • Pass -u (or -a for ignored files too) whenever new files are part of the work you’re shelving; the plain git stash silently 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 run git stash droppop won’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 list periodically and either drop stale entries or turn anything you actually want to keep into a real commit.

Practice Exercises

  • On feature/login-page you’ve modified both src/login.js and src/styles.css. Stash only the CSS change so src/login.js stays modified and staged for further testing. Hint: use a pathspec after -- with git stash push, then confirm with git status.
  • Create two stashes from two unrelated sets of changes, each with a descriptive -m message. Confirm both appear with git stash list, restore only the older one (stash@{1}) without removing the newer entry, then clean up both stashes once you’ve reviewed each with git stash show -p.
  • On feature/login-page, edit a line in src/login.js and stash it. Without switching branches, edit the same line differently and commit that change. Run git stash pop, observe the conflict, resolve the markers, stage the file, and remove the stash entry pop left behind.

Summary

  • git stash shelves staged and unstaged changes as ordinary commit objects referenced by refs/stash, without touching your branch or HEAD.
  • Untracked and ignored files are excluded by default — use -u to include untracked files and -a to include ignored ones too.
  • Stashes are numbered stash@{0} upward, most recent first, because they’re literally entries in the refs/stash reflog.
  • git stash apply reapplies without removing the entry; git stash pop reapplies and removes it, but only on a clean, conflict-free apply.
  • Always label stashes with -m so git stash list is 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.