Search code examples
gitgit-branchgit-remote

Pushing Git non-branch references to a remote


Git non-branch references (not branches, tags, remotes and notes) work just fine in a local machine but I have trubles to push them in a remote:

$ git update-ref refs/exp/ee01 6a534fb5f9aad615ebeeb9d01ebe558a679a3cd1

It was successfully created:

$ cat .git/refs/exp/ee01
6a534fb5f9aad615ebeeb9d01ebe558a679a3cd1
$ git for-each-ref refs/exp
6a534fb5f9aad615ebeeb9d01ebe558a679a3cd1 commit refs/exp/ee01

Pushing it:

$ git push origin exp/ee01
Total 0 (delta 0), reused 0 (delta 0)
To https://github.com/dmpetrov/example-get-started-exp.git
 * [new branch]      refs/exp/ee01 -> refs/exp/ee01

However, I don't see it when I clone this repo:

$ git clone https://github.com/dmpetrov/example-get-started-exp.git
$ cd example-get-started-exp/
$ git for-each-ref refs/exp  # it returns nothing

How to push non-branch references properly?

EDIT: I can fetch it by name to FETCH_HEAD. Ideally, I should see\fetch all the new refs without knowing the names in advance.

$ git fetch origin exp/ee01
From https://github.com/dmpetrov/example-get-started-exp
 * branch            refs/exp/ee01 -> FETCH_HEAD

Solution

  • Refspecs as a general concept are great, but there's a somewhat unfinished feeling to them. 😀

    When you first clone some existing repository, your git clone uses a built-in equivalent to git remote add to add the remote name. As the git remote documentation notes (a bit elliptically - see meaning 2a):

    With -t <branch> option, instead of the default glob refspec for the remote to track all branches under the refs/remotes/<name>/ namespace, a refspec to track only <branch> is created. You can give more than one -t <branch> to track multiple branches without grabbing all branches.

    What this boils down to is the fact that after git clone, the (single) default fetch refspec for the new clone is:

    +refs/heads/*:refs/remotes/<name>/*
    

    where <name> is the name from the -o option, or origin if you did not specify such an option.1

    What it doesn't mention explicitly, and is not obvious, is that the remote.remote.fetch setting in a Git configuration file is cumulative.2 This means that you can open up the existing .git/config file, once git clone has created it, and edit it. You will see:

    [remote "origin"]
        fetch = +refs/heads/*:refs/remotes/origin/*
    

    You can change this to add another line, so that it reads:

    [remote "origin"]
        fetch = +refs/heads/*:refs/remotes/origin/*
        fetch = +refs/exp/*:refs/exp/*
    

    Now any git fetch origin will overwrite any of your existing refs/exp/ references with those that are on origin. Fetching with prune = true or with the -p or --prune option will delete any of your existing refs/exp/* references that have no corresponding name on origin.

    If you wish to replace their refs/exp/* names with your own refs/rexp/origin/* names, make the second line read:

        fetch = +refs/exp/*:refs/rexp/origin/*
    

    and now you have invented exp-tracking names.

    (Given that there is no refs/tags/*:refs/tags/* refspec—with or without a leading + sign—you might wonder how tags work at all. The answer here is "somewhat magically, with internal rules that cannot be expressed through a refspec". That's part of what I mean about the somewhat unfinished feeling. It's also not obvious what to put in during a git clone, but note that git clone -c name=value lets you write configuration values at git clone time. You still need to somehow know that the remote you're cloning has refs/exp/* names, though.)


    1In a forthcoming Git release, the -o option is likely to have a configurable default, so that leaving out -o won't necessarily mean use origin, but for now, that's what it always means.

    2In contrast, a setting such as user.name or user.email uses only the last value. That is, if your configuration file says:

    [user]
        name = fred
        name = flintstone
    

    then user.name is flintstone: the earlier fred value has been discarded in favor of the later flintstone one. A cumulative setting can only obtained with git config --get-all or git config --get-regexp; it comes out as one line per value. See the git config documentation for more details.