使用 Reflog 恢复丢失的提交

Avatar of Tobias Günther
Tobias Günther

DigitalOcean 为您旅程的每个阶段提供云产品。立即开始使用 200 美元的免费额度!

本文是我们“高级 Git”系列的一部分。请务必关注我们的 Twitter订阅我们的新闻通讯以了解未来的文章!

“Reflog”是 Git 鲜为人知的功能之一,但它却非常有用。有些人称之为“安全网”,而我更喜欢把它想象成 Git 的“日记”。这是因为 Git 使用它来保存关于HEAD指针每次移动的日志(即每次提交、合并、变基、樱桃采摘、重置等)。Git 会将您的操作记录在 Reflog 中,这使得它成为一个宝贵的日志簿,并且在出现问题时是一个良好的起点。

在本“高级 Git”系列的最后一部分,我将解释git loggit reflog之间的区别,并向您展示如何使用 Reflog 恢复已删除的提交以及已删除的分支。

高级 Git 系列

git loggit reflog:有什么区别?

在之前的文章中,我建议您使用git log命令来检查之前的事件并查看您的提交历史,这正是它所做的。它显示当前的HEAD及其祖先,即其父级、下一级父级等。日志一直追溯到提交历史的早期,通过递归打印每个提交的父级。它是存储库的一部分,这意味着在您推送、获取或拉取后,它会被复制。

另一方面,git reflog是私有的、与工作区相关的记录。它不会遍历祖先列表。相反,它显示HEAD过去指向的所有提交的有序列表。这就是为什么您可以将其视为某种“撤消历史”,就像您在文字处理器、文本编辑器等中看到的那样。

从技术上讲,此本地记录不是存储库的一部分,并且它与提交分开存储。Reflog 是.git/logs/refs/heads/中的一个文件,它跟踪每个分支的本地提交。Git 的日记通常会在 90 天后被清理(这是默认设置),但您可以轻松调整 Reflog 的过期日期。要将天数更改为 180,只需键入以下命令

$ git config gc.reflogExpire 180.days.ago
Screenshot of a Terminal with a light yellow background and black text. The content shows the output from the git config gc.reflogExpire 180.days.ago command.
存储库的配置文件(.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 客户端)中的原始提交历史记录

Screenshot of the Tower application interface. On the left is a navigation with the History item selected. In the center panel is a visual outline of the commit history showing the avatars of people who made the commits. The second commit is selected and on the right is a panel showing more detail about the commit, including the author, date, committer, refs, hashes, and changed files.

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

$ git reset --hard 2b504bee
Screenshot of the Tower application interface. The master branch is selected in the right navigation, the first commit is selected in the center panel, and the detail for that commit is displayed in the right panel. The commits in the center panel are clean and linear without any additional commits or branches.

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

Screenshot of an open Terminal window with a light yellow background. The text is mainly black, but some words are highlighted in red, light blue and bright green. The top line is the git reset --hard 2b504bee command. The second line says the head is now at that commit ID. The third line is the git reflog command, which outputs the history.

您会注意到所有条目都是按时间顺序排列的。这意味着:最新的提交位于顶部。而且,如果您仔细观察,您会注意到几分钟前位于顶部的致命git reset操作。

日志似乎有效——这是个好消息。因此,让我们使用它来撤消上一步操作并恢复重置命令之前的状态。像以前一样,将哈希 ID(在此特定示例中为e5b19e4)复制到剪贴板。您可以再次使用git reset,这完全有效。但在这种情况下,我将基于旧状态创建一个新分支

$ git branch happy-ending e5b19e4

让我们再看一下我们的图形化 Git 客户端

A screenshot of the terminal with a light yellow background and the output for git reflog in it, on top of a screenshot of the Tower application window, showing the updated commit history following the command.

如您所见,新分支happy-ending已创建,并且包含我们之前删除的提交——太棒了,没有任何丢失!

让我们看另一个示例,并使用 Reflog 恢复整个分支。

恢复已删除的分支

下一个示例类似于我们的第一个场景:我们将删除某些内容——这次是整个分支。也许您的客户或您的团队负责人已告诉您删除功能分支,也许这是您自己清理的想法。更糟糕的是,一个提交(图中的C3)未包含在任何其他分支中,因此您肯定将丢失数据

Illustration showing the commit history flow of a feature/login branch with ID C2 being deleted from a C2 branch that is off the master branch. Beside the diagram is a list of the steps taken to deleted the branch, ending with step 4: you panic next to a screaming emoji.

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

Screenshot of the Tower app interface showing the login branch selected in the left panel, the commit history of that branch in the center panel with the first commit selected and highlighted in blue, then details for the commit in the right panel, including the author, date, refs, cases, and modified files.

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

Screenshot of an open Terminal window with a light yellow background and mostly black text, though the branch and committer names are highlighted in bright green. The first command is git status, the second is git checkout master, the third is git branch -vv, the fourth is git branch -D feature/login, and the last command is git branch -vv.

好的……现在假设我们的客户或团队负责人改变了主意。毕竟需要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

太好了——该分支已起死回生,并且还包含我们认为丢失的宝贵提交

Screenshot of the Tower application interface. The feature/login branch is selected in the left panel, the commit history for the branch is in the center panel with the first commit selected, and the left panel displays more information ab out the commit, including the author, date, refs, hashes, and modified files.

如果您在 Tower 等桌面 GUI 中使用 Git,则只需按CMD+Z即可撤消上一步操作,就像在键入错误时使用文本编辑器或文字处理器一样。

保持冷静并跟踪

Git 的 Reflog 确实可以成为救星!如您所见,恢复丢失的提交甚至整个分支非常容易。您需要做的就是在 Reflog 中找到正确的哈希 ID——其余部分轻而易举。

如果您想更深入地了解高级 Git 工具,请随时查看我的(免费!)“高级 Git 工具包”:它是一系列关于分支策略、交互式变基、Reflog、子模块等主题的短视频。

这是我们在 CSS-Tricks 上的“高级 Git”系列的最后一部分。希望您喜欢这些文章。祝您编码愉快!

高级 Git 系列