【问题标题】:Are "git fetch --tags --force" and "git pull <branch>" conmutative operations?“git fetch --tags --force”和“git pull <branch>”是交换操作吗?
【发布时间】:2017-06-02 05:05:25
【问题描述】:

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

我有一个脚本可以从参考存储库中刷新那些“浮动”标签。

git fetch --tags --force

并且还从一个分支中提取:

git pull origin <mybranch>

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

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

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

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

默认情况下,指向从 远程存储库被提取并存储在本地。

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

正确的顺序是什么?

【问题讨论】:

  • 认为我知道您所说的“浮动标签”是什么意思,但在这里定义它似乎很好。
  • 完成。感谢您的快速回复。

标签: git git-pull git-fetch


【解决方案1】:

这涉及到 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 fetchgit merge 命令查看实际参数是什么。

git pull 运行git mergegit 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 mergegit 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 mergegit rebase 完全不带任何参数,使用当前分支的上游配置进行合并或变基。您可以对任意数量的分支重复此操作,根本不需要运行 git pull(带有多余的 fetch)。

【讨论】:

  • AFAIK(并使用 git 2.11.0 测试),git fetch --tags 将始终更新本地标签,即使没有 --force
  • @LeGEC:很有趣。测试在 2.10.1 上显示了相同的行为。然而,--tags 添加的内置 refspec 没有设置强制位(它是预先解析的;参见 remote.c 中的 s_tag_refspec)。在重新阅读文档时,我看到了另一个怪癖:一个明确的 refspec 主题标签到 --prune--tags 据说没有。 (从源代码中根本不清楚这是如何工作的。)
  • 我没有在代码中跟踪fetch --tags的整个执行路径,但是你可以查看所有检查tags选项(在fetch.c中)的值的地方,或关于以refs/tags/ 开头的引用的硬编码规则。标签和分支的处理方式不同。
  • 事实上,我确实查看了所有代码,但仍然看不到标签有效地强制更新的位置。但是,代码在某些地方变得很奇怪,例如,本地和远程 refs 看起来像是在某一点交换了。 pushfetch 有单独的硬编码规则,允许在推送期间快进分支,但也不允许在推送期间移动标签。里面蛮狂野的:-)
【解决方案2】:

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


关于您运行的命令的注释:

  • git fetch --tags 已经“强制更新”你的本地标签
  • --force 选项仅适用于不以 + 选项开头的参考规范
  • git pull --tags origin mybranch 将一次性应用所有你想要的(获取所有标签,并更新你的本地分支)

【讨论】:

    【解决方案3】:

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

    如何在每次调用git fetchgit 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 '+')
    

    警告 :此特定行将在每次提取时丢弃您的本地 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 解决了未询问的问题,无需额外配置。问题是这样的提取应该在拉取之后还是之前完成。据我了解,通过您的配置,不需要额外的提取,对吗?因为这也会影响拉力。无论如何,问题仍然是使用 fetch .. --force... fetch 应该先还是后?
    • (几年后回答你的评论......)git fetch --tags 的问题是它将下载所有远程标签,并强制更新 all 你的本地标签这些价值观。也许这不是你想要的。此答案描述了一种仅强制更新您选择的标签的方法。
    猜你喜欢
    • 2023-03-04
    • 1970-01-01
    • 2018-10-07
    • 2020-01-31
    • 2018-10-18
    • 2018-01-01
    • 2017-02-23
    • 2018-03-05
    相关资源
    最近更新 更多