跳转到内容
关系图谱

Git revert 详解

4,676 字 18 分钟

属性 1
status
学习中

Git revert 详解

git revert 是 Git 中最安全的撤销提交命令,它通过创建一个新的「反向提交」来抵消指定提交的修改,而不是删除提交历史。这使得它成为撤销已推送到远程的提交的首选方式。

核心理念

git revert 不改写历史,而是在历史上追加一个反向操作。所有协作者只需正常 git pull 即可同步,无需强制推送。

基本语法

bash
git revert [--no-commit] [--no-edit] [--mainline <parent-number>] <commit>
git revert --continue
git revert --abort
git revert --quit

核心概念

revert 的工作原理

git revert 会分析指定提交引入的修改(diff),然后生成一个完全相反的修改并自动创建新提交。

「反向提交」的含义

如果 C2 的修改是「添加了第 10 行 console.log('debug')」,那么 revert C2 产生的 C2' 就是「删除第 10 行 console.log('debug')」。最终效果是代码回到了 C2 之前的状态,但 C2 本身的提交记录仍然保留在历史中。

revert vs reset

对比项git revertgit reset
原理追加反向提交移动 HEAD,删除提交记录
提交历史保留完整历史改写历史(提交消失)
远程协作正常 push,不影响协作者需要 --force 推送
安全性安全,不丢失任何数据--hard 可能丢失数据
适用场景已推送到远程的提交本地未推送的提交
可追溯性历史中能看到「撤销了什么」被撤销的提交从历史中消失
黄金法则

已推送到远程的提交,用 revert;本地未推送的提交,用 reset

基本用法

撤销最近一次提交

bash
# 撤销最近一次提交(自动打开编辑器编写 revert 信息)
git revert HEAD

# 使用默认信息,不打开编辑器
git revert --no-edit HEAD

撤销指定提交

bash
# 撤销某个特定提交(使用 SHA)
git revert abc1234

# 撤销某个特定提交(使用简短 SHA)
git revert abc1234

撤销多个提交

bash
# 撤销多个不连续的提交(每个生成一个 revert 提交)
git revert abc1234 def5678 ghi9012

# 撤销一个范围的提交(不含起始提交,含结束提交)
git revert abc1234..ghi9012

# 撤销最近 3 个提交
git revert HEAD~3..HEAD
范围语法的陷阱

git revert A..B 表示撤销 A 之后到 B(含 B) 的所有提交,不包含 A 本身

# C1 → C2 → C3 → C4 → C5 (HEAD)
git revert C2..C5
# 撤销 C3、C4、C5(不含 C2)
# 注意:revert 是按 逆序 执行的(先撤 C5,再撤 C4,最后撤 C3)

--no-commit:合并多个 revert

默认情况下,每个 revert 都会产生一个独立的提交。使用 --no-commit(或 -n)可以只将修改应用到暂存区,不自动提交,最后手动合并成一个提交。

bash
# 撤销最近 3 个提交,但合并成一个 revert 提交
git revert --no-commit HEAD~3..HEAD
git commit -m "revert: 撤销最近三次提交"
何时使用 --no-commit
  • 撤销多个连续提交时,避免历史中出现大量 Revert "xxx" 提交
  • 需要在 revert 的基础上做额外修改后再提交
  • 想自定义 revert 提交信息

处理冲突

revert 并非总能成功——如果目标提交之后的提交修改了同一区域的代码,就会产生合并冲突

冲突解决流程

实际操作

bash
# 1. 执行 revert
git revert abc1234

# 2. 如果出现冲突
# CONFLICT (content): Merge conflict in src/main.ts
# 手动编辑文件解决冲突

# 3. 标记冲突已解决
git add src/main.ts

# 4. 继续 revert
git revert --continue

# 或者放弃这次 revert
git revert --abort

--abort vs --quit

命令效果
git revert --abort完全取消 revert,恢复到执行 revert 之前的状态
git revert --quit退出 revert 操作,但保留已经解决的冲突和暂存区
git revert --continue冲突解决后继续 revert,创建提交
何时用 --quit

