I know it is possible to create, update or delete a remote-tracking branch
by content of a local branch
of current repository. For example:
$ git push . :refs/remotes/origin/master
Which deletes remote-tracking branch
. But is updating a remote-tracking branch
by local branch a good practice? If yes, is there another usage of that work?
First, let's define the term remote-tracking branch. A remote-tracking branch is simply a reference whose full spelling starts with refs/remotes/
, such as the refs/remotes/origin/master
in your example. In most normal interactions with Git, we drop the refs/remotes/
part and just write origin/master
, but sometimes—as with lower level, script-oriented Git commands, or when doing odd things like your example—it's necessary to spell it out completely.
Now, the purpose of a remote-tracking branch is to keep track of the ID stored in a branch on one of your remotes (hence the name). So if we look closely at your question here:
... is updating remote branch by local branch a good practice?
we need to note a few things: this is not updating a "remote branch" (which we have not really defined), but rather updating a remote-tracking branch, i.e., one of those local entities with a name starting with refs/remotes/
. Let's also note here that you are specifically deleting a remote-tracking branch (which is a fairly severe form of updating :-) ).
Still, assuming you just dropped a word and really did mean "remote-tracking branch": is updating it this way a good practice? Not generally, no.
Let me add a lot more background, though. I also need to define the term refspec here. A refspec is simply a pair of references (like master
or origin/master
, or their fully-spelled-out versions, refs/heads/master
and refs/remotes/origin/master
respectively) separated by a colon :
character. The reference on the left is called the source or src
, and the one on the right is the destination or dst
. Optionally, you can prefix the pair with a plus-sign +
, and omit at most one of src
and/or dst
. If you omit dst
you can, and probably should, omit the colon as well: git fetch origin master
, for instance, is a source with no destination. You need the colon when omitting src
though: git push origin :foo
cannot be spelled git push origin foo
.
(What it means when you omit half of a refspec varies, between git fetch
and git push
. I am not going to describe this completely, just enough to cover the cases of interest here.)
git fetch
and git push
Since Git version 1.8.4, both git push
and git fetch
will opportunistically update remote-tracking branches. That is, if you have your Git contact remote R and either obtain or set its branch B, and your configuration says to track B under refs/remotes/R/B
(as is the normal setup), your Git will (since 1.8.4) update that remote-tracking branch. (Before 1.8.4, Git did opportunistic updates for push
but not for fetch
.)
--prune
Simply running git fetch origin
(with no additional refspecs) will update all your refs/remotes/origin/
remote-tracking branches. But suppose you did this earlier and there was an xyz
branch that your Git had copied to refs/remotes/origin/xyz
, and since then, someone deleted branch xyz
on origin
?
What happens today is that your Git preserves your origin/xyz
(presumably, in case you were still using it). To tell your Git that it should automatically prune any remote-tracking branch that you have, that the remote no longer has, simply add --prune
(or -p
) to your command:
$ git fetch --prune origin
Your Git will now remove any remote-tracking branch that you have listed under origin
that is not still tracking an actual branch on the remote named origin
.
You can also spell this:
git remote update --prune origin
and there is some bug in some version(s) of Git where one of the two commands works and the other does not (I'm not sure which versions, nor which one is successful).
As a general rule, you should let Git update remote-tracking branches automatically. There are several specific cases where you might want to update them manually, though, such as after modifying your configuration by hand to change the name of a remote, or to change the mapping applied to a remote (the remote.remote.fetch
lines), or encountering a bug like the one I just mentioned. Or, for instance, if you've just used a source-less git push
, or git push --delete
, to delete a branch—git push origin :foo
—and you have an extremely slow network connection, you might be too impatient to run git fetch -p origin
and wait.
In this case, while git push . :refs/remotes/origin/foo
does work to delete origin/foo
from your repository, you might as well use a more direct command:
$ git branch -r -d origin/foo
Deleted remote-tracking branch origin/foo (was 5ace313).
In fact, any update to remote-tracking branches can be done through git branch -r
(or the underlying "plumbing" command, git update-ref
), just as updates to local branches can be done through git branch
(without -r
). The only place where I believe there is good a reason to use git push .
to update a branch is to achieve the result of git merge --ff-only
without actually checking out the branch.1
1Specifically, git push . x:y
attempts to do a fast-forward of local ID x
—this can be anything that resolves to a valid object ID—with reference y
. When y
is a local branch, it is the equivalent of:
git checkout y && git merge --ff-only x && git checkout -
except that it does not touch the work-tree at all. Except for atomicity and type-checking issues and possible symbolic ref issues, it is also the equivalent of this sequence of shell commands:
x="$1" y="$2"
# resolve x to an object ID
xid=$(git rev-parse $x) || exit
# resolve y to a reference
yref=$(git rev-parse --symbolic-full-name $y) || exit
[ -z "$yref" ] && { echo "fatal: $y: not a valid reference"; exit 1; }
# make sure the current value of y is an ancestor of (resolved) x
git merge-base --is-ancestor $yref $xid || { echo 'not a fast forward'; exit 1; }
# ok, it is a fast forward: update
git update-ref -m "fast forward" $yref $xid
Note that --symbolic-full-ref
resolves a symbolic reference like origin/HEAD
to the name it refers-to, e.g., refs/remotes/origin/master
. This is probably undesirable here, but there is no obvious flag to git rev-parse
to verify that the reference name is valid and convert it to fully-qualified, but not expand a symbolic name to its target.