搜索 K
主题
git revert 是 Git 中最安全的撤销提交命令,它通过创建一个新的「反向提交」来抵消指定提交的修改,而不是删除提交历史。这使得它成为撤销已推送到远程的提交的首选方式。
git revert 不改写历史,而是在历史上追加一个反向操作。所有协作者只需正常 git pull 即可同步,无需强制推送。
git revert [--no-commit] [--no-edit] [--mainline <parent-number>] <commit>…
git revert --continue
git revert --abort
git revert --quitgit revert 会分析指定提交引入的修改(diff),然后生成一个完全相反的修改并自动创建新提交。
如果 C2 的修改是「添加了第 10 行 console.log('debug')」,那么 revert C2 产生的 C2' 就是「删除第 10 行 console.log('debug')」。最终效果是代码回到了 C2 之前的状态,但 C2 本身的提交记录仍然保留在历史中。
| 对比项 | git revert | git reset |
|---|---|---|
| 原理 | 追加反向提交 | 移动 HEAD,删除提交记录 |
| 提交历史 | 保留完整历史 | 改写历史(提交消失) |
| 远程协作 | 正常 push,不影响协作者 | 需要 --force 推送 |
| 安全性 | 安全,不丢失任何数据 | --hard 可能丢失数据 |
| 适用场景 | 已推送到远程的提交 | 本地未推送的提交 |
| 可追溯性 | 历史中能看到「撤销了什么」 | 被撤销的提交从历史中消失 |
已推送到远程的提交,用 revert;本地未推送的提交,用 reset。
# 撤销最近一次提交(自动打开编辑器编写 revert 信息)
git revert HEAD
# 使用默认信息,不打开编辑器
git revert --no-edit HEAD# 撤销某个特定提交(使用 SHA)
git revert abc1234
# 撤销某个特定提交(使用简短 SHA)
git revert abc1234# 撤销多个不连续的提交(每个生成一个 revert 提交)
git revert abc1234 def5678 ghi9012
# 撤销一个范围的提交(不含起始提交,含结束提交)
git revert abc1234..ghi9012
# 撤销最近 3 个提交
git revert HEAD~3..HEADgit 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)可以只将修改应用到暂存区,不自动提交,最后手动合并成一个提交。
# 撤销最近 3 个提交,但合并成一个 revert 提交
git revert --no-commit HEAD~3..HEAD
git commit -m "revert: 撤销最近三次提交"--no-commitRevert "xxx" 提交revert 并非总能成功——如果目标提交之后的提交修改了同一区域的代码,就会产生合并冲突。
# 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,只退出当前卡住的那个。
撤销合并提交是 git revert 中最复杂的场景,需要使用 --mainline(或 -m)参数。
--mainline? 合并提交有两个父提交,Git 不知道你想保留哪一边的修改:
-m 1):通常是你执行 git merge 时所在的分支(目标分支)-m 2):通常是被合并进来的分支(源分支)# 撤销合并提交,保留 parent 1(目标分支)的修改
# 相当于「取消这次合并,回到合并前目标分支的状态」
git revert -m 1 <merge-commit-sha>
# 撤销合并提交,保留 parent 2(源分支)的修改
git revert -m 2 <merge-commit-sha># 场景:将 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 提交,再合并:
# 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 提交:
# 原始提交
git log --oneline
# ghi9012 (HEAD) Revert "feat: 新功能" ← revert 提交
# def5678 feat: 新功能 ← 原始提交
# 撤销 revert(恢复原始功能)
git revert ghi9012
# 现在代码回到了 def5678 时的状态这不是反模式。在团队协作中,紧急 revert 后修复问题再恢复是很正常的工作流。
# 方式 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 定位目标提交 # 查看某个文件的修改历史,找到要 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# 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 # 合并修复# 某个功能由多个提交组成,全部需要回滚
# 方式 1:逐个 revert(保留详细记录)
git revert --no-edit HEAD~5..HEAD
# 方式 2:合并为一个 revert 提交(简洁)
git revert --no-commit HEAD~5..HEAD
git commit -m "revert: 回滚用户认证功能(#123)"# 场景:撤销最近 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: 选择性回滚"在现代 DevOps 工作流中,代码合并到 main 后通常会自动触发构建和部署。如果部署后发现问题,revert 是最快的回滚手段。
# 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 自动构建部署,线上恢复一些成熟的团队会在部署流水线中加入自动回滚机制:
git revert + git push当提交中包含数据库迁移(migration)时,revert 需要额外注意:
# ❌ 危险:直接 revert 包含迁移的提交
git revert abc1234
# 代码回滚了,但数据库已经执行了迁移!
# 新代码期望旧表结构,但数据库已经是新结构
# ✅ 正确流程
# 1. 先创建 DOWN 迁移(回滚数据库变更)
# 2. 执行数据库回滚
npx prisma migrate resolve # 或 rails db:rollback 等
# 3. 再 revert 代码
git revert --no-edit abc1234
git push在 monorepo 中,一个提交可能涉及多个服务。你可能只需要 revert 某个服务的修改:
# 提交 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)"git bisect + revert 定位并修复 bug 当不确定哪个提交引入了 bug 时,使用 git bisect 二分查找,找到后立即 revert:
# 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 自动运行:
git bisect start HEAD v2.0.0
git bisect run npm test
# Git 自动执行测试,找到第一个失败的提交在团队中执行 revert 时,沟通和记录非常重要:
# 好的 revert 提交信息模板
git revert --no-edit abc1234
# 然后修改提交信息,添加更多上下文
git commit --amendRevert "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/789This reverts commit xxx 行(方便追溯)在成熟的团队中,很多时候可以用功能开关(Feature Flag)替代 revert,实现更精细的控制:
# 方案对比
# revert:代码直接回滚,需要重新提交修复
# feature flag:代码保留,通过配置关闭有问题的功能
# 如果代码支持 feature flag
if (featureFlags.isEnabled('new-payment-flow')) {
// 新的支付流程
} else {
// 旧的支付流程(降级)
}
# 出问题时:关闭 feature flag,无需改代码
# 修复后:重新开启 feature flag| 场景 | 推荐方式 |
|---|---|
| 代码有严重 bug / 崩溃 | revert(最快) |
| 功能需要微调或 A/B 测试 | Feature Flag |
| 紧急回滚,没有 flag 系统 | revert |
| 性能问题需要逐步验证 | Feature Flag |
| 安全漏洞 | revert(最安全) |
# 按文件查找:谁最近修改了这个文件
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# 查看提交修改了哪些文件
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 # 取消模拟# revert 后打标签,方便后续追溯
git revert --no-edit abc1234
git tag -a rollback-v2.1.0 -m "回滚支付功能,原因:API 超时"
git push origin main --tags# 在 ~/.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 不提交GitHub、GitLab 等平台提供了一键 revert 功能:
GitHub:
PR 页面 → 底部 → "Revert" 按钮
→ 自动创建一个新的 revert PR
→ 审核后合并
GitLab:
MR 页面 → "Revert" 按钮
→ 选择目标分支
→ 自动创建 revert MRgit log 中出现大量 Revert "xxx" 提交,怎么避免?使用 --no-commit 将多个 revert 合并为一个提交:
git revert --no-commit HEAD~5..HEAD
git commit -m "revert: 回滚 feature-x 相关的所有提交"或者在 revert 前评估是否应该用 git reset(仅限未推送的提交)。
两种方式:
git revert <revert-commit-sha>git cherry-pick <original-commit-sha>推荐方式 1,因为它保留了完整的操作历史。
git revert <merge-commit> 报错?合并提交有两个父提交,Git 需要你指定 --mainline(-m)参数来告诉它保留哪一边:
# 报错
git revert abc1234
# error: commit abc1234 is a merge but no -m option was given
# 正确
git revert -m 1 abc1234通常 -m 1 是正确的选择(保留目标分支,撤销被合并进来的修改)。
git revert --abort这会完全取消 revert,恢复到执行 revert 之前的状态。
技术上可以,但不推荐。对于未推送的提交,使用 git reset 更合适:
# 未推送的提交用 reset(不留痕迹)
git reset --soft HEAD~1
# 已推送的提交用 revert(安全、可追溯)
git revert HEADgit checkout -- <file> 有什么区别?完全不同的概念:
| 对比项 | git revert | git 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 |
| 放弃 revert | git revert --abort |
| 撤销之前的 revert | git revert <revert-commit-sha> |