深入理解Git Revert:原理与实践 – wiki词典


深入理解Git Revert:原理与实践

在Git版本控制的日常工作中,我们经常会遇到需要撤销错误提交的情况。Git提供了多种撤销操作,其中 git revert 是一个非常重要且安全的选项。本文将深入探讨 git revert 的原理、使用场景、与其他撤销命令的区别,并通过实践案例帮助您更好地理解和掌握它。

一、Git Revert 的基本原理

git reset 不同,git revert 不会删除历史记录。它的核心原理是创建一个新的提交,这个新提交的内容是撤销目标提交所引入的更改

简而言之,如果您有一个提交 C 引入了某些代码,当您 revert C 时,Git会生成一个新的提交 C'。这个 C' 提交的内容看起来就像是 C 提交从未发生过一样。它将 C 引入的所有修改全部“反向”应用,例如,如果 C 添加了一行代码,C' 就会删除那一行;如果 C 修改了一行代码,C' 就会将其改回 C 之前的状态。

关键点:

  1. 非破坏性操作: git revert 是一种“安全”的撤销方式,它不会改写任何已存在的提交历史。这意味着它非常适合在已经推送到共享仓库的公共分支上使用。
  2. 生成新提交: 每次 revert 操作都会生成一个新的提交对象。这个提交会明确记录“撤销了某个提交”这一行为。
  3. 逆向补丁: revert 的本质是应用一个与目标提交相反的补丁。

二、为何选择 Git Revert?使用场景

了解了 git revert 的原理,我们就能更好地理解它的适用场景:

  1. 公共分支上的撤销: 这是 git revert 最主要也是最推荐的用途。当您在 maindevelop 等公共分支上发现了一个已经共享给团队的错误提交时,绝对不应该使用 git reset --hard 来删除它。因为这会改写历史,导致其他成员在拉取代码时出现历史冲突,引发混乱。git revert 则通过新增提交来撤销,保持了历史的完整性。
  2. 保留历史记录: 有些时候,即使是本地分支的错误提交,我们也希望保留撤销操作的记录。例如,一个功能在开发过程中被证明是错误的,或者引入了缺陷,我们想撤销它,但同时希望在Git历史中清晰地看到“我们曾经尝试过这个方案,但后来撤销了”。git revert 正好满足这个需求。
  3. 撤销合并提交: git revert 也可以用于撤销合并提交。当您需要撤销一个合并提交时,git revert <merge_commit_hash> 会要求您指定一个 -m--mainline 选项,告诉Git应该撤销哪个父分支的更改。这通常发生在合并了一个错误的分支,或者合并后发现问题时。

三、Git Revert 的实践操作

下面我们通过一些实际的例子来演示 git revert 的用法。

1. 撤销单个提交

假设我们有以下提交历史:

A -- B -- C -- D (HEAD -> main)

其中 C 提交引入了一个错误。我们想要撤销 C 提交。

“`bash

查看提交历史,找到要撤销的提交C的哈希值

git log –oneline

撤销提交C

Git会打开一个编辑器,让您编辑新的revert提交的提交信息。

默认的提交信息会说明它revert了哪个提交。

git revert C_commit_hash
“`

执行后,历史将变为:

A -- B -- C -- D -- C' (HEAD -> main)

C' 是一个新提交,它包含了撤销 C 所引入的所有更改。

2. 撤销多个连续提交

如果您想撤销从 CD 的所有提交(假设 D 是最新的提交):

“`bash

撤销从C到D的提交

注意:revert范围是[C, D],即撤销C和D

git revert C_commit_hash..D_commit_hash
“`

Git会为每个被撤销的提交生成一个新的revert提交,并且会要求您为每个revert提交输入提交信息。

3. 撤销多个非连续提交

如果您想撤销 BD,但保留 C

bash
git revert B_commit_hash
git revert D_commit_hash

这将生成两个新的revert提交。

4. 撤销而不立即提交 ( --no-commit )

有时您可能希望在撤销更改后,先查看一下文件状态,或者做一些额外的修改,然后再手动提交。

“`bash

撤销提交C,但不自动生成新的提交

git revert C_commit_hash –no-commit
“`

此时,C 提交的更改会被反向应用到工作区和暂存区,但Git不会自动创建一个新的提交。您可以检查文件,进行调整,然后使用 git add .git commit -m "..." 手动提交。

5. 撤销合并提交 ( --mainline )

假设分支 feature 合并到了 main,生成了合并提交 M

A -- B -- C (main)
\ /
D -- M (HEAD -> main)

现在发现 feature 分支的引入导致了问题,需要撤销合并。

“`bash

查看合并提交M的哈希值,并确定哪个是主线(通常是第一个父提交)

git log –oneline –graph –all

撤销合并提交M,并指定主线父提交(通常是main分支的父提交)

1 是主线(当前分支的父提交),2 是被合并的分支的父提交。

通常我们想保持主线历史不变,撤销的是被合并分支带来的影响。

git revert M_commit_hash -m 1
“`

这将创建一个新的提交 M',它撤销了 feature 分支引入的所有更改,使得 main 分支的状态回到合并前的样子。

四、Git Revert 与 Git Reset 的区别

理解 git revert,就不得不提 git reset。虽然它们都能“撤销”更改,但作用机制截然不同:

特性 git revert git reset
原理 创建一个新提交,撤销目标提交的更改。 移动分支的HEAD指针,改写历史。
安全性 非常安全,不改写历史,适合公共分支。 危险,会改写历史,不适合已推送的公共分支。
历史 保留所有历史,并添加新的“撤销”提交。 抹去目标提交及其之后的所有历史(--hard)。
工作区 生成新的反向更改,工作区和暂存区会更新,等待新提交。 依赖模式 (--soft, --mixed, --hard) 影响工作区和暂存区。
使用场景 撤销已共享的提交;需要保留撤销记录。 撤销本地未推送的提交;清理错误;调整提交粒度。

总结:

  • git revert 是撤销“已经发生且已经共享”的提交的最佳选择。
  • git reset 是撤销“只存在于本地”的提交,或者在本地调整提交历史的工具。

五、注意事项

  • 冲突解决: 如果您要 revert 的提交与当前HEAD提交之间有其他提交对相同文件进行了修改,git revert 可能会遇到合并冲突。此时,您需要手动解决冲突,然后 git add .,最后 git revert --continue 来完成 revert 操作。
  • 多次 Revert 同一个提交: 如果您 revert 了一个提交 C,生成了 C'。随后又 revertC',那么您的代码会回到 C 提交时的状态。这相当于再次应用了 C 的更改。
  • Revert Merge 的复杂性: 撤销合并提交相对复杂,需要理解 -m 选项的含义。如果撤销合并后,您又尝试重新合并同一个分支,Git可能会感到困惑。通常,撤销合并后,最佳做法是在被合并的分支上修复问题,然后重新创建一个新的合并提交。

总结

git revert 是Git工具箱中一个强大且不可或缺的命令,尤其在团队协作和维护公共分支时,它的非破坏性特性使其成为撤销错误提交的首选。通过理解其原理、掌握其用法以及明确与 git reset 的区别,您将能够更自信、更安全地管理您的Git仓库历史。在需要回滚更改时,始终优先考虑 git revert,以确保团队成员之间的协作顺畅无阻。


滚动至顶部