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.
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 <file>..." to update what will be committed) # (use "git checkout -- <file>..." 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 <file>..." to update what will be committed)
# (use "git checkout -- <file>..." 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. From ssh://git/var/git/my_repo 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 <file>..." to update what will be committed) # (use "git checkout -- <file>..." 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 <file>..." to update what will be committed) # (use "git checkout -- <file>..." 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 <file>..." to update what will be committed)
# (use "git checkout -- <file>..." 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!
OK, I confess: sometimes Git does something either mysterious or magical and causes one to wonder what just happened. Sometimes you simply want an answer to the question, “Wait, where was I? What just happened?” Other times, you do some operation and realize, “Uh oh, I shouldn’t have done that!” But it is too late and you have already lost the top commit with a week’s worth of awesome development.
Not to worry! Git’s reflog has you covered in either case! By using the reflog, you can gain the assurance that operations happened as you expected on the branches you intended, and that you have the ability to recover lost commits just in case something goes astray.
The reflog is a record of changes to
the tips of branches within nonbare repositories. Every time an update is
made to any ref, including HEAD
, the
reflog is updated to record how that ref has changed. Think of the reflog
as a trail of bread crumbs showing where you and your refs have been. With
that analogy, you can also use the reflog to follow your trail of crumbs
and trace back through your branch manipulations.
Some of the basic operations that record reflog updates include:
Cloning
Pushing
Making new commits
Changing or creating branches
Rebase operations
Reset operations
Note that some of the more esoteric and complex operations, such as git filter-branch, ultimately boil down to simple commits and are thus also logged. Fundamentally, any Git operation that modifies a ref or changes the tip of a branch is recorded.
By default, the reflog is enabled in nonbare repositories
and disabled in bare repositories.
Specifically, the reflog is controlled by the Boolean configuration option
core.logAllRefUpdates
. It may be enabled using
the command git config core.logAllRefUpdates true or disabled with
false
as desired on a per-repository
basis.
So what does the reflog look like?
$ git reflog show
a44d980 HEAD@{0}: reset: moving to master
79e881c HEAD@{1}: commit: last foo change
a44d980 HEAD@{2}: checkout: moving from master to fred
a44d980 HEAD@{3}: rebase -i (finish): returning to refs/heads/master
a44d980 HEAD@{4}: rebase -i (pick): Tinker bar
a777d4f HEAD@{5}: rebase -i (pick): Modify bar
e3c46b8 HEAD@{6}: rebase -i (squash): More foo and bar with additional stuff.
8a04ca4 HEAD@{7}: rebase -i (squash): updating HEAD
1a4be28 HEAD@{8}: checkout: moving from master to 1a4be28
ed6e906 HEAD@{9}: commit: Tinker bar
6195b3d HEAD@{10}: commit: Squash into 'more foo and bar'
488b893 HEAD@{11}: commit: Modify bar
1a4be28 HEAD@{12}: commit: More foo and bar
8a04ca4 HEAD@{13}: commit (initial): Initial foo and bar.
Although the reflog records transactions for all refs, git reflog show displays the transactions for
only one ref at a time. The previous example shows the default ref,
HEAD
. If you recall that branch names
are also refs, you will realize that you can also get the reflog for any
branch as well. From the previous example, we can see that there is also a
branch named fred
, so we can display
its changes in another command:
$ git reflog fred
a44d980 fred@{0}: reset: moving to master
79e881c fred@{1}: commit: last foo change
a44d980 fred@{2}: branch: Created from HEAD
Each line records an individual transaction from the history of the
ref, starting with the most recent change and going back in time. The
leftmost column contains the commit ID at the time the change was made.
The entries like HEAD@{7}
from the
second column provide convenient names for the commit at each transaction.
Thus, HEAD@{0}
is the most recent
entry, HEAD@{1}
records where HEAD
was just prior to that, etc. The oldest
entry, here HEAD@{13}
, is actually the
very first commit in this repository. The rest of each line after the
colon describes what transaction occurred. Finally, for each transaction
there is a time stamp (not shown) recording when the event took place
within your repository.
So what good is all that? Here’s the interesting aspect of
the reflog: each of the sequentially numbered names like HEAD@{1}
may be used as symbolic names of
commits for any Git command that takes a commit. For example:
$ git show HEAD@{10}
commit 6195b3dfd30e464ffb9238d89e3d15f2c1dc35b0
Author: Jon Loeliger <[email protected]>
Date: Sat Oct 29 09:57:05 2011 -0500
Squash into 'more foo and bar'
diff --git a/foo b/foo
index 740fd05..a941931 100644
--- a/foo
+++ b/foo
@@ -1,2 +1 @@
-Foo!
-more foo
+junk
That means that as you go about your development process, recording
commits, moving to different branches, rebasing, and otherwise
manipulating a branch, you can always use the reflog to reference where
the branch was. The name HEAD@{1}
always references the previous commit for the branch, HEAD@{2}
names the HEAD
commit just prior to that, etc. Keep in
mind, though, that although the history names individual commits,
transactions other than git commit are
present also. Every time you move the tip of your branch to a different
commit, it is logged. Thus, HEAD@{3}
doesn’t necessarily mean the third prior git
commit operation. More accurately, it means the third prior
visited or referenced commit.
Git also supports more English-like qualifiers for the part of the reference within braces. Maybe you aren’t sure exactly how many changes took place since something happened, but you know you want what it looked like yesterday or an hour ago.
$ git log 'HEAD@{last saturday}'
commit 1a4be2804f7382b2dd399891eef097eb10ddc1eb
Author: Jon Loeliger <[email protected]>
Date: Sat Oct 29 09:55:52 2011 -0500
More foo and bar
commit 8a04ca4207e1cb74dd3a3e261d6be72e118ace9e
Author: Jon Loeliger <[email protected]>
Date: Sat Oct 29 09:55:07 2011 -0500
Initial foo and bar.
Git supports a fairly wide variety of date-based qualifiers
for refs. These include words like yesterday
, noon
, midnight
, tea
,[24] weekdays, month names, A.M. and P.M. indicators, absolute
times or dates, and relative phrases like last
monday
, 1 hour ago
, 10 minutes ago
, and combinations of these
phrases such as 1 day 2 hours ago
. And,
finally, if you omit the actual ref name and just use the @{
...}
form,
the current branch name is assumed. Thus, while on the bugfix
branch, using just @{noon}
refers to bugfix@{noon}
.
The Git tool responsible for understanding references is git rev-parse. Its manpage is extensive and details more than you would ever care to know about how refs are interpreted. Good luck!
Although these date-based qualifiers are fairly liberal, they are
not perfect. Understand that Git uses a heuristic to interpret them and
exercise some caution in referring to them. Also remember that the notion
of time is local and relative to your repository: these time-qualified refs reference the value of a ref in your local
repository only. Using the same phrase about time in a different
repository will likely yield different results due to different reflogs.
Thus, master@{2.days.ago}
refers to the
state of your local master
branch two
days ago. If you don’t have reflog history to cover that time period, Git
should warn you:
$ git log HEAD@{last-monday}
warning: Log for 'HEAD' only goes back to Sat, 29 Oct 2011 09:55:07 -0500.
commit 8a04ca4207e1cb74dd3a3e261d6be72e118ace9e
Author: Jon Loeliger <[email protected]>
Date: Sat Oct 29 09:55:07 2011 -0500
Initial foo and bar.
One last warning. Don’t let the shell trick you. There is a significant difference between these two commands:
# Bad! $git log dev@{2 days ago}
# Likely correct for your shell $git log 'dev@{2 days ago}'
The former, without single quotes, provides multiple command line arguments to your shell, whereas the latter, with quotes, passes the entire ref phrase as one command line argument. Git needs to see the ref as one word from the shell. To help simplify the word break issue, Git allows several variations:
# These should all be equivalent $git log 'dev@{2 days ago}'
$git log dev@{2.days.ago}
$git log dev@{2-days-ago}
One more concern to address. If Git is maintaining a transaction history of every operation performed on every ref in the repository, doesn’t the reflog eventually become huge?
Luckily, no. Git automatically runs a garbage collection process occasionally. During this process, some of the older reflog entries are expired and dropped. Normally, a commit that is otherwise not referenced or reachable from some branch or ref will be expired after a default of 30 days, and commits that are reachable expire after a default of 90 days.
If that schedule isn’t ideal, the configuration
variables gc.reflogExpireUnreachable
and gc.reflogExpire
, respectively, can
be set to alternate values in your repository. You can use the command
git reflog delete to remove individual
entries, or use the command git reflog
expire to directly cause entries older than a specified time to
be immediately removed. It can also be used to forcefully expire the
reflog.
$git reflog expire --expire=now --all
$git gc
As you might have guessed by now, the stash and the reflog are
intimately related. In fact, the stash is implemented as a reflog using
the ref stash
.
One last implementation detail: reflogs are stored under the
.git/logs directory. The file
.git/logs/HEAD contains the history
of HEAD
values, whereas the
subdirectory .git/logs/refs/ contains
the history of all refs, including the stash. The sub-subdirectory
.git/logs/refs/heads contains the
history for branch heads.
All the information stored in the reflogs, specifically everything
under the .git/logs directory, is
ultimately transitory and expendable. Throwing away the .git/logs directory or turning the reflog off
harms no Git-internal data structure; it simply means references like
master@{4}
can’t be resolved.
Conversely, having the reflog enabled introduces references to commits that might otherwise be unreachable. If you are trying to clean up and shrink your repository size, removing the reflog may enable the removal of otherwise unreachable (i.e., irrelevant) commits.
3.12.71.237