标签即 tag
,是人为的对提交进行命名,可以更方便更直观地表达某个提交的意义。例如:用标签名称为 V2.0.0
来表示软件的发布版本对应的某个提交。
接下来,将对一个名为 Hello World
的示例版本库进行研究,详情的介绍 tag
的创建、删除和共享,这个版本库不需要从头建立,直接从 Github (https://github.com/ossxp-com/hello-world.git)上克隆。
- 先在本地创建一个镜像,用作本地用户的共享版本库:
现在,本地建立了一个裸版本库 /git_study/repos/hello-world.git
。
- 分别在用户
user3
和user4
各自的工作区中克隆这个裸版本库,操作方式跟之前一样:
准备工作已经完成了,下面将详情介绍 tag
的相关用法。
**一、显示 tag **
- 不带任何参数执行
git tag
命令,可查看当前版本库的tag
列表:
- 创建
tag
的时候可以添加说明,要在显示tag
的时候同时显示这个说明,要使用-n{num}
参数 ,表示显示最多num
行描述说明:
上图输出显示了一行的 tag
说明 。
- 还可以使用通配符对输出进行过滤,只显示匹配通配符规则的
tag
:
上图输出表示只显示 v1
版本的 tag
,注意不是 -1
是 -l
(是字母) 。
二、命令 git log
查看日志时,加上 --decorate
参数可以看到提交对应的 tag
以及其他引用(Git
版本较高时,默认就会显示,不用加上该参数):
三、 使用命令 git describe
该命令将显示一个易记的名称,如果当前提交恰好打上了 tag
,则显示这个 tag
名称,如果当前提交没有对应的 tag
,但是在其祖先提交上有创建 tag
,则使用 {tag}-{num}-g{commit}
的格式显示。 {tag}
表示距离当前提交最近的 tag
名称,{num}
是该 tag
的提交和当前提交之间的距离(即 tag
提交之后的第几次提交),{commit}
就是当前提交的精简提交 ID
。
上图显示当前提交是最近的 tag(jx/v1.0)
之后的第 2 次提交,提交 ID
为 d901dd8
。
四、创建 tag
使用 git tag
命令创建 tag
,有以下几种创建方式:
git tag {tagname} {commit}
:创建轻量级tag
git tag -m {msg} {tagname} {commit}
:创建带说明的tag
其实还有带签名的 tag
,这里就不介绍了。创建 tag
时,需要输入 tag
名称和一个可选的提交 ID
,如果不提供,则基于 HEAD
创建 tag
。
- 创建轻量级
tag
这种 tag
最简单,无须输入任何说明信息,来演示一下:
先创建一个空提交,接着在这个提交上创建轻量级 tag
,名称为 mytag
,再查看一下本地 tag
列表:
实现方式: 当创建了 mytag
后,会在版本库的 .git/refs/tags
目录下创建一个新文件,这个文件保存的是一个提交 ID
,该提交 ID
对应的内容就是打上标签的那个提交的内容,所以可以用 tag
名称替代指定的提交 ID
。
轻量级 tag
的缺点:创建过程没有记录,因此无法知道是谁创建的 tag
,以及何时创建的 tag
。强烈建议创建带说明的 tag
,不要以用这种偷懒的方式来创建 tag
。另外,git describe
命令默认不使用轻量级 tag
来生成版本描述字符串,除非加上 --tags
参数。
上图可知,如果不加上 --tags
参数,默认还是以之前 jx/v1.0
这个标签为基准。
- 创建带说明的
tag
还是先创建一个空提交,然后通过 -m
参数来创建带说明的 tag
,再查看一下本地 tag
列表:
实现方式: 当创建了 mytag2
后,会在版本库的 .git/refs/tags
目录下创建一个新文件,这个文件保存的是一个对象哈希值 ,该哈希值指向的不再是一个提交,而是一个 tag
对象,该对象内容也不是我们熟系的提交对象的内容,而是包含了创建 tag
时的说明,以及对应的提交 ID
等信息,通过其记录的提交 ID
才能查到具体的提交信息。**
至此,Git
对象库的四类对象都涉及到了,分别是 commit
对象、tree
对象、blob
对象和 tag
对象。
虽然 mytag2
本身是一个 tag
对象,但在很多 git
命令中,可以直接将其看作一个提交,就跟轻量级 tag
一样,当作提交 ID
来使用。
注意:通过 git rev-parse mytag2
这种方式得到的 ID
是 tag
对象ID ,并不是提交对象的 ID
,需要根据对象 ID
来查看具体提交的 ID
。
上图所示,第一种方式获取的是对象 ID
,下面四种方式都可以获得 mytag2
对象所指向的提交对象的 ID
。
五、删除 tag
如果 tag
创建在错误的提交上,或者对 tag
的命名不满意,可以使用 git tag -d {tagname}
来删除指定的 tag
,下面将删除 mytag
本地标签 。
tag
没有类似 reflog
的变更机制,删除之后不易恢复,要慎用。在删除 tag
的命令输出中,会显示该 tag
所对应的提交 ID
,发现删错了,赶紧补救还来得及。
Git
并没有提供对 tag
进行重命名的命令,如果对 tag
命名不满意,可以删除旧的再重新用新的名称创建。
根据上图输出中的提交 ID
,将 tag
进行恢复:git tag mytag 3205ed1
。
注意:不要随意更改 tag
,因为 tag
从概念上讲是对历史提交的一个标记,不应该随意变动。另一个原因是 tag
已经被人同步,如果修改了 tag
,已经同步该 tag
的用户并不会自动更新,这会导致一个相同名称的 tag
在不同用户的版本库中的指向不同。
六、共享 tag
到远程版本库
- 查看一下用户
user3
当前的状态,发现有两个新的提交(两个提交都打上了tag
),再执行git push
命令进行推送,最后通过git ls-remote origin my*
查看远程以my
开头的引用信息(tag
也算是引用的一种),发现本地创建的tag
并没有一起推送到远程。需要在git push
命令中显式将tag
推送到远程:
上图可知:可以推送指定单个 tag
( git push origin mytag
) 。也可以通过通配符来批量推送( git push origin refs/tags/*
),通过 git ls-remote origin my*
命令可以看到远程版本库已经同步本地的 tag
了。
总结:创建的 tag
默认不会跟随分支的推送而推送,从而避免了不同用户本地创建的 tag
都自动推送导致的 tag
杂乱,而且不同用户相同的 tag
名称还会互相覆盖。
- 其他用户如果执行
git fetch
或git pull
操作,能自动将tag
进行同步吗?演示一下:
用户 user4
执行 git pull
之后,再查看一下本地的 tag
列表:
其实从 git pull
操作的输出中已经可以看到,在获取远程共享版本库的提交的同时,也获取到了新的 tag
。
-
tag
能够自动同步吗?来演示一下:
-
tag
是可以被强制更新的,通过添加-f
参数。用户user4
强制更新mytag2
,修改其描述说明和指向的提交ID
:
- 再显式地对这个
tag
进行推送,要加上-f
参数来强制推送才行:
- 切换到用户
user3
,执行git pull
操作 ,发现没有获取到新的tag
(默认不会自动同步已经存在的tag
),必须显式地执行拉回操作。要在git pull
的参数中使用引用表达式(用冒号分隔的引用名称或通配符)。下图的命令表示用远程共享版本库的引用refs/tag/mytag2
覆盖本地版本库的同名引用 (冒号左侧是本地,右侧是远程)。
总结:
tag
要进行推送共享,必须进行显式推送。执行fetch
或pull
操作只能自动从远程版本库获取新的tag
,并在本地版本库中创建。只会将获取的远程分支所包含的
tag
同步到本地,而不会将远程版本库的其他分支中的tag
获取到本地。如果本地已有同名的
tag
,默认不会自动同步,即使两者的指向不同。tag
被推送共享之后,最好不要再去修改。
七、删除远程版本库的 tag
删除本地的 tag
非常简单,使用 git tag -d {tagname}
就行了,如何删除已经推送到远程版本库的 tag
呢?方法也很简单,直接在本地版本库执行命令如下命令就行:git push {remote-url} : {tagname}
该命令的最后一个参数实际上是一个引用表达式,引用表达式一般的格式为:{ref}:{ref}
。该推送命令使用的引用表达式冒号前的引用被省略了,表示将一个空值推送到远程版本库对应的引用中,也就是删除远程版本库中相关的引用。该命令不仅可以用于删除 tag
,也可以用来删除远程版本库的分支(下一节再讲)。
将远程版本库中的标签 mytag2
进行删除:
可以看到,远程的 mytag2
的确被删除了,但是本地的 mytag2 还是在的,因为我们并没有执行 git tag -d mytag2
。