如何撤销和移除当前分支上的特定 Git Commit

GitGitBeginner
立即练习

💡 本教程由 AI 辅助翻译自英文原版。如需查看原文,您可以 切换至英文原版

引言

在本实验中,你将学习如何管理你的 Git commit 历史。你将练习使用 git reset 撤销更改,使用 git revert 安全地回滚已公开的 commit,以及通过交互式 rebase 从你的分支中移除特定的 commit。你还将学习如何使用 reflog 来恢复已被移除的 commit。这些都是维护清晰易懂的项目历史的关键技能。

检查 Commit 历史

在修改历史之前,你首先需要知道如何查看它。git log 命令是为此提供的主要工具。在本实验中,一个 Git 仓库已经被初始化,其中包含多个 commit 来模拟一个真实的项目历史。

让我们开始检查 commit 历史。我们将使用 --oneline 标志将每个 commit 显示在单行上,并使用 --graph 以图形方式显示 commit 历史,这对于可视化分支和合并非常有用。

在你的终端中执行以下命令:

git log --oneline --graph

你应该会看到为你创建的五个 commit 的列表,最新的 commit 在最上面。每一行显示一个唯一的 commit hash(其简短版本)和 commit message。

* 7d3d24a (HEAD -> master) chore: Add last-commit file
* 8a9f1b3 docs: Update documentation in file1
* c2e4d6f fix: Add a temporary file that should be removed
* 5e1a3b2 feat: Add file2.txt
* 1b4c0a9 feat: Add file1.txt

注意:你的 commit hash 将与上面的示例不同。这是正常的,因为每个 hash 都是唯一的。

花点时间回顾一下历史。你将在接下来的步骤中操作这些 commit。

使用 git reset 撤销上一个 Commit

有时你提交了一个 commit,但立刻意识到自己犯了错误。git reset 命令非常适合这种情况。它可以将当前分支的 HEAD 指针移动到之前的 commit,从而有效地“撤销”一个或多个 commit。

我们将使用 --soft 选项,它会撤销 commit,但会保留该 commit 中的更改在你的暂存区(index)。如果你想重新提交这些更改,例如使用不同的消息或与其他更改合并,这会很有用。

让我们撤销最近的 commit,“chore: Add last-commit file”。

git reset --soft HEAD~1

这里,HEAD~1 指的是当前 HEAD 前面的那个 commit。

现在,检查你的仓库状态:

git status

你会看到 last-commit.txt 现在列在“待提交的更改”(Changes to be committed)下。这意味着 commit 本身被撤销了,但文件更改仍然处于暂存状态。

On branch master
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
	new file:   last-commit.txt

最后,再次查看日志以确认 commit 已从历史记录中移除:

git log --oneline --graph
* 8a9f1b3 (HEAD -> master) docs: Update documentation in file1
* c2e4d6f fix: Add a temporary file that should be removed
* 5e1a3b2 feat: Add file2.txt
* 1b4c0a9 feat: Add file1.txt

正如你所见,最后一个 commit 已从日志中移除,但你的工作安全地保存在暂存区中。

使用 git revert 恢复(撤销)一个 Commit

虽然 git reset 对于本地更改非常有用,但它会重写历史,如果你已经将 commit 与他人共享,这可能会带来问题。在公共分支上撤销更改的一种更安全的方法是使用 git revert。此命令会创建一个 的 commit,该 commit 应用了指定 commit 的逆向更改。

首先,让我们通过重新提交上一步的更改来清理工作目录。

git commit -m "chore: Add last-commit file again"

现在,假设我们想撤销添加 file2.txt 的那个 commit。这个 commit 在我们的历史记录中更靠前。我们将对其进行 revert。我们可以通过它相对于 HEAD 的位置来引用它。查看步骤 1 中的日志,“feat: Add file2.txt”是距离顶部第四个 commit,因此我们可以将其引用为 HEAD~3

运行 revert 命令:

git revert HEAD~3

这将打开你的默认文本编辑器(默认为 vim),其中包含一个预先填充的 commit message,例如“Revert 'feat: Add file2.txt'”。你只需保存并关闭文件即可接受默认消息。

vim 中,输入 :wq 并按 Enter 进行保存和退出。

现在,再次检查日志:

git log --oneline --graph

你将在顶部看到一个新的 commit,“Revert 'feat: Add file2.txt'”。

* 1e2d3f4 (HEAD -> master) Revert "feat: Add file2.txt"
* 7d3d24a chore: Add last-commit file again
* 8a9f1b3 docs: Update documentation in file1
* c2e4d6f fix: Add a temporary file that should be removed
* 5e1a3b2 feat: Add file2.txt
* 1b4c0a9 feat: Add file1.txt

