Several years ago, while pairing with a teammate, I noticed he was taking a lot of time to perform a simple task, because his working environment was not clean.
To help him save some time, I suggested he use git stash
, a Git command that saves local changes for later in a stack-like structure, so we can switch branches without losing our work. However, he told me that he had once tried to use it and ended up losing his work without knowing why. As a result, he decided to never use this evil command ever again, even though it could save him lots of time when switching between tasks.
I am sure that losing the work of an entire day must be painful. However, the lessons should not be to avoid a useful command, but to learn how to use it to prevent those accidents. You can read more about this mindset in my article The Impact Of Mastering Our Day to Day Tools:
In this article, I’d like to describe not only how to use the git stash
command with all its different options, but also when we can take advantage of this powerful yet simple tool. If you or someone you know is facing an issue like this, I hope this guide helps you understand how and when to use this command with confidence.
Disclaimer: This guide is based on the latest version of Git available at the time of writing (v2.50). For the official documentation, see: https://git-scm.com/docs/git-stash/2.43.0 (latest version where stash was updated).
Background
This is a very brief introduction into the different working areas that Git uses to handle our work:
- Staged: These are the changes we’ve told Git we want to include in the next commit. We add them using
git add
. This area is also called the index: it’s like Git’s “ready to commit” list. - Unstaged: These are changes we’ve made to files Git already knows about, but we haven’t added them yet.
- Untracked: These are new files Git doesn’t know about yet, because we haven’t added them to version control.
You can see all of these areas by running git status
. That’s what we’ll use in the examples below.
Note that we can also tell Git to ignore certain files on purpose (like build output, logs, or temp files). These are listed in a .gitignore
file and don’t show up in git status
or get tracked at all. This will be important, as git stash
provides a way to bypass this.
Examples
Consider the following example repository, where we have three files: file
1.txt in the staging area (ready to be committed), file2.txt
not staged yet, file3.txt
that is not even tracked by Git, and file4.txt
, which is ignored by Git:
$ git status
Changes to be committed:
modified: file1.txt
Changes not staged for commit:
modified: file2.txt
Untracked files:
file3.txt
git stash
Let’s pretend there was a change in priorities or we need to fix a bug in production. In that case, we need to stop what we are doing and start working on something else in the same repository. But, for some reason, we do not want to commit our changes just yet. This is one of the most common scenarios where we will want to do:
$ git stash
$ git status
Untracked files:
file3.txt
This command tells git to move staged and unstaged changes to a special area called the stash. Untracked and ignored files are skipped. So, when we do git status
, we will only see the untracked files.
Although the default behaviour is to exclude untracked files, we can be explicit by saying:
$ git stash --no-include-untracked
$ git status
Untracked files:
file3.txt
If instead, we want to include untracked files, we can include the --include-untracked
option. Note that the ignored file will still be present in our file system:
$ git stash --include-untracked # or
$ git stash -u
$ git status
nothing to commit, working tree clean
$ ls file4.txt
file4.txt
If we want to also include ignored files, we can use the --all
option:
$ git stash --all # or
$ git stash -a
$ git status
nothing to commit, working tree clean
$ ls file4.txt
ls: file4.txt: No such file or directory
Note that we cannot do git stash
if our repository doesn’t have any commits yet, but I don’t think this will ever be a problem.
git stash list
To see what you have stashed, you can do:
$ git stash list
stash@{0}: WIP on branch1: 1b4635d Last commit
stash@{1}: WIP on branch2: 12a876f Another commit
As mentioned before, the stash area is a stack, where each new stash is added on top. Every stash entry has a pre-established format:
stash@{<number>}: WIP on <branch name>: <last commit ID> <last commit message>
This is useful because, since there is a single stash area per repository (i.e. all branches share the same stash), this format let us to know the branch and commit we were working on when we stashed our changes.
You can use any of the git log
options on this command as well. Since they are out of scope for this article, I’ve let a link to the official documentation so you can have a look.
git stash show
If we want to see the list of files in our stash area, rather than just the stash info, we can do:
$ git stash show
file1.txt | 1 +
file2.txt | 1 +
2 files changed, 2 insertions(+)
This will show both staged and unstaged files, and I use it quite often when I want to remember exactly what files I was working on at the time.
By default, git stash show
will show the content of the top of the stack, but we can specify what item we can to see by appending the stack number. This example shows another file, file5.txt
, which was added previously to the stash area:
$ git stash show 1
file5.txt | 5 +
1 files changed, 5 insertions(+)
By default, untracked files are not shown even if they were stashed. We can use the --include-untracked
option to see untracked (and ignored) files:
$ git stash show --include-untracked # or
$ git stash show -u
file1.txt | 1 +
file2.txt | 1 +
file3.txt | 1 +
file4.txt | 0
4 files changed, 3 insertions(+)
When we’re only interested in seeing untracked (and ignored) files, we can use the --only-untracked
options:
$ git stash show --only-untracked
file3.txt | 1 +
file4.txt | 0
2 files changed, 1 insertion(+)
As already mentioned, git stash show
shows the changes recorded in the stash entry as a diff between the stashed contents and the commit back when the stash entry was first created. However, this shows some basic information only.
Fortunately, git stash show
also supports all the options of the git diff
command, so we can enrich the output. You can refer the git diff
official documentation for details on these options, but the --patch
option is too important not to cover here.
git stash show –patch
Sometimes seeing the file names is not enough. We also want to see the actual changes that we were making in those files. I usually do this for two reasons: I want to make sure that’s the stash I want, or I want to copy some code in that stash to use somewhere else. In those cases, we can do:
$ git stash show --patch # or
$ git stash show -p # or
$ git stash show -u
diff --git a/file1.txt b/file1.txt
index 02aa577..fa49a36 100644
--- a/file1.txt
+++ b/file1.txt
@@ -1 +1,2 @@
content of file1.txt
+new changes of file1.txt
diff --git a/file2.txt b/file2.txt
index fda4ec9..3dbe8fb 100644
--- a/file2.txt
+++ b/file2.txt
@@ -1 +1,2 @@
content of file2.txt
+new changes of file2.txt
This returns a diff between the code in the stash, and the commit when the stash was saved. As mentioned earlier, the output’s format can be improved further by using the same options available for git diff
.
If we want to see the specific code changes of another entry of the stack, we can specify the stack entry in the command. For example:
$ git stash show -p 1
git stash pop
When we’re ready to go back to our original work, we need to recover the changes that we stashed for later using the git stash pop
command.
This command applies the changes at the top of the stash stack, and then removes the entry from the stack.
Note that staged and unstaged files are restored in the unstaged area, and that untracked files are left untracked.
$ git stash pop
Changes 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: file1.txt
modified: file2.txt
Untracked files:
(use "git add <file>..." to include in what will be committed)
file3.txt
no changes added to commit (use "git add" and/or "git commit -a")
Dropped refs/stash@{0} (b0488a)
If we want staged files to be restored in the staged area, we must use the --index
option:
$ git stash pop --index
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: file1.txt
Changes 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: file2.txt
Untracked files:
(use "git add <file>..." to include in what will be committed)
file3.txt
Dropped refs/stash@{0} (b0488a)
When git stash pop
encounters conflicts trying to apply the changes, it will do as much as it can to apply the changes, and then report the conflict. The entry in the stash will NOT be removed.
This was unexpected to me the first time that I saw this behaviour, but I think it is practical. Sometimes I’ve encountered difficult conflicts and having the chance to clear my mess and start again fresh, knowing that my changes are still saved, it’s a relief.
This is an example of using the git stash pop
command when encountering conflicts. Note the message at the bottom saying the stash is being kept.
$ git stash pop
Auto-merging file1.txt
CONFLICT (content): Merge conflict in file1.txt
On branch main
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: file2.txt
Unmerged paths:
(use "git restore --staged <file>..." to unstage)
(use "git add <file>..." to mark resolution)
both modified: file1.txt
Untracked files:
(use "git add <file>..." to include in what will be committed)
file3.txt
The stash entry is kept in case you need it again.
I find that git stash pop
is sometimes overly verbose. Luckily for us, we can solve that with the --quiet
option.
$ git stash pop --quiet # or
$ git stash pop -q
If there’s a conflict we’ll still be informed of it though:
$ git stash pop --quiet # or
$ git stash pop -q
Auto-merging file1.txt
CONFLICT (content): Merge conflict in file1.txt
The stash entry is kept in case you need it again.
Finally, we can specify any item in the stash area. That means that we can apply any item in the stash, not just the one at the top, and also remove it from the stash area if it was successfully applied. This is why I consider the stash area to be a stack-like structure, and not plainly a stack.
$ git stash pop 1
Changes 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: file5.txt
no changes added to commit (use "git add" and/or "git commit -a")
Dropped refs/stash@{0} (88ab04)
git stash drop
Sometimes we will want to remove items from the stash area. This can happen after solving the conflicts of a pop
operation, or if we decide that those changes are no longer needed.
In those cases, we can use the git stash drop
command, which by default removes the element at the top of the stack:
$ git stash drop
Dropped refs/stash@{0} (b0488a)
$ git stash list
stash@{0}: WIP on branch2: 12a876f Another commit
We can remove any element from the stash by just specifying it’s number:
$ git stash drop 1
Dropped refs/stash@{1} (12a876f)
$ git stash list
stash@{0}: WIP on branch1: 1b4635d Last commit
Although not particularly annoying, we can still remove the command’s output with the --quiet
option:
$ git stash drop --quiet # or
$ git stash drop -q
$ git stash list
stash@{0}: WIP on branch2: 12a876f Another commit
GIT STASH APPLY
The git stash apply
command does the same as the git stash pop
command, except that it does NOT remove the stash entry from the stash area.
$ git stash apply
Changes 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: file1.txt
modified: file2.txt
Untracked files:
(use "git add <file>..." to include in what will be committed)
file3.txt
no changes added to commit (use "git add" and/or "git commit -a")
I find this particularly useful when I want to split some code changes into different pull requests, as I can apply the changes into a branch and commit only some of the changes. Then, I can go to a different branch, do git stash apply
, and commit another subset of the changes. And so on.
This command accepts the same options as git stash pop
, so I won’t list them here.
git stash branch
The git stash branch
command creates and checks out a new branch with the name that we specify. Just like git stash pop
, the stash entry will be removed from the stash area only if the operation succeeds.
$ git stash branch my-new-branch
Switched to a new branch 'my-new-branch'
On branch my-new-branch
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: file1.txt
Changes 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: file2.txt
Untracked files:
(use "git add <file>..." to include in what will be committed)
file3.txt
Dropped refs/stash@{0} (b0488a)
The branch will start from the commit at which the stash entry was originally created, and will then apply the changes on the working area. Staged files will be applied in the staged area, as if we had run the git stash pop --index
command.
I find this command also very useful for splitting large pull requests. Another use case for this command is when applying the changes on another branch will cause conflicts: we may prefer to apply the changes, and then solve the conflicts separately, if needed.
git stash push
The git stash push
command is a more explicit version of git stash
: it does the same thing and supports the same options:
--include-untracked
or-u
--no-include-untracked
--all
or-a
But it also supports a few extra options, which we’ll explore here.
If we find git stash
to be too verbose, we can just use the --quiet
option to silent it:
$ git stash push --quiet # or
$ git stash push -q
By default, git stash push
will stash files in the staged and unstaged areas only (same behaviour as git stash
).
If we do --no-keep-index
, we’ll stash staged and unstaged files. This is the default option.
$ git stash push --no-keep-index
Saved working directory and index state WIP on branch2: 12a876f Another commit
$ git status
Untracked files:
(use "git add <file>..." to include in what will be committed)
file3.txt
nothing added to commit but untracked files present (use "git add" to track)
To only include unstaged files (i.e. exclude staged files), we can use the --keep-index
option.
$ git stash push --keep-index # or
$ git stash push -k
Saved working directory and index state WIP on branch2: 12a876f Another commit
$ git status
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: file1.txt
Untracked files:
(use "git add <file>..." to include in what will be committed)
file3.txt
git stash push
also allows us to only stash staged files:
$ git stash push --staged # or
$ git stash push -S
Saved working directory and index state WIP on branch2: 12a876f Another commit
And if we find that the default stash message is not clear enough, we can specify our own message!
$ git stash push --message "my cool message" # or
$ git stash push -m "my cool message"
Saved working directory and index state On branch2: my cool message
$ git stash list
stash@{0}: On branch1: my cool message
If you want even finer control on which files to stash, you can specify a list of files and/or directories separated by space using this command. I also find this to be particularly useful when making smaller pull requests, or anytime we want to work on some changes, but want to save some particular files for later.
$ git stash push -- file1.txt file2.txt
Saved working directory and index state WIP on branch2: 12a876f Another commit
$ git status
Untracked files:
(use "git add <file>..." to include in what will be committed)
file3.txt
no changes added to commit (use "git add" and/or "git commit -a")
git stash clear
Use this command with caution. This is one of the few commands in Git that can permanently delete our work. If you are sure you want to remove all elements from your stash, do:
git stash clear
Just as a reminder, this will delete ALL entries in your repository stash, irrespective of the branch where you create it.
Advanced Usage
git stash push (advanced)
There are a few options we can use with git stash push
that I find slightly advanced. I have never found the need to put them into practice, but I like to be aware of them for whenever they become handy, probably when writing some automation tools.
For example, we can have a file to specify what files to stash. If we have a file like this, where every file to stash is listed in a new line:
file1.txt
file2.txt
We can stash our changes using this command instead:
$ git stash push --pathspec-from-file=files-to-stash.txt
Saved working directory and index state WIP on branch2: 12a876f Another commit
$ git status
Untracked files:
(use "git add <file>..." to include in what will be committed)
file3.txt
files-to-stash.txt
nothing added to commit but untracked files present (use "git add" to track)
However, that command will not work properly if the file names have spaces (like file 1.txt
. To account for this, we can separate files with a null character rather than a newline character.
printf "file 1.txt\0file 2.txt\0" > files-to-stash.txt
And pass option --pathspec-file-nul
to our previous command:
$ git stash push --pathspec-file-nul --pathspec-from-file=files-to-stash.txt
Saved working directory and index state WIP on branch2: 12a876f Another commit
$ git status
Untracked files:
(use "git add <file>..." to include in what will be committed)
file3.txt
files-to-stash.txt
nothing added to commit but untracked files present (use "git add" to track)
git stash create + git stash store
I don’t intend to trick anyone. This is the first time I’ve ever looked at these options, and my knowledge is quite limited. However, I still wanted to try and understand what’s the purpose of these commands, and how to use them. In this section, I share with you what I’ve uncovered, which may be just the tip of the iceberg.
Whether we use git stash
or git stash push
to create a stash entry in the stash area, our working area will be cleared in the process. That is, any staged, unstaged and/or untracked objects will be removed from our working space as they are moved into the stash. And If we don’t want to revert these changes, we can do git stash apply
to move things back into our working area.
There is, however, a way to achieve the same result without ever moving things out of the working area.
First, we need to bring up the concept of a Git object. We don’t usually need to know about these objects, so this may be a hard concept to grasp. When we commit some changes, we can identify the commit by its hash id. This is a Git object that can be referenced by that id. Similarly, every entry in the stash is also a Git object with a hash id (only that it is not shown to us).
So, we will create a new Git object with this command, which will return the hash of the newly created object.
$ git stash create
1e9b483d1f9477de5c99a708f4aa512ba
$ git stash list
stash@{0}: WIP on branch2: 12a876f Another commit
The stash is still empty. This object is not tied to anything, and it will eventually be deleted by git’s garbage collector if we don’t do something with this. So, let’s add this object to the stash area with the store
command:
$ git stash store 1e9b483d1f9477de5c99a708f4aa512ba
$ git stash list
stash@{0}: Created via "git stash store".
stash@{1}: WIP on branch2: 12a876f Another commit
Our working area remained unchanged this whole time:
$ git status
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: file1.txt
Changes 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: file2.txt
Untracked files:
(use "git add <file>..." to include in what will be committed)
file3.txt
According to the documentation, we can pass a message when doing create
. Although the command accepts the option, I could not understand what the use of it. I raised a ticket with Git to either fix the behaviour (based on the behaviour that I expected), or to improve the documentation. Hopefully someone who understand the intent will explain what’s the idea of that option.
In any case, we can pass a message to the store
command, and this time the argument is used as expected:
$ git stash store --message "example" 1e9b48 # or
$ git stash store -m "example" 1e9b48
$ git stash list
stash@{0}: example
stash@{1}: WIP on branch2: 12a876f Another commit
git stash store
also supports a --quiet
option, but I’m not sure what for since the command doesn’t output anything.
My spider-sense tells me that maybe these commands are not used a lot, which would explain the presence of these bugs odd behaviours.
Final thoughts
If you change teams or jobs, even if the new place uses different technologies or programming languages, it’s very likely that you will continue using Git. This is why mastering this tool beyond the basic commands everyone knows is so important.
I find that Git is a very simple tool when considering all the features that it provides. Maybe the complexity lays in learning all the options it provides, and how to use them properly and at the right time.
If these options are new to you, I hope that the examples provided are a good starting point, but nothing will replace doing some hands-on learning by yourself. So, I’d like to invite you to try one of these commands next time you work in your repository. If you do this, you will be one step closer to mastering this important tool for our craft.
Happy coding!
José Miguel
Share if you find this content useful.
Follow me on LinkedIn to be notified of new articles.