"git fetch --tags --force" 和 "git pull <branch>" 是交换运算吗?

Are "git fetch --tags --force" and "git pull <branch>" conmutative operations?

通常 git 标签是对提交的固定引用。但有时它们用于标记某些事件(last-buildbase-line 等)并且它们经常更改。

我有一个脚本可以从参考存储库中刷新那些类型的 "floating" 标签。

git fetch --tags --force

并从一个分支拉取:

git pull origin <mybranch>

我知道许多 git 用户警告使用浮动标签,但我不得不处理这个问题。我的问题是:

如果分支由那些浮动标签之一标记...命令的执行顺序重要吗?

恐怕 git pull 不会刷新本地存在的标签,如果它先运行,它可能会与所有标签的引用一起工作。

git pull 有一个 --force 选项,但选项 --no-tags 的帮助部分将默认行为解释为:

By default, tags that point at objects that are downloaded from the remote repository are fetched and stored locally.

这是否意味着应该先下载对象才能刷新标签?在那种情况下 git pull 应该先走。

哪个顺序正确?

这进入了 Git 中比较隐蔽的角落之一,但最终答案是 "it doesn't matter initially which order you use"。但是,我建议一般避免使用 git pull,并且永远不要在脚本中使用它。另外,它确实很重要,以不同的方式,正是 何时 你获取,我们将在下面看到。所以我建议先 运行 使用你自己的 git fetch,然后根本不使用 git pull

git fetch

一个普通的git fetch(没有--tags)默认使用一个奇怪的混合标签更新,尽管每个遥控器都可以定义一个默认标签选项来覆盖这个默认值。奇怪的混合体是你引用的:指向从远程存储库下载的对象的标签被获取并存储在本地。这个的底层机制有点棘手,我会离开以后再说。