原始 commit 仍然保留在历史记录中,但其更改已被新的 revert commit 撤销。你可以通过列出目录中的文件来验证这一点。

ls

你会注意到 file2.txt 已不再存在。

使用交互式 Rebase 移除 Commit

对于更复杂的历史记录操作,例如从分支中间移除一个 commit,你可以使用交互式 rebase (git rebase -i)。这是一个非常强大的命令,可以重写 commit 历史,因此应谨慎使用,尤其是在已与其他开发者共享的分支上。

我们的目标是移除带有消息“fix: Add a temporary file that should be removed”的 commit。查看当前日志,这个 commit 现在是 HEAD~3

开始交互式 rebase 过程:

git rebase -i HEAD~4

此命令将打开你的文本编辑器(默认为 vim),其中包含最近 4 个 commit 的列表。

pick fd5a181 fix: Add a temporary file that should be removed
pick 5f27a4d docs: Update documentation in file1
pick 284be6f chore: Add last-commit file again
pick 8a460c5 Revert "feat: Add file2.txt"

## Rebase 9403080..8a460c5 onto 9403080 (4 commands)
#
## Commands:
## p, pick <commit> = use commit
## d, drop <commit> = remove commit
## ...

要移除 commit,请将包含“fix: Add a temporary file that should be removed”的行中的 pick 单词更改为 drop(或 d)。

将此更改为:

pick fd5a181 fix: Add a temporary file that should be removed

更改为:

drop fd5a181 fix: Add a temporary file that should be removed

在 vim 中,按 i 进入插入模式,进行更改,然后按 Esc 退出插入模式。输入 :wq 并按 Enter 保存并退出。Git 将在新的历史记录之上重放剩余的 commit。

检查日志以查看结果:

git log --oneline --graph

“坏”的 commit 现在已从历史记录中消失。

* a5b4c3d (HEAD -> master) chore: Add last-commit file again
* f9e8d7c docs: Update documentation in file1
* 1e2d3f4 Revert "feat: Add file2.txt"
* 5e1a3b2 feat: Add file2.txt
* 1b4c0a9 feat: Add file1.txt

同时,检查目录中的文件。文件 bad-commit-file.txt 已被移除。

ls

使用 git reflog 恢复已移除的 Commit

如果你不小心移除了一个 commit 怎么办?Git 会在名为 reflog 的特殊日志中记录所有对 HEAD 指针的更改。这是你的安全网。你可以使用它来查找和恢复丢失的 commit。

让我们查看 reflog 来找到我们刚刚重写的历史。

git reflog

你将看到一个你执行过的操作列表。查找显示 rebase (finish): returning to refs/heads/master 的行。它下面的条目,可能是 HEAD@{1},代表 rebase 之前你的分支的状态。

a5b4c3d (HEAD -> master) HEAD@{0}: rebase (finish): returning to refs/heads/master
1e2d3f4 HEAD@{1}: rebase (start): checkout HEAD~3
...

我们可以使用 git reset --hard 将我们的分支恢复到这个之前的状态。这个命令具有破坏性,会丢弃任何未提交的更改,因此请谨慎使用。

让我们将分支重置到 rebase 之前的状态,在本例中是 HEAD@{1}

git reset --hard HEAD@{1}

你将看到一条消息,确认 HEAD 现在位于 rebase 开始之前的 commit。

HEAD is now at 1e2d3f4 Revert "feat: Add file2.txt"

现在,最后一次检查日志:

git log --oneline --graph

你应该看到 rebase 之前的历史记录:

* f461400 (HEAD -> master) Revert "feat: Add file2.txt"
* acea45c chore: Add last-commit file again
* c04b3f5 docs: Update documentation in file1
* 9403080 feat: Add file2.txt
* ee39412 feat: Add file1.txt

请注意,“fix: Add a temporary file that should be removed”这个 commit 仍然不存在——这是正确的!git reset --hard HEAD@{1} 命令将我们恢复到了交互式 rebase 开始之前的状态,而不是在我们移除 commit 之前。交互式 rebase 成功地从我们的历史记录中移除了那个不需要的 commit。你可以运行 ls 来确认 bad-commit-file.txt 仍然不存在。reflog 是从 Git 错误中恢复的宝贵工具。

总结

在本实验中,你学习了管理 Git commit 历史的实用技术。你首先使用 git log 检查历史记录。然后,你练习了使用 git reset --soft 撤销 commit,使用 git revert 安全地恢复更改,以及使用强大的 git rebase -i 命令移除特定的 commit。最后,你学会了如何使用 git reflog 作为安全网来恢复从历史记录中移除的 commit。这些技能对于维护一个干净且易于管理的项目历史至关重要。