这涉及到 Git 的一个比较晦涩的角落,但最终的答案是“最初使用哪个顺序并不重要”。但是,我建议一般避免使用git pull,并且永远不要在脚本中使用它。另外,以不同的方式,准确地何时你获取它确实很重要,正如我们将在下面看到的那样。所以我建议先运行你自己的git fetch,然后根本不使用git pull。
git fetch
一个普通的git fetch(没有--tags)默认使用一个奇怪的混合标签更新,尽管每个遥控器都可以定义一个默认标签选项来覆盖这个默认值。奇怪的混合就是你引用的:指向从远程存储库下载的对象的标签被提取并存储在本地。这个的底层机制有点棘手,我将把它留到后面.
将--tags 添加到git fetch 参数与在命令行中指定refs/tags/*:refs/tags/* 的效果几乎相同。 (我们稍后会看到不同之处。)请注意,这并没有在 refspec 中设置强制标志,但测试表明提取的标签无论如何都是强制更新的。
添加--force 与在每个显式引用规范中设置强制标志具有相同的效果。换句话说,git fetch --tags --force 大致相当于运行git fetch '+refs/tags/*:refs/tags/*':如果远程有标签refs/tags/foo 指向提交1234567...,你的Git 将替换任何现有的refs/tags/foo,这样你现在就有了自己的refs/tags/foo还指向提交1234567...。 (但正如在实践中观察到的那样,即使只使用 --tags 也会这样做。)
请注意,在所有种情况下,git fetch 将有关它获取的内容的信息写入文件FETCH_HEAD。例如:
$ 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
(来自之前没有--tags 的 fetch 运行,然后):
$ 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]
我们稍后再讨论。
根据它找到的任何其他 refspecs(这通常由 remote.origin.fetch 配置条目控制),获取可能会更新一些远程跟踪分支集,并创建或更新一些标签。如果您被配置为获取镜像,并且您的更新 refspec 为 +refs/*:refs/*,那么您将获得所有内容。请注意,此 refspec 设置了 force 标志,并带来了所有分支、所有标签、所有远程跟踪分支和所有注释。关于什么时候使用 refspecs 有更多模糊的细节,但是使用 --tags,有或没有 --force,不会覆盖配置条目(而编写一组显式的 refspecs,所以这是一种方式——也许是唯一的方式方式——--tags 不同于写出refs/tags/*:refs/tags/*)。
在您自己的参考空间中进行更新——通常是您自己的远程跟踪分支和标签——确实很重要,但 ... 不是pull,我们将在下一节中看到.
git pull
我想说git pull 只是运行git fetch 后跟第二个Git 命令,其中第二个命令默认为git merge,除非你指示它使用git rebase。这是真实和正确的,但其中有一个晦涩的细节。在 git fetch 被重写为 C 代码之前,这更容易说:当它是一个脚本时,你可以按照脚本的 git fetch 和 git merge 命令查看实际参数是什么。
当git pull 运行git merge 或git rebase 时,它不使用您的远程跟踪分支和标签。相反,它使用FETCH_HEAD 中留下的记录。
如果您检查上面的示例,您会看到它们告诉我们最初,git.kernel.org 上的存储库中的refs/heads/master 指向提交e05806d...。在我运行git fetch --tags 之后,新的FETCH_HEAD 文件告诉我们git.kernel.org 上的存储库中的refs/heads/master 指向(在我运行fetch 时,它现在可能已经改变)提交d7dffce...。
当 git pull 运行 git merge 或 git rebase 时,它会传递这些原始 SHA-1 数字。 因此,您的参考名称解析什么并不重要到。我运行的git fetch 确实更新了origin/master:
$ git rev-parse origin/master
d7dffce1cebde29a0c4b309a79e4345450bf352a
但即使没有,git pull 也会将d7dffce1cebde29a0c4b309a79e4345450bf352a 传递给第二个命令。
因此,假设您在没有--force 的情况下获取标签并获得了对象1234567...。进一步假设,如果你一直在 with force 获取标签,这将是 git rev-parse refs/tags/last-build 的结果,但是因为你没有 使用 --force,你自己的存储库离开了last-build 指向 8888888...(在中国非常幸运的提交 :-))。如果您个人说“告诉我有关last-build 的信息”,您将获得修订版8888888...。但是git pull 知道它得到了1234567...,并且无论发生什么其他情况,它都会将数字1234567... 传递给它的第二个命令,如果有需要的话。
同样,它会从FETCH_HEAD 中获取该数字。所以这里重要的是FETCH_HEAD的(完整)内容,这取决于您是否使用-a/--append进行获取。您只需要/想要--append 在此处不适用的特殊情况下(当您从多个单独的存储库中获取,或出于调试目的在单独的步骤中获取时,等等)。
当然,以后有关系
如果您想要/需要您的 last-build 标签进行更新,您将不得不在某个时候运行 git fetch --tags --force — 现在我们进入原子性问题。
假设您运行了git fetch,有或没有--tags,有或没有--force,也许通过运行git pull 运行git fetch 而没有--tags。您现在在本地提交了1234567...,名称last-build 指向8888888...(未更新)或1234567...(已更新)。现在您运行git fetch --tags --force 来更新所有内容。 现在,遥控器可能再次移动了last-build。如果是这样,您将获得 new 值,并更新您的本地标记。
您可能从未见过8888888...。您可能有一个包含该提交的分支,但不知道该提交的该标签 - 现在您正在更新您的标签,您现在不会通过该标签知道8888888... ,要么。这是好事,坏事,还是无动于衷?这取决于你。
避免git pull
由于git pull 仅运行git fetch 后跟第二个命令,您可以只运行git fetch 自己,后跟第二个命令。这使您可以完全控制 fetch 步骤,并避免重复提取。
由于您确实控制了fetch 步骤,因此您可以使用参考规范精确地指定您想要更新的内容。现在也该看看奇怪的混合标签更新机制了。
使用您手边的任何存储库并运行git ls-remote。这将向您展示git fetch 在连接时看到的内容:
$ 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^{}
您的 Git 从远程 Git 获取所有引用及其目标的列表。对于(带注释的)标签的引用,这也包括标签对象的最终目标:这里是gitgui-0.10.0^{}。此语法表示 peeled 标记(请参阅gitrevisions,尽管此处未使用“peeled”一词)。
然后,默认情况下,您的 Git 会通过询问它们指向的提交以及完成这些提交所需的任何其他提交和其他对象来引入每个 分支(所有名为 refs/heads/* 的东西) . (您不必下载已经拥有的对象,只需下载您缺少但需要的对象。)然后您的 Git 可以查看所有已剥离的标签,以查看是否有任何标签指向其中一个提交。如果是这样,你的 Git 会采用给定的标签——有或没有 --force 模式,具体取决于你的 fetch。如果该标签指向一个标签对象,而不是直接指向一个提交,您的 Git 也会将该标签对象添加到集合中。
在 1.8.2 之前的 Git 版本中,Git 错误地将分支规则应用于 pushed 标签更新:只要结果是快进,它们就可以在没有--force 的情况下进行。也就是说,先前的标签目标只需是新标签目标的祖先。显然,这只影响轻量级标签,并且在任何情况下,Git 1.8.2 及更高版本在 push 上都有“永远不要替换没有--force 的标签”行为。然而,在 Git 2.10.x 和 2.11.x 中观察到的行为是,当使用 --tags 时,标签会在获取时被替换。
但无论如何,如果您的目标是按照通常的方式强制更新所有标签和所有远程跟踪分支,git fetch --tags --force --prune 会这样做;或者你可以git fetch --prune '+refs/tags/*:refs/tags/*' '+refs/heads/*:refs/remotes/origin/*',它使用+ 语法来强制标签和远程跟踪分支更新。 (--prune 像往常一样是可选的。)强制标志 可能 是不必要的,但在这里至少是无害的,并且可能在某些 Git 版本中做一些有用的事情。现在您的标签和远程跟踪分支已更新,您可以使用 git merge 或 git rebase 完全不带任何参数,使用当前分支的上游配置进行合并或变基。您可以对任意数量的分支重复此操作,根本不需要运行 git pull(带有多余的 fetch)。