重写历史记录

重写历史记录

Intro

本教程将介绍重写和更改 Git 历史记录的各种方法。Git 使用几种不同的方法来记录变更。我们将讨论这些不同方法的优缺点,并举例说明如何使用它们。本教程将讨论覆盖已提交快照的一些最常见原因,并向您展示如何避免覆盖所带来的风险。

Git 的主要工作是确保您永远不会丢失已提交的变更。但它也可以在让您完全控制开发工作流。包括让您定义项目历史记录的样式;但它也有可能导致提交丢失。Git 为历史记录重写命令提供了免责声明,指出使用这些命令可能会导致内容丢失。

Git 通过几种机制来存储历史记录和保存变更。这些机制包括:Commit --amendgit rebasegit reflog。这些选项为您提供强大的工作流自定义选项。到本教程结束时,您将熟悉可让您重新构建 Git 提交的命令,并能够避免重写历史记录时经常遇到的陷阱。

更改最近的提交:git commit --amend

git commit --amend 命令是一种修改最近提交的便捷方法。它让您将暂存的变更与先前的提交合并,而不是创建一个全新的提交。它还可以用于在不更改快照的情况下简单地编辑先前的提交消息。但是,amend 不只是改变最近的提交,它将完全取代最近的提交,这意味着经过 amend 的提交将是一个具有自己的 ref 的新实体。对 Git 来说,它将看起来像一个全新的提交,在下图中用星号 (*) 表示。有几个适合使用 git commit --amend 的常见场景。我们将在以下章节中介绍使用示例。

Git commit amend

更改最近的 Git 提交消息

git commit --amend

假设您刚刚提交,但您的提交日志消息中存在错误。在没有暂存内容时运行此命令可让您在不更改快照的情况下编辑先前的提交消息。

在日常开发过程中,过早的提交是难以完全避免的。我们很容易忘记暂存文件,或错误地格式化提交信息。--amend 标记是修正这些小错误的便捷方法。

git commit --amend -m "an updated commit message"

添加 -m 选项可让您不提示您打开编辑器的情况下从命令行传入新消息。

更改已提交的文件

以下示例演示使用 Git 的开发工作中的一个常见场景。假设我们已经编辑了一些要在单个快照中提交的文件,但我们在第一次时忘记了添加其中一个文件。修正错误只需暂存另一个文件并使用 --amend 标记提交:

# Edit hello.pyand main.pygit add hello.pygit commit
# Realize you forgot to add the changes from main.py
git add main.py
git commit --amend --no-edit

--no-edit 标记可让您在不更改提交消息的情况下对提交进行修改。得到的提交将替换不完整的提交,它将看起来像我们将变更提交到一个快照中的 hello.pymain.py

请勿修改公有提交

修改的提交实际上是全新的提交,先前的提交将不再在当前的分支上。这与重置公有快照具有相同的效果。因此要避免修改其他开发人员工作所用的提交。否则会让开发人员困惑,而且很难进行复原。

回顾

您可以使用 git commit --amend 进行最近的提交,并向其添加新的暂存变更。您可以从 Git 暂存区域添加或删除变更以应用 --amend 提交。如果没有暂存变更,则 --amend 仍将提示您修改最近的提交消息日志。对与其他团队成员共享的提交使用 --amend 时要小心。修改与其他用户共享的提交可能需要花很多时间去解决由此产生的混乱的合并冲突。

更改旧提交或多个提交

要修改旧提交或多个提交,您可以使用 git rebase 将一系列提交合并到一个新的基准提交中。在标准模式下,git rebase 可让您逐字地重写历史记录 — 自动将您当前工作分支中的提交应用到传递的分支头。由于您的新提交将替换旧提交,因此不要在推送的公有提交上使用 git rebase 很重要,否则您的项目历史记录将消失。

在这些或类似的情况下,保留干净的项目历史记录非常重要,将 -i 选项添加到 git rebase 可让您运行交互式变基。这样可让您在此过程中更改各个提交,而不是移动所有提交。您可以在 git rebase 页面上了解有关交互式变基和其他变基命令的更多信息。

更改已提交的文件

在变基期间,编辑或 e 命令将暂停该提交上的变基回放,并允许您使用 git commit --amend 进行其他变更。Git 会中断回放并显示一条消息:

Stopped at 5d025d1... formatting
You can amend the commit now, with
git commit --amend
Once you are satisfied with your changes, run
git rebase --continue

多条消息

每个常规的 Git 提交都将有一条日志消息,用来说明提交中发生的事情。这些消息为我们了解项目历史记录提供了珍贵的信息。在变基期间,您可以在提交上运行几个命令来修改提交消息。

  • 重写或“r”将停止变基回放,让您在此期间重写各个提交消息。
  • 在变基回放期间使用压缩(或“s”),那么任何标记为 s 的提交将被暂停,您将得到提示,需要将单独的提交消息编辑为一条合并消息。在下文的“压缩提交”章节中会详细介绍这方面的内容。
  • 修补(或“f”)具有与压缩相同的合并效果。但与压缩不同,修复提交将不会中断变基回放,以让您打开编辑器来合并提交消息。标记为“f”的提交将丢弃其消息,让位给先前提交的消息。

压缩提交以获取干净的历史记录

s 命令(就是 squash)可让我们了解变基的真正实用性。压缩可让您指定要将哪些提交合并到先前的提交中。这是实现“干净的历史记录”的方法。在变基回放期间,Git 将为每个提交执行指定的 rebase 命令。在压缩提交情况下,Git 将打开您配置的文本编辑器并提示合并指定的提交信息。整个过程可以如下显示:

Git 教程: git rebase -i 示例

请注意,使用 rebase 命令修改的提交具有与原始提交不同的 ID。如果先前的提交被重写,用 pick 标记的提交将会拥有一个新的 ID。

新的 Git 托管解决方案(如 Bitbucket)现在在合并时提供“自动压缩”功能。在使用托管解决方案 UI 时,这些功能将自动对您分支的提交执行变基和压缩操作。有关更多信息,请参阅“使用 Bitbucket 合并 Git 分支时压缩提交。”

回顾

Git rebase 可让您修改历史记录,而交互式变基可让您在不留下“凌乱”踪迹的情况下这样做。这让您既可以自由地纠正错误并改进工作,同时仍保持干净的线性项目历史记录。

安全网:git reflog

引用日志(或“reflogs”)是 Git 用来记录应用到分支节点的更新和其他提交引用的机制。Reflog 可让您返回到提交,即使它们没有被任何分支或标签引用。重写历史记录后,reflog 会包含有关分支的旧状态的信息,并可让您在需要时返回该状态。每次您的分支节点由于任何原因进行更新时(通过切换分支、拉入新的变更、重写历史记录或简单地添加新的提交),一个新的条目将被添加到 reflog。在本节中,我们将简要了解 git reflog 命令,并探讨一些常见用途。

Usage

git reflog

这将显示本地代码库的 reflog。

git reflog --relative-date

这将显示具有相对日期信息(例如 2 周前)的 reflog。

示例

To understand git reflog, let's run through an example.

0a2e358 HEAD@{0}: reset: moving to HEAD~2 0254ea7 HEAD@{1}: checkout: moving from 2.2 to master c10f740 HEAD@{2}: checkout: moving from master to 2.2

The reflog above shows a checkout from master to the 2.2 branch and back. From there, there's a hard reset to an older commit. The latest activity is represented at the top labeled HEAD@{0}.

如果事实证明您是意外地移回,则在您意外删除 2 个提交之前,reflog 将包含指向 (0254ea7) 的提交主干。

git reset --hard 0254ea7

使用 Git reset,可以将 master 重新更改为先前的提交。这会提供一个安全网,以防止历史记录被意外更改。

重要的是要注意,只有变更已提交到本地代码库后,reflog 才会提供安全网,并且它仅跟踪代码库分支节点的移动。此外,reflog 条目具有过期日期。reflog 条目的默认时限为 90 天。

有关其他信息,请参阅 git reflog 页面。 

Summary

在这篇文章中,我们讨论了更改 Git 历史记录和撤销 Git 变更的几种方法。我们简要了解了 git rebase 过程。一些关键的要点如下所示:

  • 有很多方法可以重写 Git 历史记录。
  • 使用 git commit --amend 来更改最近的日志消息。
  • 使用 git commit --amend 来修改最近的提交。
  • 使用 git rebase 来合并提交并修改分支的历史记录。
  • git rebase -i 相比标准 git rebase,可针对历史记录的修改提供更细致的控制。

详细了解我们在各个页面上介绍的命令: