bigywater.tech
Git · 9

注解标签与轻量标签:tag 对象里有什么

git tag v1 和 git tag -a v1 -m ... 有什么区别?这篇数对象个数、读 tag 对象原文、用 ^{} 剥开标签,再让标签指向另一个标签、tree、blob,最后看 push --follow-tags 和删除标签的后果。

3 → 3 → 4
打标签前、轻量标签后、注解标签后的对象总数
tag 对象
注解标签的 ref 指向一个独立的 tag 对象,而不是 commit
--follow-tags
本地推送实验里只带走了 2 个标签(annot、nested),轻量标签没有被推送

轻量标签

实测 · 本机执行输出
  打标签之前对象数: 3
  $ git tag light
  git tag light 之后对象数: 3
  $ cat .git/refs/tags/light; git rev-parse HEAD
  30385d36cf7f73566529aa24a6ee8c9d4c8472fe
  30385d36cf7f73566529aa24a6ee8c9d4c8472fe
  $ git tag -a annot -m '第一个注解标签'
  git tag -a annot 之后对象数: 4
  $ cat .git/refs/tags/annot; git rev-parse HEAD
  8542d98add695e90a7b4cad8a1387dfb1c999c37
  30385d36cf7f73566529aa24a6ee8c9d4c8472fe

git tag name 只在 refs/tags/ 下写一个文件,内容是目标 commit 的 hash,没有任何新对象。对它做 git cat-file -t 得到的就是 commit。

注解标签

实测 · 本机执行输出
  $ git cat-file -t annot; git cat-file -p annot
  tag
  object 30385d36cf7f73566529aa24a6ee8c9d4c8472fe
  type commit
  tag annot
  tagger Alice <alice@example.com> 1767261720 +0000
  
  第一个注解标签
  $ git cat-file -t light
  commit

git tag -a 先创建一个 tag 对象,内容是被标记对象的 hash 和类型、标签名、打标签人和时间、说明,再让 refs/tags/name 指向这个 tag 对象。所以 ref 里的 hash 不等于 commit 的 hash。

剥开标签

实测 · 本机执行输出
  $ for n in light annot annot^{} annot^{commit} annot^{tag}; do printf "%-14s" $n; git rev-parse $n 2>&1 | cut -c1-12; done
  light         30385d36cf7f
  annot         8542d98add69
  annot^{}      30385d36cf7f
  annot^{commit}30385d36cf7f
  annot^{tag}   8542d98add69
  $ git tag light2
  新提交上打了轻量标签 light2,git describe 默认只看注解标签,--tags 才看轻量标签:
  $ git describe
  annot-1-g77640ce
  $ git describe --tags
  light2
  $ git describe --tags --exact-match HEAD~1
  annot

name^{} 把标签一层层剥开到最终对象,name^{tag} 停在 tag 对象。git describe 默认只用注解标签,--tags 才把轻量标签算进去;同一个提交上有两种标签时,优先选注解标签。

标签指向什么

实测 · 本机执行输出
  $ git tag -a nested -m '标签的标签' annot
  hint: You have created a nested tag. The object referred to by your new tag is
  hint: already a tag. If you meant to tag the object that it points to, use:
  hint:
  hint: 	git tag -f nested annot^{}
  hint: Disable this message with "git config set advice.nestedTag false"
  $ git cat-file -p nested | head -3
  object 8542d98add695e90a7b4cad8a1387dfb1c999c37
  type tag
  tag nested
  $ git tag -a ontree -m 'tree 标签' HEAD^{tree}
  $ git cat-file -p ontree | head -3
  object 02439fb50f442f187d6db9a7b47287b9e3f5d49c
  type tree
  tag ontree
  $ git tag -a onblob -m 'blob 标签' HEAD:a
  $ git cat-file -p onblob | head -3
  object 0cfbf08886fca9a91cb753ec8734c84fcbe52c9f
  type blob
  tag onblob
  $ git for-each-ref refs/tags --format='%(refname:short) → %(objecttype)'
  annot → tag
  light → commit
  light2 → commit
  nested → tag
  onblob → tag
  ontree → tag

标签可以指向任何对象:另一个标签(git 会提示这是嵌套标签)、tree、blob。实际中几乎都是 commit,但对象模型上没有限制。

推送与删除

实测 · 本机执行输出
  $ git init -q --bare ../l5bare.git
  $ git push -q ../l5bare.git main --follow-tags 2>&1; git --git-dir=../l5bare.git for-each-ref refs/tags --format="%(refname:short)"
  annot
  nested
  $ git tag -d annot nested ontree onblob light
  Deleted tag 'annot' (was 8542d98)
  Deleted tag 'nested' (was d1a592a)
  Deleted tag 'ontree' (was 9ce851e)
  Deleted tag 'onblob' (was 3033fe2)
  Deleted tag 'light' (was 30385d3)
  $ git fsck --lost-found 2>&1 | head -6
  dangling tag 3033fe207118b853401a832b4245d0d088de1c08
  dangling tag d1a592abe9b63ec1eca41d84118c51c1affca28e
  dangling tag 9ce851e940d0bacae2ea7c8de9dc8852a461b412

--follow-tags 只推送带注解、并且指向正在推送的历史的标签。删除标签只删 ref,tag 对象留在对象库里,fsck 把没人引用的那些列为 dangling,直到 gc 清理。

分步演示

两种标签
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / git 2.54.0(Apple Git-157);每个实验在临时目录里新建仓库,HOME 和全局配置指向实验专用的空目录(不读用户自己的 git 配置),提交的作者与时间固定以便复现;需要网络协议的实验用本机的 file:// 或 git daemon 回环端口(9800 以上),不碰任何远程仓库;计时用 time.perf_counter,数字每次运行会有小幅抖动。推送实验的远程是本机的一个裸仓库(git init --bare),没有走网络;标签说明与时间由环境变量固定
    • 没有验证:签名标签(git tag -s,需要 GPG 或 SSH 密钥,后面有 SSH 签名一篇);服务器端对标签更新的限制(本实验只验证了本地裸仓库);git describe 在多个候选标签之间选择的完整规则(只观察了本例);轻量标签重名覆盖时的 -f 行为
    京ICP备2026067502号-1京公网安备11010502063487号