The Stash
Do you ever feel overwhelmed in your daily development cycle when the constant interruptions, demands for bug fixes, and requests from coworkers or managers all pile up and clutter the real work you are trying to do? If so, the stash was designed to help you!
The stash is a mechanism for capturing your work in progress, allowing you to save it and return to it later when convenient. Sure, you can already do that using the existing branch and commit mechanisms within Git, but the stash is a quick convenience mechanism that allows a complete and thorough capturing of your index and working directory in one simple command. It leaves your repository clean, uncluttered, and ready for an alternate development direction. Another single command restores that index and working directory state completely, allowing you to resume where you left off.
Let’s see how the stash works with the canonical use case: the so-called “interrupted work flow.”
In this scenario, you are happily working in your Git repository and have changed several files and maybe even staged a few in the index. Then, some interruption happens. Perhaps a critical bug is discovered and lands on your plate and must be fixed immediately. Perhaps your team lead has suddenly prioritized a new feature over everything else and insists you drop everything to work on it. Whatever the circumstance, you realize you must stash everything, clean your slate and work tree, and start afresh. This is a perfect opportunity for git stash!
$ cd the-git-project
# edit a lot, in the middle of something
# High-Priority Work-flow Interrupt!
# Must drop everything and do Something Else now!
$ git stash save
# edit high-priority change
$ git commit -a -m "Fixed High-Priority issue"
$ git stash pop
And resume where you were!
The default and optional operation to git stash is save. Git also supplies a default log message when saving a stash, but you can supply your own to better remind you what you were doing. Just supply it in the command after the then-required save argument:
$ git stash save "WIP: Doing real work on my stuff"
The acronym WIP is a common abbreviation used in these situations meaning “work in progress.”
To achieve the same effect with other, more basic Git commands requires manual creation of a new branch on which you commit all of your modifications, re-establishing your previous branch to continue your work, and then later recovering your saved branch state on top of your new working directory. For the curious, that process is roughly this sequence:
# ... normal development process interrupted ...
# Create new branch on which current state is stored.
$ git checkout -b saved_state
$ git commit -a -m "Saved state"
# Back to previous branch for immediate update.
$ git checkout master
# edit emergency fix
$ git commit -a -m "Fix something."
# Recover saved state on top of working directory.
$ git checkout saved_state
$ git reset --soft HEAD^
# ... resume working where we left off above ...
That process is sensitive to completeness and attention to detail. All of your changes have to be captured when you save your state, and the restoration process can be disrupted if you forget to move your HEAD back as well.
The git stash save command will save your current index and working directory state and clear them out so that they again match the head of your current branch. Although this operation gives the appearance that your modified files and any files updated into the index using, for example, git add or git rm, have been lost, they have not. Instead, the contents of your index and working directory are actually stored as independent, regular commits and are accessible through the ref refs/stash.
$ git show-branch stash
[stash] WIP on master: 3889def Some initial files.
As you might surmise by the use of pop to restore your state, the two basic stash commands, git stash save and git stash pop, implement a stack of stash states. That allows your interrupted work flow to be interrupted yet again! Each stashed context on the stack can be managed independently of your regular commit process.
The git stash pop command restores the context saved by a previous save operation on top of your current working directory and index. And by restore here, I mean that the pop operation takes the stash content and merges those changes into the current state rather than just overwriting or replacing files. Nice, huh?
You can only git stash pop into a clean working directory. Even then, the command may or may not fully succeed in recreating the full state you originally had at the time it was saved. Because the application of the saved context can be performed on top of a different commit, merging may be required, complete with possible user resolution of any conflicts.
After a successful
pop operation, Git will automatically remove your saved state from the stack of saved states. That is, once applied, the stash state will be “dropped.” However, when conflict resolution is needed, Git will not automatically drop the state, just in case you want to try a different approach or want to restore it onto a different commit. Once you clear the merge conflicts and want to proceed, you should use the
git stash drop to remove it from the stash stack. Otherwise, Git will maintain an ever growing
[23] stack of contexts.
If you just want to recreate the context you have saved in a stash state without dropping it from the stack, use git stash apply. Thus, a pop command is a successful apply followed by a drop.
Tip
In fact, you can use git stash apply to apply the same saved stashed context onto several different commits prior to dropping it from the stack.
However, you should consider carefully if you want to use git stash apply or git stash pop to regain the contents of a stash. Will you ever need it again? If not, pop it. Clean the stashed content and referents out of your object store.
The git stash list command lists the stack of saved contexts from most to least recent.
$ cd my-repo
$ ls
file1 file2
$ echo "some foo" >> file1
$ git status
# On branch master
# Changes not staged for commit:
# (use "git add ..." to update what will be committed)
# (use "git checkout -- ..." to discard changes in working directory)
#
# modified: file1
#
no changes added to commit (use "git add" and/or "git commit -a")
$ git stash save "Tinkered file1"
Saved working directory and index state On master: Tinkered file1
HEAD is now at 3889def Add some files
$ git commit --dry-run
# On branch master
nothing to commit (working directory clean)
$ echo "some bar" >> file2
$ git stash save "Messed with file2"
Saved working directory and index state On master: Messed with file2
HEAD is now at 3889def Add some files
$ git stash list
stash@{0}: On master: Messed with file2
stash@{1}: On master: Tinkered file1
Git always numbers the stash entries with the most recent entry being zero. As entries get older, they increase in numerical order. And yes, the different stash entry names are stash@{0} and stash@{1}, as explained in
The Reflog.
The git stash show command shows the index and file changes recorded for a given stash entry, relative to its parent commit.
$ git stash show
file2 | 1 +
1 files changed, 1 insertions(+), 0 deletions(-)
That summary may or may not be the extent of the information you sought. If not, adding -p to see the diffs might be more useful. Note that by default the git stash show command shows the most recent stash entry, stash@{0}.
Because the changes that contribute to making a stash state are relative to a particular commit, showing the state is a state-to-state comparison suitable for git diff, rather than a sequence of commit states suitable for git log. Thus, all the options for git diff may also be supplied to git stash show as well. As we saw previously, --stat is the default, but other options are valid, too. Here, -p is used to obtain the patch differences for a given stash state.
$ git stash show -p stash@{1}
diff --git a/file1 b/file1
index 257cc56..f9e62e5 100644
--- a/file1
+++ b/file1
@@ -1 +1,2 @@
foo
+some foo
Another classic use case for git stash is the so-called “pull into a dirty tree” scenario.
Until you are familiar with the use of remote repositories and pulling changes (see
Getting Repository Updates), this might not make sense yet. But it goes like this. You’re developing in your local repository and have made several commits. You still have some modified files that haven’t been committed yet, but you realize there are upstream changes that you want. If you have conflicting modifications, a simple
git pull will fail, refusing to overwrite your local changes. One quick way to work around this problem uses
git stash.
$ git pull
# ... pull fails due to merge conflicts ...
$ git stash save
$ git pull
$ git stash pop
At this point you may or may not need to resolve conflicts created by the pop.
In case you have new, uncommitted (and hence “untracked”) files as part of your local development, it is possible that a git pull that would also introduce a file of the same name might fail, thus not wanting to overwrite your version of the new file. In this case, add the --include-untracked option on your git stash so that it also stashes your new, untracked files along with the rest of your modifications. That will ensure a completely clean working directory for the pull.
The --all option will gather up the untracked files as well as the explicitly ignored files from the .gitignore and exclude files.
Finally, for more complex stashing operations where you wish to selectively choose which hunks should be stashed, use the -p or --patch option.
In another similar scenario, git stash can be used when you want to move modified work out of the way, enabling a clean pull --rebase. This would happen typically just prior to pushing your local commits upstream.
# ... edit and commit ...
# ... more editing and working...
$ git commit --dry-run
# On branch master
# Your branch is ahead of 'origin/master' by 2 commits.
#
# Changed but not updated:
# (use "git add ..." to update what will be committed)
# (use "git checkout -- ..." to discard changes in working directory)
#
# modified: file1.h
# modified: file1.c
#
no changes added to commit (use "git add" and/or "git commit -a")
At this point you may decide the commits you have already made should go upstream, but you also want to leave the modified files here in your work directory. However, git refuses to pull:
$ git pull --rebase
file1.h: needs update
file1.c: needs update
refusing to pull with rebase: your working tree is not up-to-date
This scenario isn’t as contrived as it might seem at first. For example, I frequently work in a repository where I want to have modifications to a Makefile, perhaps to enable debugging, or I need to modify some configuration options for a build. I don’t want to commit those changes, and I don’t want to lose them between updates from a remote repository. I just want them to linger here in my working directory.
Again, this is where git stash helps:
$ git stash save
Saved working directory and index state WIP on master: 5955d14 Some commit log.
HEAD is now at 5955d14 Some commit log.
$ git pull --rebase
remote: Counting objects: 63, done.
remote: Compressing objects: 100% (43/43), done.
remote: Total 43 (delta 36), reused 0 (delta 0)
Unpacking objects: 100% (43/43), done.
871746b..6687d58 master -> origin/master
First, rewinding head to replay your work on top of it...
Applying: A fix for a bug.
Applying: The fix for something else.
After you pull in upstream commits and rebase your local commits on top of them, your repository is in good shape to send your work upstream. If desired, you can readily push them now:
# Push upstream now if desired!
$ git push
or after restoring your previous working directory state:
$ git stash pop
Auto-merging file1.h
# On branch master
# Your branch is ahead of 'origin/master' by 2 commits.
#
# Changed but not updated:
# (use "git add ..." to update what will be committed)
# (use "git checkout -- ..." to discard changes in working directory)
#
# modified: file1.h
# modified: file1.c
#
no changes added to commit (use "git add" and/or "git commit -a")
Dropped refs/stash@{0} (7e2546f5808a95a2e6934fcffb5548651badf00d)
$ git push
If you decide to git push after popping your stash, remember that only completed, committed work will be pushed. There’s no need to worry about pushing your partial, uncommitted work. There is also no need to worry about pushing your stashed content: the stash is purely a local notion.
Sometimes stashing your changes leads to a whole sequence of development on your branch and, ultimately, restoring your stashed state on top of all those changes may not make direct sense. In addition, merge conflicts might make popping hard to do. Nonetheless, you may still want to recover the work you stashed. In situations like this, git offers the git stash branch command to help you. This command converts the contents of a saved stash into a new branch based on the commit that was current at the time the stash entry was made.
Let’s see how that works on a repository with a bit of history in it.
$ git log --pretty=one --abbrev-commit
d5ef6c9 Some commit.
efe990c Initial commit.
Now, some files are modified and subsequently stashed:
$ git stash
Saved working directory and index state WIP on master: d5ef6c9 Some commit.
HEAD is now at d5ef6c9 Some commit.
Note that the stash was made against commit d5ef6c9.
Due to other development reasons, more commits are made and the branch drifts away from the d5ef6c9 state.
$ git log --pretty=one --abbrev-commit
2c2af13 Another mod
1d1e905 Drifting file state.
d5ef6c9 Some commit.
efe990c Initial commit.
$ git show-branch -a
[master] Another mod
And although the stashed work is available, it doesn’t apply cleanly to the current master branch.
$ git stash list
stash@{0}: WIP on master: d5ef6c9 Some commit.
$ git stash pop
Auto-merging foo
CONFLICT (content): Merge conflict in foo
Auto-merging bar
CONFLICT (content): Merge conflict in bar
Say it with me: “Ugh.”
So reset some state and take a different approach, creating a new branch called mod that contains the stashed changes.
$ git reset --hard master
HEAD is now at 2c2af13 Another mod
$ git stash branch mod
Switched to a new branch 'mod'
# On branch mod
# Changes not staged for commit:
# (use "git add ..." to update what will be committed)
# (use "git checkout -- ..." to discard changes in working directory)
#
# modified: bar
# modified: foo
#
no changes added to commit (use "git add" and/or "git commit -a")
Dropped refs/stash@{0} (96e53da61f7e5031ef04d68bf60a34bd4f13bd9f)
There are several important points to notice here. First, notice that the branch is based on the original commit d5ef6c9, and not the current head commit 2c2af13.
$ git show-branch -a
! [master] Another mod
* [mod] Some commit.
--
+ [master] Another mod
+ [master^] Drifting file state.
+* [mod] Some commit.
Second, because the stash is always reconstituted against the original commit, it will always succeed and hence will be dropped from the stash stack.
Finally, reconstituting the stash state doesn’t automatically commit any of your changes onto the new branch. All the stashed file modifications (and index changes, if desired) are still left in your working directory on the newly created and checked out branch.
$ git commit --dry-run
# On branch mod
# Changes not staged for commit:
# (use "git add ..." to update what will be committed)
# (use "git checkout -- ..." to discard changes in working directory)
#
# modified: bar
# modified: foo
#
no changes added to commit (use "git add" and/or "git commit -a")
At this point you are of course welcome to commit the changes onto the new branch, presumably as a precursor to further development or merging as you deem necessary. No, this isn’t a magic bullet to avoid resolving merge conflicts. If there were merge conflicts when you tried to pop the stash directly onto the master branch earlier, trying to merge the new branch with the master will yield the same effects and the same merge conflicts.
$ git commit -a -m "Stuff from the stash"
[mod 42c104f] Stuff from the stash
2 files changed, 2 insertions(+), 0 deletions(-)
$ git show-branch
! [master] Another mod
* [mod] Stuff from the stash
--
* [mod] Stuff from the stash
+ [master] Another mod
+ [master^] Drifting file state.
+* [mod^] Some commit.
$ git checkout master
Switched to branch 'master'
$ git merge mod
Auto-merging foo
CONFLICT (content): Merge conflict in foo
Auto-merging bar
CONFLICT (content): Merge conflict in bar
Automatic merge failed; fix conflicts and then commit the result.
As some parting advice on the git stash command, let me leave you with this analogy: you name your pets and you number your livestock. So branches are named and stashes are numbered. The ability to create stashes might be appealing, but be careful not to overuse it and create too many stashes. And don’t just convert them to named branches to make them linger!