gitpushgit-detached-head

"HEAD" equivalent in detached HEAD state


Lets say I'm in a detached HEAD state and I run the following command:

git push origin HEAD:foo

git will output the following error:

error: The destination you provided is not a full refname (i.e.,
starting with "refs/"). We tried to guess what you meant by:

- Looking for a ref that matches 'foo' on the remote side.
- Checking if the <src> being pushed ('HEAD')
  is a ref in "refs/{heads,tags}/". If so we add a corresponding
  refs/{heads,tags}/ prefix on the remote side.

Neither worked, so we gave up. You must fully qualify the ref.
hint: The <src> part of the refspec is a commit object.
hint: Did you mean to create a new branch by pushing to
hint: 'HEAD:refs/heads/foo'?
error: failed to push some refs to 'https://server/DefaultCollection/Project/_git/Repo'

So my question is: What is the correct keyword to use here instead of "HEAD" to reference the current commit I'm on? I can copy paste the commit id, but that's obviously silly. I'm asking for a constant name I can type, like head, to represent the current commit I'm on.


Solution

  • As jthill notes, when HEAD is detached, Git doesn't know whether:

    git push origin HEAD:foo
    

    is meant to create refs/heads/foo (a branch named foo) on origin, or refs/tags/foo (a tag named foo) on origin, or perhaps some other kind of ref (e.g., Gerrit's magic refs/for/foo1).

    Because you are in detached-HEAD mode in your current working tree, you must assist Git here. You can:

    git push origin @:refs/heads/foo
    

    to tell Git: Use HEAD, in all upper case,2 to find the commit in my repository. Push that commit and any necessary parents to origin, then ask origin to create a branch named foo.

    It's probably easier, though, to just go ahead and create a local branch named foo:

    git switch -c foo
    

    after which git push -u origin @ suffices. You might even wish to make an alias (shell alias or Git alias) or shell function out of this:

    push-new-as foo
    

    where push-new-as expands to git switch -c $1 && git push -u origin @ for instance. (Add additional protections, such as making sure there's no origin/foo first, if you like: that's probably actually a good idea. Have the alias-or-shell-function run git fetch origin first.)


    1Gerrit is written in JGit and doesn't implement Git quite the same as C Git. Pushing to a refs/for/ namespace name creates a Gerrit changeset, which Gerrit sneakily renames into yet another name. Your own (probably C-Git based) Git thinks Gerrit created refs/for/foo successfully, as their JGit implemention returns a success indication at this point, but it really didn't. This little lie facilitates useful work and causes no actual problems, but it's definitely rather magic.

    2See HEAD vs head vs detached HEAD for why the upper vs lower case distinction is important.