首页 > 解决方案 > git log --reflog vs git reflog

问题描述

在玩过rmetc 等之后,我git reflog不再显示过去的提交。

但是,git log --reflog仍然能够显示出来。

git log --reflog如果不依赖,如何显示悬空提交git reflog

标签: gitgit-reflog

解决方案


TL;博士

Reflogs 不保存提交祖先:reflogs 保存名称更新历史记录。它是包含提交祖先 的提交图。git log想要一些起点,但在得到它们之后,它会查看提交图。 git log --reflog通常会更改起始点集,但如果您已删除所有 reflogs(或从未有过任何引用日志,例如,在裸克隆中),则不会:您将获得标准的单一起点,HEAD.

请注意,reflog 条目最终会在默认情况下在 90 天后过期,*图表永远不会过期。一个新的克隆没有 reflog 历史记录,但具有所有图形链接——并且git log --reflog仍然显示多个提交。


*某些条目的默认有效期为 30 天,大多数为 90 天,而对于特殊的 reflog refs/stash,则从不。详细信息超出了此答案的范围。


实验上:

rm .git/logs/HEAD

删除 HEAD reflog,但git reflog仍显示一些数据。另一方面:

rm -r .git/logs

删除所有 reflogs 之后git reflog什么都不显示。

在这一点上,人们可能会期望git log --reflog什么都找不到。但是,显然这使用了相同的“将HEAD自身添加为默认”行为。所以现在没有 reflogs,git log --reflog相当于:

git log HEAD

它显示了当前提交及其祖先。使用:

git log --no-walk --reflog

您只会看到由HEAD.

这意味着答案:

log --reflog如果它不依赖于 reflog,它会如何工作?

是它没有做任何普通不再做的事情git log

  • 当您向 提供特定的起始提交时git log,Git 会显示这些提交以及可从这些提交访问的提交,而不使用HEAD作为起点。(添加--no-walk使这一点更清楚。)

  • 当您提供任何特定的起始提交时,git log使用HEAD作为其起点。(再次,添加--no-walk使这一点更清楚。)

(当您确实有一些 reflog(这是正常情况)时,该--reflog参数提供 reflog 值作为起点,这将禁用“HEAD用作起点”操作。如果现在一切都有意义,您可以停在这里!)

潜在的混乱来源

重要的是,在使用 Git 时,要知道提交对您有什么作用,以及分支名称(如master)或reflog 条目对您master@{3}有什么作用。

每个 Git 提交都包含所有文件的完整快照,但这还不是全部。每个提交还包含一些元数据。这些元数据中的大部分——关于提交的信息——在git log输出中非常明显。这包括提交者的姓名、电子邮件地址、日期和时间戳,以及他们提供的日志消息。

每个提交本身也有一个唯一的哈希 ID。这个哈希 ID 本质上是提交的“真实名称”。这是 Git 在其所有提交和其他支持 Git 对象的大型数据库中查找实际提交对象的方式。

像这样的分支名称master只是保存一个特定提交的哈希 ID。根据定义,这一次提交是分支中的最后一次提交。但是一个提交,例如master分支中的最后一个提交,可以保存一个提交哈希 ID。每个提交在其元数据中都有一个哈希 ID 列表。这些是提交的父母

大多数提交只有一个父哈希 ID。这将这些提交形成简单的后向链。我们可以画出这样的链条:

... <-F <-G <-H   <-- master

如果我们使用大写字母来代表提交哈希 ID。这H是上一次提交的哈希 ID master。提交H本身在其元数据中包含早期提交的实际哈希 ID G。所以给定提交H,Git 可以使用这个哈希 ID 来查找提交G。这反过来又提供了 commit 的哈希 ID F

实际上,Git 可以让这条链条倒退。正常情况下就是git log这样。使用--no-walktell git log向我展示提交,但不要在它们的链条中倒退;只显示我通过命令行特别选择的提交。 因此,使用--no-walk,您将只看到您选择的提交,而不是他们的祖先。