当你在 revert 多个提交的过程中,已经成功 revert 了部分提交,但某个提交的冲突太复杂想放弃时,--quit 可以保留已成功的 revert,只退出当前卡住的那个。

撤销合并提交(Merge Commit)

撤销合并提交是 git revert 中最复杂的场景,需要使用 --mainline(或 -m)参数。

为什么需要 --mainline

合并提交有两个父提交,Git 不知道你想保留哪一边的修改:

  • parent 1-m 1):通常是你执行 git merge 时所在的分支(目标分支)
  • parent 2-m 2):通常是被合并进来的分支(源分支)

用法

bash
# 撤销合并提交,保留 parent 1(目标分支)的修改
# 相当于「取消这次合并,回到合并前目标分支的状态」
git revert -m 1 <merge-commit-sha>

# 撤销合并提交,保留 parent 2(源分支)的修改
git revert -m 2 <merge-commit-sha>

实战场景

bash
# 场景:将 feature 合并到 main 后发现有问题

# 当前在 main 分支,merge commit 是 abc1234
git log --oneline
# abc1234 (HEAD -> main) Merge branch 'feature'
# def5678 (feature) feat: 新功能
# ghi9012 fix: 修复 bug
# jkl3456 chore: 初始化

# 撤销合并,保留 main 的修改(-m 1)
git revert -m 1 abc1234
重新合并的陷阱

撤销合并提交后,如果以后想重新合并同一个分支,直接 git merge feature不生效!因为 Git 认为这些提交已经被合并过了。

解决方式:先 revert 那个 revert 提交,再合并:

bash
# 1. 之前撤销了合并
git revert -m 1 <merge-sha>      # 产生 revert-commit

# 2. feature 分支修复了问题后,想重新合并
# 先 revert 之前的 revert
git revert <revert-commit-sha>    # 恢复之前合并的修改

# 3. 然后正常合并新的修改
git merge feature

高级用法

撤销 revert(恢复被 revert 的提交)

如果 revert 后发现撤错了,可以再 revert 那个 revert 提交:

bash
# 原始提交
git log --oneline
# ghi9012 (HEAD) Revert "feat: 新功能"     ← revert 提交
# def5678 feat: 新功能                      ← 原始提交

# 撤销 revert(恢复原始功能)
git revert ghi9012
# 现在代码回到了 def5678 时的状态
「revert 的 revert」很常见

这不是反模式。在团队协作中,紧急 revert 后修复问题再恢复是很正常的工作流。

部分 revert(只撤销部分修改)

bash
# 方式 1:使用 --no-commit + 手动调整
git revert --no-commit abc1234
# 此时所有反向修改在暂存区
# 手动恢复你想保留的部分
git checkout HEAD -- src/keep-this-file.ts
git commit -m "revert: 部分撤销 abc1234"

# 方式 2:使用 patch 模式(Git 2.17+)
git revert --no-commit abc1234
git reset HEAD
git add -p
git commit -m "revert: 选择性撤销 abc1234 的部分修改"
git checkout -- .

配合 git log 定位目标提交

bash
# 查看某个文件的修改历史,找到要 revert 的提交
git log --oneline -- src/buggy-file.ts

# 查看某个提交的具体修改
git show abc1234

# 查看某个提交引入了哪些文件变更
git diff-tree --no-commit-id --name-only -r abc1234

# 搜索提交信息中包含特定关键字的提交
git log --grep="feature-x" --oneline

常见的 revert 工作流

工作流 1:紧急回滚线上问题

bash
# 1. 定位问题提交
git log --oneline -10

# 2. 紧急 revert
git revert --no-edit abc1234

# 3. 推送到远程(正常 push,无需 force)
git push origin main

# 4. 在 feature 分支修复
git checkout -b fix/issue-123
# ... 修复代码 ...
git commit -m "fix: 修复 issue-123"

