TL;DR
Reflogs 不保存提交祖先:reflogs 保存名称更新历史记录。它是保存提交祖先的提交图。 git log 想要一些起点,但是在得到它们之后,它会查看提交图。 git log --reflog 通常会更改起始点集,但如果您已删除所有 reflog(或从未有过任何引用日志,例如,在裸克隆中),则不会:您将获得标准的单一起点,HEAD .
请注意,reflog 条目最终会过期,默认情况下会在 90 天后过期,* 但 graph 永远不会过期。一个新的克隆没有 reflog 历史记录,但具有所有图形链接——并且git log --reflog 仍然显示多个提交。
*某些条目的默认有效期为 30 天,大多数为 90 天,而对于 refs/stash 的特殊 reflog,则从不。详细信息超出了此答案的范围。
长
实验性:
rm .git/logs/HEAD
删除了 HEAD reflog,但git reflog 仍然显示一些数据。另一方面:
rm -r .git/logs
删除所有引用日志,之后 git reflog 不显示任何内容。
此时,人们可能希望git log --reflog 找不到任何东西。但是,显然这使用了相同的“将HEAD 本身添加为默认”行为。所以现在没有引用日志,git log --reflog 相当于:
git log HEAD
显示当前提交及其祖先。使用:
git log --no-walk --reflog
您只会看到HEAD 标识的一个提交。
这意味着答案:
log --reflog 如果不依赖 reflog,它是如何工作的?
是它没有做任何普通的git log 不再做的事情:
(当您确实有一些 reflog(这是正常情况)时,--reflog 参数提供 reflog 值作为起点,这会禁用“使用 HEAD 作为起点”操作。如果现在一切都有意义,你可以在这里停下来!)
潜在的混乱来源
在使用 Git 时,了解 commit 为您做了什么,以及像 master 或 reflog 条目这样的分支名称 是很重要的 喜欢master@{3},为你服务。
每个 Git 提交都包含您所有文件的完整快照,但这还不是全部。每个提交还包含一些元数据。这些元数据中的大部分——关于提交的信息——非常明显,因为它显示在git log 输出中。这包括提交者的姓名、电子邮件地址、日期和时间戳,以及他们提供的日志消息。
每个提交本身也有一个唯一的哈希 ID。这个哈希 ID 本质上是提交的“真实名称”。这就是 Git 在其包含所有提交和其他支持 Git 对象的大型数据库中查找实际提交对象的方式。
像master 这样的分支名称仅包含一个特定提交的哈希 ID。根据定义,这一次提交是分支中的 last 提交。但是一个提交,比如master 分支中的最后一个,也可以保存一个提交哈希ID。每个提交在其元数据中都有一个哈希 ID 列表。这些是提交的父母。
大多数提交只有一个父哈希 ID。这将这些提交形成简单的后向链。我们可以画出这样的链条:
... <-F <-G <-H <-- master
如果我们使用大写字母来代替提交哈希 ID。这里H 是master 上last 提交的哈希ID。提交H 本身在其元数据中包含先前提交G 的实际哈希ID。所以给定提交H,Git 可以使用这个哈希ID 来查找提交G。这反过来又提供了提交 F 的哈希 ID。
实际上,Git 可以让这条链倒退。这就是git log 通常所做的。使用--no-walk 告诉git log:向我展示提交,但不要向后遍历它们的链;只显示我通过命令行特别选择的提交。 因此,使用--no-walk,您将只看到您选择的提交,而不是它们的祖先。
Reflogs 和分支名称一样,都包含哈希 ID。 reflog 被组织成每个名称(分支名称、标签名称等)的一个日志,以及一个用于特殊名称 HEAD 的日志。这些至少目前存储在.git/logs 目录中的普通文件中。每个日志都有条目(在这种情况下,每个文件一行),每个条目对应于名称在较早时间解析为的哈希 ID。您可以使用这些来访问较早的值,因此 master@{1} 告诉 Git 使用一步较早的值:在最近更新名称 master 之前,它解析为某个哈希 ID;现在它解析为一些(可能不同的)哈希 ID;我们希望退一步。名称 master@{2} 告诉 Git 我们想要后两步的值。
请注意,这些是名称更新步骤,而不是向后提交箭头步骤。 有时master@{1} 与master~1 相同,master@{2} 与master~2 相同,等等——但有时这些是不同的。后缀语法 master~2 或 master^2 在提交图上与 / 一起操作。后缀语法master@{<em>number</em>} 在主引用日志上使用 / 操作。
(分支名称master@{0} 的当前值不在master reflog 中,因为它在master 本身中。更新master 将采用当前值并将其添加到日志中,然后设置新值。)
您可以使用git reflog 让 Git 溢出部分或全部 reflog 的内容。如果根本没有 reflogs——如果你将它们全部删除就会出现这种情况——这里什么都不会出现,因为不再有任何保存的值。但是,所有名称仍然有它们的值,HEAD 仍然存在并包含一个分支名称,例如master。
更多细节
注意git log 的作用方式,它一次只能真正显示一个提交。为了处理这个问题,它使用priority queue。例如,您可以运行:
git log <hash1> <hash2> <hash3>
使用三个实际的哈希,或者:
git log master develop feature/tall
使用名称来查找哈希 ID,或者:
git log master master@{1} master@{2}
它使用两个 reflog 条目(加上分支名称)来查找哈希 ID。
在所有情况下,Git 都会将所有哈希 ID 插入优先级队列。
使用--reflog 作为命令行参数告诉git log 从引用日志中获取所有值并将它们插入到队列中。
如果队列中没有任何内容,Git 会将解析 HEAD 的结果插入到哈希 ID。
此时,队列大概不是空的,因为如果不出意外,我们通过解析名称HEAD得到一个哈希ID。1
git log 命令现在进入一个循环,该循环一直运行到队列为空。这个循环的工作原理如下:
- 从队列中取出最高优先级的提交。
- 使用提供给
git log 的任何选择类型参数来决定是否显示此提交。如果是,则显示提交。 (例如,git log --grep 选择显示提交,其日志消息包含给定的字符串或模式。)
- 如果
--no-walk 生效,我们就完成了这个提交。否则,根据--first-parent 标志和选择的任何历史简化选择此提交的部分或全部父项放入队列。
(请注意,如果一个提交现在或曾经在队列中,git log 不会将其放回队列中,因此您不会看到相同的提交两次。队列中的优先级是受git log 的排序选项影响。)
因此,对于--reflog,如果有引用日志,我们会从引用日志条目中给git log 多个起点。如果没有任何引用日志,git log 使用其标准默认值:以 HEAD 开头。
无论我们是否使用--reflog,git log 现在都使用提交本身中的父链接来遍历提交。这不取决于我们提供的参数,当然--no-walk 除外。2
1如果根本没有提交,或者我们在 git checkout --orphan 创建的“未出生分支”上,此时队列将为空,但 git log 将有尝试解析名称 HEAD 时出错。
2此外,使用 -g 或 --walk-reflogs 参数,git log 不会遍历 提交图。相反,它会遍历 reflog 条目。
--walk-reflogs 和 --reflog 之间的区别在于,--walk-reflogs 将整个优先级队列的东西完全抛弃:Git 仅查看 reflogs。这也改变了一些输出格式。事实上,git reflog 真的只是运行git log -g。