TL;DR:使用引用日志。
长
git commit --amend 操作具有“将旧提交推到一边”的效果。如果我们根据分支名称绘制提交,我们会得到这样的图片:
... <-F <-G <-H <--branch
这里的每个大写字母都代表一个 Git 提交哈希 ID。 分支名称,在本例中为branch,保存最新提交H的哈希ID,因此:
git rev-parse branch
打印出这个哈希 ID。 Git 能够使用这个存储的哈希 ID 从提交数据库中读取提交 H。
提交H 本身在其元数据中存储先前提交G 的原始哈希ID。因此,从数据库中读取 H 后,Git 可以找出这个哈希 ID,并使用它从数据库中检索提交 G。
在检索到 G 后,Git 具有来自 G 的元数据,它给出了早期提交 F 的哈希 ID,因此 Git 可以从数据库中检索提交 F。这当然有元数据,包括另一个更早的提交的哈希 ID,等等。
当我们以正常方式添加 new 提交时,Git 所做的是:
- 为新提交写出一个新快照(我们将这个新提交称为
I,使用字母表的下一个字母)。
- 写出提交
I 的元数据:我们的姓名、我们的电子邮件地址、当前日期和时间等等。在此元数据中,Git 包含 当前 提交 H 的哈希 ID,作为提交 I 的父级。
- 使用
git commit-tree 的内部版本将所有这些作为新提交写入对象数据库。写入提交的行为会产生新的哈希 ID:我们将称之为 I。
- 将提交
I 的哈希ID 存储在当前分支名称中。
第 4 步导致 branch 指向新提交 I,而不是现有提交 H。当我们绘制结果时,我们有:
... <-F <-G <-H <-I <--branch
如您所见,从末尾开始并向后工作的过程现在将找到提交I,然后提交H,然后提交G,依此类推。
git commit --amend 所做的是绕过 当前提交,让新提交I 直接指向父级 当前提交。在这种情况下,由于H 指向G,Git 将使新的提交I 直接指向G:
H
/
... <-F <-G <-I <--branch
提交H 确实仍然存在,反正有一段时间了。但无处可找到其哈希 ID。如果它仍然在您的屏幕上,或者在窗口中回滚,您可以通过以下方式获取它:Git needs hash ID;分支名称只是提供哈希 ID 的一种方式。如果您可以剪切和粘贴哈希 ID,那就可以了。
但这就是上面reflog这个词的意思。每当 Git 替换存储在分支名称或特殊名称 HEAD 中的值时,Git 将先前的值保存在日志中。 此日志是可选的,但对于您的存储库,它默认为“开启”(至少在您使用传统 Git 的情况下;如果您使用的是 Eclipse 或 JGit 或其他仅使用相同的数据库格式)。因此,当git commit 和git commit --amend 都覆盖存储在分支名称中的哈希 ID 时,它们会将上一个哈希 ID 保存在日志中。
不带参数运行git reflog 将转储HEAD 的引用日志。这是最活跃的 reflog,其中包含最多的“内容”,因此它有更多信息可供筛选。
运行git reflog branch,如果您在名为branch 的分支上,则转储branch 的引用日志。 (如果您使用的是 main 或 master,请使用 git reflog main 或 git reflog master。)这将显示对该分支名称的更新。
在这两种情况下,您都可以在 git commit --amend 之前找到提交的哈希 ID。这将是被推开的提交的哈希 ID。所以你现在可以运行了:
git diff <hash> HEAD
例如比较两个提交。
还有其他方法可以使用 reflog。请参阅documentation for git reflog 和the documentation for writing revision ID expressions。