Git Stash: The Under-the-Hood Clipboard of Git
TL;DR: The git stash command is Git’s temporary storage locker. It packages your uncommitted working directory changes and index modifications into standard commit objects—without linking them to any active branch—resets your working tree, and registers those commits on a stack managed by .git/refs/stash and the reflog.
What Does git stash Actually Do?
To understand git stash, we must look at how Git tracks modifications. While you are working on a feature, you may have files in two different states of modification:
- Staged Changes: Modifications added to the index via /git-add.
- Unstaged Changes: Local edits in the working directory that have not been staged yet.
If you suddenly need to switch branches (e.g., to fix a critical production bug on main using /git-checkout), Git will prevent the checkout if your uncommitted changes conflict with files modified in the target branch.
You don’t want to write a “Work In Progress” (WIP) commit to your branch history because it pollutes the log with half-finished, non-compiling code. This is where git stash is used.
The Stash Commit Structure
A stash is not saved to some hidden cache folder outside Git’s commit system. It is literally created as standard commit objects stored in .git/objects/.
When you run git stash, Git performs the following sequence:
- It creates a tree object representing the state of the index (staged changes).
- It creates a commit object
Ipointing to that tree. - It creates a tree object representing the state of your working directory (unstaged changes).
- It creates a commit object
Wpointing to the working directory tree. This commit is unique because it is configured with multiple parent pointers:- Parent 1: Your current branch commit (
HEAD). - Parent 2: The index commit
Icreated in step 2. - Parent 3 (Optional): An untracked files commit
U(if stashing with--include-untracked).
- Parent 1: Your current branch commit (
- It writes the hash of the working directory commit
Wto.git/refs/stash. - It performs a hard reset (
git reset --hard) to return your working directory and index to a clean state.
---> [Commit HEAD] (Current Branch)
/ ^ ^
/ / \ (Parent 1)
/ / \
[Commit I] (Index) [Commit W] (Stash Ref: refs/stash)
^ (Parent 2)
By referencing the stash commits, Git can later parse the trees and restore both your staging area and working directory exactly as they were.
What Happens Inside .git/ (Hands-on exploration)
Let us perform a step-by-step walkthrough to inspect how the .git/ folder changes when you stash and how Git structures stash commit objects.
Step 1: Initializing and Modifying a Repository
First, let’s create a repository and add an initial commit:
$ mkdir git-stash-demo
$ cd git-stash-demo
$ git init
Initialized empty Git repository in /users/admin/git-stash-demo/.git/
$ echo "Baseline Text" > main.txt
$ git add main.txt
$ git commit -m "Commit 1: Base"
[main (root-commit) c1c1c1c] Commit 1: Base
Our base commit is c1c1c1c. Now let’s make some local changes:
- We will modify
main.txt(an unstaged change in the working directory). - We will create
staged.txtand stage it (link to /git-add). - We will create
untracked.txt(which remains untracked in our directory).
$ echo "Working directory edit" >> main.txt
$ echo "Staged file contents" > staged.txt
$ git add staged.txt
$ echo "Untracked file contents" > untracked.txt
Let’s check the repository state using /git-status:
$ git status
On branch main
Changes to be committed:
new file: staged.txt
Changes not staged for commit:
modified: main.txt
Untracked files:
untracked.txt
Step 2: Running the Stash
Now, we run git stash with the -u (or --include-untracked) flag to save all of our changes:
$ git stash -u
Saved working directory and index state WIP on main: c1c1c1c Commit 1: Base
If we check our directory, the changes are gone. main.txt is reverted, staged.txt is gone, and untracked.txt is gone.
Let’s inspect .git/refs/stash to see where the stash is pointed:
$ cat .git/refs/stash
s1a2s3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0
It contains the SHA-1 hash of a commit object. Let’s inspect this commit object using git cat-file:
$ git cat-file -p s1a2s3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0
tree 9d8c7b6a5f4e3d2c1b0a9f8e7d6c5b4a3f2e1d0c
parent c1c1c1c8928424ef245eefb925b42d3851b2c129
parent i2i2i2i8928424ef245eefb925b42d3851b2c129
parent u3u3u3u8928424ef245eefb925b42d3851b2c129
author Developer <dev@example.com> 1783420000 +0000
committer Developer <dev@example.com> 1783420000 +0000
WIP on main: c1c1c1c Commit 1: Base
Look at the three parent lines:
parent c1c1c1c...is our base commitHEAD.parent i2i2i2i...is the commit representing the index state (staged.txt).parent u3u3u3u...is the commit representing the untracked files (untracked.txt).
Let’s inspect the index commit tree to verify staged.txt is inside:
$ git cat-file -p i2i2i2i8928424ef245eefb925b42d3851b2c129
tree a1b2c3d4...
author ...
Indeed, the trees contain exact snapshots of our files in their respective states.
Step 3: The Stash Stack and Reflog
How does Git support multiple stashes if there is only one .git/refs/stash reference? It uses the reflog of refs/stash.
Let’s look at the reflog file at .git/logs/refs/stash:
$ cat .git/logs/refs/stash
0000000000000000000000000000000000000000 s1a2s3d... Developer <dev@example.com> 1783420000 +0000 WIP on main: c1c1c1c Commit 1: Base
When you create a second stash, Git moves the new commit to .git/refs/stash and writes the old stash commit to the top of the reflog list. In user space, they are mapped as:
stash@{0}: The commit hash in.git/refs/stash(newest).stash@{1}: The first entry in the reflog.stash@{2}: The second entry, and so on.
Step 4: Popping the Stash
Now let’s apply and pop our changes:
$ git stash pop
On branch main
Changes to be committed:
new file: staged.txt
Changes not staged for commit:
modified: main.txt
Untracked files:
untracked.txt
Dropped refs/stash@{0} (s1a2s3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0)
The files are restored to their original status. Since we popped the only stash on the stack, Git deletes the reference file .git/refs/stash and its reflog, leaving the repository clean of temporary references.
Detailed Command Options & Advanced Usage
The git stash command offers several flags for surgical workspace restoration.
1. Including Untracked and Ignored Files (-u and -a)
By default, typing git stash will only save files that are currently tracked by Git.
- Include Untracked (
-uor--include-untracked): If you have created new files but haven’t rungit addon them, Git will ignore them during a standard stash. Using-uensures they are stashed. - Include All (
-aor--all): This option stashes all files, including untracked files and files ignored by your.gitignorefile (such as build files, local configuration files, or package directories). This is useful when you want to reset your workspace to a pristine state.
2. Interactive Patch Stashing (git stash -p)
If you don’t want to stash everything, you can choose specific hunks to stash.
$ git stash -p
# Or:
$ git stash --patch
Git will scan your changes and present each hunk of modifications, asking:
Stash this hunk [y, n, q, a, d, /, e, ?]?
y: Stash this hunk.n: Do not stash this hunk.q: Quit; do not stash this hunk or any remaining ones.d: Do not stash this hunk or any of the later hunks in the file.
This is highly effective when you want to isolate a bug fix from experimental modifications on the same file.
3. Creating a Branch from a Stash (git stash branch)
If you stash a set of complex modifications, continue working on your branch, and then try to restore the stash later, you may get massive merge conflicts.
To avoid this, you can create a new branch at the commit where your stash was originally created:
$ git stash branch feature-new-auth stash@{0}
What this does under the hood:
- It creates a new branch named
feature-new-authpointing to the parent commit of the stash (the state of the code when you stashed it). - It checks out that commit (so there are zero conflicts).
- It applies the stashed changes to your working directory and index.
- If successful, it drops the stash from the stack.
This isolates the stashed changes onto a fresh branch, allowing you to test and merge them safely.
Real-World Scenario: Reclaiming a Dropped Stash
Imagine you are cleaning up your stashes. You look at your list, think you don’t need a stash anymore, and run:
$ git stash drop stash@{0}
Dropped stash@{0} (s1a2s3d...)
A second later, you realize that stash contained hours of work that you had forgotten to commit. Because you dropped the reference, the stash no longer shows up in git stash list.
How to Recover It:
Since stashes are standard commits, the commit objects are still in the Git database. They are just “unreachable” because no branch or ref points to them.
- Scan for Unreachable Commits:
Run Git’s filesystem check tool:
$ git fsck --unreachable | grep commit unreachable commit s1a2s3d4e5f6... unreachable commit a9b8c7d6e5f4... - Inspect the Commits:
Look at the commit logs or contents of the unreachable commits to identify the stash:
If it displays “WIP on [branch-name]”, you have found your lost stash!$ git show s1a2s3d4e5f6 commit s1a2s3d4e5f6... Merge: c1c1c1c i2i2i2i Author: Developer <dev@example.com> Date: Jul 7 10:45:00 2026 WIP on main: c1c1c1c Commit 1: Base - Apply the Commit:
Simply apply the commit hash directly to your working directory:
Your work is restored!$ git stash apply s1a2s3d4e5f6
Common Pitfalls and Mistakes
1. Forgetting Untracked Files
Running git stash without -u will leave untracked files in your working directory. When you switch branches using /git-checkout, those untracked files will be carried over to the new branch, potentially polluting it or causing build errors.
2. Using Stash as a Long-term Backup
Stashes are meant to be short-term storage. If you keep stashes for weeks:
- You will forget what changes they contain because they lack clear names.
- The codebase will diverge, making it highly likely that popping the stash later will result in complex conflicts.
- Alternative: If you need to save work for more than a day, create a temporary branch (using /git-branch) and make a commit (using /git-commit) with the message
"WIP".
3. Accidental Loss with git stash clear
Running git stash clear deletes the entire stack of stashes. If you had saved work from other branches, it is all deleted from the stack.
- Fix: Use
git stash drop stash@{N}to delete specific stashes one at a time, rather than clearing the stack.
Command Quick Reference
| Command | Action | Git Internals Action |
|---|---|---|
git stash | Stashes tracked files | Creates 2 commits; updates refs/stash |
git stash -u | Stashes tracked and untracked files | Creates 3 commits; updates refs/stash |
git stash -a | Stashes all files (including ignored) | Creates 3 commits including ignored file trees |
git stash list | Lists all saved stashes | Reads the reflog of .git/refs/stash |
git stash apply | Restores latest stash; keeps reference | Applies tree objects; does not modify refs/stash |
git stash pop | Restores latest stash; deletes reference | Applies tree objects; pops refs/stash reflog |
git stash drop stash@{N} | Deletes stash N | Removes entry N from the refs/stash reflog |
git stash clear | Deletes all stashes | Deletes .git/refs/stash and the reflog file |
FAQ (People Also Ask)
What is the difference between git stash pop and git stash apply?
git stash apply restores the changes to your working directory but leaves the stash on the stack. git stash pop restores the changes and immediately deletes the stash from the stack. Use apply if you want to test the stashed changes without losing the backup, or if you want to apply the same stash to multiple branches.
Why does git stash pop say “Conflict” and not delete the stash?
If applying the stash results in merge conflicts, Git will halt and leave the conflict markers in your files. In this state, Git will not delete the stash from your stack. This is a safety measure to ensure you don’t lose the original, clean stash changes in case you make a mistake while resolving the conflict. Once you resolve the conflicts, you must manually run git stash drop to delete the stash.
Can I stash changes on one branch and apply them to another?
Yes! Since a stash is a standard set of commit objects, you can run git stash on branch-a, checkout branch-b, and run git stash pop. Git will perform a merge of the stashed changes onto branch-b.
Test Your Knowledge
Loading quiz…