-1

When doing git branch --all, this:

remotes/origin/HEAD -> origin/master
remotes/origin/master

both shows up, along with other branches. What is the first line for? I thought, the HEAD is just a ref that points to the latest commit of a branch. So why is there one single HEAD on the remote server that points to master, instead of multiple HEADs, one for each branch?

naraghi
  • 430
  • 1
  • 6
  • 18
  • 1
    The HEAD on a remote is the default branch of the remote. It is the branch you will check out when you clone it. – Kim Nov 24 '22 at 15:14
  • That's confusing, that HEAD means different things remotely and locally. Thank you for clearing that up! – naraghi Nov 24 '22 at 16:25
  • It does not mean different things. If you clone a local repo you will also clone HEAD – mousetail Nov 25 '22 at 06:41

1 Answers1

3

Summary

My advice is to ignore this entry. You don't care about it, so ... don't care about it.

Long

Every Git repository must have a HEAD setting. The HEAD in a normal repository—the kind in which you do your own work, for instance—stores the current branch name, or else the repository is in what Git calls detached HEAD mode and HEAD stores the current commit hash ID as a raw big-ugly-hash-ID. So HEAD in this kind of repository represents the currently checked out commit. As there is always—well, almost always—a currently checked out commit, Git can simply read HEAD to find out which branch name or hash ID locates that commit.1

But even in a "bare" clone, which does not have a currently-checked-out commit—it has no working tree so that it literally can't have a checked out commit—Git still requires that HEAD exist, and contain a branch name. That name represents the default branch name, as Kim noted in a comment. This should be the name of some branch that does exist in that repository.2 The point of this default branch name is simple: you, as a client, will run:

git clone ssh://git@github.com/user/repo.git

for example, or a similar git clone request to some other URL, perhaps on GitHub or perhaps elsewhere. You can add:

-b somebranch

to tell your git clone operation that it should do a git switch soembranch immediately after cloning: that will be the (only) actual branch name in your new clone. But if you don't bother with the -b option—and most people mostly don't—your own Git software will ask the hosting site's Git software: Say, which branch name do you recommend? The answer that comes back is the branch name stored in the HEAD.3

The point of doing all this is simple enough. Git can work with no branch names at all. Git does not actually need branch names. Git only needs commits and their big ugly hash IDs. It's us humans, not Git, who need branch names: the hash IDs are too big and ugly for us. But when you make a new clone, Git turns all the original repository's branch names into your own remote-tracking names: their main or master becomes your origin/main or origin/master, for instance. That creates room for you to have your branch names, which may well hold different big-ugly-hash-IDs.

So, when you first clone some existing Git repository—let's say it is one with four branch names in it—you'll have four remote-tracking names, one for each of the original repository's branch names. You will have zero of your own branch names. That's not a good start, because humans don't work well without the names. So the last step of git clone is to create one new branch name, in your new clone, and then switch to that branch name by checking out the right commit. Which commit is the right commit? Well, obviously,4 it's the commit obtained by using the corresponding remote-tracking name:

  • They have branches br1, br2, br3, and main.
  • Their br2 means commit c3ff4cec66ec004d884507f5296ca2a323adbbc5.
  • Therefore your origin/br2 means commit c3ff4cec66ec004d884507f5296ca2a323adbbc5.
  • Therefore, if you said -b br2, the correct commit hash ID for your newly created br2 branch is c3ff4cec66ec004d884507f5296ca2a323adbbc5.

If you didn't say -b br2, your Git asks their Git which of the four names they recommend, and as long as they don't come back and give your Git a wrong one like master (see footnote 2 and Google hosting), your Git can use the corresponding origin/main or origin/br1 or whatever that your Git just created to get the right hash ID.


1The almost always case occurs when the repository is on what Git calls an orphan branch. Here, HEAD still contains a branch name, but it's the name of a branch that does not yet exist.

2If the HEAD of a bare repository contains the name of a branch that does not yet exist, that bare repository is "on" an orphan branch, as noted in footnote 1. This is not harmful to the bare repository, but it does mean that when you use git clone to clone the bare repository, the other Git software gets, from the bare clone, the name of a branch that doesn't exist. That other Git software—on the client that's making the clone—then tries to check out that branch by its name, and can't, because there is no such name. This causes the client Git to spit out some annoying error messages, confusing the users at the client end. Hence the advice that the bare repository contain the name of a branch that does exist, here.

