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:

  1. Staged Changes: Modifications added to the index via /git-add.
  2. 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:

  1. It creates a tree object representing the state of the index (staged changes).
  2. It creates a commit object I pointing to that tree.
  3. It creates a tree object representing the state of your working directory (unstaged changes).
  4. It creates a commit object W pointing 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 I created in step 2.
    • Parent 3 (Optional): An untracked files commit U (if stashing with --include-untracked).
  5. It writes the hash of the working directory commit W to .git/refs/stash.
  6. 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:

  1. We will modify main.txt (an unstaged change in the working directory).
  2. We will create staged.txt and stage it (link to /git-add).
  3. 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 commit HEAD.
  • 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 (-u or --include-untracked): If you have created new files but haven’t run git add on them, Git will ignore them during a standard stash. Using -u ensures they are stashed.
  • Include All (-a or --all): This option stashes all files, including untracked files and files ignored by your .gitignore file (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:

  1. It creates a new branch named feature-new-auth pointing to the parent commit of the stash (the state of the code when you stashed it).
  2. It checks out that commit (so there are zero conflicts).
  3. It applies the stashed changes to your working directory and index.
  4. 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.

  1. Scan for Unreachable Commits: Run Git’s filesystem check tool:
    $ git fsck --unreachable | grep commit
    unreachable commit s1a2s3d4e5f6...
    unreachable commit a9b8c7d6e5f4...
  2. Inspect the Commits: Look at the commit logs or contents of the unreachable commits to identify the 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
    If it displays “WIP on [branch-name]”, you have found your lost stash!
  3. Apply the Commit: Simply apply the commit hash directly to your working directory:
    $ git stash apply s1a2s3d4e5f6
    Your work is restored!

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

CommandActionGit Internals Action
git stashStashes tracked filesCreates 2 commits; updates refs/stash
git stash -uStashes tracked and untracked filesCreates 3 commits; updates refs/stash
git stash -aStashes all files (including ignored)Creates 3 commits including ignored file trees
git stash listLists all saved stashesReads the reflog of .git/refs/stash
git stash applyRestores latest stash; keeps referenceApplies tree objects; does not modify refs/stash
git stash popRestores latest stash; deletes referenceApplies tree objects; pops refs/stash reflog
git stash drop stash@{N}Deletes stash NRemoves entry N from the refs/stash reflog
git stash clearDeletes all stashesDeletes .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…