查找没有分支名称/标签的 git "branch" 在该分支上具有提交哈希

时间:2021-02-23 17:25:10

标签: git branch

有没有办法让 Git 列出特定提交的子提交?也就是说,如果我有 Git 分支:

A---B---C---D---E

而且我知道 C 的提交哈希,有没有办法从 D 获取 C

这里更大的问题是我丢失了一个分支,因为我移动了唯一指向它的分支标签。所以我有这样的事情:

A---B---C---D (master, moved-branch-label)
     \
      \---E---F---G---H

假设我有 EF 的哈希值。我如何恢复 H

这个有a similar existing question。最大的区别在于 OP 不知道 EFGH 中的任何一个。在这种情况下,唯一的答案是使用 reflog 回溯您的步骤并手动查找 H 的哈希值。

但是在这里,我知道我要找的分支在哪里!我只需要跟随我所知道的提交中的孩子们。我不敢相信在 Git 中没有办法做到这一点。对于 Git 来说,这难道不是一个简单的操作吗?给定 E,它不需要知道 F 吗?看来我应该可以用这样的操作找到E-F-G-H分支的结尾。

顺便说一句,我很震惊地了解到,如果您不为 git log 提供其中一个的哈希值,则无法获取上述节点 E、F、G 或 H 的 Git 日志条目。分支上缺少标签意味着 Git 会忽略该分支。所以 git log --all 不会显示这些提交。我一直认为 git log --all 会从字面上显示对存储库执行的所有提交。但似乎并非如此。如果有人可以反驳这一点,或者告诉我如何强制 git log 向我展示那些孤立的提交,那将非常有帮助。

2 个答案:

答案 0 :(得分:4)

仅在本地搜索:

git reflog --parents | grep {HASH_OF_COMMIT_F}

仅搜索您的远程存储库:

git reflog --parents --remotes | grep {HASH_OF_COMMIT_F}

在本地和远程存储库中搜索:

git reflog --parents --all | grep {HASH_OF_COMMIT_F}

这些将以 COMMIT_F 格式显示以 {COMMIT_HASH} {HASH_OF_PARENT_1} {HASH_OF_PARENT_2} . . . 作为父项的提交列表。这将使您获得 COMMIT_F 的所有直系子代,这将有助于您进行搜索。

请注意,使用了缩短的提交哈希(即前 7 个字符)

答案 1 :(得分:0)

这是一个旁注(因此应该是一个评论,但我需要格式化,还有更多的空间——好吧,更多的空间——比评论中的空间更多):

<块引用>

顺便说一句,我很震惊地了解到,如果您不为 git log 提供其中一个的哈希值,则无法获取上述节点 E、F、G 或 H 的 Git 日志条目。分支上缺少标签意味着 Git 会忽略该分支。所以 git log --all 不会显示这些提交。我一直认为 git log --all 会从字面上显示对存储库执行的所有提交。

这在一些其他版本控制系统中是有意义的,但在 Git 中却不是:

  • --all 指的是所有引用,而不是所有提交;
  • Git 通过从给定的哈希 ID 开始查找提交——可能来自参考,或者只是你在命令行中列出的原始哈希 ID——然后在提交本身内向后工作 ;
  • 每次提交都在零个或多个分支上。在大多数存储库中,(单个)根提交位于每个分支上。

“丢弃”提交,例如 E-F-G-H,在 Git 中自然发生:它们是 git rebase 的结果,例如,在将 E-F-G-H 链复制到一些新的集合之后- 和改进的提交。例如,您可能希望 E 的副本的父项是 D 而不是 B,并将旧的 F+G 压缩在一起,以得到:

           E'-FG-H'   <-- somebranch
          /
A--B--C--D   <-- master
    \
     E--F--G--H   ??? [was somebranch, earlier]

git reflog 找到这些的原因和方式是每个 ref 都有一个 log 它曾经保存的值。所以在上面的例子中,somebranch 的 reflog 将显示在某一时刻,它命名为 commit E;在另一个——可能就在之后——它命名为 commit F。这将针对 GH 重复,然后变基操作将一次性将名称 somebranch 拉过来以提交 H'E'-FG-H' 链是由 git rebase 使用 分离 HEAD 模式构建的,因此唯一包含这些哈希 ID 的引用日志是 HEAD 本身的引用日志,它也是一个参考1

请注意,“挤压提交”FG 本身是通过首先制作提交 F' 的副本 F,然后将该副本推到一边以构建 FG 来构建的,因此我们可以很好地将上述内容绘制为:

             F'   ???
            /
           E'-FG-H'   <-- somebranch
          /
A--B--C--D   <-- master
    \
     E--F--G--H   ??? [was somebranch, earlier]

事实上,Git 中分支的整个概念往好里说是可疑的,往坏了说,是胡说八道。请注意在上图中,提交 A 如何在“所有分支”上,包括通过从现在丢弃的提交 H 向后工作形成的隐含分支。我们可以随时创建、销毁和/或移动分支,而无需更改任何现有提交。名称只是充当标签,指向进入图形。当名称是分支名称时,人们将导致并包括指向的提交称为“分支”。如果我们添加两个名称,一个指向 F',一个指向 H,则提交 A 现在位于四个分支上。如果没有这些名称,A 位于两个分支上。但是,如果我们对提交 C 进行 detached-HEAD 检出呢?那是一个分支吗?如果是这样,A 在它上面。

与此同时,无论何时何地,创建临时对象(包括临时提交)的想法在 Git 中无处不在; 显示所有对象对于完成任何事情至关重要,因为对象太多了。 Git 的垃圾收集器 git gc 会在一段时间后删除它们(如果它们确实未使用)。

git gc 还会删除旧的 reflog 条目。 reflog 条目具有创建时间戳,一段时间后(默认情况下为 30 天或 90 天,尽管您可以调整这两者),reflog 条目被认为足够陈旧而无趣,并被删除。一旦删除了某些内部 Git 对象的all 提及,并且满足了其他几个条件,git gc 将删除该对象。这就是 Git 在各种 Git 操作后在后台剥离 git gc --auto 的原因:清理剩余的垃圾。

这就是以其他方式丢弃的提交的 30 天宽限期的来源。 30 天的时间限制是某些特定引用日志的 reflogExpireUnreachable 设置的结果。 90 天期限是 reflogExpire 设置的结果。请注意,这两个设置至少可能每个 reflog 有两个值:当 ref {{1 的 reflog 过期时,存储在 gc.pattern.reflogExpire 中的时间值会覆盖 gc.reflogExpire 中存储的时间}},如果 namepattern 匹配。 The documentation 是 ... 对此处 name 的构成不甚了解。它也未能正确描述 patternexpireUnreachable 超时之间的区别:

<块引用>

expire
gc.reflogExpire
git reflog expire 删除早于此时间的引用日志条目; 默认为 90 天。值“now”使所有条目过期 立即,“从不”完全抑制过期。和 “”(例如“refs/stash”)在中间设置适用 仅适用于与 匹配的引用。

gc.<pattern>.reflogExpire
gc.reflogExpireUnreachable
git reflog expire 删除早于这个时间的 reflog 条目并且 无法从当前提示到达;默认为 30 天。这 值“now”立即使所有条目过期,“never”抑制 完全到期。使用“”(例如“refs/stash”)在 中间,该设置仅适用于匹配 <模式>。

notreachable from the current tip 表示 Git 会检查当前存储在 ref 中的实际值。如果这标识了一个提交,该提交返回哈希 ID 存储在 reflog 条目中的提交,则 Git 选择 gc.<pattern>.reflogExpireUnreachable 时间。如果它标识了一个提交,不会返回 reflog 条目中的提交,则 Git 会选择 expire 时间。顾名思义,听起来 expireUnreachable 会同时查看此类条目的两次,但实际上 git gc 只是假设“无法访问”的宽限期将小于或等于用于可达的提交。

正如所有这些暗示的那样,可达性是 Git 中的一个核心概念。太多的 Git 介绍中没有正确教授它。要获得好的解释,请参阅 Think Like (a) Git

(我自己不知道 git gc 是如何工作的。如果没有在 Git 源代码中摸索或进行实验,我的猜测是 Git 在这里使用了全局样式匹配,但即使是这样,我们也应该想知道:在一端或两端是否有任何隐含的 <pattern>* globs?也就是说,** 真的是 refs/stash,还是锚定在 {{1} } 和/或 **/refs/stash/** 结束?我从未尝试调整我的 refs 调用的引用日志过期时间:默认值很好。)


1由于ref被定义为*以stash开头的东西,git gc不能完全是一个引用。但它仍然有一个 reflog,这意味着它是一个 ref。我们可以将其与 pseudorefs 进行比较,例如 refs/HEADORIG_HEAD 等,它们不会获得 reflog。 Git 文档在 CHERRY_PICK_HEAD 中有点软性,呃,关于 MERGE_HEAD 是否算作参考,这里是模糊的。

事实上,HEAD——像这样全部大写——是特别的。有一种象征性的方式来引用它,使用字符 HEAD,这可能有助于强调它的特殊性。不过,将 HEAD 用于 @ 首次出现在 Git 1.8.5 中,并且随着时间的推移修复了各种故障。这种特殊性体现在其他方面:例如,@ 永远不会打包,如果保存它的文件消失了,Git 就不再认为存储库存储库:文件的存在是内部“这是一个 Git 存储库”测试中的三个标准之一。此外,HEAD 现在是每个工作树的引用,但这也适用于,例如,二等分引用。由于添加了 HEAD,每个工作树引用的整个概念在 Git 2.5 中都是新的。有些事情在 Git 2.7 中得到了一些纠正,并且在 Git 2.14 和 2.15 之前没有修复一些影响 HEAD 的讨厌的每个工作树项目。出于这个原因,如果您的 Git 不是至少 2.15,我建议您注意 git worktree

请注意,分支、标签、远程跟踪名称等都是一般形式的子集。名称以 git gc 开头的引用是分支名称。