Git & GitHub · lesson 4 of 12
Stashing Work in Progress
Shelve an unfinished edit and an untracked file with git stash, make an unrelated commit on a clean tree, then reapply the stash with a real auto-merge.
By SkillByExample · Published
git stash shelves uncommitted work so you can switch context — pull a hotfix, review
something else — without committing half-finished changes. This lesson shelves a real
edit and an untracked file, commits something unrelated in between, then brings the
work back.
1) Two kinds of uncommitted change: modified and untracked
$ echo "DEBUG = True" > config.py
$ # ...edit app.py mid-thought...
$ git status -s
M app.py
?? config.py
app.py is a modified tracked file; config.py is a brand-new untracked one. Plain
git stash would only shelve the first.
2) Stash both, including the untracked file
$ git stash push -u -m "WIP: greeting formatting"
$ git status -s
Saved working directory and index state On main: WIP: greeting formatting
-u pulls untracked files into the stash too. git status -s afterward prints
nothing — the working directory is back to matching the last commit.
3) Stashes are a named list, not a single slot
$ git stash list
stash@{0}: On main: WIP: greeting formatting
You can stash multiple times; each entry gets an index. stash@{0} is always the most
recently stashed.
4) The tree moves on while the stash sits
$ # ...append test_farewell() to app.py...
$ git commit -am "Urgent: add farewell test"
$ git log --oneline -2
c489307 Urgent: add farewell test
d7da0e1 Revert "Change greeting to Hi (oops)"
This is the actual point of stashing: a real commit landed on app.py while the WIP
edit was shelved, unrelated to it.
5) Popping replays the stash against the new tree
$ git stash pop
Auto-merging app.py
On branch main
Changes not staged for commit:
modified: app.py
Untracked files:
config.py
Dropped refs/stash@{0} (8c38a562...)
Git doesn’t just overwrite app.py — it re-applies the stashed diff on top of the
current tree, which now includes c489307. Auto-merging means it succeeded
without a conflict; config.py reappears as untracked, exactly as it was stashed.
Practice
-
Run
git stash push(without-u) on a tree with both a modified file and an untracked file. Confirm only the modified file gets stashed and the untracked file is left sitting in the working directory. -
Stash twice in a row with different messages, then run
git stash listto see both entries. Usegit stash pop stash@{1}to pop a specific one instead of the most recent.Solution
$ git stash list stash@{0}: On main: second WIP stash@{1}: On main: first WIP $ git stash pop stash@{1}Popping an older stash by name leaves the newer one in place.
-
Force a stash-pop conflict: stash an edit to a specific line, then commit a different edit to that same line before popping. Read the conflict markers and resolve them the same way you would a merge conflict.
-
Run
git stash show -pon a stash (before popping it) to preview its diff without applying it yet. -
Use
git stash dropto discard a stash you no longer need, and confirm withgit stash listthat it’s gone — unlikepop,dropnever reapplies the changes.