标签(Tag)的工作原理
在Git中,每个标签都指向一个(单一的)提交。实际上,分支也是如此:一个分支名也只是指向一个提交。
使这种工作方式成为可能的有两个因素:
- 每个提交也指向另一个提交(或者可能是几个),以及
- 对于分支(仅限于分支),它所指向的提交会自动“向前移动”。也就是说,当你添加新的提交时——从某种意义上来说,这就是Git主要做的事情:将新的提交添加到它的集合中,有点像《星际迷航》TNG系列中的博格(Borg)一样——无论你在哪个分支上,该分支都会被重新调整为指向新的提交。
因此,分支和标签之间的主要区别在于标签不会移动。
为了看到这是如何工作的,请考虑一个只有三个提交的简单Git存储库。让我们把这些提交标记为A
、B
和C
。第一个提交(A
)没有指向任何东西,因为它是第一个提交,分支master
指向A
:
A <
当你进行第二次提交时,Git 会创建一个指向 A
的 B
,并将分支名称推进到指向 B
:
A <- B <
当你提交第三次时,Git会将其指向其父提交,并推进该分支:
A <- B <- C <
如果您现在创建一个标签,该标签默认会指向提交C
:
A <- B <- C <-- master
^
|
tag: sometag
如果您随后创建了一个新的提交D
,Git 会推进分支,但不会推进标签:
A <- B <- C <- D <-- master
^
|
tag: sometag
您可以随时创建或删除指向特定提交的任何标签:
$ git tag -d sometag
将删除标签sometag
,之后:
$ git tag sometag master~2
将添加一个指向提交B
的sometag
标签。1
(我们刚刚证明了标签可以移动。真正的区别在于,不期望标签移动,而期望分支移动;Git 不会自动移动标签。2 分支通常期望以“前进”的方式移动,即如果master
曾经指向提交C
,现在指向提交D
,则通常应该通过从D
开始向后工作来找到提交C
。 每当您移动分支以违反此规则时,您都在“重写历史记录”;关于什么情况下这是可以接受的,什么情况会给人们带来麻烦,请参见其他文章。)
推送标签
使用git push
时,实际上是命令其他 git 存储库获取你所拥有的任何新提交,并设置一些名称 - 通常是分支和/或标签 - 以指向结果集合中的某些提交(每个提交一个)。3 这些名称(分支、标签等)通常被称为“引用”,但现在我们只使用“分支”和“标签”。
git push
后的参数指定要推送到的存储库(通常通过“remote”名称,如origin
)。如果您省略它,Git 将尝试找到一个存储库,但是如果要添加分支或标签名称,则需要明确包含它,因为这里的第一个单词被认为是远程名称。 (也就是说,git push master
尝试使用 master
作为远程名称而不是分支名称。)
要推送所有标签,只需将--tags
添加到git push
命令中:
git push --tags origin
要推送一个特定的标签,你可以给它命名:
git push origin sometag
就像你可以推送一个特定的分支一样:
git push origin master
事实上,第四个参数是一对名称,例如
master:master
或
sometag:sometag
,但在大多数情况下默认情况下两边使用相同的名称。
4
如果您不需要使用名称
origin
来使所有参数生效,例如
git push --tags
与
git push --tags origin
相同(假设您所有的推送都是到
origin
),则可以省略它。
将标签设置到远程仓库中,首先要本地设置标签:
git tag name commit-identifier
。使用任何您喜欢的查看器确保已正确设置。然后,使用
git push origin name
或
git push --tags
将其推送上去。
1master~2
语法指示git从通过
master
找到的提交开始,然后向后退两步。您也可以在此处编写提交
B
的原始SHA-1。
2旧版的git(版本号小于1.8.4)在推送时错误地将分支规则应用于标签(在远程端,即如果标签是“快速向前”的话就会移动)。
3在某些情况下,您可以将名称指向“带注释的标签”,但是没有什么阻止名称指向“树”或“blob”对象,但这不是常规设置。
4实际上,分支的默认
dst refspec很复杂:它取决于您的
push.default
配置,是否有
remote.repository.push
设置和是否已配置上游等等。对于标签,规则较简单,因为没有“上游”的概念。