查找 git "branch" 没有分支 name/label 在该分支上有提交散列

Find git "branch" with no branch name/label having a commit hash on that branch

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

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

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

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

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

假设我有 EF 的散列。如何恢复 H

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

的哈希值

但是在这里,我知道我要找的分店在哪里!我只需要从我知道的提交中关注 children。我无法相信在 Git 中没有办法做到这一点。 Git 执行这不是一个简单的操作吗?给定 E,它不需要知道 F 吗?看来我应该可以使用这样的操作来找到E-F-G-H分支的结尾。

顺便说一句,如果您不提供 git log哈希其中之一。分支上缺少标签意味着 Git 忽略该分支。所以 git log --all 不会显示那些提交。我一直认为 git log --all 会从字面上显示对存储库执行的所有提交。但似乎并非如此。如果有人可以反驳这一点,或者告诉我如何强制 git log 向我展示那些孤立的提交,那将非常有帮助。

只在本地搜索:

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_HASH} {HASH_OF_PARENT_1} {HASH_OF_PARENT_2} . . . 格式向您显示以 COMMIT_F 作为父项的提交列表。这将为您提供 COMMIT_F 的所有直系子代,这应该有助于您的搜索。

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

这是一个旁白(因此应该是一个评论,但我需要格式化,还有更多space——好的,far更多space——比评论里的要多):

BTW, I was shocked to learn that there is no way to get the Git log entries for nodes E, F, G or H above if you don't supply git log with the hash for one of them. The lack of a label on a branch means that Git ignores that branch. So git log --all will not show those commits. I always figured that git log --all would literally show all commits performed against the repository.

这在某些 other 版本控制系统中有意义,但在 Git:

中则不然
  • --all所有引用,不是所有提交;
  • Git finds 从给定的哈希 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 本身的引用日志,它也是 ref.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 向后工作形成的隐含分支。我们可以随时创建、销毁、and/or 移动分支而不更改任何现有提交。这些名称只是充当标签,将 指向 图表。当名称是 分支名称 时,人们将导致并包括 指向该名称的提交称为“分支” .如果我们添加两个名称,一个指向 F',一个指向 H,提交 A 现在在四个分支上。没有这些名称,A 位于两个分支上。但是,如果我们对提交 C 进行分离式 HEAD 检出怎么办?那是 分支吗? 如果是,A 就在上面。

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

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

这就是 30 天的宽限期用于否则丢弃的提交的来源。 30 天的时间限制是某些特定 reflog 的 reflogExpireUnreachable 设置的结果。 90 天期限是 reflogExpire 设置的结果。请注意,这两个设置至少可能对每个 reflog 有两个值:存储在 gc.<em>pattern</em>.reflogExpire 中的时间值覆盖了一个存储在 gc.reflogExpire 中,当 ref name 的引用日志过期时,如果 patternnameThe documentation 是……对构成 pattern 的内容一无所知。它也未能正确描述 expireUnreachableexpire 超时之间的区别:

gc.reflogExpire
gc.<pattern>.reflogExpire
      git reflog expire removes reflog entries older than this time; defaults to 90 days. The value "now" expires all entries immediately, and "never" suppresses expiration altogether. With "<pattern>" (e.g. "refs/stash") in the middle the setting applies only to the refs that match the <pattern>.

gc.reflogExpireUnreachable
gc.<pattern>.reflogExpireUnreachable
      git reflog expire removes reflog entries older than this time and are not reachable from the current tip; defaults to 30 days. The value "now" expires all entries immediately, and "never" suppresses expiration altogether. With "<pattern>" (e.g. "refs/stash") in the middle, the setting applies only to the refs that match the <pattern>.

not reachable from the current tip 短语表示 Git 检查存储在 ref 中的实际值片刻。如果这标识了一个提交 导致返回 其哈希 ID 存储在 reflog 条目中的提交,Git 选择 expire 时间。如果它识别出 不会导致返回到 reflog 条目中的提交,则 Git 选择 expireUnreachable 时间。按照措辞,听起来 git gc 对此类条目进行了 两次 次查看,但实际上 git gc 只是假设“无法访问”的宽限期将小于或等于可到达的提交。

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

(我不确定 <pattern> 是如何工作的。如果没有在 Git 源代码中探索或进行实验,我的猜测是 Git 使用 glob-style在这里匹配,但即使是这样,我们应该想知道:在一端或两端是否有任何隐含的 *** glob?也就是说,refs/stash 真的是 **/refs/stash/**,还是它锚定在 refs and/or stash 端?我从来没有尝试调整我的 git gc-调用的 reflog 过期:默认设置很好。)


1因为 ref 被定义为 *以 refs/ 开头的东西,HEAD 不能不太可能成为裁判。但它仍然有一个 reflog,这意味着它是一个 ref。我们可以将其与 pseudorefs 进行比较,例如 ORIG_HEADCHERRY_PICK_HEADMERGE_HEAD 等,它们不会获得 reflog。 Git 文档在 HEAD 中有点 软,呃,关于 HEAD 是否算作 ref 模糊不清,在这里。

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

请注意,分支、标签、远程跟踪名称等都是一般形式的子集。名称以 refs/heads/ 开头的 ref 是分支名称。