Stash: set unfinished work aside
The interactive lesson needs JavaScript. The text below is the complete lesson.
After this lesson you can
- set unfinished changes aside with
git stash push -mand bring them back withgit stash popapply - explain the difference between
git stash popandgit stash apply, and when you need-uunderstand - choose between a stash and a WIP commit on a branch of its own from how long the interruption lasts and whether the work must move to another machine evaluate
Understand: When you get interrupted
You are in the middle of a change on the branch feature. The README is half written, and you have a new file with ideas. Then the message comes: something on main must be fixed now, but your work is not ready for a commit.
If you try to switch to main now, Git may refuse. The README looks different on main, and the switch would overwrite your unfinished text. Git never throws away unsaved work unless you ask it to.
git stash push takes the changes to tracked files and puts them on a shelf beside the history. The tracked files match the last commit again. With -m you give the stash a message, so you know later what it holds.
Look at the graph: the shelf is a commit of its own, labelled stash@{0}. It has two parents. One is the commit you were on. The other is a small commit that keeps the staging area. No branch moved.
Look at the working tree: ideas.txt stayed behind. Untracked files are not stashed unless you add -u.
When you are back, git stash pop puts the changes back and removes the entry if they applied without conflict. The label stash@{0} disappears, and the stash commits become ghosts. git stash apply puts the changes back too, but keeps the entry until you run git stash drop.
A stash is local and is not included in an ordinary push. It has a number, not a name, and the numbers shift: stash again, and stash@{0} becomes stash@{1}. For a long break, commit the work as WIP on a branch of its own instead. It has a name and can be pushed.
Watch: Interruption, fix and back again
We are on feature with unfinished work. In the short format, M means modified and two question marks mean untracked. The README is modified, and ideas.txt is new.
$ git status --short
Now we try to switch to main. Git refuses and says why: the local change to the README would be overwritten. Git itself suggests committing or stashing.
$ git switch main
We set the work aside with a message. Watch the graph: a stash commit labelled stash@{0} appears beside feature. The README is back to the last commit.
$ git stash push -m "WIP search filters"
git stash list shows the shelf. The newest entry is on top as stash zero, with your branch and your message. It is the same stash@{0} as in the graph.
$ git stash list
Now we try the switch again. The stash stays hanging off feature, where it was made. But where does ideas.txt end up?
$ git switch main
Here we make the urgent fix and commit it. The graph gets a new commit on main.
$ echo "Contact: team@example.com" >> README.md && git commit -am "Add contact line"
The fix is done. We go back to feature.
$ git switch feature
Finally we bring the work back. pop puts the changes back and removes the entry because there was no conflict. The label stash@{0} disappears from the graph, and the README is modified again, exactly where you left off.
$ git stash pop
Tasks
- Set the unfinished changes aside with a descriptive message.
- Switch to
main. - Bring the unfinished work back and remove the entry from the stash list.
Practise: Transfer: everything aside, and a better plan
A new repository on main. The README is modified, and todo.txt is a new file. No demo this time.
Tasks
- Put away all the unfinished work, including the new file
todo.txt, so the working tree is completely clean. - Bring the work back, but keep the entry in the stash list in case something goes wrong.
- The job turned out bigger than expected. Save the work as a commit on a new branch
wip-todoinstead, and remove the stash entry you no longer need.
Check yourself: Recall it
You create the new file new.txt, run git add new.txt and then git stash push. Is new.txt put away?
- No, new files need
-u - Yes, it is tracked because it is in the staging area
- Only the staged copy; the file stays in the working tree
What is the difference between git stash pop and git stash apply?
popremoves the entry once the changes are applied without conflict;applykeeps itpopbrings back only the newest stash;applybrings back all of themapplyoverwrites the working tree;popmerges the changes
It is Friday. The feature is half finished, and on Monday you will continue it from another machine. What do you do?
git stash push -u, and thengit push- A WIP commit on a branch of its own, and then push the branch
- A WIP commit straight on
main, and thengit push
Review from “Undo in your files”: instead of stashing, you run git restore README.md on an unfinished change you never staged, committed or stashed. Can you get it back afterwards?
- Yes, with
git stash pop - Yes, from the reflog
- No, the change is gone for good
Common misconceptions
- “Stash takes everything.” Without
-u, untracked files stay in the working tree. - “A stash is a backup.” A stash exists only in your local repository, is not included in an ordinary
git push, and a dropped stash is hard to find again. - “
git stash popandgit stash applyare the same.”popremoves the entry once the changes were applied without conflict;applykeeps it.
Read more
What a stash really is
A stash is not a hidden folder. git stash push makes ordinary commit objects and points the special ref refs/stash at the newest one. That stash commit has two parents: the commit you were on, and an extra commit that records your staging area. With -u or -a a third parent holds the untracked files. That is why the live graph draws the stash as stash@{0} hanging off the commit you were on:
$ git stash push -m "WIP search filters"
Saved working directory and index state On feature: WIP search filters
$ git log --oneline --graph stash@{0}
* aa05d36 On feature: WIP search filters
|\
| * 167af41 index on feature: 88dbfe3 Start search feature
|/
* 88dbfe3 Start search feature
* 227577f Initial snapshot
The stash list is the reflog of refs/stash. stash@{0} is the newest entry, stash@{1} the one before, and the numbers shift every time you push or drop an entry. So read git stash list before you use a number, and give every stash a message with -m. "WIP on feature" tells you nothing a week later.
Look before you bring it back
| Command | Shows |
|---|---|
git stash list |
every entry, newest first |
git stash show |
which files the newest stash changes |
git stash show -p stash@{1} |
the full diff of one specific entry |
git stash show --include-untracked |
also the untracked files saved with -u |
$ git stash show -p
diff --git a/README.md b/README.md
index 5793643..ea67787 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,3 @@
Welcome
Feature: search
+Search supports filters
What gets stashed
- Plain
git stash pushtakes changes to tracked files, staged and unstaged. Untracked files stay where they are. -u(--include-untracked) also takes new, untracked files.-a(--all) also takes ignored files, such as build output. That is rarely what you want.--keep-indexstashes everything but leaves the staged changes in place, so you can test exactly what you are about to commit. The staged part is still recorded in the stash, so it comes back when you pop. If you edit those lines before that, the pop can conflict.git stash push -- notes.txtstashes only the paths you name.
pop, apply, drop and conflicts
git stash apply merges the stashed changes into your working tree and keeps the entry. git stash pop does the same and then drops the entry, but only if it applied cleanly. Both bring changes to existing files back unstaged, while new files you had staged come back staged. Add --index to restore the staged and unstaged split exactly as it was.
If the stash collides with changes made since, you get conflict markers, status shows UU, and Git keeps the entry:
$ git stash pop
Auto-merging README.md
CONFLICT (content): Merge conflict in README.md
...
The stash entry is kept in case you need it again.
Resolve the file as after a merge, git add it, and remove the entry yourself with git stash drop once you are sure the work is safe.
When the base has moved: stash branch
If the branch has moved on and the stash no longer fits, git stash branch creates a new branch at the commit where the stash was made, applies the stash there, where it is guaranteed to fit, and drops it:
$ git stash branch search-filters stash@{1}
Switched to a new branch 'search-filters'
...
Dropped stash@{1} (aa05d36ebb5dca462ca44e0d15313d1e1bff66eb)
Commit the work on the new branch, then merge or rebase it like any other branch.
Dropped by mistake?
git stash drop and git stash clear delete the ref, not the commits. drop prints the ID of the commit it dropped, and as long as you have that ID you can apply it again with git stash apply dcb6cf2. Without the ID, look for commits nothing points to any more:
$ git fsck --unreachable
...
unreachable commit 71170d9e92aa9dc024f199860e70bad17c592d26
unreachable commit dcb6cf266285a1ae2eddd115659790c56acc7039
$ git log --oneline --no-walk 71170d9 dcb6cf2
71170d9 index on main: a69bcbf Initial snapshot
dcb6cf2 On main: two kinds
$ git stash apply dcb6cf2
The commit whose message starts with "On" or "WIP on" is the stash itself; the "index on" commit is its staging-area parent. This works only until garbage collection removes unreachable objects, so it is a rescue, not a backup.
Stash or commit?
A stash is local. An ordinary git push does not send it, it has no branch name, its number changes, and it is easy to forget. Use it for interruptions measured in minutes. For anything longer, make a WIP commit on a branch of its own: it has a name, it can be pushed as a copy, and you can tidy it later with interactive rebase. Before git stash clear, read git stash list: clear removes every entry at once.
Per-Arne's Git Good!