# 5. 重新合并(可能需要先 revert 之前的 revert)
git checkout main
git revert <revert-commit-sha>   # 恢复原始修改
git merge fix/issue-123           # 合并修复

工作流 2:批量回滚一个功能

bash
# 某个功能由多个提交组成,全部需要回滚
# 方式 1:逐个 revert(保留详细记录)
git revert --no-edit HEAD~5..HEAD

# 方式 2:合并为一个 revert 提交(简洁)
git revert --no-commit HEAD~5..HEAD
git commit -m "revert: 回滚用户认证功能(#123)"

工作流 3:revert + cherry-pick 选择性保留

bash
# 场景:撤销最近 3 个提交,但其中一个提交想保留

# 1. 先全部 revert
git revert --no-commit HEAD~3..HEAD

# 2. 放弃 revert 状态
git reset HEAD

# 3. 选择性 cherry-pick 想保留的提交
git cherry-pick <想保留的-commit-sha>

# 4. 重新提交
git add .
git commit -m "revert: 选择性回滚"

实际项目中的 revert 场景

场景 1:CI/CD 流水线中的紧急回滚

在现代 DevOps 工作流中,代码合并到 main 后通常会自动触发构建和部署。如果部署后发现问题,revert 是最快的回滚手段。

bash
# 1. 监控报警,发现 API 响应时间暴涨
# 2. 快速定位最近合并的 PR
git log --merges --oneline -5
# abc1234 Merge pull request #456 from team/feature-payment
# def5678 Merge pull request #455 from team/fix-typo

# 3. 撤销导致问题的合并提交
git revert -m 1 --no-edit abc1234

# 4. 推送触发新的部署
git push origin main

# 5. CI/CD 自动构建部署,线上恢复
在 CI/CD 中配置自动回滚

一些成熟的团队会在部署流水线中加入自动回滚机制:

  • 部署后自动运行健康检查(smoke test)
  • 如果检查失败,自动执行 git revert + git push
  • 通知团队并创建 issue

场景 2:涉及数据库迁移的 revert

当提交中包含数据库迁移(migration)时,revert 需要额外注意:

bash
# ❌ 危险:直接 revert 包含迁移的提交
git revert abc1234
# 代码回滚了,但数据库已经执行了迁移!
# 新代码期望旧表结构,但数据库已经是新结构

