Home > Software design >  origin/HEAD vs origin/master
origin/HEAD vs origin/master

Time:08-01

I initially had two branches in bitbucket - master and feature ( 4/-2 commits as compared to master).

In process of deleting the feature branch - I have messed up with the origin/HEAD. Now my master is changed to HEAD and the feature branch is changed to the default branch master.

Please provide insights and suggestions to fix this.

Before Current Expected
master HEAD master
feature master (delete branch)
$ git branch -r
origin/HEAD -> origin/master
origin/master

CodePudding user response:

origin/HEAD represents the default branch on the remote. origin - is just alias to remote url. And master - is just name. Git does not have some special meaning of master. So your feature branch can't become master, actually - that does not make sense. But it could be merged into master - which is apparently your case.

origin/HEAD -> origin/master means, that your default remote branch is master branch. And based on the output of git branch -r it is seen, that on remote server you have only master, no feature anymore.

So if you merged feature into master, deleted feature, and git branch -r gives you the output which you posted - seems that everything is fine.

CodePudding user response:

Here's some additional background information to supplement kosist's answer (q.v.):

  • HEAD, in any Git repository, is a very special name. It is normally a symbolic reference, which in effect means that it contains some other branch's name. This gets a bit more complicated on server Git repositories than on clients, but the general idea here is the same regardless of server vs client.

  • When you run git clone, you make a new repository by cloning some existing repository. By default, this new repository copies all the commits from the original repository, and none of the branch names.

Of course, the new repository is a Git repository. This means it has a special name, HEAD, that is normally a symbolic reference. This is your Git repository's HEAD, and in your repository, you will create new branch names and switch to them, e.g.:

git switch -c newbranch

At this point your HEAD refers to your new branch newbranch.

The repository you cloned, which your Git repository calls origin, still has all of its original branch names. It presumably still has the same branch as its current branch, so that its HEAD still refers to its main or master or whatever.

We now get into one of the complications. A server repository is normally a "bare" repository, i.e., one with no working tree. We use git switch or git checkout to update the working tree in our (client) Git repository, and this changes which branch the name HEAD remembers, in our (client) Git repository. If you could log in to GitHub, or wherever the server may be, and cd path/to/repo.git and git switch somebranch there, that would change the branch name stored in the server's Git-repository-HEAD—but if that server has only bare clones, the git switch and git checkout commands don't work in those clones.1

Nonetheless, most service providers (e.g., GitHub) provide some way to change the branch name stored in that service provider's repositories. On GitHub, you do this with their web interface, using their "set default branch name" pages. (You can also use the gh cli, if you install that.) The only reason to do this is that when users run:

git clone <url>

the new branch they get in their new clone is a copy of the default branch. However, if they write:

git clone -b develop <url>

