问题描述
有没有可能说轻量级标签永远是首选?
我的问题:我使用 git 标签来获取标签的哈希值。使用这个散列,我将向我的数据库中添加一个文件。问题是,我必须始终确保保存轻量级标签的哈希值。如果有人在创建标签时添加了一条消息,它将是一个带注释的标签。所以我想改变我的 git.describe 函数,让它只返回轻量级标签。
我认为这是可能的,但我找不到任何例子。我唯一阅读的是文档和使用参考/标签。但我不知道如何。
我为此使用了 gitpython:https://gitpython.readthedocs.io/en/stable/tutorial.html
现在我是这样做的,现在自动首选带注释的标签:
repo_dir = "example-git-repo-url.com"
repo = git.Repo(repo_dir)
tag_name = repo.git.describe(["--tags","--abbrev=0","--first-parent"])
tag_hash = repo.git.rev_parse(["--short=8",tag_name])
logger.info("Latest tag: %s (%s)",tag_name,tag_hash)
我不明白 documentation 的这一部分:
--标签
不要只使用带注释的标签,而是使用在 refs/tags 命名空间中找到的任何标签。此选项启用匹配轻量级(非注释)标签。
使用 refs/tags 中的任何标签是什么意思?怎么样?
解决方法
您使用的 Python 包装器要么调用 Git,要么重新实现 Git(或两者兼而有之——其中一些包装器可以被告知是使用它们的内部实现,还是使用 subprocess
调用 Git)。如果您的特定包装器实现了自己的 git describe
,它可能有办法做您想做的事。
如果您使用 Git 自己的内置 git describe
,则没有直接的方法来获得您想要的东西。不过,--exclude
选项可能会让您足够接近。有关如何从 Python 包装器中使用它的想法,请参阅较长的部分。
基本问题是这样的:默认情况下,git describe
试图找到一个注释标签。选项 --tags
只是添加使用仅轻量级标签的能力。它永远不会取消使用带注释的标签的能力。
长
我没有得到这部分文档:
--tags
不要只使用带注释的标签,而是使用 refs/tags
命名空间中的任何标签。此选项启用匹配轻量级(非注释)标签。
使用在 refs/tags 中找到的任何标签是什么意思?
Ciro Santelli linked 到 How can I list all lightweight tags? 这个问题有我关于如何使用 git for-each-ref
枚举所有轻量级标签的答案。这里缺少的背景(以及在上面引用的 the git tag
documentation 中)是,在 Git 中,所有名称——分支名称、标签名称等等——都是 的特定形式>refs 或 references。这些引用位于 namespaces。
有关更完整的定义和一组示例,请参阅链接的维基百科文章,但现实世界中的一个常见名称空间示例与人类和 "given names" vs "surnames" 有关。如果您发现自己参加的聚会有太多人叫布鲁斯,您通常可以使用全名或表示字母(“布鲁斯 A”、“布鲁斯 J”)等来区分。
同样的想法在 Git 中也适用:如果您有一个名为 xyz
的分支和一个名为 {{1} 的标签 },您可以使用全名或更全名来拼出您的意思:xyz
是分支,refs/heads/xyz
是标签。 所有标签都位于 refs/tags/xyz
下。我们通常只是省略 refs/tags/
部分并说“标签 xyz”。
我在回答中注意到一个带注释的标签refs/tags/
实际上由两部分组成:一个名为atag
的轻量级标签,以及带注释的标签类型的内部 Git 对象。内部 Git 对象的名称是哈希 ID。也就是说,Git 通过它的哈希 ID 找到这个带注释的标签对象:
- 该对的轻量级标签部分是 Git ref 或 reference,并且
- 所有 Git 引用都持有一个哈希 ID。
所以轻量级标签 atag
保存了带注释的标签对象的哈希 ID。带注释的标签对象反过来将标签目标的哈希 ID 作为其数据的一部分保存,这通常是提交。我们可以通过查看 Git 存储库中 Git 本身的标签来了解所有这些,例如 refs/tags/atag
:
v2.30.0
第一个散列 ID $ git rev-parse v2.30.0
2d9685d47a7e516281aa093bf0cddc8aafa72448
$ git cat-file -p 2d9685d47a7e516281aa093bf0cddc8aafa72448 | sed 's/@/ /'
object 71ca53e8125e36efbda17293c50027d31681a41f
type commit
tag v2.30.0
tagger Junio C Hamano <gitster pobox.com> 1609110954 -0800
Git 2.30
-----BEGIN PGP SIGNATURE-----
[snipped]
是带注释的标签对象。 2d9685d47a7e516281aa093bf0cddc8aafa72448
命令打印出带注释的标签对象的内容,它以 git cat-file -p
行开头,然后是 object
行,然后是 type
行,依此类推。这个特殊的带注释的标签的主要目的是携带 PGP 签名(这里剪掉了)。
tag
行包含此特定注释标签的目标的哈希 ID:object
,它是一个提交对象。如果 71ca53e8125e36efbda17293c50027d31681a41f
是一个轻量级标签,而不是一个带注释的标签,那么名称 v2.30.0
将直接包含 refs/tags/v2.30.0
。但是,71ca53e8125e36efbda17293c50027d31681a41f
指的是带注释的标签对象 refs/tags/v2.30.0
,因此我们称 2d9685d47a7e516281aa093bf0cddc8aafa72448
是带注释的标签。它由构建这一对:名称指的是第一个对象,而第一个对象指的是另一个对象。
考虑到这一点,请考虑 v2.30.0
要使 git describe
执行其默认工作,它必须枚举带注释的标签。这意味着查看每个 git describe
名称。每个这样的名称要么是一个轻量级标签,直接引用某个不是带注释的标签对象的对象,要么是一个带注释的标签,因为它引用了一个带注释的标签对象,该对象引用了到某个目标对象。这有点吃不完——或满头——所以如果需要,请多看几次。
由于 refs/tags/*
只想查看带注释的标签,它所做的就是查看每个标签,然后扔掉 那些直接引用非注释标签的标签。这意味着 git describe
现在只有带注释的标签的表格。然后它可以继续工作并找出这些标签名称中的哪一个(如果有的话)是合适的。
git describe
标志只是告诉 --tags
:不要丢弃仅轻量级的标签。这让它查看所有标签在 git tag
命名空间中,即所有标签。
使用 refs/tags
--exclude
选项旨在丢弃某种标记名称模式。例如,假设某个存储库作者将他们的标签组织成“发布”、“早期”和“实验”:标签 --exclude
是 1.0 版,而 releases/1.0
和 early/1.0-alpha
是 alpha和 1.0 版的测试版。与此同时,early/1.0-beta
可能有一些正在试验中的功能,可能会也可能不会进入 2.0 版。
您可能只对各种实验感兴趣,不包括早期版本。在这种情况下,您可以使用以下命令排除所有 experiments/featureX
和 releases/*
:
early/*
(这里的引号是为了防止 git describe --exclude 'releases/*' --exclude 'early/*'
被你的 shell 吃掉;这在各种情况下可能是不必要的,但很少有伤害)。
虽然 exclude 选项采用“glob 模式”,但任何实际标签都是有效的 glob 模式。只是它只匹配那个标签。1 所以,在你的 Python 程序中,你可以:
-
查找所有标签名称,使用您可以使用的任何工具来枚举
*
空间中的所有名称。请注意,使用refs/tags/
时不需要尾部斜杠,因为它会添加git for-each-ref
本身,但您使用的任何 API 都可能需要。检查文档。2 -
对于每个带注释的标记,添加
'/*'
和标记。
然后使用 --exclude
和这些 git describe --tags
来想出一个描述性标签(如果有的话)。通过排除所有带注释的标签,您将只获得一个轻量级标签。
1如果标签的名称包含特殊的全局字符,即 --exclude
、*
和 ?
,我们可能会在这里遇到麻烦。一个可以:
- 希望没有标签使用这些字符,或者
- 如果需要,请检查并引用它们。
但另请参见 the git check-ref-format
documentation,它明确禁止此类字符。我已经看到 StackOverflow 上的问题暗示某些分支名称包含禁止字符,因此它们确实出现在实际情况中,可能是从非 Git 软件写入它不应该写入的文件。然后由程序员决定是否检查这些,如果发现,如何处理它们。
2这通常会导致发现文档不完整。在这一点上,您可以检查实现 - 换句话说,阅读源代码 - 和/或实验以发现实际行为。然而,这通常表明编写 API 的人没有仔细考虑边缘情况,并且不同版本的程序或库可能表现不同。