本文是我们“高级 Git”系列的一部分。请务必关注我们的 Twitter或订阅我们的新闻通讯以了解未来的文章!
“Reflog”是 Git 鲜为人知的功能之一,但它却非常有用。有些人称之为“安全网”,而我更喜欢把它想象成 Git 的“日记”。这是因为 Git 使用它来保存关于HEAD
指针每次移动的日志(即每次提交、合并、变基、樱桃采摘、重置等)。Git 会将您的操作记录在 Reflog 中,这使得它成为一个宝贵的日志簿,并且在出现问题时是一个良好的起点。
在本“高级 Git”系列的最后一部分,我将解释git log
和git reflog
之间的区别,并向您展示如何使用 Reflog 恢复已删除的提交以及已删除的分支。
高级 Git 系列
- 第 1 部分:在 Git 中创建完美的提交
- 第 2 部分:Git 中的分支策略
- 第 3 部分:通过拉取请求实现更好的协作
- 第 4 部分:合并冲突
- 第 5 部分:变基与合并
- 第 6 部分:交互式变基
- 第 7 部分:在 Git 中挑选提交
- 第 8 部分:使用 Reflog 恢复丢失的提交(您现在所处位置!)
git log
或 git reflog
:有什么区别?
在之前的文章中,我建议您使用git log
命令来检查之前的事件并查看您的提交历史,这正是它所做的。它显示当前的HEAD
及其祖先,即其父级、下一级父级等。日志一直追溯到提交历史的早期,通过递归打印每个提交的父级。它是存储库的一部分,这意味着在您推送、获取或拉取后,它会被复制。
另一方面,git reflog
是私有的、与工作区相关的记录。它不会遍历祖先列表。相反,它显示HEAD
过去指向的所有提交的有序列表。这就是为什么您可以将其视为某种“撤消历史”,就像您在文字处理器、文本编辑器等中看到的那样。
从技术上讲,此本地记录不是存储库的一部分,并且它与提交分开存储。Reflog 是.git/logs/refs/heads/
中的一个文件,它跟踪每个分支的本地提交。Git 的日记通常会在 90 天后被清理(这是默认设置),但您可以轻松调整 Reflog 的过期日期。要将天数更改为 180,只需键入以下命令
$ git config gc.reflogExpire 180.days.ago

.git/config
)现在包含变量reflogExpire
,其值为180.days.ago
或者,您可以决定您的 Reflog 永远不会过期
$ git config gc.reflogExpire never
提示:请记住,Git 区分存储库的配置文件(.git/config
)、全局用户配置文件($HOME/.gitconfig
)和系统范围设置(/etc/gitconfig
)。要调整用户或系统的 Reflog 过期日期,请向上面显示的命令添加--system
或--global
参数。
足够的理论背景——让我向您展示如何使用git reflog
来纠正错误。
恢复已删除的提交
想象一下以下场景:查看提交历史记录后,您决定删除最后两个提交。您勇敢地执行了git reset
,这两个提交从提交历史记录中消失了……一段时间后,您注意到这是一个错误。您刚刚丢失了宝贵的更改并开始恐慌!

您真的必须从头开始吗?不必。换句话说:保持冷静并使用git reflog
!
因此,让我们搞砸并让这个错误在现实生活中发生。下图显示了我们在Tower(一个图形化 Git 客户端)中的原始提交历史记录

我们想要删除两个提交,并使“更改关于和印象的标题”提交(ID:2b504bee
)成为master
分支上的最后一个修订版。我们需要做的就是将哈希 ID 复制到剪贴板,然后在命令行上使用git reset
并输入该哈希值
$ git reset --hard 2b504bee

瞧。提交已消失。现在,假设这是一个错误,让我们看一下 Reflog 以恢复丢失的数据。键入git reflog
以在您的终端中查看日志

您会注意到所有条目都是按时间顺序排列的。这意味着:最新的提交位于顶部。而且,如果您仔细观察,您会注意到几分钟前位于顶部的致命git reset
操作。
日志似乎有效——这是个好消息。因此,让我们使用它来撤消上一步操作并恢复重置命令之前的状态。像以前一样,将哈希 ID(在此特定示例中为e5b19e4
)复制到剪贴板。您可以再次使用git reset
,这完全有效。但在这种情况下,我将基于旧状态创建一个新分支
$ git branch happy-ending e5b19e4
让我们再看一下我们的图形化 Git 客户端

如您所见,新分支happy-ending
已创建,并且包含我们之前删除的提交——太棒了,没有任何丢失!
让我们看另一个示例,并使用 Reflog 恢复整个分支。
恢复已删除的分支
下一个示例类似于我们的第一个场景:我们将删除某些内容——这次是整个分支。也许您的客户或您的团队负责人已告诉您删除功能分支,也许这是您自己清理的想法。更糟糕的是,一个提交(图中的C3
)未包含在任何其他分支中,因此您肯定将丢失数据

让我们实际执行此操作,然后稍后恢复分支

在您可以删除分支feature/login
之前,您需要离开它。(如您在屏幕截图中看到的,它是当前的HEAD
分支,您无法在 Git 中删除HEAD
分支。)因此,我们将切换分支(到master
),然后我们将删除feature/login

好的……现在假设我们的客户或团队负责人改变了主意。毕竟需要feature/login
分支(包括其提交)。我们该怎么办?
让我们看一下 Git 的日记
$ git reflog
776f8ca (HEAD -> master) HEAD@{0}: checkout: moving from feature/login to master
b1c249b (feature/login) HEAD@{1}: checkout: moving from master to feature/login
[...]
事实证明我们再次幸运。最后一个条目显示我们从feature/login
切换到master
。让我们尝试返回到之前的状态,并将哈希 ID b1c249b
复制到剪贴板。接下来,我们将基于所需状态创建一个名为feature/login
的分支
$ git branch feature/login b1c249b
$ git branch -vv
feature/login b1c249b Change Imprint page title
* master 776f8ca Change about title and delete error page
太好了——该分支已起死回生,并且还包含我们认为丢失的宝贵提交

如果您在 Tower 等桌面 GUI 中使用 Git,则只需按CMD+Z即可撤消上一步操作,就像在键入错误时使用文本编辑器或文字处理器一样。
保持冷静并跟踪
Git 的 Reflog 确实可以成为救星!如您所见,恢复丢失的提交甚至整个分支非常容易。您需要做的就是在 Reflog 中找到正确的哈希 ID——其余部分轻而易举。
如果您想更深入地了解高级 Git 工具,请随时查看我的(免费!)“高级 Git 工具包”:它是一系列关于分支策略、交互式变基、Reflog、子模块等主题的短视频。
这是我们在 CSS-Tricks 上的“高级 Git”系列的最后一部分。希望您喜欢这些文章。祝您编码愉快!
高级 Git 系列
- 第 1 部分:在 Git 中创建完美的提交
- 第 2 部分:Git 中的分支策略
- 第 3 部分:通过拉取请求实现更好的协作
- 第 4 部分:合并冲突
- 第 5 部分:变基与合并
- 第 6 部分:交互式变基
- 第 7 部分:在 Git 中挑选提交
- 第 8 部分:使用 Reflog 恢复丢失的提交(您现在所处位置!)
一系列很棒的文章。我特别感谢您将所有示例都包含在 CLI 中,而不是采取在 GUI(在本例中为 Tower)中完成所有操作的立场。希望大家都能从中获得两全其美的体验,不再害怕 CLI 并购买 Tower。
非常感谢,Piotr!
很棒的文章!作为团队开发的初学者,我从中获得了很大的启发!