threads / discuss / 40031

resolving a (possibly remote) branch HEAD to a hash

Subject: resolving a (possibly remote) branch HEAD to a hash

## tl;dr

4 messages between Aug 7, 2015 and Aug 10, 2015.

replies: 3people: 2as markdown or json

Perry Hutchison· Aug 7, 2015, 09:16 UTC · lore

Given the name of a branch, which might be in either refs/heads or refs/remotes, how do I spell a query to obtain the HEAD commit of the refs/heads instance if it exists, else of the refs/remotes instance?

If the branch is local, I can get the hash of its HEAD commit using git rev-parse:

  $ git rev-parse master
  23eba8be773637c1a995a3ffe3aeabe921edef76

However, if I try to do the same thing with a remote branch, rev-parse barfs:

  $ git rev-parse r5.0.1
  r5.0.1
  fatal: ambiguous argument 'r5.0.1': unknown revision or path not in the working tree.
  Use '--' to separate paths from revisions
It works if I explicitly specify that I want the remote instance:
  $ git rev-parse origin/r5.0.1
  bb193a818fc984adbfd631951f3c89c16d5d3476
and the reference is, in fact, not ambiguous:
  $ git for-each-ref --format "%(refname)" | grep '/r5\.0\.1$'
  refs/remotes/origin/r5.0.1
Interestingly, master -- the one that works -- _is_ ambiguous:
  $ git for-each-ref --format "%(refname)" | grep '/master$'
  refs/heads/master
  refs/remotes/origin/master

Unfortunately grep is not a usable solution in general because the branch name may contain . characters which grep (and egrep) treat specially; nor is fgrep because it does _not_ treat $ specially; and I haven't found a for-each-ref pattern that will select what I want.

One approach would be "git checkout r5.0.1" followed by "git rev-parse HEAD", but I really don't want to perform a checkout (e.g. the working tree is not necessarily clean). I just want the same "resolve this reference" logic that checkout clearly has to be using, but as a pure query that does not change the state of either the repo or the working tree.

Junio C Hamano· Aug 7, 2015, 17:24 UTC · re: Perry Hutchison · lore

Re: resolving a (possibly remote) branch HEAD to a hash

perryh@pluto.rain.com (Perry Hutchison) writes:
>   $ git rev-parse r5.0.1
>   r5.0.1
>   fatal: ambiguous argument 'r5.0.1': unknown revision or path not in the working tree.
>   Use '--' to separate paths from revisions

This is not because of ambiguity among refs. The message is telling you:

    r5.0.1 is not interpretable as a revision, and it is not likely
    to be interpretable as a path.  If you meant to use it as a
    revision, put '--' after it, if you meant to use it as a path,
    put '--' before it.

When we try to see if the user meant "r5.0.1" as a revision or a path on a command line that does not have "--", we make sure that it can be interpreted only as a revision but not as a path or the other way around. You see the above error when it cannot be a revision and it does not appear in the _current_ working tree.

The "not likely to be" part comes because this is a heuristic to catch your typo. "git log r5.0.1" _could_ be a request to show a simplified history that ends with the current commit (i.e. HEAD) that touched the path r5.0.1 that used to exist but was removed in the history, and it is way too expensive to dig the history at that point to see if a path r5.0.1 ever existed, so we only check the current working tree.

Now, admittably, if you say "git rev-parse r5.0.1 --" in this situation, it is not likely that the corrected command line will succeed. After all, the error message was issued because we already know that r5.0.1 is _not_ a correct way to spell any revision. So the message _might_ want to be reworded to make it clear that:

 * 'r5.0.1' is not a valid revision name.  Perhaps you misspelt it?
 * 'r5.0.1' does not exist in the current working tree.  Perhaps you
   misspelt it?
 * With 'r5.0.1', you may be trying to refer to a path that (might)
   existed in the past.  If that is the case, please have "--"
   before it to explicitly tell us that you mean a path, not a
   revision.
> It works if I explicitly specify that I want the remote instance:
>
>   $ git rev-parse origin/r5.0.1
>   bb193a818fc984adbfd631951f3c89c16d5d3476

This is the correct behaviour and the expected usage. When talking about r5.0.1 that came from origin, origin/r5.0.1 is the shortest and still valid way to spell it.

> and the reference is, in fact, not ambiguous:
>
>   $ git for-each-ref --format "%(refname)" | grep '/r5\.0\.1$'
>   refs/remotes/origin/r5.0.1

Because it is not about ambiguity among refs, this observation is irrelevant ;-).

Show 5 quoted lines
> Interestingly, master -- the one that works -- _is_ ambiguous:
>
>   $ git for-each-ref --format "%(refname)" | grep '/master$'
>   refs/heads/master
>   refs/remotes/origin/master
The thing is, there is no ambiguity among
    refs/heads/master
    refs/remotes/origin/master
    refs/remotes/another/master

