简体   繁体   中英

Git remote head refers to feature branch instead of master

Prior warning, I'm a bit of a Git newbie, so sorry if some of this is in distinctly non-tech speak (or I just use the wrong terminology).

Last week I noticed a bit of an oddity on my company's Git repo, it appears that the remote HEAD has been moved from pointing at master to pointing at a feature branch instead.

So, it used to be

remotes/origin/HEAD -> origin/master
remotes/origin/featureBranch1
remotes/origin/featureBranch2
remotes/origin/master

and is now

remotes/origin/HEAD -> featureBranch1
remotes/origin/featureBranch1
remotes/origin/featureBranch2
remotes/origin/master

I cloned the repo into anew, and sure enough, the default branch that was cloned was featureBranch1 , not master .

The feature branches on this project generally have a lifetime of a week or two, and in the next few days, featureBranch1 is going to be merged into master and deleted.

Being a Git newbie, I'm not entire sure what the effects of the remote HEAD pointing to that branch will be, if any, so what I'd like to know is is this an issue, and if so, how much of one?

Will the feature branch merge and delete ok? Will this cause issues with the repo if the remote HEAD branch is deleted? Will it automatically revert to master ? Will we all be given a day off because no one can do any dev work?

We're using Git version 2.9.3.windows.2.

(Note: I'm also vaguely interested in how this happened, seeing as developers don't generally have permissions to change anything on the server, but that's less important at the moment than any possible knock on effects)

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 reference 1 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 , eg, 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, ie, 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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM