Git:当同一提交上有 2 个/更多标签时如何获取签出标签的名称

Git: How to get the name of checked out tag when 2/more tags on same commit

  1. 我有一个带有 2 个标签的 git 提交,如下所示:commit1-----tagA,tagB
  2. 结帐 "tagA" git checkout tagA
  3. 问题:如何获取当前结账的标签名称?我已经尝试 git describe,但它总是 return 名称 "tagB",期待 return "tagA".

    好像git describe只能return最近的标签名,看自git manual

    The command finds the most recent tag that is reachable from a commit. If the tag points to the commit, then only the tag is shown. Otherwise, it suffixes the tag name with the number of additional commits on top of the tagged object and the abbreviated object name of the most recent commit.

还有其他方法吗?

与此问题相关的目的:

我想让文件根据标签名称自动构建版本号,git describe 在使用 1 个标签提交 1 次时效果很好,但在上述情况下没有用。

您至少可以获取给定提交的所有标签:

git tag --points-at HEAD

来自git tag man page

--points-at <object>

Only list tags of the given object.

从那里,你可以提取你想要的那个。

tagAtagB 都指向那个特定的提交,所以就 git 而言,任何一个名称都同样好(或同样坏),如果你想检查或查看该特定提交。

文档的措辞 "most recent tag" 在这里可能会产生误导(尽管这可能是您得到 tagB 作为输出的原因)。


如何得到你想要的结果

如果你想知道你给 git checkout 的标签名是什么,你可以自己保存,或者查阅 HEAD 的 reflog:

8004647 HEAD@{0}: checkout: moving from master to v2.3.1

reflog 方法的优点是它已经实现了;但 reflog 仅保留一段时间(可配置,默认为可访问引用的 90 天),然后过期以防止 reflog 永远增长。


"git describe" 如何得出你不想要的答案

"Most recent" 意味着如果标签有关联的日期和时间戳——带注释的标签有,轻量级标签没有——那么那些时间较晚的标签被认为是 "better",如下所述.

请注意,在任何时候,任何 git 命令都可以轻松查找所有外部引用并将它们转换为 SHA-1。要查看它是如何工作的,只需 运行 git for-each-ref(您可能希望将其通过管道传递给 less 之类的寻呼机)。输出看起来像这样:

c2e8e4b9da4d007b15faa2e3d407b2fd279f0572 commit refs/heads/maint
9ab698f4000a736864c41f57fbae1e021ac27799 commit refs/heads/master
[snip]
74d2a8cf12bf102a8cedaf66736503bb3fe88dfb tag    refs/tags/v2.2.0
[more snippage]

这些是分支和标签——在这个 git 存储库中(对于 git 本身)所有标签都被注释了——以及它们相应的 SHA-1。如果有一个活动的藏匿处,它也会显示出来(在 refs/stash 下)。

无论如何,假设 git describe 此时具有这些提交 ID 之一(SHA-1),并且 describe 也找到了两个或更多解析为那个身份证。这些可能只是带注释的标签名称,这是您在没有选项的情况下获得的名称,或者它们可能是 --all 允许的另一个名称(例如分支名称);但重要的是假设有两个或更多的名字,都指向同一个提交。

describe命令可以尝试记住所有这些名字,但它没有。取而代之的是,它 运行 通过一种一次两次的锦标赛来确定名字 "wins a contest":

  • 如果两个名字都是带注释的标签,使用两个名字上的日期戳来选择保留哪个标签,丢弃哪个标签。这里的想法似乎是,如果同一个提交被赋予两个或多个带注释的标签,那么稍后创建的标签可能是 "better" 并且是 git describe 应该使用的。
  • 如果一个名字是注释标签,另一个不是,则保留注释标签;扔掉另一个名字。换句话说,任何带注释的标签都优于任何轻量级标签。
  • 如果两个名称都不是带注释的标签,但一个是(轻量级)标签而另一个不是,则保留该标签;扔掉非标签。换句话说,任何标签都比其他任何标签都好。
  • 否则(两个名称都不是任何类型的标签),保留 "earliest-encountered" 名称(这取决于 git 的内部名称遍历机制,因此不一定是可预测的)。

一旦此提交的所有名称都相互竞争以选择 "winning name","winning name" 将与提交 SHA-1 一起保存。


现在,我们如何首先获得该提交 ID?答案是 git describe 以你的论点开头:

$ git describe       # no args, means ...
$ git describe HEAD  # use HEAD to get the SHA-1

您的参数(或 HEAD)使用 git rev-parse(好吧,它的 C 代码等价物)转换为适当的原始 SHA-1:

$ git rev-parse HEAD
9ab698f4000a736864c41f57fbae1e021ac27799

然后,git describe 调用 git for-each-ref(或其等效的 C 代码)将您允许它使用的所有名称(默认情况下,所有带注释的标签)转换为 SHA-1 ID。如果它们中的任何一个匹配 this SHA-1,它们就会被保存。如果有多场比赛,他们会 运行 通过比赛选出一个获胜者。

对于您的特定情况,这部分成功:tagAtagB 都是完全匹配,所以在这两者之间选择一个获胜标签后整个事情就停止了。在你的情况下,这是你想要的标签。

但总的来说,git describe 经常需要继续前进。例如,考虑以下来自 git 项目本身的提交:

088c9a8 strbuf.h: format asciidoc code blocks as 4-space indent
aa07cac strbuf.h: drop asciidoc list formatting from API docs
6afbbdd strbuf.h: unify documentation comments beginnings
bdfdaa4 strbuf.h: integrate api-strbuf.txt documentation
eae6953 tests: correct misuses of POSIXPERM
1767c51 t/lib-httpd: switch SANITY check for NOT_ROOT
b4a56a3 "log --pretty" documentation: do not forget "tformat:"

现在假设我们让一个标签X指向提交b4a56a3,一个标签Y指向提交eae6953,一个标签Z指向提交 aa07cac。如果我们然后要求 git 到 "describe" 提交 088c9a8,它可以描述为以下任何一种:

  • 提交后六步发生的提交X,或
  • 在提交 Y 之后四步发生的提交,或者
  • 提交后一步发生的提交Z

git describe 的输出将使用标记 Z,因为从 "tag Z"(提交 aa07cac)到有问题的提交(088c9a8).这实际上是如何发生的非常复杂(也许不必要,尽管这是一个价值判断:-))。

git describe 在这里所做的是遍历(部分)提交图(如果指定则应用 --first-parent)以找到确实有名称的 "nearby" 提交。搜索顺序有点难以描述:它部分基于日期(la git log 的日期排序提交列表),但如果遇到带注释的标签,它会提前停止。否则它会累积一个 "candidate names" 的列表(使用上面描述的竞赛方法),当它累积足够(默认 10 个)候选名称时停止,然后对列表进行排序。排序基于 "graph distance",因此假设 XYZ 都是轻量级标签——如果有注释标签,那么它现在已经被选中了搜索停止了——Z 在这里获胜。

最后,找到一个指向合适提交的名称,该提交实际上不是给定的参数提交,git describe 打印名称,距离的步数("depth") ,以及 g 和实际 SHA-1 的一部分:

v2.3.3-220-g9ab698f

(尽管深度和 g 可以被抑制,如果根本找不到名称或者如果您只要求完全匹配,则整个事情可能会失败,等等)。