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
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 therefs/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.