Reflog 和分支名称一样,都包含哈希 ID。reflog 被组织成每个名称(分支名称、标签名称等)的一个日志,以及一个用于特殊名称的日志HEAD。这些至少目前存储在.git/logs目录中的普通文件中。每个日志都有条目(在这种情况下,每个文件一行),每个条目对应于名称在较早时间解析为的哈希 ID。您可以使用这些来访问较早的值,因此master@{1}告诉 Git 使用一步较早的值:在最近更新 name 之前master,它解析为某个哈希 ID;现在它解析为一些(可能不同的)哈希 ID;我们希望退一步。该名称master@{2}告诉 Git 我们想要后退两步的值。

请注意,这些是名称更新步骤,而不是向后提交箭头步骤。 有时master@{1}与 相同master~1master@{2}与 相同master~2,依此类推——但有时这些是不同的。后缀语法master~2master^2在提交图上使用 / 操作。后缀语法与 / 对 master 的 reflog 进行操作。master@{number}

(分支名称的当前值 ,master@{0}不在masterreflog 中,因为它master本身就在其中。更新master将采用当前值并将其添加到日志中,然后设置新值。)

你可以让 Git 使用git reflog. 如果根本没有 reflogs——如果你将它们全部删除就会出现这种情况——这里什么都不会出现,因为不再有任何保存的值。但是,所有名称仍然具有它们的值,并且HEAD仍然存在并包含分支名称,例如master.

更多细节

请注意,这种方式git log起作用,它一次只能真正显示一个提交。为了处理这个问题,它使用了一个优先队列。例如,您可以运行:

git log <hash1> <hash2> <hash3>

使用三个实际的哈希值,或者:

git log master develop feature/tall

它使用名称来查找哈希 ID,或者:

git log master master@{1} master@{2}

它使用两个 reflog 条目(加上分支名称)来查找哈希 ID。

在所有情况下,Git 都会将所有哈希 ID 插入优先级队列。

--reflog用作命令行参数告诉从reflogsgit log中获取所有值并将它们插入到队列中。

如果队列中没有任何内容,Git 会将解析结果插入HEAD到哈希 ID。

此时,队列可能不是空的,因为如果没有别的,我们通过解析 name 得到了一个哈希 ID HEAD1

git log命令现在进入一个循环,该循环一直运行到队列为空。这个循环的工作原理如下:

  • 从队列中取出最高优先级的提交。
  • 使用提供的任何选择类型参数git log来决定是否显示此提交。如果是,则显示提交。(例如,git log --grep选择显示提交,其日志消息包含给定的字符串或模式。)
  • 如果--no-walk生效,我们就完成了这个提交。--first-parent否则,根据标志和选择的任何历史简化选择此提交的部分或全部父项放入队列。

(请注意,如果提交现在或曾经在队列中,git log则不会将其放回队列中,因此您不会看到两次相同的提交。队列中的优先级受git log的排序选项的影响.)

因此,如果有 reflog --reflog,我们会从 reflog 条目中给出git log多个起点。如果没有任何 reflog,则git log使用其标准默认值:HEAD.

不管我们是否使用过--refloggit log现在都使用提交本身中的父链接来遍历提交。这不取决于我们提供的参数,当然对于--no-walk. 2


1如果根本没有提交,或者我们处于由 . 创建的“未出生分支”上git checkout --orphan,此时队列将为空,但git log在尝试解析 name 时会出错HEAD

2此外,使用-gor--walk-reflogs参数,git log不会遍历提交图。相反,它会遍历 reflog 条目。

--walk-reflogs和之间的区别在于--reflog--walk-reflogs整个优先级队列的东西被完全抛弃了:Git查看 reflogs。这也改变了一些输出格式。事实上,git reflog真的只是运行git log -g


推荐阅读