在 Git 版本控制中,撤销更改是开发中常见的操作。无论是误提交了代码、需要回退到某个历史状态,还是想撤销已经推送到远程仓库的提交,Git 提供了多种工具来处理这些场景。本文将重点讲解 git reset
和 git revert
的区别与用法,帮助你根据实际需求选择合适的工具,高效管理提交历史。
背景:为什么需要撤销操作?
在使用 Git 的过程中,你可能会遇到以下情况:
- 提交了错误的代码,想完全撤销。
- 提交历史中包含不必要的提交,想清理。
- 已经推送到远程仓库的提交需要撤销,但不能影响其他开发者。
- 需要回退到某个历史状态,但保留后续修改。
Git 提供了两种主要命令来处理撤销操作:git reset
和 git revert
。它们虽然都能“撤销”提交,但作用和适用场景截然不同。
Git Reset 和 Git Revert 的核心区别
1. 功能和行为
git reset
:- 作用:将当前分支的
HEAD
回退到指定状态,并(可选地)修改工作目录和暂存区。 - 特点:重写提交历史,直接移除指定提交及之后的所有提交。
- 结果:被撤销的提交从历史中消失,仿佛从未发生。
- 适用场景:适合本地开发中清理未推送的提交,或者完全回退到某个状态。
- 作用:将当前分支的
git revert
:- 作用:创建一个新的提交,反向应用指定提交的更改。
- 特点:不修改现有历史,而是通过新增一个“反向提交”来撤销更改。
- 结果:提交历史中会多一个新的提交,记录撤销操作。
- 适用场景:适合撤销已推送到远程仓库的提交,尤其是在多人协作项目中。
2. 历史记录的影响
git reset
:重写历史,删除指定提交及之后的所有提交。git revert
:保留历史,新增一个反向提交,历史记录线性增长。
3. 协作安全性
git reset
:不安全。如果分支已推送,强制推送(git push --force
)会覆盖远程历史,可能导致其他开发者的工作丢失。git revert
:安全。新增提交不会破坏历史,其他开发者可以正常拉取和合并。
4. 适用场景对比
特性 | git reset |
git revert |
---|---|---|
历史记录 | 重写历史,删除提交 | 保留历史,新增反向提交 |
适用场景 | 本地未推送的提交 | 已推送的提交或多人协作 |
协作安全性 | 不安全(需谨慎使用 --force ) |
安全 |
操作复杂度 | 简单,但可能丢失修改 | 稍复杂,需处理冲突 |
Git Reset 的用法
git reset
有三种主要模式,根据参数不同,影响范围也不同:
--soft
:仅移动HEAD
,不修改暂存区和工作目录。--mixed
(默认):移动HEAD
,重置暂存区,但不修改工作目录。--hard
:移动HEAD
,重置暂存区和工作目录,所有更改丢失。
示例:撤销未推送的提交
假设当前提交历史如下:
* 5d8f9a2 (HEAD -> feature) fix typo
* e7b3c4d add new feature
* 2a1b3c4 initial setup
* 9c8d7e6 (main) initial commit
你发现 5d8f9a2
(fix typo)提交是错误的,想撤销。
1. 使用 --soft
撤销
- 目标:撤销提交,但保留更改在暂存区。
- 命令:
git reset --soft HEAD^ # 回退到上一个提交
- 结果:
- 提交历史变为:
- e7b3c4d (HEAD -> feature) add new feature
- 2a1b3c4 initial setup
- 9c8d7e6 initial commit
fix typo
的更改仍在暂存区,可以重新提交:git status
输出:
Changes to be committed: ...
- 提交历史变为:
2. 使用 --mixed
撤销(默认)
- 目标:撤销提交,将更改放回工作目录(未暂存)。
- 命令:
git reset --mixed HEAD^ # 或直接 git reset HEAD^
- 结果:
- 提交历史同上。
- 更改回到工作目录,未暂存:
git status
输出:
Changes not staged for commit: ...
3. 使用 --hard
撤销
- 目标:完全撤销提交,丢弃所有更改。
- 命令:
git reset --hard HEAD^
- 结果:
- 提交历史同上。
- 所有更改(
fix typo
的修改)被丢弃,无法恢复。 - 警告:使用
--hard
会永久丢失未保存的更改,谨慎操作。
4. 撤销到更早的提交
如果需要回退到更早的提交(例如 2a1b3c4
):
git reset --hard 2a1b3c4
- 结果:
* 2a1b3c4 (HEAD -> feature) initial setup * 9c8d7e6 initial commit
e7b3c4d
和5d8f9a2
都被移除。
Git Revert 的用法
git revert
通过创建一个反向提交来撤销指定提交的更改,适合已推送的提交。
示例:撤销已推送的提交
假设提交历史如下,且已推送:
* 5d8f9a2 (HEAD -> feature, origin/feature) fix typo
* e7b3c4d add new feature
* 2a1b3c4 initial setup
* 9c8d7e6 (main) initial commit
你发现 e7b3c4d
(add new feature)有问题,需要撤销。
1. 运行 git revert
- 命令:
git revert e7b3c4d
-
过程:
- Git 会创建一个新的提交,反向应用
e7b3c4d
的更改。 - 可能会打开编辑器,让你编辑新提交信息,默认信息为:
Revert "add new feature"
This reverts commit e7b3c4d...
- 保存并关闭。
- Git 会创建一个新的提交,反向应用
2. 检查结果
- 命令:
git log --oneline
- 输出:
* a1b2c3d (HEAD -> feature) Revert "add new feature" * 5d8f9a2 fix typo * e7b3c4d add new feature * 2a1b3c4 initial setup * 9c8d7e6 initial commit
- 说明:
- 新提交
a1b2c3d
反向应用了e7b3c4d
的更改。 - 历史记录完整保留。
- 新提交
3. 推送到远程
- 命令:
git push
- 结果:远程分支更新,其他开发者可以正常拉取。
4. 撤销多个提交
如果需要撤销连续的提交(例如 e7b3c4d
和 5d8f9a2
):
git revert e7b3c4d..5d8f9a2
- 注意:需要按时间倒序列出范围(从旧到新)。
- Git 会为每个提交创建一个反向提交。
解决冲突
git reset
的冲突
git reset
通常不会产生冲突,因为它直接重写历史。- 但如果有未提交的更改(工作目录或暂存区),可能需要先处理:
git stash # 暂存更改 git reset --hard HEAD^ git stash pop # 恢复更改
git revert
的冲突
- 如果
git revert
遇到冲突,Git 会暂停并提示:error: could not revert e7b3c4d... add new feature hint: after resolving the conflicts, mark the corrected paths hint: with 'git add
' or 'git rm ' hint: and commit the result with 'git commit' - 解决步骤:
- 打开冲突文件,手动解决冲突。
- 添加解决后的文件:
git add
- 继续 revert:
git revert --continue
- 如果想放弃:
git revert --abort
注意事项
- 备份:
- 在使用
git reset --hard
或推送前,创建备份分支:git branch backup-branch
- 在使用
- 远程协作:
- 如果使用
git reset
后强制推送(git push --force
),确保通知团队成员,避免覆盖他人工作。 git revert
是协作中的首选。
- 如果使用
- 恢复误操作:
- 如果
git reset --hard
后想恢复,使用git reflog
:git reflog
输出示例:
5d8f9a2 HEAD@{0}: reset: moving to HEAD^ e7b3c4d HEAD@{1}: commit: fix typo
恢复:
git reset --hard 5d8f9a2
- 如果
实践场景对比
场景 1:撤销本地未推送的提交
- 历史:
* 5d8f9a2 (HEAD -> feature) fix typo * e7b3c4d add new feature * 2a1b3c4 initial setup
- 操作:使用
git reset
:git reset --soft HEAD^ # 撤销 fix typo,保留更改 git commit -m "Add new feature and fix typo"
- 结果:
* a1b2c3d (HEAD -> feature) Add new feature and fix typo * 2a1b3c4 initial setup
场景 2:撤销已推送的提交
- 历史:
* 5d8f9a2 (HEAD -> feature, origin/feature) fix typo * e7b3c4d add new feature * 2a1b3c4 initial setup
- 操作:使用
git revert
:git revert e7b3c4d # 撤销 add new feature git push
- 结果:
* b2c3d4e (HEAD -> feature, origin/feature) Revert "add new feature" * 5d8f9a2 fix typo * e7b3c4d add new feature * 2a1b3c4 initial setup
总结
git reset
:适合本地开发,清理未推送的提交,重写历史,灵活但需谨慎。git revert
:适合协作环境,撤销已推送的提交,保留历史,安全但可能增加历史记录。- 选择建议:
- 如果提交未推送,使用
git reset
更高效。 - 如果提交已推送,使用
git revert
更安全。
- 如果提交未推送,使用
- 最佳实践:
- 定期备份分支。
- 熟悉
git reflog
以恢复误操作。 - 在多人协作中优先使用
git revert
。
通过熟练掌握 git reset
和 git revert
,你可以灵活应对各种撤销需求,提升 Git 使用的专业性。欢迎在评论区分享你的使用经验!
补充理解
git revert
操作可以理解为,针对某次commit A的内容进行反向的一次新的commit B,以还原为A提交之前的代码状态。且A因为比B旧,所以其他分支有A的提交再合并进来也没用,会被B覆盖。
如果想还原被git revert
还原的commit内容,需要对revert的那次commit(如上述的commit B)再进行一次git revert
操作,即可还原为最初的状态(包含commit A内容的状态)。
git reset
则是对分支进行操作,直接reset到某次commit节点的状态(回到链表的某个节点),中间的所有commit都直接没了。
Comments NOTHING