Reputation: 726
I was playing around with stashes in git and ran into some seemingly strange behavior.
After creating a number of stashes, I wanted to see what was in them. I ran git stash show -p
and it showed me a diff of the last stash as one would expect. I then wanted to confirm that index 0 referred to the most recent stash, so I (incorrectly) ran git stash show -p @{0}
. This gave me the following error:
fatal: ambiguous argument '': unknown revision or path not in the working tree.
Use '--' to separate paths from revisions, like this:
'git <command> [<revision>...] -- [<file>...]'
Fair enough. But what's weird was that it then gave me a diff of a bunch of changes that I didn't recognize and that didn't exist in any of my stashes.
What was @{0}
referring to in this context and where was that diff coming from?
Upvotes: 0
Views: 133
Reputation: 487785
To expand a bit on kostix's answer, the git stash
code uses the following principles:
refs/stash
, so that stash@{number}
is a reflog entry for this reference.stash
reference points to the working-directory or work-tree commit, which has two (or three if the third commit is present) parent commits. This means that if the work-tree commit is treated as if it were a regular commit made by regular Git commands, it would simply be a merge commit. (Actually treating it as an ordinary merge can produce peculiar effects, so it's best to use the git stash
command to work with it.)This second point—which is an implementation detail—nonetheless affects the git stash
code quite strongly. In particular, git stash
will believe that any commit is a valid stash if and only if it is a merge commit. It does not actually require that the commit be named via refs/stash
or one of the corresponding reflog entries.
If you write stash@{1}
, the rules as outlined in gitrevisions will normally find refs/stash
and extract the 1-th reflog entry (i.e., the first one, not counting the stash@{0}
entry, which technically isn't a reflog entry at all: Git just retrieves the value of the reference itself, instead of an entry from the reflog).
Assuming stash@{1}
exists, this will name one of your stash work-tree commits, which will be a merge commit. The git stash
code verifies that the commit you have named is "stash-like", i.e., has the prescribed form.
But this also means that if you run some of the git stash
sub-commands (such as show
or apply
) using a hash ID, or a branch name, or any other name acceptable to the revision parsing code—including an ordinary reflog-like reference like @{0}
, which means "the value extracted from HEAD"—the stash code will attempt to do its thing with that commit.
If that commit passes the "is stash-like" test, the stash code will treat it as if it were a stash. Since the "is stash-like" test is basically "is a merge commit", what happens with git stash show @{0}
or git stash show HEAD
is that if the current commit is a merge, git stash show
tends to show part of it (technically it simply diffs HEAD^1
against HEAD
). If it's not a merge commit, git stash show
should complain and quit:
'HEAD' is not a stash-like commit
but older versions of Git are not always as clever. If your Git is truly ancient, it may attempt to show some existing stash, passing the extra arguments on to git diff
which will complain about them.
Upvotes: 1
Reputation: 55443
From the gitrevisions(7)
manual page
(you can run git help revisions
):
@{<n>}
, e.g.@{1}
You can use the
@
construct with an empty ref part to get at a reflog entry of the current branch.For example, if you are on branchblabla
then@{1}
means the same asblabla@{1}
.
Upvotes: 1