【问题标题】:Git failed to create a branch on a tagGit 无法在标签上创建分支
【发布时间】:2021-12-08 00:09:31
【问题描述】:

我有以下分支:

xxx@box:~/src$ git branch
  jira_6500
* main
xxx@box:~/src$ git rev-parse main
bfd271932228f8ce33b68b82ffee5ee3b2386a17
xxx@box:~/src$ git rev-parse jira_6500
bfd271932228f8ce33b68b82ffee5ee3b2386a17
xxx@box:~/src$

我尝试从标签v2.6.0-rc3 创建一个新分支,如下所示:

xxx@box:~/src$ git rev-parse v2.6.0-rc3
ff8db8992102ca7ce76f55169d06173c888c9447

xxx@box:~/src$ git checkout -b test001 v2.6.0-rc3
Switched to a new branch 'test001'
xxx@box:~/src$ git branch
  jira_6500
  main
* test001

然后我检查新分支的 rev 哈希。我希望与标签v2.6.0-rc3 相同。但事实并非如此。与jira_6500 分支相同。

xxx@box:~/src$ git rev-parse test001
bfd271932228f8ce33b68b82ffee5ee3b2386a17

我做了与以下线程相同的操作。我记得我以前做过。

rev 哈希怎么会出错?

How to create a new branch from a tag?

【问题讨论】:

  • 标签的哈希值永远不会与标签指向的提交的哈希值相同。这是一种不同的对象。见:Get the commit hash for a tag
  • 更正:仅适用于带注释的标签。轻量级标签 do 返回与它们指向的提交相同的哈希值。

标签: git git-branch git-tag


【解决方案1】:

您的分支创建正在按照您希望的方式工作。您所看到的原因与 Git 标记的内部结构有关,这有点奇怪。

Git 在其小小的 gitty 心中,都是关于 commits 的,它们由散列 ID 编号,通常以十六进制表示:例如,bfd271932228f8ce33b68b82ffee5ee3b2386a17

为了使提交工作,Git 需要另外两个内部支持对象,Git 称之为 treesblob。这些也有哈希 ID。您通常不会看到这些哈希 ID:它们不会“泄露”太多。 (不过,Blob 哈希 ID 确实出现在 git diff 输出中的 index: 行中,如果您查找它们,可以找到树哈希:这些都不是隐藏的。他们只是不get all up in your face 提交哈希 ID 的方式。)

标签,在 Git 中,标记一个提交——但你可以在这里选择:轻量级标签直接保存一个提交哈希 ID,所以如果你有提交 bfd27...,你可以创建一个轻量级存储该哈希 ID 的标签。但是,如果您想存储更多信息,Git 有一个支持对象,称为标记对象带注释的标记对象。我们让 Git 创建其中一个对象,存储额外的数据(例如 PGP 签名或其他数据),并且该对象获得自己的唯一哈希 ID,例如 ff8db8992102ca7ce76f55169d06173c888c9447

标签对象本身与注释数据一起存储 commit 哈希 ID bfd271932228f8ce33b68b82ffee5ee3b2386a17。由于这些哈希 ID 各自唯一标识对应的对象,Git 可以使用 tag ID ff8db... 找到 commit 对象,通过读取标签对象并找到存储的提交哈希 ID。 (不可能走另一条路:commit bfd27... 在我们创建任何指向它的标签之前就已经确定了,因此我们无法添加那些标签 ID 稍后提交。因此,像往常一样使用 Git,我们必须向后工作,从较新的对象到较旧的对象。)

使用git rev-parse v2.6.0-rc3,您可以获得带注释的标签对象的哈希ID。从这里 Git 可以找到提交。标签名称可以直接指向一个提交——这再次使它成为一个轻量级标签——或者指向一个标签对象,使标签名称成为一个带注释的标签。 Git 可以通过任何一种方式找到提交。

Branch 名称与标签名称不同,受到限制:它们可能只包含一些(现有)commit 的哈希 ID。所以当创建一个新的分支名称时,如果你给 Git 一个带注释的标签对象的哈希 ID,或者一个解析 is 的带注释的标签对象的名称,Git 会继续跟随带注释的标签对象到它的目标,需要是一个提交。1

