是“git fetch --tags --force”和“git pull <branch>”交换操作吗?

时间:2017-01-17 23:33:27

标签: git git-pull git-fetch

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

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

git fetch --tags --force

并从一个分支拉出来:

git pull origin <mybranch>

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

如果分支由其中一个浮动标记标记...命令的执行顺序是否重要?

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

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

  

默认情况下,指向从中下载的对象的标记   远程存储库被提取并存储在本地。

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

哪个订单正确?

3 个答案:

答案 0 :(得分:9)

这进入了Git中一个比较模糊的角落,但最终答案是&#34;最初你使用哪个订单&#34;并不重要。但是,我建议一般不要使用git pull,也不要在脚本中使用它。另外,正如我们将在下面看到的那样,以不同的方式确切地 。因此,我建议您首先运行自己的git fetch,然后根本不使用git pull

git fetch

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

--tags添加到git fetch参数与在命令行上refs/tags/*:refs/tags/*指定几乎相同。 (我们稍后会看到差异。)请注意,这没有在refspec中设置强制标志,但测试表明所取出的标签都是强制更新的。

添加--force与在每个显式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的提取运行,然后是):

$ 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]

我们马上回过头来看看。

获取可能取决于它找到的任何其他refspec-这通常由remote.origin.fetch配置条目控制 - 更新一些远程跟踪分支,并创建或更新某些标记。如果您被配置为获取镜像,并且更新refspec为+refs/*:refs/*,那么您将获得所有内容。请注意,此refspec设置了强制标志,并带来所有分支,所有标记,所有远程跟踪分支和所有注释。关于什么时候使用refspec会有更多模糊的细节,但使用--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中留下的记录。

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

git merge运行git rebasegit fetch时,它会通过这些原始SHA-1号码。因此,您的参考无关紧要名称解析为。我运行的origin/master确实更新了$ git rev-parse origin/master d7dffce1cebde29a0c4b309a79e4345450bf352a

git pull

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

因此,假设您在没有1234567...的情况下获取标记并获得了对象git rev-parse refs/tags/last-build。进一步假设,如果您使用强制获取标记,这将是--force的结果,但是因为您使用last-build,你自己的存储库留下8888888...指向last-build(在中国是一个非常幸运的提交:-))。如果你个人说'#34;告诉我关于8888888...&#34;您将获得修订版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... 1}}。您现在已在本地提交last-build,名称8888888...指向1234567...(未更新)或git fetch --tags --force(已更新)。现在您运行last-build来更新所有内容。 现在可能,遥控器再次移动了8888888...。如果是这样,您将获得值,并更新您的本地代码。

按照这个顺序,您从未见过8888888...。您可能有一个包含该提交的分支,但不知道该标记的提交 - 现在您 更新了您的标记,您不会通过该标记知道git pull现在 。这是好事,坏事还是无动于衷?这取决于你。

避免git pull

由于git fetch仅运行git fetch后跟第二个命令,因此您可以自己运行fetch,然后运行第二个命令。这使您可以完全控制fetch步骤,并且可以避免冗余提取。

由于您执行控制git ls-remote步骤,您可以使用refspecs精确指定您想要更新的内容。现在是时候访问奇怪的混合标签更新机制了。

获取您方便的任何存储库并运行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^{} 在连接时看到的内容:

gitgui-0.10.0^{}

你的Git从远程Git获取所有引用及其目标的列表。对于作为(带注释的)标记的引用,这也包括标记对象的最终目标:这里是refs/heads/*。此语法表示去皮标记(请参阅gitrevisions,但它不使用单词&#34;去皮&#34;此处)。

默认情况下,您的Git会将每个分支 - 所有名为--force的内容 - 通过询问他们指向的提交,以及完成所需的任何其他提交和其他对象那些承诺。 (您不必下载已有的对象,只需要那些缺少但需要的对象。)然后,您的Git可以浏览所有剥离的标记,以查看是否有任何标记指向其中一个提交。如果是这样,您的Git接受或不接受--force模式,具体取决于您的获取 - 给定标记。如果该标记指向标记对象,而不是直接指向提交,那么Git也会将该标记对象添加到集合中。

在1.8.2之前的Git版本中,Git错误地将分支规则应用于推送标签更新:只要结果是快进,它们就不允许--force。也就是说,先前的标记目标仅需要是新标记目标的祖先。这显然只影响轻量级标签,无论如何,Git版本1.8.2及更高版本在没有--tags&#34;的情况下永远不会替换标签。 推送上的行为。然而,在使用git fetch --tags --force --prune时,Git 2.10.x和2.11.x的观察行为是标记在获取时被替换。

但无论如何,如果你的目标是以通常的方式强制更新所有标记所有远程跟踪分支,git fetch --prune '+refs/tags/*:refs/tags/*' '+refs/heads/*:refs/remotes/origin/*'将会这样做;或者您可以+使用--prune语法强制标记和远程跟踪分支更新。 (git merge像往常一样是可选的。)强制标志可能是不必要的,但在这里至少是无害的,并且可能在某些Git版本中做一些有用的事情。现在您的标签和远程跟踪分支已更新,您可以使用git rebasegit pull完全没有参数,使用当前分支的上游进行合并或变基。您可以根据需要为多个分支重复此操作,从不需要运行fetch(及其冗余{{1}})。

答案 1 :(得分:1)

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

关于您运行的命令的说明:

  • git fetch --tags已经&#34;强制更新&#34;您当地的标签
  • --force选项仅适用于不以+选项开头的refspec
  • git pull --tags origin mybranch将一次性应用您想要的所有内容(获取所有代码,并更新您当地的分支机构)

答案 2 :(得分:0)

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

  

每次致电git fetchgit pull时,如何自动更新固定的一组代码?

我的地方情况完全相同,这就是我对它的看法。

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

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

这就是它只从遥控器获取分支的原因 - 它只从遥控器获取refs/heads/*个引用。

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

您可以使用erefspec tell 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