5

git describe有据可查

可以从提交中访问的最新标签。

资源:git describe --help

我有点不明白如何从提交中准确地访问标签。在分支中运行时,我没有看到我期望的行为,我不明白为什么。

https://github.com/nodemcu/nodemcu-firmware使用发布方案,其中所有更改都进入dev分支,然后master定期恢复。发布和带注释的标签是从master. git describe继续运行master会产生预期的结果。

运行时,dev我得到一个两年前创建的标签。

~/Data/NodeMCU/nodemcu-firmware (dev) > git describe
2.0.0-master_20170202-439-ga08e74d9

为什么是这样?

类似的情况是我们为某些用户保留的旧版本的(或多或少)冻结的分支。

~/Data/NodeMCU/nodemcu-firmware (1.5.4.1-final) > git describe
0.9.6-dev_20150627-953-gb9436bdf

该分支是在这个带注释的标签https://github.com/nodemcu/nodemcu-firmware/releases/tag/1.5.4.1-master_20161201之后创建的,此后只有少数提交落在该分支上。

4

2 回答 2

15

文档是谎言。您无法从提交中找到标签。您可以从标签中找到提交,这就是git describe 真正的作用,以一种非常曲折的方式,我们稍后会看到。

谎言是一个有用的、描述性的谎言。我认为,它在这方面的成功程度值得商榷。让我们看看git describe真正的工作原理(不要太深入细节)。不过,首先,我们可能需要一些背景知识。

背景(如果您知道所有这些,请跳到下一部分)

在我们开始之前您需要了解的内容:

  • 有两种“类型”的标签:带注释的标签和轻量级标签。带注释的标签是由git tag -aor制作的git tag -m,实际上有两部分:它是一个轻量级标签加上一个实际的 Git 对象,我们稍后会介绍。

    默认情况下,git describe仅查看带注释的标签。使用--tags使其查看所有标签。

  • 标签是更通用实体的一种特定形式,简称referenceref。像这样的分支名称master也是引用,并且git describe允许使用任何引用,通过--all.

  • 您还可以使用提交图从提交中找到提交。

作为上述所有内容的基础,Git 既有引用又有对象。这些存储在两个独立的数据库中。1 一个存储名称,所有的形式refs/...,映射到一个哈希值(SHA-1 目前,虽然 SHA-256 正在计划中)。另一种是由哈希值索引的简单键值存储。所以:

       refs                                objects
+--------------------------------+    +----------------------+
| refs/heads/master   a123456... |    | 08aef31...  <object> |
| refs/tags/v1.2      b789abc... |    | a123456...  <object> |
+--------------------------------+    | b789abc...  <object> |
                                      | <lots more of these> |
                                      +----------------------+

对象数据库通常比参考数据库大得多。

里面其实有四种对象:commit对象、tree对象、blob对象、tag对象。每个对象都有一个对象 ID或 OID,它实际上只是一个哈希(同样,目前是 SHA-1,最终是 SHA-256;将其称为 OID 背后的想法是与最终的转换隔离)。 Blob对象包含 Git 本身不解释的数据。2 所有其他人都持有 Git 至少可以使用的数据。

提交和标记对象是这里特别有趣的对象,因为标记对象包含作为标记目标的 OID 而提交对象包含提交的每个级的 OID 。

提交引用(refs/heads/master等)被限制为仅包含提交对象的 OID。提交对象的父 OID 同样受到限制:每个必须是另一个提交对象的 OID。任何提交的父级都是在创建该特定提交时存在的一些较旧的提交。

如果我们要查看存储库中的所有对象(例如,git gcgit fsck),我们可以构建所有提交对象的,单向箭头从每个提交链接到其所有父对象。如果我们放大一个特定的双父提交,我们可能会看到:

 ... <commit>  <--  +--------+
                    | commit |  <-- <commit> ...
 ... <commit>  <--  +--------+

缩小,我们看到一个整体的有向无环图或所有提交的DAG。同时,存储在分支名称中的 OID(以及保存提交哈希的任何其他引用中)充当此图的入口点,我们可以从这里开始,然后继续关注父链接。

带注释的标签是一个标签引用——或多或少的轻量级标签——它指向一个标签对象。如果基础标记对象然后指向一个提交,那么它也可以作为提交 DAG 的入口点。但是,允许标记对象直接指向树或 blob,或其他标记对象。剥离标签的过程是指跟随指向另一个标签对象的注释标签。我们只是继续跟踪,直到我们到达一些非标签对象:这是这个分层标签的最终目标。如果最终目标是提交,那么这就是 DAG 的另一个入口点。