这正是您在这里看到的。创建分支名称遵循标记提交的标记。其他分支名称也指向同一个提交——这很好也很正常。当您使用git checkoutgit switch 获得这些分支名称之一并进行new 提交时,Git 将像往常一样进行新提交,并且作为@ 的最后一步987654334@,会将新提交的hash ID写入当前分支名,导致分支前进。

使用git checkout v2.6.0-rc3git switch --detach v2.6.0-rc3 检出标签将使Git 进入分离的HEAD 模式,其中HEAD 包含提交的原始哈希ID。在这种情况下,进行新提交将新提交的哈希 ID 直接存储在特殊名称 HEAD 中,而不是任何分支名称中。这意味着重新附加HEAD——它用分支名称而不是提交哈希ID覆盖HEAD存储槽——“丢失”新的提交,这就是你通常不做新工作的原因在 detached-HEAD 模式下。2

这里要提到最后一件事,那就是git rev-parse 有很多句法技巧来处理这个问题。它们都包含在 the gitrevisions documentation 中,但在这里对相关的快速概述很有用:

  • git rev-parse v2.6.0-rc3 只是为您获取 v2.6.0-rc3 解析为的任何 ID:在这种情况下,refs/tags/v2.6.0-rc3 解析为带注释的标签。

  • git rev-parse v2.6.0-rc3^{commit} 找到与v2.6.0-rc3 关联的commit:也就是说,如果这是一个标签,它会剥离标签并要求结果是一个提交。

  • git rev-parse v2.6.0-rc3^{tree} 找到与v2.6.0-rc3 关联的:也就是说,如果这是一个标签,它会剥离标签;如果这是一个提交,它会找到存储在该提交中的顶级树in;它要求最终结果是树的哈希 ID。

  • git rev-parse v2.6.0-rc3^{} 查找与v2.6.0-rc3 关联的哈希 ID,如果是标签,则剥离标签(然后成功停止并生成哈希 ID,无论找到的对象类型如何)。

在这种情况下,git branch test001 v2.6.0-rc3git checkout -b test001 v2.6.0-rc3 在内部具有与使用 v2.6.0-rc3^{commit}git rev-parse 相同的效果。

这些语法技巧适用于大多数 Git 命令:在任何可能需要哈希 ID 的地方,您都可以使用名称,并且您提供的任何名称都会经过 git rev-parse 用来将其转换为哈希 ID 的相同过程。


1带注释的标签可以直接指向树或blob对象。如果这样做,则不能使用它们来创建新的分支名称。带注释的标签对象也可以包含另一个带注释的标签对象的哈希ID作为它们的目标哈希ID;在这种情况下,Git 会继续间接寻址,直到找到最终对象。这种重复的间接方式称为 peeling 标签,其概念来自于一层一层剥洋葱,直到你找出里面是什么。当然,就洋葱而言,当你剥去所有层时,除了气味之外什么都没有了。 ?

2这里的例外情况包括:

  • git rebase 故意使用这种模式来构建一个新的提交链。完成后,git rebase 会强制要重新设置的分支名称指向新提交的最后一个。

  • 如果你喜欢,你可以在这种模式下工作一段时间,然后自己创建一个新的分支名,或者强制一些现有的分支名指向新的提交。

  • 如果您确实在 detached-HEAD 模式下工作不小心,您可以使用 git reflog 找到您想要的提交并创建一个分支名称(或标签名称!)来找到它。

Git 主要只是在这里提供机制,您可以在此基础上构建任何您喜欢的东西。

【讨论】:

  • 非常感谢您的详细回答。
  • 顺便说一句,^{type} 被称为剥离运算符。
  • @smwikipedia:我在脚注 1 中或多或少说了同样的话。但这是正确的:我们可以使用^{} 进行通用剥离,或者使用^{type} 剥离到特定的对象类型。
猜你喜欢
  • 2016-01-27
  • 1970-01-01
  • 1970-01-01
  • 2011-06-03
  • 2021-08-17
  • 1970-01-01
  • 2021-10-05
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多