Menu for Per-Arne's Git Good!
Courses and project Per-Arne's Git Good!

Stash: set unfinished work aside

Module 5: The toolbox · 12 min

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 -m and bring them back with git stash pop apply
  • explain the difference between git stash pop and git stash apply, and when you need -u understand
  • 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

  1. Set the unfinished changes aside with a descriptive message.
  2. Switch to main.
  3. 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

  1. Put away all the unfinished work, including the new file todo.txt, so the working tree is completely clean.
  2. Bring the work back, but keep the entry in the stash list in case something goes wrong.
  3. The job turned out bigger than expected. Save the work as a commit on a new branch wip-todo instead, 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?

  • pop removes the entry once the changes are applied without conflict; apply keeps it
  • pop brings back only the newest stash; apply brings back all of them
  • apply overwrites the working tree; pop merges 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 then git push
  • A WIP commit on a branch of its own, and then push the branch
  • A WIP commit straight on main, and then git 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 pop and git stash apply are the same.” pop removes the entry once the changes were applied without conflict; apply keeps 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 push takes 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-index stashes 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.txt stashes 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.

Official documentation