# ✅ 正确流程
# 1. 先创建 DOWN 迁移(回滚数据库变更)
# 2. 执行数据库回滚
npx prisma migrate resolve   # 或 rails db:rollback 等
# 3. 再 revert 代码
git revert --no-edit abc1234
git push
数据库迁移回滚的注意事项
  • 删除列/表的迁移无法安全回滚(数据已丢失)
  • 先部署兼容新旧表结构的代码,再执行迁移(expand-contract 模式
  • 生产环境的迁移应该是可逆的,每个 UP 迁移都要有对应的 DOWN

场景 3:Monorepo 中的精确 revert

在 monorepo 中,一个提交可能涉及多个服务。你可能只需要 revert 某个服务的修改:

bash
# 提交 abc1234 修改了 3 个服务
git show --name-only abc1234
# packages/auth/src/login.ts
# packages/payment/src/checkout.ts
# packages/common/src/utils.ts

# 只 revert auth 服务的修改
git revert --no-commit abc1234
# 恢复不需要 revert 的文件
git checkout HEAD -- packages/payment/ packages/common/
git commit -m "revert: 撤销 auth 模块的登录修改 (abc1234)"

场景 4:使用 git bisect + revert 定位并修复 bug

当不确定哪个提交引入了 bug 时,使用 git bisect 二分查找,找到后立即 revert:

bash
# 1. 开始二分查找
git bisect start
git bisect bad                    # 当前版本有 bug
git bisect good v2.0.0            # v2.0.0 时没有 bug

# 2. Git 自动检出中间提交,你测试并标记
git bisect good                   # 这个版本没问题
git bisect bad                    # 这个版本有问题
# ... 重复几次 ...

# 3. Git 找到引入 bug 的提交
# abc1234 is the first bad commit

# 4. 结束 bisect
git bisect reset

# 5. revert 引入 bug 的提交
git revert --no-edit abc1234
git push
自动化 bisect

如果有自动化测试,可以让 bisect 自动运行:

bash
git bisect start HEAD v2.0.0
git bisect run npm test
# Git 自动执行测试,找到第一个失败的提交

场景 5:revert 后的代码审查与沟通

在团队中执行 revert 时,沟通和记录非常重要:

bash
# 好的 revert 提交信息模板
git revert --no-edit abc1234

# 然后修改提交信息,添加更多上下文
git commit --amend
Revert "feat: 新的支付流程 (#456)"

This reverts commit abc1234.

回滚原因:
- 合并后支付成功率下降 15%
- Sentry 报告 TypeError: Cannot read property 'amount' of undefined
- 影响范围:所有使用信用卡支付的用户

临时措施:回滚代码,使用旧的支付流程
后续计划:在 #789 中修复后重新上线

相关链接:
- 报警工单:https://pagerduty.com/incidents/12345
- Sentry 错误:https://sentry.io/issues/67890
- 修复 PR:https://github.com/org/repo/pull/789
revert 提交信息最佳实践
  • 保留自动生成的 This reverts commit xxx 行(方便追溯)
  • 说明回滚原因(是什么问题、影响多大)
  • 记录后续计划(谁来修复、在哪个 issue/PR 中跟踪)
  • 附上相关链接(监控报警、错误追踪、修复 PR 等)

场景 6:Feature Flag 替代 revert

在成熟的团队中,很多时候可以用功能开关(Feature Flag)替代 revert,实现更精细的控制:

bash
# 方案对比
# revert:代码直接回滚,需要重新提交修复
# feature flag:代码保留,通过配置关闭有问题的功能

# 如果代码支持 feature flag
if (featureFlags.isEnabled('new-payment-flow')) {
  // 新的支付流程
} else {
  // 旧的支付流程(降级)
}

# 出问题时:关闭 feature flag,无需改代码
# 修复后:重新开启 feature flag
何时用 revert,何时用 Feature Flag
场景推荐方式
代码有严重 bug / 崩溃revert(最快)
功能需要微调或 A/B 测试Feature Flag
紧急回滚,没有 flag 系统revert
性能问题需要逐步验证Feature Flag
安全漏洞revert(最安全)

实用技巧

技巧 1:快速找到要 revert 的提交

bash
# 按文件查找:谁最近修改了这个文件
git log --oneline -5 -- src/payment/checkout.ts

# 按时间范围:最近 2 天的提交
git log --oneline --since="2 days ago"

# 按作者:某人的提交
git log --oneline --author="张三"

# 按关键字:提交信息包含特定内容
git log --oneline --grep="payment"

# 按内容变更:哪个提交添加/删除了特定代码
git log --oneline -S "processPayment"

# 图形化查看分支合并历史
git log --oneline --graph --all -20

技巧 2:revert 前先确认影响范围

bash
# 查看提交修改了哪些文件
git show --stat abc1234

# 查看详细 diff(确认要撤销的内容)
git show abc1234

# 查看提交涉及多少行修改
git show --shortstat abc1234
# 3 files changed, 45 insertions(+), 12 deletions(-)

# 模拟 revert(不实际执行),检查是否会冲突
git revert --no-commit abc1234
git diff --cached --stat            # 查看 revert 会改动什么
git revert --abort                  # 取消模拟

技巧 3:给 revert 添加 Git 标签便于追踪

bash
# revert 后打标签,方便后续追溯
git revert --no-edit abc1234
git tag -a rollback-v2.1.0 -m "回滚支付功能,原因:API 超时"
git push origin main --tags

技巧 4:配置 Git alias 加速 revert 操作

bash
# 在 ~/.gitconfig 中添加
[alias]
    # 快速 revert(不打开编辑器)
    rv = revert --no-edit
    # revert 合并提交
    rvm = "!f() { git revert -m 1 --no-edit $1; }; f"
    # revert 但不提交(方便批量操作)
    rvn = revert --no-commit
    # 查看最近可 revert 的提交
    recent = log --oneline -10

# 使用
git rv abc1234         # 快速 revert
git rvm abc1234        # revert 合并提交
git rvn HEAD~3..HEAD   # 批量 revert 不提交

技巧 5:在 PR/MR 平台上 revert

GitHub、GitLab 等平台提供了一键 revert 功能:

GitHub:
  PR 页面 → 底部 → "Revert" 按钮
  → 自动创建一个新的 revert PR
  → 审核后合并

GitLab:
  MR 页面 → "Revert" 按钮
  → 选择目标分支
  → 自动创建 revert MR
平台 revert vs 命令行 revert
  • 平台 revert 会自动创建 PR/MR,走正常的代码审查流程,适合非紧急回滚
  • 命令行 revert 可以直接推送到 main,跳过审查流程,适合紧急回滚
  • 紧急情况下用命令行,事后补一个解释性的 PR 或 issue

常见问题

revert 后 git log 中出现大量 Revert "xxx" 提交,怎么避免?

使用 --no-commit 将多个 revert 合并为一个提交:

bash
git revert --no-commit HEAD~5..HEAD
git commit -m "revert: 回滚 feature-x 相关的所有提交"

或者在 revert 前评估是否应该用 git reset(仅限未推送的提交)。

revert 后想重新引入被撤销的修改怎么办?

两种方式:

  1. revert the revertgit revert <revert-commit-sha>
  2. cherry-pick 原始提交git cherry-pick <original-commit-sha>

推荐方式 1,因为它保留了完整的操作历史。

为什么 git revert <merge-commit> 报错?

合并提交有两个父提交,Git 需要你指定 --mainline-m)参数来告诉它保留哪一边:

