问题
Normally the git tags are a fixed reference to a commit. But sometimes they are used to mark some event (last-build
, base-line
, etc..) and they change frequently.
I have an script that refreshes those kind of "floating" tags from the reference repository.
git fetch --tags --force
and also make pull from one branch:
git pull origin <mybranch>
I know that many git users warn about using floating tags, but i am forced to deal with that. My question is:
If the branch is marked by one of those floating tags... does the execution order of the commands matter?
I am afraid that git pull
doesn't refresh the tags when they exist locally, and that it might work with the reference of the all tags if it runs first.
git pull
has a --force
option, but the help of the option --no-tags
explain the default behavior as:
By default, tags that point at objects that are downloaded from the remote repository are fetched and stored locally.
Does it means that the objects should be downloaded first in order to be able to refresh the tags ? In that case git pull
should go first.
Which is the correct order?
回答1:
This gets into one of the more obscure corners of Git, but in the end the answer is "it doesn't matter initially which order you use". However, I'd recommend avoiding git pull
in general, and never using it in scripts anyway. Plus, it does matter, in a different way, precisely when you fetch, as we will see below. So I'd recommend running your own git fetch
first, then simply not using git pull
at all.
git fetch
A plain git fetch
(without --tags
) uses a weird hybrid tag update by default, although each remote can define a default tag option that overrides this default. The weird hybrid is what you quoted: tags that point at objects that are downloaded from the remote repository are fetched and stored locally. The underlying mechanism for this is a bit tricky and I'll leave that for later.
Adding --tags
to the git fetch
arguments has almost the same effect as specifying, on the command line, refs/tags/*:refs/tags/*
. (We'll see the difference in a moment.) Note that this does not have the force flag set in the refspec, yet testing shows that the fetched tags are force-updated anyway.
Adding --force
has the same effect as setting the force flag in each explicit refspec. In other words, git fetch --tags --force
is roughly equivalent to running git fetch '+refs/tags/*:refs/tags/*'
: if the remote has tag refs/tags/foo
pointing to commit 1234567...
, your Git will replace any existing refs/tags/foo
so that you now have your own refs/tags/foo
also pointing to commit 1234567...
. (But as observed in practice, it does that even with just --tags
.)
Note that in all cases, git fetch
writes information about what it fetched to the file FETCH_HEAD
. For instance:
$ cat .git/FETCH_HEAD
e05806da9ec4aff8adfed142ab2a2b3b02e33c8c branch 'master' of git://git.kernel.org/pub/scm/git/git
a274e0a036ea886a31f8b216564ab1b4a3142f6c not-for-merge branch 'maint' of git://git.kernel.org/pub/scm/git/git
c69c2f50cfc0dcd4bcd014c7fd56e344a7c5522f not-for-merge branch 'next' of git://git.kernel.org/pub/scm/git/git
4e24a51e4d5c19f3fb16d09634811f5c26922c01 not-for-merge branch 'pu' of git://git.kernel.org/pub/scm/git/git
2135c1c06eeb728901f96ac403a8af10e6145065 not-for-merge branch 'todo' of git://git.kernel.org/pub/scm/git/git
(from an earlier fetch run without --tags
, and then):
$ git fetch --tags
[fetch messages]
$ cat .git/FETCH_HEAD
cat .git/FETCH_HEAD
d7dffce1cebde29a0c4b309a79e4345450bf352a branch 'master' of git://git.kernel.org/pub/scm/git/git
a274e0a036ea886a31f8b216564ab1b4a3142f6c not-for-merge branch 'maint' of git://git.kernel.org/pub/scm/git/git
8553c6e5137d7fde1cda49817bcc035d3ce35aeb not-for-merge branch 'next' of git://git.kernel.org/pub/scm/git/git
31148811db6039be66eb3d6cbd84af067e0f0e13 not-for-merge branch 'pu' of git://git.kernel.org/pub/scm/git/git
aa3afa0b4ab4f07e6b36f0712fd58229735afddc not-for-merge branch 'todo' of git://git.kernel.org/pub/scm/git/git
d5aef6e4d58cfe1549adef5b436f3ace984e8c86 not-for-merge tag 'gitgui-0.10.0' of git://git.kernel.org/pub/scm/git/git
[much more, snipped]
We will come back to this in just a moment.
The fetch may, depending on whatever additional refspecs it finds—this is usually controlled by the remote.origin.fetch
configuration entries—update some set of remote-tracking branches, and create or update some of your tags. If you are configured as a fetch mirror, with your update refspec being +refs/*:refs/*
, you get literally everything. Note that this refspec has the force flag set, and brings over all branches, all tags, all remote-tracking branches, and all notes. There are more obscure details about what refspecs get used when, but using --tags
, with or without --force
, does not override the configuration entries (whereas writing an explicit set of refspecs does, so this is one way—maybe the only way—--tags
differs from writing out refs/tags/*:refs/tags/*
).
Updates in your own reference space—your own remote-tracking branches and tags, usually—do matter, but ... not for pull
, as we'll see in the next section.
git pull
I like to say that git pull
just runs git fetch
followed by a second Git command, where the second command defaults to git merge
unless you instruct it to use git rebase
. This is true and correct, but there is an obscure detail in the way. This was easier to say before git fetch
was rewritten as C code: back when it was a script you could follow the script's git fetch
and git merge
commands and see what the actual arguments were.
When git pull
runs either git merge
or git rebase
, it does not use your remote-tracking branches and tags. Instead, it uses the records left behind in FETCH_HEAD
.
If you examine the examples above, you will see that they tell us that initially, refs/heads/master
in the repository on git.kernel.org
pointed to commit e05806d...
. After I ran git fetch --tags
, the new FETCH_HEAD
file tells us that refs/heads/master
in the repository on git.kernel.org
pointed (at the time I ran fetch
, it may have changed by now) to commit d7dffce...
.
When git pull
runs git merge
or git rebase
, it passes these raw SHA-1 numbers through. So it does not matter what your reference names resolve to. The git fetch
I ran did in fact update origin/master
:
$ git rev-parse origin/master
d7dffce1cebde29a0c4b309a79e4345450bf352a
but even if it had not, git pull
would pass d7dffce1cebde29a0c4b309a79e4345450bf352a
to the second command.
So, suppose you were fetching tags without --force
and got object 1234567...
. Suppose further that, had you been fetching tags with force, this would be the result of git rev-parse refs/tags/last-build
, but because you did not use --force
, your own repository left last-build
pointing to 8888888...
(a very lucky commit in China :-) ). If you, personally, say "tell me about last-build
" you will get revision 8888888...
. But git pull
knows that it got 1234567...
and no matter what else happens, it will just pass the number 1234567...
to its second command, if something calls for that.
Again, it gets that number out of FETCH_HEAD
. So what matter here are the (full) contents of FETCH_HEAD
, which are determined by whether you fetch with -a
/ --append
, or not. You only need/want --append
in special cases that won't apply here (when you are fetching from multiple separate repositories, or fetching in separate steps for debugging purposes, or some such).
Of course, it does matter later
If you want / need your last-build
tag to get updated, you will have to run git fetch --tags --force
at some point—and now we get into atomicity issues.
Suppose that you have run git fetch
, with or without --tags
and with or without --force
, perhaps by running git pull
which runs git fetch
without --tags
. You now have commit 1234567...
locally, and the name last-build
points to either 8888888...
(not updated) or 1234567...
(updated). Now you run git fetch --tags --force
to update everything. It's possible that now, the remote has moved last-build
yet again. If so, you'll get the new value, and update your local tag.
It's possible, with this sequence, that you never saw 8888888...
. You might have a branch that incorporates that commit, but not know that commit by that tag—and now that you are updating your tags, you won't know 8888888...
by that tag now, either. Is that good, bad, or indifferent? That's up to you.
Avoiding git pull
Since git pull
merely runs git fetch
followed by a second command, you can just run git fetch
yourself, followed by the second command. This gives you full control over the fetch
step, and lets you avoid a redundant fetch.
Since you do control the fetch
step, you can specify precisely, using refspecs, just what you want updated. Now it's time to visit the weird hybrid tag update mechanism as well.
Take any repository you have handy and run git ls-remote
. This will show you what it is that git fetch
sees when it connects:
$ git ls-remote | head
From git://git.kernel.org/pub/scm/git/git.git
3313b78c145ba9212272b5318c111cde12bfef4a HEAD
ad36dc8b4b165bf9eb3576b42a241164e312d48c refs/heads/maint
3313b78c145ba9212272b5318c111cde12bfef4a refs/heads/master
af746e49c281f2a2946222252a1effea7c9bcf8b refs/heads/next
6391604f1412fd6fe047444931335bf92c168008 refs/heads/pu
aa3afa0b4ab4f07e6b36f0712fd58229735afddc refs/heads/todo
d5aef6e4d58cfe1549adef5b436f3ace984e8c86 refs/tags/gitgui-0.10.0
3d654be48f65545c4d3e35f5d3bbed5489820930 refs/tags/gitgui-0.10.0^{}
33682a5e98adfd8ba4ce0e21363c443bd273eb77 refs/tags/gitgui-0.10.1
729ffa50f75a025935623bfc58d0932c65f7de2f refs/tags/gitgui-0.10.1^{}
Your Git gets, from the remote Git, a list of all references and their targets. For references that are (annotated) tags, this includes the tag object's final target as well: that's the gitgui-0.10.0^{}
here. This syntax represents a peeled tag (see gitrevisions, though it does not use the word "peeled" here).
Your Git then, by default, brings over every branch—everything named refs/heads/*
—by asking for the commits to which they point, and any additional commits and other objects needed to complete those commits. (You do not have to download objects you already have, only those you lack-but-need.) Your Git can then look through all the peeled tags to see if any of the tags points to one of those commits. If so, your Git takes—with or without --force
mode, depending on your fetch—the given tag. If that tag points to a tag object, rather than directly to a commit, your Git adds that tag object to the collection as well.
In Git versions before 1.8.2, Git mistakenly applies the branch rules to pushed tag updates: they are allowed without --force
as long as the result is a fast-forward. That is, the previous tag target would merely need to be an ancestor of the new tag target. This only affects lightweight tags, obviously, and in any case Git versions 1.8.2 and higher have "never replace a tag without --force
" behavior on push. Yet the observed behavior for Git 2.10.x and 2.11.x is that tags get replaced on fetch, when using --tags
.
But no matter what, if your goal is to forcibly update all tags and all remote-tracking branches in the usual way, git fetch --tags --force --prune
will do it; or you can git fetch --prune '+refs/tags/*:refs/tags/*' '+refs/heads/*:refs/remotes/origin/*'
, which uses the +
syntax to force both tag and remote-tracking branch updates. (The --prune
is optional as usual.) The force flag may be unnecessary, but is at least harmless here, and might do something useful in some Git versions. And now that your tags and remote-tracking branches are updated, you can use git merge
or git rebase
with no arguments at all, to merge or rebase using the current branch's configured upstream. You can repeat this for as many branches as you like, never needing to run git pull
(with its redundant fetch
) at all.
回答2:
Regarding the order : any order works (it commutes).
A note on the commands you run :
git fetch --tags
will already "force update" your local tags- the
--force
option only applies to refspecs which do not start with the+
option git pull --tags origin mybranch
will apply all you want in one go (get all tags, and update your local branch)
回答3:
I'm going to answer the following question (which you did not explicitly ask) :
How can I update automatically a fixed set of tags each time I call
git fetch
orgit pull
?
We have the exact same situation at my place, and this is how I delt with it .
By default, the refspec for a remote is :
[remote "origin"]
url = git@server:repo # or whatever
fetch = +refs/heads/*:refs/remotes/origin/*
This is why it only fetches branches from the remote - it only gets refs/heads/*
references from the remote.
This is the default configuration, but you can add whatever references you see fit.
You can use the refspec to tell git to also fetch refs/tags/last-build
from the remote, and to automatically update your local tag :
[remote "origin"]
url = git@server:repo # or whatever
fetch = +refs/heads/*:refs/remotes/origin/*
fetch = +refs/tags/last-build:refs/tags/last-build
# this line tells :
# - get the 'refs/tags/last-build' (first ref, before ':') from the remote
# - store it in my local tag (second ref after, ':')
# - allow forced updates (initial '+')
warning : this specific line will trash your local last-build
tag on each fetch, and git does not keep a reflog for tags. Given the meaning of such tags, I find this behavior ok.
If you feel uncomfortable with this, you can specify another local ref :
# you will see two tags 'last-build' and 'origin/last-build' in your repo :
fetch = +refs/tags/last-build:refs/tags/origin/last-build
Obviously, add one such line for each relevant tag ...
reference : refspec doc
来源:https://stackoverflow.com/questions/41708925/are-git-fetch-tags-force-and-git-pull-branch-conmutative-operations