所以,最后,我们通常有一个分支名称master它指向一个主要是线性的提交字符串中的最后一个提交:

... <-o <-o <-o <-o   <--master

内部箭头都指向后的事实通常不是很有趣,尽管它会影响git describe,所以我将它包含在此处。

在存储库生命周期中的不同时间,我们选择一个提交并为其添加一个标签,无论是轻量级的还是带注释的。如果它是带注释的标签,则有一个实际的标签对象:

  tag:v1.1  tag:v1.2
      |       |
      v       v
      T       T
      |       |
      v       v
... <-o <-o <-o <-o   <--master

其中os 是提交对象,Ts 是标记对象。


1参考数据库非常俗气:它实际上只是一个平面文件.git/packed-refs,加上一堆单独的文件和子目录,.git/refs/**/*. 尽管如此,在 Git 内部,有一个用于添加新数据库的插件接口,并且考虑到平面文件和单个文件的所有问题,我希望最终会有一个真正的数据库作为选项。

2大多数情况下,这是您自己的文件数据。例如,对于符号链接,符号链接的目标存储为 blob 对象,因此数据随后会由您的主机操作系统解释。


git describe工作原理

git describe命令想要找到一些名称——通常是一些带注释的标记对象——这样您要求描述的提交是标记提交的后代。也就是说,标签可以直接指向提交 X,或者指向作为 X 的直接父级的提交(后退一步),或者指向从 X 后退一些步的提交,希望不会太多。

在 Git 中,很难找到某个特定提交的后代。但是很容易找到某个特定提交的祖先。因此,Git 不必从每个标记开始并向前工作,而是必须从提交 X 开始并向工作。X 本身是由某个标签描述的吗?如果不是,请尝试 X 的每个父母:他们是某个标签的直接目标吗?如果不是,请尝试 X 的每个祖父母:他们是某个标签的直接目标吗?

因此git describe它会找到所有或至少一些有趣引用(带注释的标签,或所有标签,或所有引用)的目标。当它在我们的示例中执行此“有趣的 refs”时,它会找到两个提交,我们将用 标记*

  tag:v1.1  tag:v1.2
      |       |
      v       v
      T       T
      |       |
      v       v
... <-* <-o <-* <-o   <--master

现在它从我们想要描述的提交开始:master. 从该提交开始,它可以向后工作一跳以到达从v1.2. 或者,它可以向后工作跳以找到从v1.1.

由于v1.2是“更接近”,这就是git describe将使用的带注释的标签名称。同时,它确实必须从master. 所以输出将是:

v1.2-1-g<hash>

where 是指向哪个提交的缩写 OID master

这个图表——图表本身和两个带注释的标签——都非常简单。由于分支和合并,大多数真实的图都非常打结。即使我们只画另一个相当简单的,我们也可以得到这样的东西:

                  tag-A       tag-B
                    v           v
         o--o--...--o           o--o   <-- branch1
        /            \         /
...-o--o              o--...--o--o   <-- branch2
        \            /
         o--o--...--o
                ^
              tag-C

在这种情况下, tag-A 将“更接近” 的尖端branch2,并且应该git describe选择。内部的实际算法git describe非常复杂,我不清楚在一些更棘手的情况下它选择了哪个标签:Git 没有简单的方法来加载整个图并进行广度优先搜索,而且代码非常广告特设的。但是,很明显tag-B是不合适的,因为它指向了一个无法通过开始branch2和向后工作来实现的提交。

现在我们可以更仔细地查看您的最后一个示例。我克隆了存储库并这样做了:

$ git log --decorate --graph --oneline origin/1.5.4.1-final 1.5.4.1-master_20161201 
* b9436bdf (origin/1.5.4.1-final) Replace unmainted Flasher with NodeMCU PyFlasher
* 46028b25 Fix relative path to firmware sources
* 6a485568 Re-organize documentation
* f03a8e45 Do not verify the Espressif BBS cert
* 1885a30b Add note about frozen branch
* 017b4637 Adds uart.getconfig(0) to get the current uart parameters (#1658)
* 12a7b1c2 BME280: fixing humidity trimming parameter readout bug (#1652)
* c8176168 Add note about how to merge master-drop PRs
* 063cb6e7 Add lua.cross to CI tests. (#1649)
* 384cfbec Fix missing dbg_printf (#1648)
* 79013ae7 Improve SNTP module: Add list of servers and auto-sync [SNTP module only] (#1596)
* ea7ad213 move init_data from .text to .rodata.dram section (#1643)
* 11ded3fc Update collaborator section
* 9f9fee90 add new rfswitch module to handle 433MHZ devices (#1565)
* 83eec618 Fix iram/irom section contents (#1566)
* 00b356be HTTP module can now chain requests (#1629)
* a48e88d4 EUS bug fixes (#1605)
| *   81ec3665 (tag: 1.5.4.1-master_20161201) Merge pull request #1653 from nodemcu/dev-for-drop
| |\  
| |/  
|/|   
* | 85c3a249 Fix Somfy docs
* |   016f289f Merge pull request #1626 from tae-jun/patch-2
|\ \  
| * | 58321a92 Fix typo at rtctime.md
|/ /  
* | 1032e9dd Extract and hoist net receive callbacks

请注意, commit的b9436bdf尖端没有commit作为祖先。Tag指向对象,该对象是一个带注释的标签对象,而该对象又指向 commit :origin/1.5.4.1-final81ec36651.5.4.1-master_201612014e41546281ec3665

$ git rev-parse 1.5.4.1-master_20161201
4e415462bc7dbc2dc0595a8c55d469740d5149d6
$ git cat-file -p 1.5.4.1-master_20161201
object 81ec3665cb5fe68eb8596612485cc206b65659c9
...

您希望找到的标签1.5.4.1-master_20161201,符合描述 commit的条件b9436bdf。在这个特定的图表中没有提交是 commit 的后代81ec3665

使用git log --all --decorate --oneline --graph,我发现在完整图表中有一些这样的提交,例如b96e3147

* | | e7f06395 Update to current version of SPIFFS (#1949)
| | *   c8ac5cfb (tag: 2.1.0-master_20170521) Merge pull request #1980 from node mcu/dev
| | |\  
| |_|/  
|/| |   
* | |   787379f0 Merge branch 'master' into dev
|\ \ \  
| | |/  
| |/|   
| * | 22e1adc4 Small fix in docs (#1897)
| * |   b96e3147 (tag: 2.0.0-master_20170202) Merge pull request #1774 from node mcu/dev
| |\ \  
| * \ \   81ec3665 (tag: 1.5.4.1-master_20161201) Merge pull request #1653 from nodemcu/dev-for-drop
| |\ \ \  
| * | | | ecf9c644 Revert "Next 1.5.4.1 master drop (#1627)"

b96e3147它本身有自己的(带注释的)标签,所以这就是git describe应该和确实列出的内容:

$ git describe b96e3147
2.0.0-master_20170202

最终的问题是,任何给定的提交对之间都没有简单的“祖先/后代”关系。 一些提交确实有这样的关系。其他人只是兄弟姐妹:他们有一些共同的祖先。如果您有一个包含多个根提交的图表,还有一些可能没有共同的祖先。

在任何情况下,git describe通常都需要内部箭头的方向相反:它必须找到一个标记的提交,以便要描述的提交是该标记的后代。它实际上无法做到这一点,因此它将问题转化为它可以解决的问题:从所有标记提交的集合中找到一些标记提交,这样标记提交是所需提交的祖先——然后,计数从所需提交向后移动到此标记提交所需的跃点数。

于 2019-08-28T16:58:25.487 回答
3

Git 2.26(2020 年第一季度)(v2.26.0-rc0 及更高版本)改变了可访问的内容:git describe在具有多个根提交的存储库中,“”有时会放弃寻找描述给定提交的最佳标签,但过早,这一直是调整。

describe: 搜索标签时不要过早中止

签字人:Benno Evers
签字人:Junio C Hamano

在为候选标记搜索提交图时,git-describe 只要只剩下一个活动分支并且它已经找到一个带注释的标记作为候选标记,它将立即停止。

只要所有分支最终都连接回一个共同的根,这种方法就可以很好地工作,但是如果在没有共同祖先的分支中找到标签

              B
              o----.
                    \
      o-----o---o----x
      A

一个分支上的搜索可能会因为在另一个独立分支上找到标签而过早终止。
这种情况并不像听起来那么晦涩难懂,因为有限深度的克隆通常会在提交图中引入许多独立的“死胡同”。

状态的帮助文本git-describe非常清楚地表明,在描述提交时D,附加到发出tag X的数字应该对应于git log X..D.

因此,此提交修改了停止条件,仅当只剩下一个分支要搜索并且所有当前最佳候选者都是该分支的后代时才中止搜索。

对于具有单个根的存储库,此条件始终为真:当搜索减少到单个活动分支时,当前提交必须是所有候选标记的祖先。
这意味着在常见情况下,此更改不会对性能产生负面影响,因为将遍历与以前相同数量的提交。

于 2020-03-09T17:16:05.590 回答