Web hosting sites like Bitbucket, GitHub, and GitLab must provide some way—some hosting-provider specific method—for you to set the "default branch", as there's nothing built in to the Git push protocol to do this. If they don't provide that, you may be stuck with an unwanted branch name. Google, for some reason, fail to provide such a way, and you're stuck with having to name a branch master or else have client clones produce this annoying message. It doesn't mean you can't use the hosting service, but it sure is annoying.

3If the hosting site has a detached HEAD, or is running a truly ancient Git server, there's no way for it to provide a branch name here. In that case, your own local Git software can fall back on a compiled-in default name, and if even that doesn't work, it will complain and not check anything out after all, printing out an annoying message instead. You will be forced to run your own git checkout or git switch command to create the first and only-so-far branch in your new repository. See also footnote 2.

4It's clearly obvious to everyone who's been using Git for a decade, which makes it obvious and trivial like in your typical math proof. See also this list of proof techniques.


OK, but what does this have to do with origin/HEAD?

Let's now look at origin: origin is the remote name, and is the source of the origin/ part in origin/br1, origin/br2, origin/br3, and origin/main.5 The name origin, stored in your .git/config (which you can look at, or even edit, if you like—it's a plain-text file—just be careful when editing it!)—serves a number of functions. The primary two, which are always there immediately upon the creation of a remote like origin, are these:

  • It stores a url, so that git fetch origin knows where to reach the other Git software.
  • It stores a default fetch refspec, so that git fetch origin knows which names to create or update in your own repository. Using --single-branch at git clone time alters this default fetch refspec.

We won't go into any detail on the fetch refspec here, and aren't too worried about the URL either, but I'll note that if the URL changes, it's easy to edit .git/config and fix it. You can do this instead of using git remote set-url origin new-url, as long as you're careful to use an editor that preserves the .git/config file format (a modified INI format; must be saved as UTF-8).

In effect, then, origin represents the other Git repository: the one you just cloned. That's why you have remote-tracking names: each one represents a branch name in that other repository.

Your own Git has its own HEAD, but if that other Git over at origin is a repository—and it must be, because you just cloned it—then it too must have its own HEAD, representing the "default branch" in that repository. The authors of Git decided that it would make sense to copy their HEAD to your origin/HEAD.

Now, the mechanism by which HEAD contains a branch name—assuming, of course, that HEAD does contain a branch name, which gets us right back to footnotes 2 and 3 when we're talking about the server-side Git repository—is what Git calls a symbolic reference. So your own Git software uses another symbolic reference in your clone to represent the symbolic reference that, we assume, exists in the origin clone. If they have HEAD -> main, then, you should have origin/HEAD -> origin/main. If they have HEAD -> master, you should have origin/HEAD -> origin/master.

Your Git (your software working with your repository) simply re-creates the adjusted symbolic reference that your Git assumes their Git has, based on the default-branch information your Git got from their Git. And that's what your origin/HEAD represents after git clone.


5You can, if you like, use some name other than origin. You can also, or instead, add more remotes to your clone after the first standard origin remote. But we won't get into these details here.


But what use is origin/HEAD?

None.

Okay, that's not really true: the correct answer is almost none.

You can change the symbolic reference stored in your own origin/HEAD any time you like, using:

git remote set-head origin <name>

You can delete origin/HEAD using:

git remote set-head --delete origin

You can have your Git call up the origin Git software, as if doing the initial clone, and ask them what their HEAD is now and have your Git update your origin/HEAD appropriately with:

git remote set-head --auto origin

This lets you change your own origin/HEAD to whatever you like, which may not be the same as what they actually have in their HEAD. Git's rule here is a little odd, but does make sense, sort of: all your origin/* names are yours, they're just automatically matched up to theirs by default, except that you have to manually use --auto to automatically match up origin/HEAD...?

But what good is something that you can set, if you can't query it? Well, that's where the real weirdness comes in. You can query it, in several ways:

  • You can run git branch -r or git branch -a and see it. What good is that? I don't know, perhaps you can come up with something.
  • You can run git rev-parse to read it, in one of three ways; see below.
  • Last, you can use the word origin, naked like this, to read it ... sometimes. Sometimes, the word origin means the remote. Sometimes, it means origin/HEAD. When does it mean which? That's the tricky part.

Using git rev-parse

As noted above, what Git really needs, to get its Gitty bits of dirty-work done, are hash IDs. Branch names—and other names like tag names—are just clever methods by which Git allows us humans to use human-oriented names to find the hash IDs.