the new branch they get in their new clone is develop (a copy of the develop branch in the server's clone).

So, in short (if it's not too late), the point of setting HEAD in a server repository is to set the default branch that people running git clone from some client will get. And that's all it's good for ... well, almost all.

Now, as you probably already know and I just said, when you clone some other repository (e.g., https://github.com/git/git.git), you get all their commits and none of their branches and then your own Git software creates one branch—the default branch, unless you used -b—but that's not the full story. The full story goes on to mention that each of their branch names becomes one of your remote-tracking names. That is, because this Git-repository-for-Git has branches named seen and next for instance, you get origin/seen and origin/next in your clone. The really full story then has to go on to add more conditionals here, since --single-branch or --depth can turn off this behavior, but we'll stop at this point and just claim this:

  • All of their branch names become remote-tracking names in your clone.

So if they have a master or main (the Git repository for Git now has both), and a seen and a next for instance, you get an origin/master or origin/main and an origin/seen and an origin/next. So here's Git's somewhat dim-bulb version of a bright idea: They have a HEAD, so why not make an origin/HEAD too?

That's exactly what your origin/HEAD is, or is supposed to be: a copy of their HEAD. Their HEAD is a symbolic reference to their main, so your origin/HEAD is now a symbolic reference to your origin/main. Or, their HEAD is a symbolic reference to their ice-cream-is-tasty branch, so your origin/HEAD is now a symbolic reference to your origin/ice-cream-is-tasty name.

Note that your origin/* names are not branch names ... well, not exactly. I like to call them remote-tracking names. Git officially calls them remote-tracking branch names, but they don't work like your own branch names. They stick around so that your Git can remember their Git's branch names. Your git fetch command will update your remote-tracking names. Unless you run git fetch --prune or git remote origin prune or similar, though, your Git software won't delete your remote-tracking name when they delete their branch name—so that's one way they don't work right, sort of—and you can't git switch to one of these remote-tracking names, which is why they're not branch names.2

With all of this, you might expect that every time you run git fetch origin, your Git would find out if they've moved their HEAD from (say) their main to (say) their develop, and if so, your Git would make your origin/HEAD a symbolic reference to your origin/develop. That would brighten up Git's dim bulb a bit, so therefore it doesn't actually happen. Instead, your origin/HEAD gets set up once, when you run git clone, and is never touched again.

Well, that is, it isn't touched unless you run git remote set-head. The git remote command has a bunch of sub-commands, and the set-head command is how you change your own origin/HEAD for your remote named origin. If you'd like your origin/HEAD to refer to your origin/seen, you run git remote set-head, like this:

$ git branch -r
  origin/HEAD -> origin/master
  origin/main
  origin/maint
  origin/master
  origin/next
  origin/seen
  origin/todo
$ git remote set-head origin seen
$ git branch -r
  origin/HEAD -> origin/seen
  origin/main
  origin/maint
  origin/master
  origin/next
  origin/seen
  origin/todo

If you'd like your Git to call up their Git (over at origin, i.e., to call up whatever Git-like software answers at the URL for the original repository) and ask them about their HEAD and update your origin/HEAD accordingly, you run git remote set-head origin --auto:

$ git remote set-head origin --auto
origin/HEAD set to master

and now I'm back to the old origin/HEAD -> origin/master setup.


1Well, not without a --work-tree or equivalent, anyway. This lays a different trap for those working with server-side bare repositories, of course.

2Note that git switch requires the --detach option when using a remote-tracking name. The git checkout command doesn't require the option, it just assumes it. Either way you end up in what Git calls detached HEAD mode, which is Git's weird way of saying that you are no longer on any branch at all. Since you aren't on a branch, when you use these names, they must not be branch names, even if Git calls them "remote-tracking branch names". The word branch in here is just plain misleading.


Besides a lot of pointless fussing about, what good is any of this?

Some things will show origin/HEAD -> origin/master or whatever. We saw above that git branch -r will do that, for instance. This is, at least potentially, stale information; you must run git remote set-head origin --auto to update it. Then git branch -r will only be a few seconds stale, instead of hours, days, weeks, or years stale (depending on how long ago you ran git clone, vs how long ago you ran git remote set-head).

It is information, stale or not, but is it any good? We now turn to the gitrevisions documentation, which is very important and which you should probably re-read every week until you're pretty familiar with the whole thing. It's very long and you should probably just study a paragraph or two each time. This documentation tells you how git log, git checkout / git switch, git branch, git reset, and so many other commands use the string arguments you give them to find at least one commit. For instance, when you run:

git log develop

how does Git know what commits to show? The answer starts with this gitrevisions documentation.

In general, you can give a raw commit hash ID to most Git commands—with the important exception of git switch / git checkout, which do something different when you give them a branch name than they would do with a raw hash ID. Giving Git a raw hash ID means that commit, so if origin/main means 30cc8d0f147546d4dd77bf497f4dec51e7265bd8, git show origin/main and git show 30cc8d0f147546d4dd77bf497f4dec51e7265bd8 do exactly the same thing.

Up near the top of this documentation page, under the SPECIFYING REVISIONS section, there's a six-step sequence listed this way:

<refname>, e.g., master, heads/master, refs/heads/master
      A symbolic ref name. E.g. master typically means the commit object referenced by refs/heads/master. If you happen to have both heads/master and tags/master, you can explicitly say heads/master to tell Git which one you mean. When ambiguous, a <refname> is disambiguated by taking the first match in the following rules:

  1. If ...
  2. otherwise ...
  3. otherwise ...
  4. otherwise, refs/heads/<refname> if it exists;
  5. otherwise, refs/remotes/<refname> if it exists;
  6. otherwise, refs/remotes/<refname>/HEAD if it exists.

I've left in only steps 4 through 6 here because they're the ones we're going to encounter now:

  • If you say git show master or git show main, and there's a branch name master or main, this matches the step 4 rule and that's the commit you see.
  • If you say git show origin/main and there's a remote-tracking name refs/remotes/origin/main, this matches the step 5 rule and that's the commit you see.
  • But if you just say git show origin and there's a remote-tracking name refs/remotes/origin/HEAD, this matches the step 6 rule, and that's the commit you see.

So the fact that there's an origin/HEAD—assuming you haven't deleted it, which you can do with git remote set-head as it has a --delete option—means that just writing origin where a commit hash ID is required causes your Git to find origin/HEAD, which is presumably a symbolic reference to the default branch over on origin.

That's it. That's the "good thing", if you consider it a good thing. You can use the name origin to mean origin/HEAD. Personally, I consider this a bad thing because it means that if you run:

git push origin origin

it works and the first origin means the remote named origin and the second origin means the commit found by looking up refs/remotes/origin/HEAD and this is horribly confusing to newbies, and of virtually no use to anyone who understands what I just said without having to plow through the Git manual pages multiple times.

(Is this a rant? Perhaps it is: I think Git is incredibly useful and has a lot of things going for it, and at the same time, it has some really bad user interface decisions embedded in it, that make it really hard for people to use. Each of these features has a reason for its existence, but taken as a whole, they're a nightmarish jumble of traps for the unwary. That's not how software ought to be, if possible. Still, Git is what it is; we just have to be careful how we use it, and—like C in so many ways—be careful which feature set we pick to use.)

  •  Tags:  
  • git
  • Related