深入理解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 之前的状态。
关键点:
- 非破坏性操作:
git revert是一种“安全”的撤销方式,它不会改写任何已存在的提交历史。这意味着它非常适合在已经推送到共享仓库的公共分支上使用。 - 生成新提交: 每次
revert操作都会生成一个新的提交对象。这个提交会明确记录“撤销了某个提交”这一行为。 - 逆向补丁:
revert的本质是应用一个与目标提交相反的补丁。
二、为何选择 Git Revert?使用场景
了解了 git revert 的原理,我们就能更好地理解它的适用场景:
- 公共分支上的撤销: 这是
git revert最主要也是最推荐的用途。当您在main、develop等公共分支上发现了一个已经共享给团队的错误提交时,绝对不应该使用git reset --hard来删除它。因为这会改写历史,导致其他成员在拉取代码时出现历史冲突,引发混乱。git revert则通过新增提交来撤销,保持了历史的完整性。 - 保留历史记录: 有些时候,即使是本地分支的错误提交,我们也希望保留撤销操作的记录。例如,一个功能在开发过程中被证明是错误的,或者引入了缺陷,我们想撤销它,但同时希望在Git历史中清晰地看到“我们曾经尝试过这个方案,但后来撤销了”。
git revert正好满足这个需求。 - 撤销合并提交:
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. 撤销多个连续提交
如果您想撤销从 C 到 D 的所有提交(假设 D 是最新的提交):
“`bash
撤销从C到D的提交
注意:revert范围是[C, D],即撤销C和D
git revert C_commit_hash..D_commit_hash
“`
Git会为每个被撤销的提交生成一个新的revert提交,并且会要求您为每个revert提交输入提交信息。
3. 撤销多个非连续提交
如果您想撤销 B 和 D,但保留 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'。随后又revert了C',那么您的代码会回到C提交时的状态。这相当于再次应用了C的更改。 - Revert Merge 的复杂性: 撤销合并提交相对复杂,需要理解
-m选项的含义。如果撤销合并后,您又尝试重新合并同一个分支,Git可能会感到困惑。通常,撤销合并后,最佳做法是在被合并的分支上修复问题,然后重新创建一个新的合并提交。
总结
git revert 是Git工具箱中一个强大且不可或缺的命令,尤其在团队协作和维护公共分支时,它的非破坏性特性使其成为撤销错误提交的首选。通过理解其原理、掌握其用法以及明确与 git reset 的区别,您将能够更自信、更安全地管理您的Git仓库历史。在需要回滚更改时,始终优先考虑 git revert,以确保团队成员之间的协作顺畅无阻。