TL;DR
Use git stash branch
to create a new branch and apply the stash:
git stash branch newbranch
This always works (well, as long as your current status is all clean: see the long section below) and is the equivalent of creating a new branch whose parent is the commit that was current when you made the stash, then running git apply --index
(meaning it restores the index and work-tree separately).
A literal answer to the question
All you need to do is find out whether any branch name(s) identify the same commit as the parent of the stash in question, so:
git for-each-ref --format='%(refname:short)' \
--points-at $(git rev-parse refs/stash~1) refs/heads
will find the possible branch name(s), if there are any. (This relies on relatively modern features of git for-each-ref
.)
Long
Each stash is itself just two, or sometimes three, commits. What git stash
does is that it makes these two (or three) commits, but puts them on no branch.
The word branch in Git is itself a bit ambiguous (see What exactly do we mean by "branch"?), but here, we're using it in the sense that a branch name like master
or develop
simply points to the last commit on its branch:
...--E--F--G <-- master
\
H <-- develop
When you git checkout
a branch name like develop
, you are telling Git to:
- Extract the content of that commit into the index (the place where you build the next commit, also called the staging area or sometimes the cache)
- Take the now-unfrozen, but still Git-form / compressed files out of the index and copy them into the work-tree where you can work on them.
Attach the name HEAD
to the branch name, so that Git knows which commit you have checked out now:
...--E--F--G <-- master
\
H <-- develop (HEAD)
Now Git knows that the commit whose hash is H
is the current or HEAD
commit, as pointed-to by the name develop
.
If you make an ordinary new commit at this point (by copying updated or new files from the work-tree back into the index / staging-area, then running git commit
to freeze them into a new commit), Git writes the new commit with the current commit as its parent, and then updates the branch name:
...--E--F--G <-- master
\
H--I <-- develop (HEAD)
The name HEAD
remains attached to the branch name, but now the branch name identifies commit I
instead of commit H
.
What git stash
does is to make two commits—one for the index, and one for the work-tree—that don't have a branch name. Instead, Git uses the name refs/stash
to find the w
commit (and w
finds both the original commit and the i
commit):
...--E--F--G <-- master
\
H <-- develop (HEAD)
|\
i-w <-- refs/stash
After saving the i
and w
commits, git stash
runs git reset --hard
, to set the index and work-tree back to matching the current commit (here, H
).
In this case, at this point in time, the parent commit of the stash is also pointed-to by the existing branch name develop
. But suppose you have done this git stash
, and then gone on to make a different set of changes and made a new commit I
? Then you have:
...--E--F--G <-- master
\
H--I <-- develop (HEAD)
|\
i-w <-- refs/stash
There is now no branch name pointing to existing, historical commit H
, even though that's the one commit where refs/stash
is guaranteed to apply.
So, if your index and work-tree are now clean (i.e., match the contents of commit I
, to which HEAD
is attached), and you now run git stash branch recover
, what git stash
will do is attach a new branch name, recover
, to commit H
, and check out that commit and make the new branch the current branch by attaching HEAD
to it:
...--E--F--G <-- master
\
H <-- recover (HEAD)
\
I <-- develop
and, as the last step of the git stash branch
operation, apply and drop the stash, so that the index is back to the way the index was when you had H
checked out and ran git stash
, and the work-tree is back to the way the work-tree was when you had H
checked out and ran git stash
. You can now finish git add
ing and git commit
to make new commit J
:
...--E--F--G <-- master
\
H--J <-- recover (HEAD)
\
I <-- develop
What if develop
still points to commit H
?
If you haven't moved the previous branch, well, now you have a new branch anyway. That is, now instead of the above drawing, you would have:
...--E--F--G <-- master
\
H <-- develop
\
J <-- recover (HEAD)
(I left the new commit as J
here to make it easier to match it up with the earlier diagram). There's nothing wrong with this setup. Of course if you don't want the recover
branch here, you don't have to use git stash branch
, but it does still work.
What about the three-commit stash?
Three-commit stashes are those made by git stash save -a
(--all
) or git stash save -u
(--include-untracked
). Applying (or popping) such a stash requires that the files in the third commit not collide with files in the work-tree. In general, this means the work-tree must not only be "clean" according to git status
, but also clean in the sense provided by running git clean
with appropriate options. In this particular case, even git stash branch
can fail sometimes. If it does fail, it leaves the stash un-popped.
For more, see Why does git stash pop say that it could not restore untracked files from stash entry?