Appearance
git revert 详解
git revert 是撤销已提交内容的安全方式。与 git reset 不同,revert 不修改历史,而是通过创建新的提交来撤销指定提交的改动,因此适用于已推送到共享仓库的提交。
revert 的核心原理
git revert 分析指定提交引入的改动,然后创建一个"反向"提交(anti-commit)来撤销这些改动:
revert 前:
A ← B ← C (HEAD)
git revert B 后:
A ← B ← C ← B' (HEAD)
(B' 撤销了 B 的改动,历史保留完整)这与 reset 的区别:
reset(删除提交):
A ← B ← C 变为 A ← C(或 A ← B)
revert(撤销改动但保留历史):
A ← B ← C 变为 A ← B ← C ← B'(B' 撤销了 B 的改动)revert 普通提交
bash
# revert 最近一次提交
git revert HEAD
# revert 指定提交
git revert abc123
# revert 会打开编辑器填写提交信息(默认是 "Revert ...")
# 可以直接使用默认信息,也可以修改默认提交信息格式:
Revert "feat: 添加用户登录功能"
This reverts commit abc123456789abcdef...revert 合并提交(-m 1 / -m 2)
合并提交有两个父提交,revert 时需要指定保留哪一侧(即"主线"是哪一侧):
合并前:
A ← B ← C (main) ← 第一父提交(-m 1)
\
D ← E (feature) ← 第二父提交(-m 2)
合并后(合并提交 M):
A ← B ← C ← M (main)
\ /
D ← Ebash
# revert 合并提交 M,保留 main 的代码(撤销 feature 引入的改动)
git revert -m 1 <merge-commit-sha>
# revert 合并提交 M,保留 feature 的代码(撤销 main 原有的改动)
git revert -m 2 <merge-commit-sha>
# 通常情况下用 -m 1(保留第一父提交,即主线)什么时候用 revert 合并提交:
- 发现某次合并引入了严重 bug,需要紧急回退
- 已经 push 到共享分支,无法使用 reset
revert 多个提交
bash
# revert 多个独立提交(创建多个 revert 提交)
git revert abc123 def456 ghi789
# revert 一个范围内的提交(注意顺序:从新到旧)
git revert HEAD~3..HEAD
# 等价于:
git revert HEAD
git revert HEAD~1
git revert HEAD~2
# revert 指定范围(不包含 abc123,包含 def456)
git revert abc123..def456--no-commit 模式
--no-commit(或 -n)执行 revert 但不自动创建提交,让你先检查或合并多个 revert 再一次性提交:
bash
# revert 多个提交,但不立即提交
git revert --no-commit HEAD~3..HEAD
# 检查 revert 效果
git diff --cached
git status
# 满意后手动提交
git commit -m "revert: 撤销最近 3 次提交"
# 如果不满意,取消所有 revert
git revert --abortrevert 与 reset 的选择
| 对比项 | git revert | git reset |
|---|---|---|
| 历史处理 | 保留历史,新增撤销提交 | 删除/修改历史 |
| 适用范围 | 已推送的公共提交 | 未推送的本地提交 |
| 安全性 | 高,不影响他人 | 低,影响他人的仓库 |
| 可再次撤销 | 可以 revert revert | 一旦 reset 不易恢复 |
| 历史清晰度 | 有额外的 revert 提交 | 历史简洁 |
决策规则:
- 已 push 到共享分支 → 用
git revert - 只在本地,未 push → 可以用
git reset - push 到个人功能分支 → 用
git reset后 force push
实际场景
场景1:线上发现 bug,需要紧急回滚
bash
# 找到引入 bug 的提交
git log --oneline
# abc123 feat: 添加新的结算逻辑 ← 这个引入了 bug
# def456 feat: 更新商品列表
# ...
# revert 这个提交(生成一个新提交撤销其改动)
git revert abc123
# 推送到远程(安全,不需要 force push)
git push场景2:撤销一次错误的合并
bash
# 找到合并提交
git log --oneline --merges
# ghi789 Merge branch 'feature/wrong-feature'
# revert 合并提交(-m 1 保留主线)
git revert -m 1 ghi789
# 推送
git push场景3:撤销多个连续提交
bash
# 将最近 3 个提交都撤销,但合成一个 revert 提交
git revert --no-commit HEAD~3..HEAD
git commit -m "revert: 回退上周的功能迭代(功能 A/B/C 存在严重问题)"revert 后再次合并的问题
这是一个重要的陷阱:
场景:
1. feature 分支合并到 main(M1)
2. revert M1(R1),main 恢复到合并前状态
3. feature 继续开发,再次合并到 main(M2)
问题:M2 合并时,Git 认为 feature 的旧提交(在 M1 之前的)
已经合并过了,不会再次引入。只有 M1 之后的新提交会被合并。解决方案:
bash
# 方案1:revert 掉 revert(即恢复 feature 的改动)
git revert R1
# 这会重新引入 feature 的原始改动
# 方案2:在 feature 分支上重建提交
git switch feature
git rebase main # 变基到当前 main
# 现在的提交是新的,不会有"已合并"的问题总结
| 操作 | 命令 |
|---|---|
| revert 最近提交 | git revert HEAD |
| revert 指定提交 | git revert <sha> |
| revert 合并提交 | git revert -m 1 <merge-sha> |
| revert 多个提交 | git revert <old>..<new> |
| revert 不自动提交 | git revert --no-commit <sha> |
| 放弃 revert 操作 | git revert --abort |
git revert 是在共享代码库中撤销改动的正确方式,它保留了完整的历史记录,让所有协作者都能清晰了解发生了什么。