虽然Marcelo Ávila de Oliveira's answer已经正确,但我想添加另一个答案,因为我想画出图形部分。 :-)
通常我喜欢绘制这样的提交图,至少对于StackOverflow是这样:
...--A--B--C <-- foobranch
\
D--E <-- barbranch
这里有两个分支上的两个提示(最右边)提交,C和E,每个都有一个指向它们的分支名称。也就是说,refs/heads/foobranch包含提交C的ID,而refs/heads/barbranch包含提交E的ID。
轻量级标签
轻量级标签的工作方式与分支名称完全相同。如果我们添加标签bartag以指向提交E,则会得到:
...--A--B--C <-- foobranch
\
D--E <-- barbranch, tag: bartag
refs/heads/bartag
(除非已经“打包”并存储在文件.git/packed-refs
中,否则它是.git
中的实际文件)还存储提交E
的ID。轻量级标签和分支之间有三个区别:
- 轻量级标签的全名以
refs/tags/
开头,而不是refs/heads/
。
- 轻量级标签不应更改以指向另一个提交(Git只半强制执行此规则,在1.8之前的版本中执行效果较差,但分支名称通常会更改以指向不同的提交,而标签则不会)。
- Git强制执行分支名称仅能指向提交对象的规则。通常标签名称也只指向提交对象,但可能会指向树或blob。还有一种对象类型——注释标签对象——但这使得标签不同!保留这个想法,让我们完成这个部分。
一个轻量级标签就是一个引用,其全名为
refs/tags/...
。这个外部引用存在于某个地方,通常是像
.git/refs/tags/bartag
这样的单独文件,并且它指向存储在仓库中的Git对象(
.git/objects/...
,可能被打包成一个包文件)。当它指向一个提交时,这通常是正常情况,它会将我们带入提交DAG:标签定位到提交,这可以让我们获得工作树,并且还可以通过跟随“parent”ID来探索之前(祖先)的提交,从提交
E
返回到
D
。
注释标签
注释标签使用几乎相同的图片,除了现在,轻量级标签
bartag
不再直接指向提交,而是现在Git将一个注释标签对象存储到仓库中。这个注释标签对象有自己的数据(日期、标记者、消息、可选数字签名和任何其他你喜欢的内容),并且还存储一个哈希ID。哈希ID是标签的目标(或Git拼写的
object
)。
我对这些绘画没有特定的偏好风格,所以我会随便创造一些:
...--A--B--C <-- foobranch
\
D--E <-- barbranch
^
:
t <-- tag: annotag
在这里,Git存储了一个新的注释标签对象t
到仓库中,现在我们有了外部引用refs/tags/annotag
指向t
。与此同时,是标签对象t
指向提交E
。
这意味着标签annotag
涉及到两个哈希值:注释标签对象的ID和提交E
的ID。同样,引用指向注释标签对象,而对象指向下一个东西,在这种情况下,指向提交E
。
与轻量级标签一样,注释标签对象可以指向除提交以外的其他对象类型。轻量级标签不能指向注释标签对象,但这只是因为当引用指向注释对象时,我们不再将其称为“轻量级”标签,而是称其为“注释”标签。然而,注释标签对象可以指向另一个注释标签对象。让我们这样做,让zomgtag
指向对象t
:
...--A--B--C <-- foobranch
\
D--E <-- barbranch
^
:
t <-- tag: annotag
^
:
z <-- tag: zomgtag
如果您删除其中一个...
现在让我们尝试删除标签annotag
. Git的一个有趣之处在于,删除参考实际上不会删除底层对象。底层对象通常保留到存储库中有太多垃圾时,此时Git会为您运行git gc --auto
。GC(垃圾回收器)查找未引用的对象并实际将它们删除。因此,这个GC是一种类似鬼神或者收割者的存在,可以将死去的对象重新回收利用,腾出可用磁盘空间。
这适用于分支名称引用,例如:删除分支名称只是放弃分支尖端提交,而不是实际删除它。此外,如果有其他方法可以到达该提交,则提交本身不会消失,即使Grim Collector过来了。如果仍然有一些链接,GC会将对象保留在原地。对于正常(未删除)的分支,在重新设置基础(将提交链复制到新链)时,原始提交链尖端ID存储在分支的reflog中,直到reflog条目过期为止,整个链才能访问。 (这意味着您可以在至少30天的默认情况下返回并恢复重新设置的提交,因为30天和90天是默认的reflog过期时间。)
但是,这些规则也适用于注释标记对象!因此,如果我们删除annotag,同时保留zomgtag,则现在的情况如下:
...--A--B--C <-- foobranch
\
D--E <-- barbranch
^
:
t
^
:
z <-- tag: zomgtag
现在标签对象没有名称了,但是可以通过
z
访问,我们可以通过
refs/tags/zomgtag
访问到它,因此将永久保存在存储库中(除非
zomgtag
也被删除,这样将变成未引用状态)。
现在zomgtag
涉及到两个Git对象:从外部引用开始,我们找到注释的标签对象z
。从这里,我们找到注释的标签对象t
,并且从我们找到提交E
。
Git有一种特殊的语法描述在the gitrevisions
documentation中,用于“剥离”标签:zomgtag^{}
。描述如下:
一个后缀符号
^
跟着一对空括号表示对象可能是一个标签,递归地解引用标签,直到找到一个非标签对象为止。
如果我们创建更多的注释标签,我们可以让
refs/tags/wacky
指向一个标签对象,该标签对象指向第二个标签对象,该标签对象又指向另一个标签对象,最终在跟随多个标签之后,指向
z
,然后指向
t
,再指向
E
。符号
wacky^{}
的意思是“查找非标签对象”(在这种情况下是提交,尽管作为终点的也可以是树或blob)。
git rev-list -n 1 my_tag
? - Schwern