Skip to content
C Codeloom
Git

Git Stash Tutorial: Saving Work in Progress

Learn how to use git stash to safely shelve uncommitted changes, switch contexts, and recover work using push, pop, apply, and branch workflows.

·4 min read · By Codeloom
Intermediate 8 min read

What you'll learn

  • When stashing beats a throwaway commit
  • How the stash stack works internally
  • Stashing untracked and partial changes
  • Recovering and inspecting old stashes
  • Turning a stash into a branch

Prerequisites

  • Familiar with shell
  • Comfortable with git add and git commit

What and Why

You are halfway through a feature when an urgent bug report lands on main. Your working tree is dirty, the change is not ready to commit, and git checkout main will refuse or carry your changes along. git stash solves exactly this: it saves your uncommitted work to a private stack, restores a clean working tree, and lets you pop the work back later.

Compared to a “WIP” commit, stash keeps your branch history clean and supports untracked files, partial hunks, and multiple shelved changes at once.

Mental Model

The stash is a LIFO stack stored in refs/stash. Each entry is a real commit (actually two or three commits) referenced by stash@{0}, stash@{1}, and so on. Newest stash sits at index 0. Each entry records:

  • The state of tracked files (index + working tree),
  • Optionally untracked or ignored files,
  • The branch and commit you were on when you stashed.

Because entries are commits, you can git show, git diff, and even git log them. Nothing is lost unless you explicitly drop an entry.

Hands-on Example

Let’s walk through a realistic interruption. You’re editing two files, but a hotfix comes in.

# You have uncommitted edits
$ git status -s
 M src/api/users.go
 M src/api/users_test.go
?? scratch.md

# Shelve everything, including the untracked file
$ git stash push -u -m "wip: refactoring user handler"
Saved working directory and index state On feature/users: wip: refactoring user handler

$ git status
nothing to commit, working tree clean

# Fix the bug on main
$ git checkout main
$ git commit -am "fix: null check in auth middleware"
$ git push

# Resume your work
$ git checkout feature/users
$ git stash list
stash@{0}: On feature/users: wip: refactoring user handler

$ git stash pop
On branch feature/users
Changes not staged for commit:
  modified:   src/api/users.go
  modified:   src/api/users_test.go
Untracked files:
  scratch.md
Dropped refs/stash@{0}
Working tree           Stash stack
------------           ------------
[dirty]   ---push--->  [stash@{0}] new
                     [stash@{1}]
                     [stash@{2}]

[clean]   <---pop---   [stash@{0}] removed
                     [stash@{1}] now top
                     [stash@{2}]

apply = restore but keep entry
drop  = delete entry only
Stash stack lifecycle: push shelves work, pop restores and removes the top entry

For partial stashes use git stash push -p and select hunks interactively. To stash only a single file, pass paths: git stash push -m "auth tweak" src/api/auth.go.

Common Pitfalls

  • Forgetting -u. By default stash ignores untracked files. New files vanish from git status after a checkout because they are simply left in place, but a fresh git clean -fd will delete them. Use -u to include untracked, -a to also include ignored files.
  • pop conflicts leave the entry behind. Many users assume pop always removes the stash. If applying produces conflicts, the entry remains on the stack so you can retry. Resolve conflicts, then git stash drop.
  • Stashing on the wrong branch. A stash records the source branch in its message but applies to whichever branch is checked out. Popping a stash made on feature/A onto main often produces conflicts.
  • Lost stashes after a hard reset. git stash clear and git reset --hard can orphan entries. Use git fsck --unreachable | grep commit to recover dangling stash commits.

Practical Tips

  • Name your stashes. git stash push -m "wip: tax calc rewrite" is far more useful than the default WIP on branch... message a week later.
  • Prefer apply for reusable WIPs. apply restores the changes but keeps the stash entry, so you can apply the same shelved diff onto multiple branches.
  • Promote a stash to a branch when you realize the work deserves its own line of history: git stash branch feature/tax-rewrite stash@{2}. Git creates the branch at the stash’s parent commit, applies the changes, and drops the entry on success.
  • Inspect before popping. git stash show -p stash@{1} prints a full patch so you can audit what is about to land in your working tree.
  • Treat the stash as short-term storage. Long-lived stashes get stale and conflict-prone. For anything older than a day, make a real branch.

Wrap-up

git stash is a small but high-leverage tool for context switching without polluting history. Once you internalize that the stash is a stack of real commits—pushed, popped, applied, or dropped—you stop fearing dirty working trees and gain a fast escape hatch for urgent interruptions. Name your entries, prefer branches for anything long-lived, and always check whether -u is needed.