git stash
With git stash you can save your local modifications—the changes in your working directory and index—and reset the working tree to match HEAD. That gives you a clean slate without committing work in progress.
You can restore those changes later with git stash pop or git stash apply, even on top of a different commit.
Stash is handy when you need to switch tasks, pull upstream changes, or fix something urgent without losing unfinished work.
The common use case
Section titled “The common use case”Probably the most common use case (and what probably sums up everything the average developer knows about git stash)
is running git stash to get back to a clean working tree that matches HEAD, and then doing git stash pop later.
So a developer is working, and then maybe they get interrupted. For example, they need to do a task on a different branch,
like fixing a bug in production, and then after they finish they want to go back to their branch and continue their work.
To practice this common use case, we will create an empty directory for this lesson, open a terminal (or cmd), and cd into that directory.
We will initialize git in that directory by doing:
git initThis will initialize git in that directory and place us on the repository’s initial branch (often main).
Now let’s create our first commit.
Create a file called foo in our directory and place the text Hello world in that file.
We will commit this file by doing:
git add -Agit commit -m "initial commit"We just created our first commit.
Now let’s say we are working on a new feature: making changes to the foo file and adding text at the end of that file,
while also creating a new file bar with some text in it.
We are not committing yet because we are in the middle of our work.
So after the change, I can run git status and see something like this:
git statusOn branch mainChanges not staged for commit: (use "git add <file>..." to update what will be committed) (use "git restore <file>..." to discard changes in working directory) modified: foo
Untracked files: (use "git add <file>..." to include in what will be committed) bar
no changes added to commit (use "git add" and/or "git commit -a")Now we got some interrupt and we need to stash our work and context-switch to something different. We can run:
git stashRunning git status again we should see the following result:
git statusOn branch mainUntracked files: (use "git add <file>..." to include in what will be committed) bar
nothing added to commit but untracked files present (use "git add" to track)Notice that the git stash command reverted the changes in the tracked foo file, but since the file bar is untracked,
it did not do anything with that file. The same file is still in our worktree with the same changes.
Leaving this file while context-switching, maybe to a different branch to fix something else, is not ideal - we can accidentally commit that file,
and most likely we want that file to be stashed as well.
So instead of using git stash, we can add the flag --include-untracked or -u: git stash -u will stash tracked and untracked files.
That flag is quite handy and important to be familiar with.
We usually use git stash when we have some sort of interrupt that forces us to context-switch to another task.
Those interrupts can come in different shapes and sizes. Some can take longer; for longer switches, I personally find it better
not to use git stash, but to use git worktree.
In any case, if you are using git stash and return after a while working on something different, you might have other items in your stash.
git stash is not a one-change kind of thing; it can hold as many changes as you want.
For example, after doing the first stash with the command git stash -u, we can now create another change in the foo file and stash that as well:
echo "111111" >> foogit stash # we do not need the -u flag now we don't have any untracked files in the changeNow we have 2 entries saved in the stash, and we can view them by doing:
git stash listThis will print:
stash@{0}: WIP on main: b05fdec initial commitstash@{1}: WIP on main: b05fdec initial commitBy default, git will write a message next to each stash with the following format:
stash@{<num in stash>}: WIP on <branch name>: <commit sha> <commit message>
commit sha/message - is the sha/message of the HEAD commit.
A bit hard to understand, since both stash entries share the same parent commit,
so it is hard to know which change belongs to each.
We can also peek into a stash entry to examine the changes of that stash entry.
We do that with the git stash show command:
git stash show -p stash@{1}You will see the diff of that stash. Another option that helps you distinguish between stash entries is replacing the default name of the entry with something you can easily recognize.
Let’s try to attach a message to a stash entry. We will pop the latest entry that we pushed to the stash:
git stash popAnd now we will push again to the stash, but this time we will attach a message:
git stash -m "placed ones at the end of foo"This works because git stash push is the default subcommand and -m is a flag on git stash push.
Now when I want to view the stash entries:
git stash listI see the following:
stash@{0}: On main: placed ones at the end of foostash@{1}: WIP on main: b05fdec initial commitSo we stashed our changes so we can deal with some sort of interrupt. After that interrupt is dealt with, we return to where we left off
and apply the changes that we stashed.
Usually we apply the changes by running git stash pop.
This applies the changes and also removes them from the stash.
You can also apply the changes without removing them by running git stash apply.
This is in case you want to keep the changes in the stash (perhaps you will need to use them again in a different branch).
So essentially git stash pop is equal to (provided that pop/apply ran successfully):
git stash applygit stash dropAnd you do not have to pop/apply/drop the latest entry in the stash. For example, if we want to apply a different entry, we can
attach the entry number at the end.
For example:
git stash pop stash@{1}This would apply not the latest entry, but the one before that.
I have to say that in most cases, I tend to use git stash pop and not the apply version.
Usually I want to apply and remove that entry from the stash.
Built-in defaults
Section titled “Built-in defaults”Many Git commands let you omit arguments because Git fills them in for you. That is why git stash feels short: you rarely type every part of the command.
With stash, the defaults look like this:
git stash→git stash pushgit stash pop→git stash pop stash@{0}git stash apply→git stash apply stash@{0}
The same idea shows up elsewhere. For example (when your current branch has an upstream configured):
git push→ pushes the current branch to its configured upstream (exact behavior depends onpush.default)git pull→git pull <upstream remote> <tracked branch>git fetch→ fetches from the configured default remote(s); in many repositories this means fetching fromorigin.
How to read a Git command as layers
Section titled “How to read a Git command as layers”It helps to read a Git command from left to right, one layer at a time:
git— the program itself (global options can go here).<command>— for examplestash,push, orpull.<subcommand>(optional) — for examplepush,pop, orapplyundergit stash.- Arguments — things like a stash ref (
stash@{1}) or a branch name. - Flags — options like
-uor-m "message".
Flags and options can appear in different positions depending on the command.
At each layer, Git may use a built-in default, a flag you pass on the command line, or a value from your config.
For git stash, that means:
- You can stop at
git stash, and Git runs the default subcommandpush. - You can write the full form
git stash pushwhen you want to be explicit. - You can run
git stash popwithout a ref, and Git assumesstash@{0}(the latest stash).
Once you see commands this way, flags and config make more sense: they are just another way to set values at a specific layer instead of accepting the default.
Viewing the stash object
Section titled “Viewing the stash object”Here’s a cool exercise. While it’s not critical for using git stash,
understanding this will give you a better grasp of the theory and how Git works.
Git stores objects with information about commits,
and we can use git cat-file to inspect those objects—including our stash entries.
At the moment, when we run git stash list, we get the following result:
stash@{0}: On main: placed onesstash@{1}: WIP on main: b05fdec initial commitLet’s view what Git stored for those objects.
git cat-file -t stash@{1}The -t flag prints the type of the object.
We can see that each stash entry is of type commit.
git cat-file -p stash@{1}The -p flag pretty-prints the content of the stored object.
You will see something similar to this:
tree af8c2190bbafc7d1b7f26eb424939b3be172d9f5parent b05fdec75a2f969d25548edda138d6cb0855c179parent b08c36be9633f63e7462a10ed0f7b7bdfad11519parent dfa85498dae9bf862bb3186893562372c4cd428bauthor academeez <...@academeez.com> 1779875537 +0300committer academeez <...@academeez.com> 1779875537 +0300The tree represents the working tree—a snapshot of the working directory that the stash holds.
Each parent is a commit that relates to the stash entry.
We see 3 parents in this case; let’s try to figure out what each one represents.
We will run git cat-file on the latest stash entry:
git cat-file -p stash@{0}This returned the following:
tree 93499ae1a789bac701f80b428c56a15b614bed4bparent b05fdec75a2f969d25548edda138d6cb0855c179parent 6d16fa9941e100cc584ded87efb727c9abacc36dauthor ...committer ...We can see that both stash entries contain the same parent: b05fdec75a2f969d25548edda138d6cb0855c179.
There is a useful command, git name-rev, that we can give a commit SHA,
and if available it will show us a tag, branch name, or symbolic name (if one exists) for that SHA.
git name-rev b05fdec75a2f969d25548edda138d6cb0855c179b05fdec75a2f969d25548edda138d6cb0855c179 mainWe can see that the common parent those two stash entries share is the commit that HEAD pointed to (through the main branch) when the stashes were created.
Let’s add another stash—this time with something in the index.
echo "222222" >> fooNow let’s stage our changes.
git add fooWe will not commit; instead we will stash.
git stashNotice that we stashed while our index was not empty this time, but don’t worry—we can pop from the stash while including the index:
git stash pop --indexYou can run git status and you will notice that the --index flag restored the staged state that was saved in the stash, including which changes were staged.
That means this data has to be stored: the stash commit object holds the working tree, and another parent commit records the state of the index.
Regarding the 3rd parent we saw, we can use git cat-file to figure out what it contains:
git cat-file -p dfa85498dae9bf862bb3186893562372c4cd428bThis prints:
tree fb2dbf937fb2186693660ed28b0de48def6af5e7We can examine that tree:
git cat-file -p fb2dbf937fb2186693660ed28b0de48def6af5e7This shows bar:
100644 blob 38cd9f0c9e279c611e55c213704c9647c69f8dc1 barIf you recall, the bar file was an untracked file that we stored in the stash by running git stash -u.
So the 3rd parent holds the untracked files.
While taking a moment to understand the parents of a stash entry, we learned some useful commands like cat-file and name-rev, and we understand a
bit better the data Git stores in a commit and in a stash entry.
Stash popping a non-existent file
Section titled “Stash popping a non-existent file”Let’s challenge git stash…
In the following exercise, we will create a new branch and make a commit with a new file. We will then change that file
and stash those changes. After that, we will move back to the main branch, which does not have the file the stash is based on, and try git stash pop
there to see what happens.
Create a new branch and switch to it:
git checkout -b stash-on-new-fileWe are now on a new branch called stash-on-new-file. On that branch, we will create a new file bar and add some text to it:
echo "hello world" > barWe will create a new commit for the initial bar file:
git add bargit commit -m "initial bar file"Now we will create a new stash entry with changes to the bar file:
echo "foo bar" >> bargit stashNow let’s try to stash pop on the main branch. As a reminder, the main branch does not contain the bar file:
git checkout maingit stash popIn this case, Git cannot apply the stash cleanly and prints the following message:
CONFLICT (modify/delete): bar deleted in Updated upstream and modified in Stashed changes. Version Stashed changes of bar left in tree.On branch mainUnmerged paths: (use "git restore --staged <file>..." to unstage) (use "git add/rm <file>..." as appropriate to mark resolution) deleted by us: bar
no changes added to commit (use "git add" and/or "git commit -a")The stash entry is kept in case you need it again.We are presented with a (modify/delete) conflict: on one side the stash has a modified file, and on the other side that file does not exist on main, so Git treats it as deleted.
Git suggests two options:
(use "git restore --staged <file>..." to unstage)— not relevant here, because we do not have any staged files(use "git add/rm <file>..." as appropriate to mark resolution)— this is the one we need. From here we can either run:
git add barif we want to keep the modified bar from the stash,
or we can run:
git rm barand then we resolve the conflict in favor of the version from main, leaving the repository without a bar file.
The message also states at the bottom that no commit was created and that, because of the conflict, the stash entry was not removed.
So git stash pop can be applied in a different context—not necessarily on the branch where the stash was created.
We also need to be ready to deal with conflicts when running git stash pop (or git stash apply).
This example shows that a stash is not simply a copy of files. Git attempts to merge the stashed changes into the current branch, which is why stash operations can produce merge conflicts.
Keep in mind to read the messages Git prints when you run a command, or when you run git status.
Reading those messages and understanding them is one of the differences between a Git beginner and a Git expert.
Stash is doing a merge
Section titled “Stash is doing a merge”We figured out from the previous example that git stash pop behaves a lot like a merge and can produce merge conflicts.
In the previous example, it was a fairly simple conflict where we had to choose whether we want the file to exist or be deleted.
In other cases, it might not be that simple.
As we mentioned, we use stash when we have an interrupt. A more common case is to work on a branch, get interrupted, switch to a different branch and fix something
—which might advance main (or another developer might have advanced main in the meantime)—then go back to your branch, grab the latest main, rebase your branch on top of main,
and run git stash pop, only to find that after taking the latest changes from main you suddenly have conflicts with your stash.
Since this is the conflict you will encounter most often, it is worth practicing it.
Currently on the main branch we have the foo file, which contains the text hello world.
We will create a new branch, add some text to foo, and stash our changes.
In the meantime, main will move forward: we will add a commit to main that adds text to foo, then return to our branch and rebase
it so it is up to date with the latest changes.
We will then pop from our stash and encounter conflicts.
From that conflicted state, we will walk through the steps to resolve them.
From main, let’s create a new branch and switch to it:
git checkout -b conflict-on-fooLet’s add some changes to the foo file and stash them:
echo "changes from the branch conflict-on-foo" >> foogit stashNow let’s go back to main and add a commit with changes to foo as well. This simulates main having progressed.
In real life, when working with other developers, someone might have pushed changes to the remote repository. In that case, you can
update your local main from the remote upstream main without leaving your current branch by running:
git fetch origin main:mainIn this lesson we are simplifying things, and there is no remote upstream.
We will manually switch to main and add a commit that changes the foo file.
git checkout mainecho "changes from the main branch" >> foogit commit -am "changes to foo"We added a commit to main with changes at the end of the foo file.
(git commit -a stages all changed tracked files before committing.)
Now let’s go back to our branch. Our branch does not yet contain the latest changes from main.
What we can do is rebase our branch on top of main to pick up those changes.
git checkout conflict-on-foogit rebase mainThis should place our branch conflict-on-foo on the latest main, which means you should see the changes we added to foo:
cat fooYou should see:
hello worldchanges from the main branchFrom this state, let’s try to stash pop our changes:
git stash popWe see the following result:
Auto-merging fooCONFLICT (content): Merge conflict in fooRecorded preimage for 'foo'On branch conflict-on-fooUnmerged paths: (use "git restore --staged <file>..." to unstage) (use "git add <file>..." to mark resolution) both modified: foo
no changes added to commit (use "git add" and/or "git commit -a")The stash entry is kept in case you need it again.We now have a conflict on the foo file. If we print the contents of foo, we will see:
hello world<<<<<<< Updated upstreamchanges from the main branch=======changes from the branch conflict-on-foo>>>>>>> Stashed changesAt the top, Updated upstream is the current version on your branch (what came from main after the rebase). Below that, Stashed changes is what was saved in the stash.
After you decide how foo should look, you can stage the file by running git add foo.
You can also go back to the state before you ran git stash pop (the stash entry still exists, so you will not lose anything—you can run git stash pop again later).
We can restore foo to before the pop using git restore:
git restore --worktree --source=HEAD --staged fooThis restores the foo file in the working tree from HEAD—the commit at the tip of your branch, before the failed pop.
Instead of running git restore, we will resolve the conflict by making foo include both changes:
hello worldchanges from the main branchchanges from the branch conflict-on-fooOur conflict is resolved. Mark it as resolved in Git (stage it) and then commit the fixed file:
git commit -am "fixed conflicts on foo"For more complicated conflicts, you can use git mergetool, but that is a topic for another lesson.
Summary
Section titled “Summary”git stash saves your local changes—the working directory and index—and resets your branch to a clean state at HEAD, without creating a commit. It is the tool you reach for when an interrupt forces you to context-switch: fix something on another branch, pull upstream changes, or handle urgent work, then come back and continue where you left off.
In this lesson we covered the full stash workflow:
- Stashing and restoring —
git stash(same asgit stash push),git stash pop, andgit stash apply.popapplies and removes the entry;applyleaves it in the list. Usegit stash dropto remove an entry without applying it. - Untracked files — plain
git stashignores untracked files; usegit stash -uwhen you need those included. - Multiple entries — the stash is a stack, not a single slot. List with
git stash list, inspect withgit stash show -p, and name entries withgit stash push -m "message". - Built-in defaults — Git commands are layered (command → subcommand → arguments → flags). Many arguments have defaults, such as
stash@{0}for the latest entry. - What a stash actually is — each entry is a commit object. With
git cat-filewe saw how parents recordHEAD, the index, and (with-u) untracked files. Usegit stash pop --indexwhen you need the staged state restored too. - Conflicts —
git stash popmerges stashed changes into your current branch, so it can conflict. That can happen on a different branch than where you stashed, or after rebasing onto an updatedmain. Read Git’s output, resolve conflicts (or undo withgit restore), stage the result, and commit when you are ready. On failure, the stash entry is kept.
For longer interruptions, consider git worktree instead of stacking stashes. For everyday interrupts, git stash and git stash pop are enough—but knowing the rest helps you use stash confidently when things get messy.