使用 Git 解决错误的指南(第 2 部分)
已发表: 2022-03-10在我们“用 Git 解决错误”系列的第二部分中,我们将再次勇敢地直视危险:我准备了四个新的世界末日场景——当然,包括一些拯救我们脖子的聪明方法! 但在我们深入研究之前:查看之前关于 Git 的文章,了解更多帮助您消除 Git 错误的自救方法!
我们走吧!
使用 Reflog 恢复已删除的分支
你有没有删除过一个分支,不久之后,你意识到你不应该这样做? 万一您不知道这种感觉,我可以告诉您,这不是一种好感觉。 当你想到那个分支提交的所有辛勤工作,你现在丢失的所有有价值的代码时,悲伤和愤怒的混合物在你身上蔓延。
幸运的是,有一种方法可以使该分支起死回生——借助名为“Reflog”的 Git 工具。 我们在本系列的第一部分中使用了这个工具,但这里有一点更新:Reflog 就像一个日志,Git 记录了本地存储库中 HEAD 指针的每一次移动。 换句话说,不那么讨厌的话:任何时候你结帐、提交、合并、变基、挑选等,都会创建一个日记条目。 这使得 Reflog 在出现问题时成为完美的安全网!
我们来看一个具体的例子:
$ git branch * feature/login master
我们可以看到我们当前已经签出了我们的分支feature/login
。 假设这是我们要删除的分支(无意中)。 然而,在我们这样做之前,我们需要切换到不同的分支,因为我们无法删除当前的 HEAD 分支!
$ git checkout master $ git branch -d feature/login
我们有价值的特性分支现在已经消失了——我会给你一点时间来 (a) 了解我们错误的严重性和 (b) 哀悼一下。 等你擦干眼泪,我们得想办法把这根树枝带回来! 让我们打开 Reflog(只需键入git reflog
)并查看它为我们存储的内容:
这里有一些注释可以帮助您理解输出:
- 首先,您需要知道 Reflog 按时间顺序对其条目进行排序:最新的条目位于列表顶部。
- 最顶层(因此也是最新的)项目是我们在删除分支之前执行的
git checkout
命令。 它被记录在 Reflog 中,因为它是 Reflog 忠实记录的这些“HEAD 指针移动”之一。 - 为了挽回我们的严重错误,我们可以简单地回到之前的状态——这也清楚地记录在 Reflog 中!
所以让我们尝试一下,通过创建一个新分支(名称为“丢失”分支),该分支从这个“之前”状态 SHA-1 哈希开始:
$ git branch feature/login 776f8ca
瞧! 您会很高兴看到我们现在已经恢复了我们看似丢失的分支!
如果您使用的是“Tower”之类的 Git 桌面 GUI,您可以使用一个不错的快捷方式:只需按键盘上的CMD + Z即可撤消上一个命令——即使您刚刚暴力删除了一个分支!
将提交移动到不同的分支
在许多团队中,有一个协议是不提交长期运行的分支,如main
或develop
:像这样的分支应该只通过集成(例如合并或变基)接收新的提交。 然而,当然,错误是不可避免的:尽管如此,我们有时会忘记并提交这些分支! 那么我们如何才能清理我们制造的烂摊子呢?
幸运的是,这些类型的问题很容易得到纠正。 让我们卷起袖子开始工作吧。
第一步是切换到正确的目标分支,然后过度使用cherry-pick
命令移动提交:
$ git checkout feature/login $ git cherry-pick 776f8caf
您现在将在所需的分支上进行提交,它应该首先出现在该位置。 惊人的!
但是还有一件事要做:我们需要清理一开始它意外降落的分支! 可以这么说, cherry-pick
命令创建了提交的副本——但原始版本仍然存在于我们长期运行的分支中:
这意味着我们必须切换回我们长期运行的分支并使用git reset
来删除它:
$ git checkout main $ git reset --hard HEAD~1
如您所见,我们在这里使用git reset
命令擦除错误的提交。 HEAD~1
参数告诉 Git “返回 HEAD 之后的 1 个修订版”,有效地从该分支的历史记录中删除最顶层(在我们的例子中:不需要的)提交。
瞧:提交现在应该放在最开始的地方,我们长期运行的分支是干净的——就好像我们的错误从未发生过一样!
编辑旧提交的消息
将拼写错误偷偷带入提交消息太容易了——直到很久以后才发现。 在这种情况下, git commit
的旧--amend
选项不能用来解决这个问题,因为它只适用于最后一次提交。 为了更正任何比这更早的提交,我们必须求助于一个名为“Interactive Rebase”的 Git 工具。
首先,我们必须告诉 Interactive Rebase 我们要编辑提交历史的哪一部分。 这是通过给它提供一个提交哈希来完成的:我们想要操作的那个的父提交。
$ git rebase -i 6bcf266b
然后将打开一个编辑器窗口。 它包含我们在命令中作为交互式 Rebase 基础提供的提交之后的所有提交的列表:
在这里,重要的是不要跟随你的第一个冲动:在这一步中,我们还没有编辑提交消息。 相反,我们只告诉 Git 我们想要对哪些提交进行什么样的操作。 非常方便的是,在此窗口底部的注释中有一个动作关键字列表。 对于我们的例子,我们用reword
标记第 1 行(从而替换标准的pick
)。
在此步骤中剩下要做的就是保存并关闭编辑器窗口。 作为回报,将打开一个新的编辑器窗口,其中包含我们标记的提交的当前消息。 现在终于到了进行编辑的时候了!
整个过程一目了然:
纠正一个破碎的提交(以一种非常优雅的方式)
最后,我们将看一下fixup
,这是撤消工具的瑞士军刀。 简而言之,它允许您在事后修复损坏/不完整/不正确的提交。 它确实是一个很棒的工具,原因有两个:
- 问题是什么并不重要。
您可能忘记了添加文件、应该删除了某些内容、进行了不正确的更改,或者只是拼写错误。fixup
适用于所有这些情况! - 它非常优雅。
我们对提交中的错误的正常、本能反应是生成一个新的提交来解决问题。 这种工作方式,无论看起来多么直观,都会让你的提交历史看起来非常混乱,很快。 你有“原始”提交,然后这些小的“创可贴”提交修复了原始提交中出现的问题。 你的历史中充斥着小的、毫无意义的创可贴提交,这使得你很难理解你的代码库中发生了什么。
这就是fixup
的用武之地。它允许您仍然进行这种更正的创可贴提交。 但是神奇的地方来了:然后它将它应用于原始的、损坏的提交(以这种方式修复它),然后完全丢弃丑陋的创可贴提交!
我们可以一起看一个实际的例子! 假设这里选择的提交被破坏了。
假设我已经在一个名为error.html
的文件中准备了可以解决问题的更改。 这是我们需要做的第一步:
$ git add error.html $ git commit --fixup 2b504bee
我们正在创建一个新的提交,但我们告诉 Git 这是一个特殊的提交:它是对具有指定 SHA-1 哈希(在本例中为2b504bee
)的旧提交的修复。
现在,第二步是启动 Interactive Rebase 会话——因为fixup
属于 Interactive Rebase 的大工具集。
$ git rebase -i --autosquash 0023cddd
关于这个命令有两件事值得解释。 首先,为什么我在这里提供0023cddd
作为修订哈希? 因为我们需要在我们破碎的伙伴的父提交处开始我们的交互式 Rebase 会话。
其次, --autosquash
选项有什么用? 这需要我们肩上的大量工作! 在现在打开的编辑器窗口中,一切都已经为我们准备好了:
多亏了--autosquash
选项,Git 已经为我们完成了繁重的工作:
- 它用
fixup
action 关键字标记了我们的小创可贴提交。 这样,Git 会将它与上面的提交直接结合,然后丢弃它。 - 它还相应地重新排序了行,将我们的创可贴提交直接移动到我们想要修复的提交下方(再次:
fixup
通过将标记的提交与上面的提交结合起来工作!)。
简而言之:除了关上窗户,我们无事可做!
让我们来看看最终的结果。
- 以前损坏的提交已修复:它现在包含我们在创可贴提交中准备的更改。
- 丑陋的创可贴提交本身已被丢弃:提交历史清晰且易于阅读——就好像根本没有发生任何错误一样。
知道如何消除错误是一种超能力
恭喜! 你现在可以在许多困难的情况下挽救你的脖子了! 我们无法真正避免这些情况:无论我们作为开发人员的经验如何,错误只是工作的一部分。 但既然您知道如何应对它们,您就可以用悠闲的心率来面对它们。
如果你想了解更多关于使用 Git 撤消错误的信息,我可以推荐免费的“Git 急救包”,这是一系列关于这个主题的短视频。
享受犯错的乐趣——当然,轻松地撤消它们!