--tags 添加到 git fetch 参数几乎与在命令行中指定 refs/tags/*:refs/tags/* 具有相同的效果。 (我们稍后会看到不同之处。)请注意,这没有在 refspec 中设置强制标志,但测试表明获取的标签无论如何都是 force-updated。

添加 --force 与在每个显式 refspec 中设置强制标志具有相同的效果。换句话说,git fetch --tags --force 大致等同于 运行ning 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,然后):

$ 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 配置条目控制——更新一些 remote-tracking b运行ches,并创建或更新您的一些标签。如果您被配置为获取镜像,并且您的更新 refspec 为 +refs/*:refs/*,您几乎可以得到所有内容。请注意,此 refspec 设置了强制标志,并带来了所有 b运行ches、所有标签、所有 remote-tracking b运行ches 和所有注释。关于什么时候使用什么 refspecs 有更多模糊的细节,但是使用 --tags,有或没有 --force,都不会覆盖配置条目(而写一组显式的 refspecs 会覆盖,所以这是一种方法—也许是唯一的方法—--tags 不同于写出 refs/tags/*:refs/tags/*).

您自己的参考资料中的更新 space—您自己的 remote-trackingb运行 目录和标签,通常—确实很重要,但是。 .. 不适用于 pull,我们将在下一节中看到。

git pull

我想说 git pull 只是 运行s git fetch 后跟第二个 Git 命令,第二个命令默认为 git merge 除非你指示它使用 git rebase。这是真实和正确的,但其中有一个模糊的细节。在 git fetch 被重写为 C 代码之前,这更容易说:当它是一个脚本时,您可以按照脚本的 git fetchgit merge 命令查看实际参数是什么。

git pull 运行 是 git mergegit rebase 时,它 不使用 你的 remote-tracking b运行切和标签。相反,它使用 FETCH_HEAD.

中留下的记录

如果您检查上面的示例,您会发现它们告诉我们,最初 git.kernel.org 上的存储库中的 refs/heads/master 指向提交 e05806d...。在我 运行 git fetch --tags 之后,新的 FETCH_HEAD 文件告诉我们 git.kernel.org 上的存储库中的 refs/heads/master 指向(当时我 运行 fetch,现在可能已经改变)提交 d7dffce....

git pull运行s git mergegit rebase时,它通过这些原始SHA-1数字。所以您的引用 names 解析为什么并不重要。 git fetch 我 运行 实际上更新了 origin/master:

$ git rev-parse origin/master
d7dffce1cebde29a0c4b309a79e4345450bf352a

但即使没有,git pull 也会将 d7dffce1cebde29a0c4b309a79e4345450bf352a 传递给第二个命令。

因此,假设您在没有 --force 的情况下获取标签并获得了对象 1234567...。进一步假设,如果您使用 强制获取标签,这将是 git rev-parse refs/tags/last-build 的结果,但是因为您没有 而不是 使用--force,您自己的存储库 last-build 指向 8888888...(在中国非常幸运的提交 :-))。如果您个人说 "tell me about last-build",您将获得修订版 8888888...。但是 git pull 知道它得到了 1234567... 并且无论发生什么,它都会将数字 1234567... 传递给它的第二个命令,如果有需要的话。

再次,它从 FETCH_HEAD 中获取该数字。所以这里重要的是 FETCH_HEAD 的(完整)内容,这取决于您是否使用 -a / --append 获取。您仅在此处不适用的特殊情况下 need/want --append (当您从多个单独的存储库中获取,或在单独的步骤中获取以进行调试时,等等)

当然,以后有关系

如果你想要/需要更新你的 last-build 标签,你将不得不 运行 git fetch --tags --force 在某些时候——现在我们遇到了原子性问题。

假设您有 运行 git fetch,有或没有 --tags,有或没有 --force,也许 运行ning git pull 其中 运行s git fetch 没有 --tags。您现在已经在本地提交 1234567...,名称 last-build 指向 8888888...(未更新)或 1234567...(已更新)。现在你 运行 git fetch --tags --force 来更新一切。 现在,遥控器可能又移动了last-build。如果是这样,您将获得 new 值,并更新您的本地标签。

对于这个序列,您可能从未见过 8888888...。你可能有一个包含那个提交的 b运行ch,但不知道那个标签的那个提交——现在你正在更新你的标签,你不会知道8888888... 通过那个标签 现在 ,要么。那是好是坏还是无动于衷?由你决定。

避免git pull

由于 git pull 仅 运行s git fetch 后跟第二个命令,您可以自己 运行 git fetch 后跟第二个命令。这使您可以完全控制 fetch 步骤,并避免重复提取。

由于您 do 控制 fetch 步骤,您可以使用 refspecs 精确指定您想要更新的内容。现在也该访问奇怪的混合标签更新机制了。

使用手边的任何存储库 运行 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 然后,默认情况下,通过请求提交,带来每个 b运行ch - 所有名为 refs/heads/* 的东西它们指向的内容,以及完成这些提交所需的任何其他提交和其他对象。 (您不必下载您已经拥有的对象,只需下载那些您 lack-but-need。)然后您的 Git 可以查看所有剥离的标签,看看是否有任何标签指向这些提交之一。如果是这样,您的 Git 将采用给定的标记(有或没有 --force 模式,具体取决于您的提取)。如果该标记指向标记对象,而不是直接指向提交,您的 Git 也会将该标记对象添加到集合中。

在 1.8.2 之前的 Git 版本中,Git 错误地将 b运行ch 规则应用于 pushed 标签更新:它们是只要结果是 fast-forward,就可以不使用 --force。也就是说,先前的标签目标只需要是新标签目标的祖先。显然,这只影响轻量级标签,并且在任何情况下 Git 版本 1.8.2 和更高版本在 push 上具有 "never replace a tag without --force" 行为。然而,观察到的 Git 2.10.x 和 2.11.x 的行为是,当使用 --tags.

时,标签在获取时被替换

但不管怎样,如果你的目标是以通常的方式强制更新所有标签所有remote-tracking b运行ches,git fetch --tags --force --prune 会做的;或者您可以 git fetch --prune '+refs/tags/*:refs/tags/*' '+refs/heads/*:refs/remotes/origin/*',它使用 + 语法来强制更新标签和 remote-tracking b运行ch。 (--prune 像往常一样是可选的。)强制标志 可能 是不必要的,但至少在这里是无害的,并且可能在某些 Git 版本中做一些有用的事情.现在您的标签和 remote-tracking b运行ches 已更新,您可以使用 git mergegit rebase 完全不带参数,使用当前的 b[ 进行合并或变基=258=]ch 的配置上游。您可以根据需要对任意多的 b运行 重复此操作,根本不需要 运行 git pull(及其多余的 fetch)。

我将回答以下问题(您没有明确提出):

How can I update automatically a fixed set of tags each time I call git fetch or git pull ?

我们家的情况完全一样,我就是这样处理的。

默认情况下,遥控器的 refspec 是:

[remote "origin"]
    url = git@server:repo # or whatever
    fetch = +refs/heads/*:refs/remotes/origin/*

这就是为什么它只从远程获取 分支 - 它只从远程获取 refs/heads/* 引用。

这是默认配置,但您可以添加您认为合适的任何引用。


您可以使用 refspec 告诉 git 也从远程获取 refs/tags/last-build,并自动更新您的本地标签:

[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 :此特定行将在每次提取时丢弃您的本地 last-build 标记,并且 git 不会保留标记的 reflog。考虑到这些标签的含义,我觉得这个行为没问题。

如果您对此感到不舒服,可以指定另一个本地参考:

 # you will see two tags 'last-build' and 'origin/last-build' in your repo :
 fetch = +refs/tags/last-build:refs/tags/origin/last-build

显然,为每个相关标签添加一行...


参考:refspec doc

关于订单:任何订单都有效(通勤)。


关于命令的注释 运行 :

  • git fetch --tags 已经 "force update" 您的本地标签
  • --force 选项仅适用于不以 + 选项开头的 refspecs
  • git pull --tags origin mybranch 将一次性应用所有你想要的(获取所有标签,并更新你的本地分支)