使用 git describe 使 Git 在分支上显示正确的标签

Mar*_*tör 5 git github git-describe

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
Run Code Online (Sandbox Code Playgroud)

为什么是这样?

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

~/Data/NodeMCU/nodemcu-firmware (1.5.4.1-final) > git describe
0.9.6-dev_20150627-953-gb9436bdf
Run Code Online (Sandbox Code Playgroud)

该分支创建之后此注释标签https://github.com/nodemcu/nodemcu-firmware/releases/tag/1.5.4.1-master_20161201只提交了一把落在因为分支。

tor*_*rek 12

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

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

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

在我们开始之前你需要知道什么:

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

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

  • 标签是更一般实体的特定形式,简称为参考参考。分支名称 likemaster也是引用,并且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> |
                                      +----------------------+
Run Code Online (Sandbox Code Playgroud)

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

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

Commit和标签对象是特别有趣在这里的,因为标签对象包含的OID那就是目标的标签,以及承诺的对象包含每个的OID父母的承诺。

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

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

 ... <commit>  <--  +--------+
                    | commit |  <-- <commit> ...
 ... <commit>  <--  +--------+
Run Code Online (Sandbox Code Playgroud)

缩小后,我们会看到所有提交的整体有向无环图DAG。同时,存储在分支名称中的 OID——以及保存提交哈希的任何其他引用中——充当该图中的入口点,我们可以从这些位置开始,然后继续跟踪父链接。

注释的标签是一个标签引用——一个或多或少的轻量级标签——指向一个标签对象。如果底层标记对象随后指向提交,则它也充当提交 DAG 的入口点。不过,标签对象可以直接指向树或斑点,或其他标签对象。剥离标签的过程是指跟随指向另一个标签对象的带注释的标签。我们只是继续跟踪,直到我们到达某个非标签对象:这是这个分层标签的最终目标。如果最终目标是提交,则这是 DAG 的另一个入口点。

因此,在最后,我们通常有一个分支名master指向最后在提交的大多是线性串承诺:

... <-o <-o <-o <-o   <--master
Run Code Online (Sandbox Code Playgroud)

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

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

  tag:v1.1  tag:v1.2
      |       |
      v       v
      T       T
      |       |
      v       v
... <-o <-o <-o <-o   <--master
Run Code Online (Sandbox Code Playgroud)

其中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它会找到所有或至少一些有趣的引用(带注释的标签,或所有标签,或所有引用)的目标。当它在我们的示例中执行这个“有趣的引用”时,它会找到两个提交,我们将用 标记*

  tag:v1.1  tag:v1.2
      |       |
      v       v
      T       T
      |       |
      v       v
... <-* <-o <-* <-o   <--master
Run Code Online (Sandbox Code Playgroud)

现在它从我们想要描述的提交开始:master. 从提交,也可以逆向操作一个跳达到承诺这是一个从出演v1.2。或者,它可以向后工作跳以找到从v1.1.

由于v1.2是“closer”,这是git describe将使用的带注释的标签名称。与此同时,它确实必须从 往回走一跳master。所以输出将是:

v1.2-1-g<hash>
Run Code Online (Sandbox Code Playgroud)

哪里是master指向的提交的缩写 OID 。

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

                  tag-A       tag-B
                    v           v
         o--o--...--o           o--o   <-- branch1
        /            \         /
...-o--o              o--...--o--o   <-- branch2
        \            /
         o--o--...--o
                ^
              tag-C
Run Code Online (Sandbox Code Playgroud)

在这种情况下,标记-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
Run Code Online (Sandbox Code Playgroud)

请注意, commitb9436bdf的尖端origin/1.5.4.1-final没有commit81ec3665作为祖先。Tag1.5.4.1-master_20161201指向 object 4e415462,它是一个带注释的标记对象,它又指向 commit 81ec3665

$ git rev-parse 1.5.4.1-master_20161201
4e415462bc7dbc2dc0595a8c55d469740d5149d6
$ git cat-file -p 1.5.4.1-master_20161201
object 81ec3665cb5fe68eb8596612485cc206b65659c9
...
Run Code Online (Sandbox Code Playgroud)

您希望找到的标签1.5.4.1-master_20161201,符合描述 commit 的条件b9436bdf。在这个特定的图中没有commit 是 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)"
Run Code Online (Sandbox Code Playgroud)

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

$ git describe b96e3147
2.0.0-master_20170202
Run Code Online (Sandbox Code Playgroud)

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

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

  • 哇,只是哇!远远超出了我一次性消化的范围:)在一个已删除的答案中(并非所有人都可以看到这些),有人建议“如果你想要更好的 git 描述你的功能分支(例如 dev)中的结果,你应该在每个新分支之后分叉 dev 分支标签。” (2认同)
  • 绝对惊人的答案! (2认同)