Pak
Pak

Reputation: 726

what does the incorrect syntax of `git stash show -p @{0}` show (instead of using the correct `stash@{0}`)

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

Answers (2)

torek
torek

Reputation: 487785

To expand a bit on kostix's answer, the git stash code uses the following principles:

  • "The" stash (the default one) is identified by the name refs/stash, so that stash@{number} is a reflog entry for this reference.
  • Each stash is in practice made up of two (or sometimes three) commits, as shown in the diagram in Is this a valid visualization of the "git stash" operation? The 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

kostix
kostix

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 branch blabla then @{1} means the same as blabla@{1}.

Upvotes: 1

Related Questions