这涉及到Git中比较晦涩的角落,但最终答案是“最初使用哪种顺序并不重要”。然而,我建议一般情况下避免使用
git pull
,并且在脚本中永远不要使用它。此外,确切地说,
何时获取数据是很重要的,我们将在下面看到。因此,我建议先运行自己的
git fetch
,然后根本不使用
git pull
。
git fetch
普通的git fetch
(没有--tags
)默认使用奇怪的混合标签更新,尽管每个远程仓库可以定义一个默认的标签选项来覆盖此默认值。你引用的就是这个奇怪的混合标签:指向从远程仓库下载的对象的标签被获取并存储在本地。其基础机制有点棘手,我会在后面讲到。
将
--tags
添加到
git fetch
参数中,几乎与在命令行上指定
refs/tags/*:refs/tags/*
具有相同的效果。(稍后我们将看到区别。)请注意,这不在refspec中设置force标志,然而测试显示获取的标签仍会被强制更新。
添加
--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]
我们稍后会回到这个问题。
取回操作可能会根据找到的其他引用规范(通常由remote.origin.fetch配置条目控制)更新一些远程跟踪分支,并创建或更新一些标签。如果您配置为提取镜像,并且其更新引用规范为+refs/*:refs/*,则会获取所有内容。请注意,此引用规范设置了force标志,并带有所有分支、所有标签、所有远程跟踪分支和所有注释。关于使用哪些引用规范的更加晦涩的细节,但使用--tags选项,无论是否使用--force选项,都不会覆盖配置条目(而明确编写一组引用规范会覆盖配置条目,因此这是--tags与编写refs/tags/*:refs/tags/*之间可能唯一的区别)。
在你自己的引用空间中进行的更新——通常是你自己的远程跟踪分支和标签——很重要,但...对于pull命令来说却不是,我们将在下一节中看到。
git pull
我想说,
git pull
实际上只是运行了两个 Git 命令:首先是
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
文件告诉我们,在我运行
fetch
时(现在可能已经改变),在
git.kernel.org
上的存储库中,
refs/heads/master
指向提交
d7dffce...
。
当
git pull
运行
git merge
或
git rebase
时,它会将这些原始SHA-1数字传递过去。因此,您的引用名称解析为什么并不重要。我运行的
git fetch
实际上确实更新了
origin/master
:
$ git rev-parse origin/master
d7dffce1cebde29a0c4b309a79e4345450bf352a
即使没有这个问题,git pull
命令也会将 d7dffce1cebde29a0c4b309a79e4345450bf352a
传递给第二个命令。
因此,假设您在没有使用 --force
的情况下获取标签,并获得了对象 1234567...
。进一步假设,如果您使用了 --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
(不带
--tags
参数)来运行的。现在,您在本地拥有提交
1234567...
,名称
last-build
指向
8888888...
(未更新)或
1234567...
(已更新)之一。然后,您运行
git fetch --tags --force
以更新所有内容。现在,远程可能已经再次移动了
last-build
。如果是这样,您将获得新值,并更新本地标签。
使用此序列,您可能从未看到过
8888888...
。您可能有一个包含该提交的分支,但不知道该提交的标记 - 现在您正在更新标记,因此您也无法用该标记知道
8888888...
。这是好事、坏事还是无关紧要?这取决于您。
避免使用git pull
git pull
仅运行git fetch
后跟第二个命令,你可以自己运行git fetch
,然后再运行第二个命令。这样可以完全控制fetch
步骤,并避免重复的获取。
由于您可以控制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^{}
。此语法表示一个已剥离的标签(参见
gitrevisions
,尽管此处没有使用“peeled”一词)。
然后,默认情况下,您的Git通过请求它们指向的提交以及完成这些提交所需的任何其他提交和对象来获取每个分支(即所有命名为
refs/heads/*
的内容)。 (您无需下载已经拥有的对象,只需要缺少但需要的对象。)然后,您的Git可以查看所有已剥离的标签,以查看这些标签是否指向其中之一提交。如果是这样,您的Git将使用给定的标签(取决于您的获取方式是否使用
--force
模式)。如果该标签指向标记对象而不是直接指向提交,则您的Git也会将该标记对象添加到集合中。
在Git 1.8.2之前的版本中,Git错误地将分支规则应用于已推送的标签更新:只要结果是快进,则允许它们不使用
--force
。也就是说,先前的标签目标只需要是新标签目标的祖先。显然,这仅影响轻量级标签,在任何情况下,Git 1.8.2及更高版本在
push上有“永远不要替换标签而不使用
--force
”的行为。然而,观察到Git 2.10.x和2.11.x的行为是,在使用
--tags
时,在获取(fetch)时会替换标签。
但是无论如何,如果你的目标是强制更新所有标签和所有远程跟踪分支,
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
)。