Git Push: What is the difference between HEAD:refs/heads/<branch> and <branch>?
What does command 1 do that command 2 doesn’t?
1. git push <projectpath> HEAD:refs/heads/<branch> 2. git push <projectpath> <branch>
What is the meaning of “HEAD:refs/heads/”?
VonC’s answer is correct (and upvoted), but I think another way of looking at this might make more sense.
Note that all of this is assuming that you’re using the four-word form of
git push, i.e.,
git push remote refspec. The
remote part here is usually just the name
origin. We’ll define
refspec better in a moment.
git push does
git push needs to do (and therefore does) is to call up another Git instance on another machine,1 then give that other Git a set of references (usually branch names, sometimes tag names) to update. A reference is simply a name, like
v1.2, that ideally should be fully-qualified (
refs/tags/v1.2) so that we can be sure what kind of reference it is—branch, tag, or whatever.
In order for the other Git to update the references your Git hands over, your Git must also hand over some of those big ugly SHA-1 hashes: one per reference. In other words, your Git is going to ask their Git to set their
refs/heads/master to, say,
ed4f38babf3d81693a68d06cd0f5872093c009f6. (At this point—actually, just a bit before this point, really—your Git and their Git have a conversation about which objects yours want to send them, and which objects they already have, all done by these big ugly hash IDs. Once the two Gits agree about what’s going to be sent over, yours does the
counting objects and
compressing objects and then sends them the objects. The “now, please set some names” part happens nearly last.)
Getting the name and hash parts
Note that there are two parts to your Git’s request: (1) a fully-qualified reference, and (2) the big-ugly-hash. (In fact, there’s also a third part, the
--force flag, but that part is easy and we can just ignore it.) But where does your Git get these?
If you write:
git push origin somename
you’ve given your Git two pieces of information: the name
origin, which your Git uses to look up the URL, and the name
somename. Your Git uses this to figure out the full name. Is
somename a tag? If so, the full name is
somename a branch? If so, the full name is
refs/heads/somename. Either way works. Of course, you can also write out the full name yourself—and if the name is both a branch and a tag, you may want to do that, rather than letting Git pick one for you.2
So, where does your Git get the big ugly hash? The answer is: from that same name. The name
somename, whether it’s a branch or a tag, just names some particular Git object. If you want to see the hash yourself, you can do that any time:
git rev-parse somename
will show it to you. This is, in fact, how I got
ed4f38babf3d81693a68d06cd0f5872093c009f6: I went to a Git repository for Git and did
git rev-parse v2.1.1 and it printed out that hash, because
v2.1.1 is a valid tag in any complete copy of the Git repository since version 2.1.1 came out.
Note that when you do use this form—this
git push remote name form—Git looks up the
name argument in your repository for both purposes: to find out its full name, and to get its hash. It doesn’t matter where your
HEAD is, only what that full name points to.
But Git does not have to use your branch’s (or tag’s) ID
The fourth argument to
git push is called a refspec, and its syntax actually allows two parts separated by a colon:
git push origin src:dst
In this case, the
dst part supplies the name, but the
src part supplies the hash. Git runs the
src part through
git rev-parse and that produces the hash. So you can:
git push origin mybranch:refs/tags/v42
to create tag
v42 in the other Git repository, using whatever commit hash your branch
HEAD contains a branch name
HEAD always names the current commit. Usually it does so by naming a branch, and letting the branch name the commit. So, usually
HEAD contains a branch name like
master, and a branch name always gets you the tip commit of that branch (that’s how Git defines “tip commit”; see the definition of branch in the Git glossary). But always,3
HEAD can be turned into a commit:
$ git rev-parse HEAD 2b9288cc90175557766ef33e350e0514470b6ad4
HEAD is either a branch name (which is then the tip commit), or else you have a “detached HEAD”, in which case Git stores the current commit ID directly in
Pushing when HEAD is detached
Remember that in order to push, Git needs to get those two pieces of information: the hash, and a (full) name. When
HEAD isn’t “detached”, Git can get both from it:
HEAD has a branch name—in the full name form, in fact—and the branch name has the hash. But when you are in “detached HEAD” mode,
HEAD only has a hash. Git can’t find a branch name in
HEAD. There might not be one: you might have checked out a commit by ID, or maybe you checked out by tag name, as in:
$ git checkout v2.1.1
which put you in this “detached HEAD” mode.
In this case, Git demands that you supply both the source hash
src—you can still use the name
HEAD to get it—and the
dst destination name. And, if you use
HEAD as the source, Git really needs you to spell out the full destination, because Git can’t tell, at this point, if it should be a branch (
refs/heads/dst) or a tag (
Other forms of
You can run
git push with fewer arguments, e.g.:
git push origin
or even just:
What happens here is that without a
refspec, Git consults your
push.default setting first. Usually this is
simple (the default since Git version 2.0). In this case, Git simply uses
HEAD to figure out what to push—which, of course, works only when
HEAD is not detached. That’s just what we described above.
(Three of the other settings also use
HEAD. One of them—the one that was the default before Git version 2.0—does not, but that particular setting proved too error-prone, which is why the default changed. You probably should not use it, at least not unless you are a Git master.)
(And, if you leave out the
remote, Git again uses
HEAD to figure out where to push to, defaulting, if needed, to
You can also push multiple refspecs:
git push origin branch1 branch2 tag1 HEAD:refs/tags/tag2
In this case, each refspec is handled in the usual way: get its fully qualified name if needed, so that your Git can give their Git a fully qualified name each time; and look up its hash ID if you didn’t use the
src:dst form (or if you did use the
src:dst form, look up
src‘s ID instead).
You can use wildcards in refspecs:
git push origin 'refs/heads/*:refs/heads/*'
(some shells will eat, mangle, fold, spindle, or mutilate the
*s so you may need to use quotes, as in this example; other shells won’t—or at least usually won’t—but it doesn’t hurt to quote). This will push all your branches, or at least try to. This tends to be overly enthusiastic, pushing all your temporary work and experimentation branches, and is probably not what you want, but it’s what Git did by default prior to version 2.0.
And, you can use an empty
git push origin :refs/heads/deleteme
which is a special-case syntax that means “have my Git ask their Git to delete that reference” (to delete a tag, spell out the tag). As with a detached HEAD, the lack of a fully-qualified name on your side means you should fully-qualify the name for their side. (See footnote 4 again.)
The force flag
If you add
--force to your
git push command, your Git passes this flag on to their Git. Instead of a polite request—”please, sir, would you like to set your
ed4f38babf3d81693a68d06cd0f5872093c009f6?”—your Git will send it as a rather insistent demand. Their Git can still refuse either way, but their Git will, by default, do it even if it’s not sensible.
Refspecs allow you to control this flag more tightly. The force flag in an individual refspec is a leading plus sign
+. For instance, suppose you have new commits for both
develop branches, and also a new set of rebased commits for
experiment, which everyone else has agreed that you are allowed to force-push.
You could do this:
git push origin develop master; git push -f origin experiment
but you can combine it all into one big push:
git push origin develop +experiment master
experiment makes that one a command (“update
experiment!”) while leaving the others as polite requests (“please, sir, if you like, update
(This is all a bit esoteric for
push, but is actually something you use regularly every day with
git fetch, which uses refspecs with
+ flags to create and update your remote-tracking branches.)
1If the “other repo” is on your same machine and you’re using a
file:// or local path based URL, this isn’t quite true, but the principle is the same and the operations go the same way.
2Better yet, don’t get yourself in this situation in the first place. It’s very confusing to have one name that is both a branch name and a tag name. (There are similar confusing situations to avoid due to Git’s habit of abbreviating: don’t name branches with names that resemble remote names, for instance. Git will handle them just fine, but you might not. 🙂 )
3Actually, there’s one exception to this rule, which most people will never notice: when
HEAD names an “unborn branch”. Mostly this occurs in a new repository, which has no commits at all. Obviously, if there are no commits, there is no commit ID that
HEAD could name. It also occurs when you use
git checkout --orphan to create a new orphan branch.
4If you use an unqualified name, their Git will look up the name to qualify it. This means you may not know what kind of name you are trying to update or delete. It’s generally not a good idea, anyway.
Answered By – torek