The git rev-parse command is a general purpose plumbing command6 in Git that lets you do the same things other Git commands do, to work with branch and tag and other names. Because it's a very central Git command, it does a lot of other things too; we're only going to worry on the branch and remote-tracking name oriented ones here. We will run it like this:

$ git rev-parse --symbolic-full-name origin/HEAD
refs/remotes/origin/master
$ git rev-parse --abbrev-ref origin/HEAD
origin/master

These show two ways to examine the actual origin/HEAD in a clone of the Git repository for Git. As you can see from the above, in my clone, origin/HEAD is a symbolic name for origin/master (whose full name is refs/remotes/origin/master, but we can usually abbreviate as just origin/master).

To get the hash ID of origin/master, we can again use git rev-parse:

$ git rev-parse origin/master
c000d916380bb59db69c78546928eadd076b9c7d

So rev-parse can find the raw hash ID of a commit, turning a name—a branch name like main or master, or a remote-tracking name like origin/master—into a commit hash ID. That's what has happened here. But suppose I give git rev-parse the name origin? That's not a branch name, not a tag name, and not even a remote-tracking name. Let's try it:

$ git rev-parse origin
c000d916380bb59db69c78546928eadd076b9c7d

Let's try this too:

$ git rev-parse origin/HEAD
c000d916380bb59db69c78546928eadd076b9c7d

OK, this last one makes sense: origin/HEAD is a remote-tracking name that is a symbolic reference to origin/master, which in turn means c000d916380bb59db69c78546928eadd076b9c7d. But why did origin, without /HEAD or /master or whatever added, also turn into c000d916380bb59db69c78546928eadd076b9c7d?

The answer is in the gitrevisions documentation, which lists a six-step process for resolving a potentially ambiguous name. If we give the string zog to git rev-parse, is that a branch name? Is it a tag name? Is it a remote-tracking name ... well, it's definitely not a remote tracking name, as those have both a remote and a name, and zog has no slash in it. But to answer the question of what kind of name it is and whether it's valid at all—maybe it's just an error—git rev-parse goes through the six steps listed. Follow the link above and scroll down a little until you see the text that begins with:

<refname>, e.g. master, heads/master, refs/heads/master
A symbolic ref name. ...

Read through the six steps. Note that step 6 reads:

  1. otherwise, refs/remotes/<refname>/HEAD if it exists.

Note carefully the order of the six steps. Step 3 mentions refs/tags/<refname>; refs/heads/<refname> is later, at step 4. This means that if zog is both a tag and a branch name, git rev-parse will use the tag name!

This is in fact true of most Git commands. The git branch and git switch commands are some of the big exceptions here: they know, or at least assume, that you're probably giving them a branch name, so they generally try names as branch names before trying them in any other way.

This fact—that tag names take priority, except when they don't—is one of several reasons to avoid having, e.g., zog be both a branch name and a tag name. Git is a computer, following precise rules in a precise order—but each Git command can have different rules, so while Git won't get them mixed up, you, as a human, probably will.

In any case, the fact that step 6 exists, and if given origin will look for refs/remotes/origin/HEAD, means that you can—in some places—use the name origin by itself to mean origin/HEAD, which in turn means whatever name origin/HEAD is a symbolic reference for. So that's a way you can use origin. But sometimes, when you use origin, Git will treat that as a remote, rather than as a name to resolve through git rev-parse. As a human who can get confused about when a dual-use zog-branch-and-zog-tag gets used as a branch name or as a tag name, you as a human can also get confused about when origin as a name gets used as a remote name, and when it's turned into origin/HEAD. So just don't do that.


6Git divides its commands (not always entirely successfully) into "plumbing" commands that do some low-level task and "porcelain" commands that users run. A "porcelain" command might be implemented by three or more "plumbing" commands plus some kind of nicer user interface. The original git commit worked this way, for example. See What does the term "porcelain" mean in Git? and Which are the plumbing and porcelain commands?


Conclusion

The above is really just a very long way to go into the fact that Git has all kinds of fancy stuff that you probably shouldn't use because it's a bad idea. Don't use these esoteric methods of working unless they solve some very specific problem you have that you can't solve some other, clearer way. This whole thing is almost certainly a bad idea. Git has to keep supporting it for backwards compatibility, but you don't have to use it. Don't use it.

halfer
  • 19,824
  • 17
  • 99
  • 186
torek
  • 448,244
  • 59
  • 642
  • 775
  • Yes. The takeaway is that git is wacky powerful, to the point you can make some bold mistakes. – Kit Nov 25 '22 at 08:06