git reset 和 git revert 有什么本质区别?
git reset 和 git revert 都是 Git 中用于“撤销”操作的命令,但它们的本质区别在于对待历史记录的态度。
用一句话概括就是:
git reset是“修改历史”(把时间倒流,抹除提交)。git revert是“追加历史”(用一次新的提交来抵消之前的错误,保留所有痕迹)。
下面为你详细拆解它们的本质区别和使用场景。
1. 本质上的三大区别
① 历史记录的处理方式(毁灭性 vs 建设性)
git reset(重置): 它是破坏性的。它会将当前分支的指针(HEAD)向后移动到指定的历史提交上。就好像你把日记本上的最后几页直接撕掉,假装它们从未发生过。git revert(反转/撤销): 它是安全性、建设性的。它不会改变过去的提交,而是新建一个提交,这个新提交的内容正好和你要撤销的提交内容相反(比如你之前加了一行代码,revert 就会删掉这行代码)。就像在日记本上新写一页:“请忽略前天写的那一页内容”。
② 对团队协作的影响(危险 vs 安全)
git reset: 绝对不要在已经推送到远程的公共分支上使用! 因为它改变了历史,如果你强制推送(git push -f),会导致其他同事的本地仓库与远程仓库历史不一致,引发严重的冲突和混乱。git revert: 非常适合在公共分支上使用。 因为它只是增加了一个新的提交,历史记录是顺延的。你可以像推送普通提交一样把它推送到远程,其他同事只需git pull即可同步,完全不会有冲突。
③ 作用范围(回退到某个状态 vs 撤销某个具体动作)
git reset <commit_id>: 撤销的是从当前位置到<commit_id>之间的所有提交。git revert <commit_id>: 仅仅撤销<commit_id>那一次提交的更改,不管它是一次最新提交还是历史中间的某次提交。
2. 图解演示
假设你的提交历史是这样的: A -> B -> C (当前HEAD),现在你发现 C 提交写错了。
使用 git reset HEAD~1 (或 reset 到 B):
- 历史变成了:
A -> B (当前HEAD) C提交从历史线中消失了(变成了孤儿提交,最终会被 Git 垃圾回收)。
使用 git revert HEAD (或 revert 提交 C):
- 历史变成了:
A -> B -> C -> D (当前HEAD) - 这里的
D是一个全新的提交,它的内容是撤销C所做的修改。C依然完好无损地留在历史记录中。
3. 详细参数与使用场景
git reset 的三种模式
当你用 reset 撕掉历史时,你还可以决定要不要保留你写的代码(工作区和暂存区):
--soft:只撤销 commit,代码保留在暂存区(适合你想把几次提交合并成一次时)。--mixed(默认):撤销 commit 和暂存区,代码保留在工作区(适合你发现提交错了,想重新修改代码后再提交)。--hard:最危险,撤销 commit、暂存区和工作区,代码彻底变回旧版本(除非你有未提交的备份,否则代码彻底丢失)。
适用场景: 本地自己开发的分支,代码还没推送到远程服务器,发现刚才提交得太乱了,想重新整理历史。
git revert
没有任何破坏性参数,直接生成新提交。
如果发生冲突(比如你 revert 一个很早以前的提交,而后面的提交修改了同一行代码),你需要手动解决冲突然后再提交。
适用场景: 代码已经合并到了 main 或 master 等多人协作的公共分支,线上发现 Bug 需要紧急回滚。此时必须用 revert。
4. 总结:黄金法则
记住一条 Git 界的黄金法则:
👉 私有分支/本地代码回退,用 git reset(干净利落)。
👉 公共分支/远程代码回退,用 git revert(安全可靠)。