两者tagA
和都tagB
指向一个特定的提交,因此如果您想查看或查看该特定提交,就 git 而言,任何一个名称都同样好(或同样坏)。
文档的措辞“最新标签”在这里可能会产生误导(尽管这可能是您获得tagB
输出的原因)。
如何得到你想要的结果
如果你想知道你给的标签名git checkout
,你可以自己保存,或者查阅 reflog HEAD
:
8004647 HEAD@{0}: checkout: moving from master to v2.3.1
reflog 方法的优点是它已经实现了;但是 reflogs 只保留一段时间(可配置,默认 90 天用于可访问的引用),然后过期以防止 reflogs 永远增长。
“git describe”如何得出你不想要的答案
“最近”意味着如果标签具有相关的日期和时间戳——带注释的标签有,轻量级标签没有——那么那些时间较晚的标签被认为是“更好的”,如下所述。
请注意,任何时候,任何 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
还找到了两个或多个解析为该 ID 的名称。这些可能只是带注释的标签名称,这是您在没有选项的情况下得到的,或者它们可能是 允许的另一个名称(如分支名称)--all
,例如;但重要的是假设有两个或多个名称,都指向同一个提交。
该describe
命令可以尝试记住所有这些名称,但它没有。取而代之的是,它通过一次两次的锦标赛来运行名称,以查看哪个“赢得比赛”:
- 如果两个名称都是带注释的标签,请使用两个名称上的日期戳来选择要保留的标签,以及要丢弃的标签。这里的想法似乎是,如果给同一个提交两个或多个带注释的标签,那么稍后创建的那个可能“更好”并且
git describe
应该使用。
- 如果一个名字是带注释的标签,另一个不是,保留带注释的标签;扔掉另一个名字。换句话说,任何带注释的标签都比任何轻量级标签都要好。
- 如果两个名称都不是带注释的标签,但一个是(轻量级)标签而另一个不是,则保留该标签;扔掉非标签。换句话说,任何标签都比其他任何标签都好。
- 否则(两个名称都不是任何类型的标签),保留“最早遇到”的名称(这取决于 git 的内部名称遍历机制,因此不一定是可预测的)。
一旦此提交的所有名称相互竞争以选择“获胜名称”,“获胜名称”将与提交 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。如果其中任何一个与此SHA-1 匹配,则它们将被保存。如果有多场比赛,他们将通过比赛来挑选一个获胜者。
对于您的特定情况,这部分成功:两者tagA
都是tagB
完全匹配的,因此在这两者之间选择了一个获胜标签后,整个事情就停止了。在您的情况下,那是您不想要的标签。
但是,总的来说,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 “描述” commit 088c9a8
,它可以被描述为以下任何一种:
- 提交后六步发生的提交
X
,或
- 提交后四个步骤发生的提交
Y
,或
- 提交后一步发生的提交
Z
的输出git describe
将使用 tag Z
,因为从“tag Z”(提交aa07cac
)到有问题的提交(088c9a8
)需要更少的步骤。这实际上是如何发生的非常复杂(也许是不必要的,尽管这是一个价值判断:-))复杂。
这里要做git describe
的是遍历(部分)提交图(--first-parent
如果指定,则应用)以找到确实有名称的“附近”提交。搜索顺序有点难以描述:它部分是基于日期的(a lagit log
的按日期排序的提交列表),但如果遇到带注释的标签,它会提前停止。否则,它会累积“候选名称”列表(使用上述竞赛方法),当它累积足够多(默认 10 个)候选名称时停止,然后对列表进行排序。排序是基于“图距离”的,所以假设X
,Y
和Z
都是轻量级标签——如果有的话是带注释的标签,它现在已经被选中并且搜索停止了——<code>Z 在这里获胜。
最后,找到一个指向合适提交的名称,该提交实际上不是给定的参数提交,git describe
打印名称、距离的步数(“深度”)以及g
实际 SHA-1 的一部分:
v2.3.3-220-g9ab698f
(尽管深度和g
可以被抑制,如果根本找不到名字或者如果你只要求完全匹配,那么整个事情可能会失败,等等)。