You are halfway through a login form. A hotfix needs to land on main. git switch refuses because your working tree is dirty. The tempting escape is a fake commit titled WIP — clutter you will forget to undo, and a snapshot you never meant to keep.
Snapshot basics live in Git Without the Panic. Creating and publishing a feature branch lives in Stay Off Main. This page is the companion that post promised: park uncommitted edits, switch, restore them when you come back.
Warm-up: stash lab
Reuse the bare-remote pattern so the lab matches the rest of this series. From a scratch folder:
mkdir -p git-stash-lab
cd git-stash-lab
git init --bare remote.git
git clone remote.git app
cd app
echo 'App v1' > README.md
git add README.md
git commit -m "Initial README"
git branch -M main
git push -u origin main
git switch -c feature/login
echo 'Login form placeholder' > login.txt
git add login.txt
git commit -m "Add login placeholder"
echo 'WIP: password field' >> login.txt
You now have a feature branch with a dirty tracked file and no extra commit. Everything below runs inside app/ unless noted. Delete git-stash-lab when you finish.
Note: Prefer git switch for changing branches. Older docs use git checkout; the switch forms below are the everyday ones.
Mental model: a stash is a stack, not a branch
A stash is a stack of snapshots of uncommitted work — not a branch name and not a commit on your feature line. The newest entry is stash@{0}. Older ones shift to stash@{1}, stash@{2}, and so on. Branch pointers stay put; only the working tree (and the index, if you had staged files) is parked.
List what Git thinks is dirty:
git status
On branch feature/login
Changes not staged for commit:
modified: login.txt
That modified login.txt is what a stash will hold. After a successful stash, git status should report a clean tree so git switch can proceed.
Note: Stashes live only in this clone. They are not pushed to origin. A new clone, or another machine, will not see them.
Hero use case: stash, switch, restore
You need main for a moment. Do not invent a WIP commit. Park the dirty file, switch, then bring the edits back.
git stash
git status
Saved working directory and index state WIP on feature/login: ...
On branch feature/login
nothing to commit, working tree clean
login.txt is back to the last commit. The WIP line is on the stash stack, not in history. Switch, do the other work, then return and restore:
git switch main
git switch feature/login
git stash pop
git status
login.txt is dirty again — the password-field line is back. pop restores the snapshot and removes it from the stack. git stash list should now be empty.
Note: If the restored patch conflicts with the current files, Git keeps the stash on the stack (it did not finish the pop). Fix the overlapping lines the same way as in When Push Fails, then git add and git stash drop if the entry is still listed.
Name, list, and inspect stashes
A default stash message is easy to forget once you have two. Park with a label:
echo 'WIP: remember-me checkbox' >> login.txt
git stash push -m "login remember-me checkbox"
git stash list
stash@{0}: On feature/login: login remember-me checkbox
git stash push -m is the same park as git stash, plus a name you can read later. Preview without restoring:
git stash show
git stash show -p
git stash show prints a diffstat for stash@{0}. -p prints the patch. Pass stash@{1} (or another index from git stash list) to inspect an older entry.
Note: After pop, apply, or drop, the {n} numbers shift. Always git stash list before you target a specific entry.
pop vs apply, drop, and untracked files
pop is restore-and-remove. apply restores and leaves the stash on the stack — useful when you want the same patch on more than one branch, or you are not sure the restore will stick.
git stash apply
git stash list
The remember-me stash is still stash@{0}. Remove it without touching files:
git stash drop
# or: git stash drop stash@{0}
git stash list
git stash clear deletes every stash in this clone. Use it only when you intend to throw the whole stack away.
apply left edits in the tree. Park them and drop that stash so the next demo is only about a new file:
git stash
git stash drop
Default stash ignores brand-new files. Create one and park without -u:
echo 'scratch' > scratch.tmp
git stash
git status
No local changes to save
On branch feature/login
Untracked files:
scratch.tmp
Plain git stash does not take untracked files. Include them explicitly:
git stash push -u -m "untracked scratch"
git status
The untracked file is gone from the tree until you git stash pop (or apply). -u is --include-untracked. Use it when the WIP includes new files you have not git added yet.
Note: pop that stash (or drop it) before the drills so scratch.tmp does not linger.
When a real commit is clearer
Stash is a short detour: dirty tree, need another branch, come back the same day. A commit on your feature branch is clearer when the work should survive overnight, another clone, or a teammate’s review.
echo 'WIP: password field' >> login.txt
git add login.txt
git commit -m "WIP: password field on login form"
git switch main
git switch feature/login
That WIP commit is honest history on feature/login. Keep building on it, or undo it locally later with the reset options in When Push Fails if it never left your machine. Prefer a commit when you might push the branch, or when losing the work would hurt. Prefer a stash when you only need git switch to succeed for a moment.
Quick reference card
| Goal | Command |
|---|---|
| Park tracked edits | git stash |
| Park with a name | git stash push -m "message" |
| Include untracked files | git stash push -u -m "message" |
| List stashes | git stash list |
| Preview newest (stat / patch) | git stash show / git stash show -p |
| Restore and remove | git stash pop |
| Restore, keep the stash | git stash apply |
| Delete one stash | git stash drop or git stash drop stash@{n} |
| Switch after a clean tree | git switch main |
Practice drills
Inside git-stash-lab/app (recreate the bare remote and clone if needed):
- On
feature/login, dirtylogin.txt, stash, confirmgit statusis clean, switch tomain, switch back, andgit stash pop. - Create two named stashes with
git stash push -m, rungit stash list, andgit stash showthe newest without applying. - Stash a change,
git stash apply, confirm the entry is still listed, thengit stash dropit. - Add an untracked file, confirm plain
git stashleaves it, thengit stash push -u -m "untracked"and confirm the file is gone until you pop. - Dirty the feature again. Instead of stashing, commit a WIP message, switch to
main, and switch back — the commit path for work you will resume later.
Solid answers — clear and portable:
# 1
echo 'WIP: password field' >> login.txt
git stash
git status
git switch main
git switch feature/login
git stash pop
# 2
echo 'a' >> login.txt && git stash push -m "stash a"
echo 'b' >> login.txt && git stash push -m "stash b"
git stash list
git stash show
# 3
echo 'apply-me' >> login.txt
git stash
git stash apply
git stash list
git stash drop
# 4
echo 'scratch' > scratch.tmp
git stash
git status
git stash push -u -m "untracked"
git status
git stash pop
# 5
echo 'more wip' >> login.txt
git add login.txt && git commit -m "WIP: password field"
git switch main
git switch feature/login
If those five feel routine, you already have the switch-with-a-dirty-tree habit: park with stash, or commit on the feature branch when the work should last. Day-one snapshots remain in Git Without the Panic. Branch create, publish, and merge remain in Stay Off Main. Next skill when you need it is Git Rebase — replay a feature branch onto origin/main without a merge commit.