because we do not say "append 'refs/remotes/<anything>/' for various values of <anything> and see if such a ref exists" when resolving an abbreviated refname 'master'.

Ambiguity among refs exists if you had these
    refs/heads/master
    refs/tags/master
    refs/remotes/master/HEAD
But that is not what we are seeing in your case.
Perry Hutchison· Aug 8, 2015, 07:05 UTC · re: Junio C Hamano · lore

Re: resolving a (possibly remote) branch HEAD to a hash

Junio C Hamano <gitster@pobox.com> wrote:
Show 18 quoted lines
> perryh@pluto.rain.com (Perry Hutchison) writes:
>
> >   $ git rev-parse r5.0.1
> >   r5.0.1
> >   fatal: ambiguous argument 'r5.0.1': unknown revision or path not in the working tree.
> >   Use '--' to separate paths from revisions
>
> This is not because of ambiguity among refs.  The message is telling
> you:
>
>     r5.0.1 is not interpretable as a revision, and it is not likely
>     to be interpretable as a path.  If you meant to use it as a
>     revision, put '--' after it, if you meant to use it as a path,
>     put '--' before it.
> ...
> Now, admittably, if you say "git rev-parse r5.0.1 --" in this
> situation, it is not likely that the corrected command line will
> succeed.
Yes, I tried that also.  It didn't work.
> ... we do not say "append 'refs/remotes/<anything>/' for various
> values of <anything> and see if such a ref exists" when resolving
> an abbreviated refname 'master'.
checkout appears to.
If I enter "git checkout foo"
   and the local branch refs/heads/foo exists
checkout will:
1. recognize that foo refers to a local branch
2. check out that local branch (refs/heads/foo).
OTOH, if I enter "git checkout foo"
   and there is no foo in refs/heads or refs/tags
   and refs/remotes/origin/foo _does_ exist
checkout will:
1. recognize that foo refers to a remote branch
2. set up a local branch (refs/heads/foo) that tracks
   the remote branch (refs/remotes/origin/foo)
3. check out the (now) local branch (refs/heads/foo).

However I don't think the command I want is spelled "checkout" because, in either case, I want just step 1 -- with the result being returned as the HEAD commit id of the selected branch. I don't want the whole checkout experience, just the identification of the commit that is the HEAD of the specified branch, which may be either local (in refs/heads) or remote (in refs/heads/origin).

It is certainly possible that the command I am looking for is not spelled "rev-parse" either -- and it does not matter whether it is considered plumbing or porcelain. It just needs to produce the correct result, for either the local or the remote case.

(In the situation at hand I happen to know that the name will always refer to a branch, not a tag, so a solution that looks only in refs/heads and refs/remotes -- and not in refs/tags -- would be fine. A solution that, like checkout, also looks in refs/tags would also be OK.)

Junio C Hamano· Aug 10, 2015, 18:41 UTC · re: Perry Hutchison · lore

Re: resolving a (possibly remote) branch HEAD to a hash

perryh@pluto.rain.com (Perry Hutchison) writes:
Show 5 quoted lines
>> ... we do not say "append 'refs/remotes/<anything>/' for various
>> values of <anything> and see if such a ref exists" when resolving
>> an abbreviated refname 'master'.
>
> checkout appears to.
You are referring to this part of the documentation:
    If <branch> is not found but there does exist a tracking branch in
    exactly one remote (call it <remote>) with a matching name, treat as
    equivalent to
        $ git checkout -b <branch> --track <remote>/<branch>

A reader needs to read this part of the documentation a bit more carefully in order to notice that it never says it is equivalent to:

        $ git checkout -b <branch> -t <branch> ;# NOT CORRECT

This behaviour was brought in by somebody who thought that, in the context of "checkout" (and only in that context), it is clear that missing <branch> that can only mean the sole <remote>/<branch> and make that signal something more than what the user told "checkout" to do: "If you want to check out a <branch>, and it does not exist yet, you must wanted to create your own <branch> and start it at the same commit as somebody else has at the tip of his <branch>".

This "clever" dwim is very specific to the way you interact with "checkout" and generally does not apply when you want to run anything other than "checkout", e.g. "rev-parse" or "log".

But it is _so_ convenient a short-cut, that it lets new people form into an illusion that <branch> could be naming <remote>/<branch>. That is an incorrect perception.

The rationale behind "signal something more" above goes like this: if the user wanted to detach the head at the same commit as somebody else's <branch>, she would explicitly have written

	$ git checkout <remote>/<branch>

to do so. Because <remote>/<branch> is the shortest valid way to name that remote-tracking branch (i.e. exactly because <branch> is not a valid abbreviation for <remote>/<branch>), we can treat

	$ git checkout <branch>
when <branch> is not a local branch name specially.

It is sad and ironic that this checkout-specific DWIM works only because <branch> does not mean <remote>/<branch>, but presence of the DWIM gives a wrong illusion that it does X-<.

← back to recent threads