Are the names of a remote-tracking branch and the corresponding remote branch being tracked necessarily the same?
If they can have different names, how does git fetch
match the two
branches then? (a typical refspec
to git fetch
is +refs/heads/*:refs/remotes/remote/*
)
If I am correct, given a remote-tracking branch, we can create a
local-tracking branch that associates with it but has a different
branch name. (by the -b
option in git checkout
)
Further if the names of the remote-tracking branch and the
corresponding remote branch being tracked are the same, how does
git push
match the local-tracking branch and the remote branch? (a typical refspec
to git push
is +refs/heads/*:refs/heads/*
)
Are the names of a remote-tracking branch and the corresponding remote branch being tracked necessarily the same?
No. However, making them not-match results in a lot of pain (and I haven't tested it on the push side).
If they can have different names, how does git fetch match the two branches then? (a typical refspec to
git fetch
is+refs/heads/*:refs/remotes/remote/*
)
You may have multiple fetch =
lines, so you could, for instance, do this:
[remote "strange"]
fetch = +refs/heads/master:refs/remotes/strange/surprise
fetch = +refs/heads/other:refs/remotes/strange/surprise2
Note, however, that you can no longer use refs/heads/*
on the left of any additional fetch refspecs, since that will match master
and other
and (presumably) map them to names other than surprise
and surprise2
, and git fetch
aborts with an error message in this case. This effectively forces you to list every refs/heads
name that you wish to copy from the given remote (strange
, in this case).
(As I said, I haven't tested this with push and I don't know if push obeys the same mapping rules as fetch. There were some recent changes, around git 2.5 or so, to handle "triangular" work-flows better, where you fetch from localmirror
and push to centralserver
for instance. One of those changes was to add the same kind of name-mapping for the push remote. Presumably, before this new code went in, doing this kind of pushing brought even more pain, possibly even without a triangular work-flow; and presumably now it works better....)
We might call this "silly renaming tricks", and my advice would be: don't use them. :-) I think they'll work correctly with most commands, and fail with others, though I can't point to any specific failure examples (just vague memories of how I used to do things).
If I am correct, given a remote-tracking branch, we can create a local-tracking branch that associates with it but has a different branch name. (by the -b option in git checkout)
Yes; and this works fine for all kinds of local work. Again, I'd avoid the phrase "local-tracking branch", and just say "local branch with an upstream" as this is the direction git documentation has moved since about 1.7 (see below).
Remember that "the upstream of local branch $branch
" is produced by:
git config --get branch.$branch.remote
, andgit config --get branch.$branch.merge
through the fetch =
refspecs for that remote.Thus, suppose we've created two local branches test1
and test2
and have the following:
$ git config --get branch.test1.remote
origin
$ git config --get branch.test1.merge
refs/heads/test
$ git config --get branch.test2.remote
origin
$ git config --get branch.test2.merge
refs/heads/test
Both test1
and test2
refer to refs/heads/test
, which is the name of a branch on the other git repository that will be located via the name origin
: this is why we need to run these through the fetch =
map(s) for origin
.
In the absence of silly renaming tricks, the "mapped through" part leaves the branch-name part (everything after refs/heads
) unchanged, and just replaces the middle bits, so that refs/heads/test
becomes refs/remotes/origin/test
. That's really easy to make assumptions about. I believe some lazy script-writers (including myself in the past) may have used this bit of shell script code:
fullbranch=$(git rev-parse --symbolic-full-name $branch) || exit 1
remote=$(git config --get branch.$branch.remote)
rmtbranch=refs/remotes/$remote/$branch
which not only assumes the lack of silly renaming tricks, it even assumes that if we're on branch test1
, the upstream must be origin/test1
, not origin/test
. Slightly less lazy script writers (including myself in the past) then had to fix their scripts, e.g.:
fullbranch=$(git rev-parse --symbolic-full-name $branch) || exit 1
remote=$(git config --get branch.$branch.remote)
theirname=$(git config --get branch.$branch.merge)
rmtbranch=refs/remotes/$remote/${theirname#refs/heads/}
which now assumes that refs/heads/test
on the origin
maps to refs/remotes/origin/test
in the local repository.
Adding silly renaming tricks means that we can't find the actual upstream name easily at all, but various commands (e.g., git merge
, git rebase
) automatically find the correct upstream. To make it easier for scripting, git version 1.7.0 grew the @{upstream}
notation: you can now simply write $branch@{upstream}
. The git parser looks up the upstream for you, and the above (broken) script fragment can be rewritten as:
rmtbranch=$(git rev-parse --symbolic-full-name $branch@{upstream}) || exit 1
This is all fine and good for fetch, but what about push? Well, if you're pushing to the same remote that you're fetching from, you just do the same thing. But you can, for any number of reasons,1 split them up: fetch from repository F
and push to repository P
. In this case, we might need different mappings for F
and P
.
Git 2.5 introduced @{push}
, as noted by VonC in some earlier SO thread I can't find offhand, and in this github blog posting. The new @{push}
notation simply switches from using the fetch upstream to using the push upstream, i.e., using the P
mapping instead of the F
mapping.
There's still one good question, and it's your last one here:
Further if the names of the remote-tracking branch and the corresponding remote branch being tracked are the same, how does git push match the local-tracking branch and the remote branch? (a typical refspec to git push is
+refs/heads/*:refs/heads/*
)
My answer for git 2.5, with the new push stuff, is: I don't really know for sure, but your "typical refspec" is no longer the default (since git 2.0). When you run git push
with no refspec arguments, git looks up your push.default
setting (along with a bunch more optional settings but push.default
is effectively2 required). It has five possible values, and only one of them—not the default—amounts to refs/heads/*:refs/heads/*
.
One of those settings is upstream
, and that setting runs the current branch's merge
setting through the map functions (presumably, in git 2.5 and later, through the new separate push map if present, else through the fetch map).
1One reason is to use a pull-request repository, as in the linked github blog post. Another is to fetch from a local mirror (as at a large corporate setup with local mirrors for various branch offices), but push to a single central server (the corporate-designated "master" site that all the local mirrors actually mirror).
2If you fail to set push.default
, git spits out a lot of annoying text. The best way to shut it up is to set push.default
, so that means you're "required" (in some sense anyway) to set it. And, since git 2.0, the default setting, if you haven't set it, is simple
, which forbids silly renaming tricks.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With