I was working on a large set of changes to a code base. Some of the changes had been staged, and some had not. I needed to switch to a different branch, but was not ready to make a commit, so I stashed my current state using git stash
.
Later, I went to apply my stash with, git stash apply
. Then I ran git status
. I noticed that my staged changes no longer appeared as ‘staged’, and instead appear to be included as ‘Changes not staged for commit’. Am I correct in my understanding that no data was actually lost, but instead ‘staged’ data is now simply converted to ‘unstaged’ data?
EDIT: I should add that some of the files in question had staged versions, and unstaged versions at the time of the stash. For example, file A had some changes made, which were staged. Then, some more changes were made to file A, which had not yet been staged. Then, a stash was made.
The answer to the question as asked (“does stash convert staged files to unstaged”) is both yes and no.
If you’ve applied with git stash apply
(vs git stash pop
), you’re in great shape because the stash is still present. But let’s back up a bit and look at the underlying mechanism, since it matters here.
When you run git stash push
(the new verb) or git stash save
(the old verb) (or plain git stash
which does a push
/save
), git makes two1 commits that are not on any branch. One commit holds the state of the index, i.e., whatever you’ve staged. The second commit holds state of the work tree, i.e., everything else.
Later, when you use git stash apply
, git smushes the changes together so that nothing is staged, unless you add --index
2 to the apply
operation, in which case it restores (if it can) your previous arrangement of staged vs unstaged.
When you use apply
, the stash script keeps the stash commits around as well, so if the apply does not go the way you wanted—including if you forgot --index
or misspelled it (see footnote 2)—you can git reset --hard
(assuming you had everything in a clean state when you started, anyway) and re-do the apply
.
If you’ve used pop
, though, and git thinks the apply worked, it then drops the stash. I generally recommend using separate apply and drop just for this reason.
(Side note: I actually recommend avoiding git stash
as much as possible. It has too many traps for the unwary, plus it historically has had a number of bugs. But if you’ve used it and now regret it, consider git stash branch
, which turns a saved stash into a branch. See ADTC’s comment below. You can do this with the raw hash ID of an already-popped stash, though you will generally need a “clean” state. The new branch branches-out from the commit you were on at the time you made the stash.)
1With -u
or -a
, which save not just staged and unstaged files abut also ignored and/or all files, the stash script makes three commits. Without these flags, such files don’t go in to either part of the stash, though.
2Confusingly, the stash script also has a --keep-index
flag, which it allows you to specify for apply
operations but has no meaning there. Instead, --keep-index
affects what stash
does after making its special stash commits. Occasionally I’ve accidentally done git stash apply --keep-index
instead of git stash apply --index
, having mixed up the two options.
7
Yes, your thinking is correct. It’s described in the following sections of man git-stash
:
Use git stash when you want to record the current state of the working
directory and the index, but want to go back to a clean working
directory. The command saves your local modifications away and reverts
the working directory to match the HEAD commit.
(...)
pop [--index] [-q|--quiet] [<stash>] Remove a single stashed
state from the stash list and apply it on top of the
current working tree state, i.e., do the inverse operation
of git stash save. The working directory must match the
index.
(...)
If the --index option is used, then tries to reinstate not
only the working tree’s changes, but also the index’s
ones.
(...)
apply [--index] [-q|--quiet] [<stash>]
Like pop, but do not remove the state from the stash list.
NO, no changes have been lost.
Acc. to the documentation:
Git re-modifies the files you uncommitted when you saved the stash. In this case, you had a clean working directory when you tried to apply the stash, and you tried to apply it on the same branch you saved it from; but having a clean working directory and applying it on the same branch aren’t necessary to successfully apply a stash. You can save a stash on one branch, switch to another branch later, and try to reapply the changes. You can also have modified and uncommitted files in your working directory when you apply a stash — Git gives you merge conflicts if anything no longer applies cleanly.
The changes to your files were reapplied, but the file you staged
before wasn’t restaged. To do that, you must run the git stash apply
command with a –index option to tell the command to try to reapply
the staged changes. If you had run that instead, you’d have gotten
back to your original position:<code>$ git stash apply --index# On branch master# Changes to be committed:# (use "git reset HEAD <file>..." to unstage)## modified: index.html## Changes not staged for commit:# (use "git add <file>..." to update what will be committed)## modified: lib/simplegit.rb#</code><code>$ git stash apply --index # On branch master # Changes to be committed: # (use "git reset HEAD <file>..." to unstage) # # modified: index.html # # Changes not staged for commit: # (use "git add <file>..." to update what will be committed) # # modified: lib/simplegit.rb # </code>$ git stash apply --index # On branch master # Changes to be committed: # (use "git reset HEAD <file>..." to unstage) # # modified: index.html # # Changes not staged for commit: # (use "git add <file>..." to update what will be committed) # # modified: lib/simplegit.rb #
1