bash
# 报错
git revert abc1234
# error: commit abc1234 is a merge but no -m option was given

# 正确
git revert -m 1 abc1234

通常 -m 1 是正确的选择(保留目标分支,撤销被合并进来的修改)。

revert 产生冲突,但我不想处理,想直接放弃?
bash
git revert --abort

这会完全取消 revert,恢复到执行 revert 之前的状态。

能否 revert 一个还没推送的提交?

技术上可以,但不推荐。对于未推送的提交,使用 git reset 更合适:

bash
# 未推送的提交用 reset(不留痕迹)
git reset --soft HEAD~1

# 已推送的提交用 revert(安全、可追溯)
git revert HEAD
revert 和 git checkout -- <file> 有什么区别?

完全不同的概念:

对比项git revertgit checkout -- <file>
作用对象提交工作区文件
操作创建新的反向提交丢弃文件的未暂存修改
是否创建提交
影响范围整个提交的所有修改单个文件

速查表

目标命令
撤销最近一次提交git revert HEAD
撤销指定提交git revert <sha>
撤销但不打开编辑器git revert --no-edit <sha>
撤销多个提交(逐个提交)git revert <sha1> <sha2> <sha3>
撤销范围提交(逐个提交)git revert <older-sha>..<newer-sha>
撤销多个提交合为一个git revert --no-commit <sha1>..<sha2> + git commit
撤销合并提交git revert -m 1 <merge-sha>
解决冲突后继续git revert --continue
放弃 revertgit revert --abort
撤销之前的 revertgit revert <revert-commit-sha>

相关命令

  • git reset — 通过移动 HEAD 撤销提交(适用于未推送的提交)
  • git filter-repo — 批量重写仓库历史(官方推荐)
  • Git filter-branch 详解 — 批量重写仓库历史(已弃用)
  • git cherry-pick — 将指定提交应用到当前分支,可配合 revert 进行选择性恢复
  • git merge — 合并分支,revert 常用于撤销合并提交
  • git bisect — 二分查找定位问题提交后用 revert 回滚

参考资料