I tried to find a suitable duplicate and didn't.
The short answer is that git clone
will still work, but might become slightly annoying. See the example below. Any existing clone will be unaffected.
The complete answer is a bit complicated as there are two Gits involved, and hence two Git versions, and the primary effect is only on git clone
run from a client.
What is HEAD
, really?
First we should establish what HEAD
, in any Git repository, is and represents. Literally, HEAD
is just a file, usually containing a symbolic reference1 to some existing branch:
$ cat .git/HEAD
ref: refs/heads/master
However, HEAD
may be "detached", in which case it contains a raw hash ID. It's also possible for HEAD
to point to a non-existent branch name:
$ git checkout --orphan newbranch
Switched to a new branch 'newbranch'
$ cat .git/HEAD
ref: refs/heads/newbranch
and yet there is no branch named newbranch
yet. If we now git checkout
some other branch, the name newbranch
vanishes entirely, as if it had never existed.
This gives us the definition for what HEAD
is: HEAD
provides to Git the notion of the current branch. This is usually a branch name, and that branch name is usually a valid reference: .git/refs/heads/master
exists and contains a valid hash ID, or else .git/packed-refs
contains an entry for refs/heads/master
, for instance. Alternatively, it may be a raw commit hash, meaning that the current branch has no name and is based on the current commit (whose ID is in HEAD
).
When HEAD
is a symbolic reference, it simply contains the name of another reference (and this is further constrained to be only a branch reference). Git actually allows any reference to be symbolic, but only HEAD
is really useful (if you make non-HEAD
symbolic references, they act all weird,2 to use a highly technical description :-) ).
How git clone
chooses a branch to check out
The syntax for git clone
includes the -b
option:
git clone -b <name> <url>
This tells Git to connect to the URL, download everything needed into the new clone, and then, in the new clone, run git checkout <name>
. The <name>
part need not actually be a branch name—tags are accepted here—but usually it is a branch name.
If you omit the branch name, Git chooses some branch to check out, but—which one? The answer is that your Git attempts to consult the other (server) Git to find out what branch its HEAD
names. This is where Git versions matter.
In some old versions of Git (those predating 1.8.4.3), Git does not know how to ask for, or tell to another Git, anything about any symbolic references. Instead, an old server tells about, or an old client asks for, the hash ID for HEAD
. If HEAD
contains a raw hash ID or a valid branch name, the server and client will be able to communicate the corresponding ID. The client then uses a quick and dirty method to guess at the server's branch name: it looks for any branch that has the same hash ID.
As long as both client and server are newer, though, the client will ask for, and the server will send, the actual branch name. As long as that branch name is valid, the client will be able to use it.
If all of these steps go as planned, the client will run:
git checkout <name>
as expected. Since the client also just cloned the server's refs/heads/<name>
to refs/remotes/origin/<name>
,3 the client has, e.g., an origin/master
and can make a new local master
pointing to the same commit.
But—here's the answer to the primary question—if the server's name is not valid, the client simply gives up and creates a master
, though not in quite the right way:
$ git symbolic-ref HEAD refs/heads/nobranch
$ git clone [url-obscured] tt
Cloning into 'tt'...
remote: Counting objects: 122, done.
remote: Compressing objects: 100% (81/81), done.
remote: Total 122 (delta 42), reused 99 (delta 34)
Receiving objects: 100% (122/122), 24.79 KiB | 0 bytes/s, done.
Resolving deltas: 100% (42/42), done.
warning: remote HEAD refers to nonexistent ref, unable to checkout.
$ cd tt
(note: the blank line—the extra newline—really is in there).
$ ls
$ git status
On branch master
Initial commit
nothing to commit (create/copy files and use "git add" to track)
$ git branch -a
remotes/origin/branch
remotes/origin/foobranch
remotes/origin/master
Note that there is an origin/master
but git clone
did not check it out correctly.4
This somewhat-bogus master
is an "orphan" branch, i.e., is one of those not-yet-existing branches: our new clone has a HEAD
that says ref: refs/heads/master
but there is no refs/heads/master
yet. Hence, we can get fix things by checking out any branch. In an odd quirk, even git checkout master
works:
$ git checkout master
Branch master set up to track remote branch master from origin.
Already on 'master'
Weird, no? The first output line tells us Git has just created the master
branch, then the second one tells us that Git did nothing and checked out nothing. But in fact Git filled in the index and work-tree too:
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
nothing to commit, working tree clean
and now we have all our files.
1, 2 In very early versions of Git, symbolic references were actually just symbolic links to the "real" reference file. This is where all the weird behavior of symbolic references comes from: they all act exactly like symbolic links on Unix/Linux systems. So it's not actually weird at all, except that it's pure mechanism, instead of practicality.
3 You can change the origin
part with another command line option, but that changes both halves of this equation the same way, so that the result is the same.
4 This is because git clone
has the core git checkout
code built in to it, but not quite coded the same way as git checkout
itself. It does not literally run git checkout master
. If it did, it would have discovered that origin/master
exists and would have behaved sensibly. A future Git might fix this otherwise-trivial bug, of course.
What about remotes/origin/HEAD
?
(This part actually is a duplicate, but I include it here for completeness.)
Everything in refs/remotes/origin
is just your client's way of remembering what it saw on the remote named origin
. This is also true of origin/HEAD
, but in a rather peculiar way.
Note that above, we did not get an origin/HEAD
. This is because the client could not figure out the server's HEAD
(since I deliberately broke it). If I repair the server's repository and re-clone, things go a bit more smoothly:
Cloning into 'tt'...
remote: Counting objects: 122, done.
remote: Compressing objects: 100% (81/81), done.
remote: Total 122 (delta 42), reused 99 (delta 34)
Receiving objects: 100% (122/122), 24.79 KiB | 0 bytes/s, done.
Resolving deltas: 100% (42/42), done.
$ cd tt
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
nothing to commit, working tree clean
$ git branch -a
* master
remotes/origin/HEAD -> origin/master
remotes/origin/branch
remotes/origin/foobranch
remotes/origin/master
Now we have a symbolic reference, refs/remotes/origin/HEAD
, pointing to refs/remotes/origin/master
. (Git abbreviates these, though the amount of abbreviation is inconsistent. Use git branch -r
, and Git drops refs/remotes/
; but use git branch -a
, and Git drops only refs/
from the left hand side, yet still drops refs/remotes/
from the target of the symbolic reference.)
All of this happens only during cloning. Once the clone is done, the symbolic ref refs/remotes/origin/HEAD
is essentially fixed, unless you explicitly update it yourself, on your own client. You may do this at any time by running:
git remote set-head origin
and this particular command takes a bunch of options:
-a
, --auto
: your (client) Git calls up the other (origin
, in this case) Git and goes through the same query process it would use for cloning. Whatever symbolic name it figures out, your Git changes your origin/HEAD
to match.
-d
or --delete
: your (client) Git deletes the symbolic reference.
A name: your (client) Git attempts to resolve this name to a remote-tracking branch currently in your own repository, for this particular remote (again, origin
in this case). If it finds one, your Git sets the symbolic reference to that remote-tracking branch.
You can also use a Git plumbing command (not really meant for humans to run directly) to set or delete refs/remotes/origin/HEAD
. This bypasses all the normal checking, so you can set it to something nonsensical, or to "detach" it (point it directly to a commit), neither of which git remote set-head
allows.
But what good is origin/HEAD
?
The thing about refs/remotes/origin/HEAD
is that it's not really useful for anything. You have these various ways to have it set or to change it in your clone, and yet, the only thing it does is let you use the name origin
to refer to whatever remote-tracking branch refs/remote/origin/HEAD
refers to.
This is actually just step 6 of the six-step name-resolving process described in the gitrevisions documentation. Since refs/remotes/origin/HEAD
is a symbolic reference, and origin
matches refs/remotes/origin/HEAD
through step 6, Git replaces this with the target of the symbolic reference (such as refs/remotes/origin/master
) which is the corresponding remote-tracking branch. (This then resolves to a commit hash ID if appropriate, which is usually the case.)
In essence, this saves typing out /HEAD
: you could have written origin/HEAD
, which would resolve at step 5 of the six-step sequence. But you don't have to type in the /HEAD
part, because step 5 is followed by step 6.