>通常,git 标签是对提交的固定引用。但有时它们被用来标记一些事件(last-build
、base-line
等),并且它们经常变化。
我有一个脚本可以从参考存储库刷新这些"浮动"标签。
git fetch --tags --force
并且还从一个分支进行拉取:
git pull origin <mybranch>
我知道许多 git 用户警告使用浮动标签,但我被迫处理这个问题。我的问题是:
如果分支由其中一个浮动标签标记...命令的执行顺序是否重要?
恐怕git pull
在本地存在标签时不会刷新标签,如果它首先运行,它可能会与所有标签的引用一起使用。
git pull
有一个--force
选项,但该选项的帮助部分--no-tags
将默认行为解释为:
默认情况下,指向从 远程存储库在本地获取和存储。
这是否意味着应该先下载对象以便能够刷新标签?在这种情况下,git pull
应该先走。
哪个顺序正确?
这进入了 Git 的一个更晦涩的角落,但最终答案是"最初使用哪种顺序并不重要"。 但是,我建议一般避免使用git pull
,并且无论如何都不要在脚本中使用它。 另外,它确实很重要,以不同的方式,正是当您获取时,我们将在下面看到。 所以我建议先运行你自己的git fetch
,然后根本不使用git pull
。
git fetch
普通git fetch
(不带--tags
)默认使用奇怪的混合标记更新,尽管每个远程都可以定义一个覆盖此默认值的默认标记选项。 奇怪的混合是你引用的:指向从远程存储库下载的对象的标签被获取并存储在本地。这样做的基本机制有点棘手,我将留待以后再讨论。
向git fetch
参数添加--tags
与在命令行上指定refs/tags/*:refs/tags/*
具有几乎相同的效果。 (我们稍后会看到差异。 请注意,这没有在 refspec 中设置强制标志,但测试表明获取的标签无论如何都是强制更新的。
添加--force
与在每个显式引用规范中设置强制标志具有相同的效果。 换句话说,git fetch --tags --force
大致相当于运行git fetch '+refs/tags/*:refs/tags/*'
:如果远程有指向提交1234567...
的标签refs/tags/foo
,你的 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
配置条目控制),获取可能会更新一组远程跟踪分支,并创建或更新一些标签。 如果您被配置为获取镜像,并且您的更新 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
中留下的记录。
如果您检查上面的例子,您会发现它们告诉我们,最初,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 rebase
时,它会传递这些原始的SHA-1数字。因此,引用名称解析为什么并不重要。 我运行的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...
(在中国非常幸运的提交:-))。 如果您个人说"告诉我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
移动。 如果是这样,您将获得新值,并更新本地标记。
通过这个序列,您可能从未见过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
,尽管此处没有使用"剥离"一词)。
然后,默认情况下,您的 Git 会通过请求每个分支指向的提交以及完成这些提交所需的任何其他提交和其他对象来引入每个分支(refs/heads/*
命名的所有内容)。 (您不必下载已有的对象,只需下载那些缺少但需要的对象。 然后,您的 Git 可以查看所有剥离的标签,以查看是否有任何标签指向其中一个提交。 如果是这样,您的 Git 会采用给定的标签(无论是否使用--force
模式,具体取决于您的获取。 如果该标记指向标记对象,而不是直接指向提交,则 Git 也会将该标记对象添加到集合中。
在 1.8.2 之前的 Git 版本中,Git 错误地将分支规则应用于推送标签更新:只要结果是快进,就可以不--force
地允许它们。 也就是说,以前的标记目标只需要是新标记目标的祖先。 显然,这只会影响轻量级标签,并且在任何情况下,Git 版本 1.8.2 及更高版本在推送时都有"从不替换没有--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
像往常一样是可选的。 force 标志可能是不必要的,但至少在这里是无害的,并且在某些 Git 版本中可能会做一些有用的事情。 现在您的标签和远程跟踪分支已更新,您可以使用完全没有参数的git merge
或git rebase
,使用当前分支配置的上游进行合并或变基。 您可以对任意数量的分支重复此操作,根本不需要运行git pull
(及其冗余fetch
)。
关于订单:任何订单都有效(它通勤)。
关于您运行的命令的说明:
git fetch --tags
已经"强制更新"您的本地标签--force
选项仅适用于不以+
选项开头的 refspecgit pull --tags origin mybranch
将一次性应用您想要的所有内容(获取所有标签,并更新您的本地分支机构)
我将回答以下问题(您没有明确提出):
如何在每次调用
git fetch
或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 '+')
警告:此特定行将在每次获取时丢弃您的本地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
显然,为每个相关标签添加一个这样的行......
参考 : 参考规范文档