James Smith
James Smith

Reputation: 364

Why is 'remotes/origin/HEAD' sometimes mapped to 'origin/master' and sometimes not?

In some of my repositories I see...

> git branch --all
* master
  remotes/origin/master

...whilst in others I see:

> git branch --all
* master
  remotes/origin/HEAD -> origin/master
  remotes/origin/master

The former strikes me as correct, the latter makes me uneasy. What's going on here?

I've previously included an additional common branch mapped to a different repository and setting this up may have caused the issue. This additional branch has now been removed but this odd setup in the latter repository is still there. There are no additional remotes in either repository, just the standard origin one.

It seems to me that, in the first, remotes/origin/master is an abbreviation for remotes/origin/master -> origin/master. Only when the local and remote branches differ in some respect does the mapping need to be spelled out explicitly? So there is this odd remotes/origin/HEAD -> origin/master mapping, if that is the word for it, which seems to be taking precedence?

Upvotes: 6

Views: 3499

Answers (2)

torek
torek

Reputation: 488193

Mark Adelsberger's answer covers how you get these (git clone makes them). But let's look more generally at branch names, and then what Git calls references and symbolic references.

You already know that branch names are relatively simple: they're just strings like master and develop. And, thanks to git branch --all output, you also know that you have what Git sometimes calls remote-tracking branch names like origin/master. I prefer to call these remote-tracking names, so as to avoid using the word "branch" too much.

What you may not know is that both of these are just specialized forms of how Git lumps everything together. A regular branch name like master is actually a reference whose full name is spelled refs/heads/master. A remote-tracking name like origin/master is a reference whose full name is spelled refs/remotes/origin/master. Tags, too, are just references whose full name starts with refs/tags/.

If you run git branch -r, which lists only the remote-tracking names, you'll see origin/master instead of remotes/origin/master. This is because Git is not always consistent about how much of the refs/remotes/ part it strips off. (Why Git is inconsistent here, I don't know.) In general Git likes to shrink these things down to something more manageable, though. Since all references always start with refs/—that's how Git defines them—you can, in general, always drop the refs/ part, and usually more: you can usually1 drop refs/heads/, refs/remotes/, and refs/tags/.

What a reference does for you—well, a very important part of what each reference does—is to save one of those big ugly Git hash IDs that you've seen in, e.g., git log output. Internally, Git needs those hash IDs. They're quite impossible for mere humans to work with, though, so Git gives us names we can use to remember the IDs.

But there's a special case allowed for any reference, although it's wisest to reserve it for ones that use the word HEAD. Any reference can be a symbolic reference. Instead of containing a big ugly hash ID, a reference can contain the name of another reference. Git will then turn the name into the hash ID stored in the other reference.

Thus, if refs/remotes/origin/HEAD contains the name refs/remotes/origin/master, you can say origin/HEAD anywhere you could say origin/master. This isn't much help—I, at least, find it harder to type origin/HEAD with its all-uppercase HEAD—but Git adds yet another special case: if you use the name origin by itself, in a place where Git "wants" a hash ID, Git will notice that origin corresponds to refs/remotes/origin/, then see that refs/remotes/origin/HEAD exists. Git will read it and see that refs/remotes/origin/HEAD is a symbolic reference to refs/remotes/origin/master. Git will read that and see a hash ID—and now Git will know the hash ID you meant.

So, if origin/HEAD (or refs/remotes/origin/HEAD) "points to" origin/master, that means you can write origin in a few places where you would ordinarily have to write out origin/master.

You can manipulate these special remote-tracking HEAD names using the git remote command, specifically with its set-head sub-command. I never actually bother with any of this myself—I just type in origin/master when I mean that, and ignore the symbolic HEADs that go with these remote-tracking names. But if you like them, they are there for you to use.


1I say "usually" here because there is a special case: if you accidentally have, say, the name foo as both a branch name (refs/heads/foo) and a tag name (refs/tags/foo), it suddenly becomes a problem to just say foo. One solution is to spell out heads/foo and tags/foo, or even refs/heads/foo. There are more details on this, and much more, in the gitrevisions documentation.

Upvotes: 7

Mark Adelsberger
Mark Adelsberger

Reputation: 45649

The line remotes/origin/HEAD -> origin/master just tells you which branch origin has checked out - which in turn is the default branch checked out in a new clone.

(I probably should say, which branch origin had checked out when last the local remotes/origin/HEAD ref was updated. In my tests, this ref has only been updated when the clone is initially created. There may be ways I haven't thought of to force it to update.)

If you have a repo where you don't see this line, it probably means that no branch was checked out at the time the clone was made - e.g. if you cloned an empty repository.

Upvotes: 2

Related Questions