git tag v1 和 git tag -a v1 -m ... 有什么区别?这篇数对象个数、读 tag 对象原文、用 ^{} 剥开标签,再让标签指向另一个标签、tree、blob,最后看 push --follow-tags 和删除标签的后果。
打标签之前对象数: 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
annotname^{} 把标签一层层剥开到最终对象,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 清理。