git revert 是 Git 中一个安全的撤销命令,它通过创建一个新的提交来反转之前某个或多个提交所引入的更改。与直接删除或修改历史记录的命令不同,revert 是一种”前向移动”的撤销操作,它保留完整的项目历史,这使得它特别适合在共享分支和生产环境中使用。
Revert 的核心工作原理如下:
与 reset 和 rebase 不同,revert 不会修改已有的提交历史,而是通过添加新提交的方式来撤销更改。
在以下情况下,revert 特别有用:
git revert <commit-hash>
git revert [options] <commit>...
常用选项:
<commit-hash>:要撤销的提交的哈希值-e, --edit:在提交前编辑提交信息(默认行为)--no-edit:使用默认的撤销提交信息,不打开编辑器-n, --no-commit:应用反向变更但不自动提交-m parent-number:指定要撤销到哪个父提交(用于 merge commit)--continue:解决冲突后继续 revert 过程--abort:取消 revert 操作并恢复到之前的状态--quit:退出 revert 但保留已经成功的更改--no-edit:跳过编辑提交信息的步骤# 1. 查看提交历史,找到要撤销的提交
git log --oneline
# 2. 执行 revert(会打开编辑器编辑提交信息)
git revert abc1234
# 3. 或者使用 --no-edit 跳过编辑器
git revert --no-edit abc1234
# 4. 验证结果
git log --oneline -3
git show HEAD
问题描述:
你刚刚向 main 分支推送了一个提交,但发现代码中存在严重 bug,导致生产环境出现问题。由于其他团队成员可能已经拉取了这个提交,你不能使用 reset 来修改历史。
解决方案:
# 1. 确认要撤销的提交
git log --oneline -5
# 假设错误提交的哈希值为 a1b2c3d
# 2. 查看该提交的详细内容
git show a1b2c3d
# 3. 执行 revert 撤销该提交
git revert a1b2c3d
# 4. Git 会打开编辑器,显示默认的提交信息:
# Revert "原始提交信息"
#
# This reverts commit a1b2c3d.
#
# 你可以添加更多说明,比如:
# Reason: 发现空指针异常导致服务崩溃
# 5. 保存并关闭编辑器,完成 revert
# 6. 推送到远程仓库
git push origin main
# 7. 验证代码已恢复正常
# ... 运行测试 ...
优点:
问题描述: 你的团队刚刚部署了一个新功能到生产环境(包含 3 个相关提交),但用户反馈这个功能存在严重的性能问题。需要立即回滚这些更改,待修复后再重新上线。
解决方案:
# 1. 查看最近的提交历史
git log --oneline -10
# 假设需要回滚的提交(从新到旧):
# e5f6g7h feat: 添加实时通知功能
# d4e5f6g feat: 实现通知数据缓存
# c3d4e5f feat: 添加通知 WebSocket 连接
# 2. 按照从新到旧的顺序依次 revert(重要!)
git revert e5f6g7h --no-edit
git revert d4e5f6g --no-edit
git revert c3d4e5f --no-edit
# 或者使用范围语法一次性 revert 多个提交
# 注意:revert 会按照相反的顺序处理(自动从新到旧)
git revert --no-edit c3d4e5f..e5f6g7h
# 或者
git revert --no-edit HEAD~2..HEAD
# 3. 推送到远程仓库
git push origin main
# 4. 部署到生产环境
# ... 执行部署流程 ...
# 5. 当功能修复完成后,有两种方式恢复:
# 方式 A:revert 之前的 revert 提交(双重否定等于肯定)
git revert <revert-commit-hash-1> <revert-commit-hash-2> <revert-commit-hash-3>
# 方式 B:使用 cherry-pick 重新应用原始提交
git cherry-pick c3d4e5f d4e5f6g e5f6g7h
注意事项:
问题描述:
你合并了一个功能分支到 main 分支并推送,但后来发现这个功能分支存在未发现的 bug。需要撤销整个合并操作。
解决方案:
# 1. 查看提交历史,找到 merge commit
git log --oneline --graph --all -10
# 假设 merge commit 的哈希值为 m1e2r3g
# 它有两个父提交:
# - 父提交 1 (main 分支): a1b2c3d
# - 父提交 2 (feature 分支): f4e5a6t
# 2. 查看 merge commit 的详细信息
git show m1e2r3g
# 3. 撤销 merge commit(指定保留哪个父提交)
# -m 1 表示保留第一个父提交(main 分支)的内容
git revert -m 1 m1e2r3g
# 4. 添加详细的提交信息说明
# Revert "Merge branch 'feature/new-ui' into main"
#
# This reverts commit m1e2r3g.
#
# Reason: 发现新 UI 存在浏览器兼容性问题
# 待修复后重新合并
# 5. 推送到远程
git push origin main
重要说明:
-m 参数指定主线(mainline parent)-m 1 表示保留第一个父提交(通常是目标分支)-m 2 表示保留第二个父提交(通常是源分支)git show <merge-commit> 查看父提交顺序重新合并的注意事项:
# 当 bug 修复后,不能直接 merge,因为 Git 认为这些提交已经合并过
# 需要先 revert 之前的 revert 操作:
git revert <revert-merge-commit-hash>
# 或者在 feature 分支上创建新的修复提交后再合并:
git checkout feature/new-ui
# ... 修复 bug ...
git commit -m "fix: 修复浏览器兼容性问题"
git checkout main
git merge feature/new-ui
问题描述: 你需要撤销一个提交的大部分内容,但想保留其中某些修改。或者你想在撤销后对代码进行一些额外的调整。
解决方案:
# 1. 使用 --no-commit 选项应用 revert 但不提交
git revert --no-commit h8i9j0k
# 2. 查看撤销后的状态
git status
git diff --cached
# 3. 如果想保留某些文件的修改,可以将其从暂存区移除
git reset HEAD src/components/KeepThis.js
git checkout -- src/components/KeepThis.js
# 4. 或者手动编辑某些文件,进行额外修改
vim src/config.js
# 5. 查看最终的变更
git diff --cached
# 6. 满意后提交
git commit -m "Revert 部分更改并调整配置
这个提交撤销了 h8i9j0k 中的大部分更改,
但保留了 KeepThis.js 中的重要修复,
并更新了相关配置。"
# 7. 推送到远程
git push origin develop
使用场景:
问题描述: 在最近的 10 个提交中,有 3 个不连续的提交存在问题,需要分别撤销它们。
解决方案:
# 1. 查看提交历史,识别有问题的提交
git log --oneline -10
# 假设需要撤销以下提交(不连续):
# HEAD~2: p1q2r3s (有问题)
# HEAD~5: s4t5u6v (有问题)
# HEAD~8: v7w8x9y (有问题)
# 2. 方法 A:逐个 revert(推荐)
git revert --no-edit p1q2r3s
git revert --no-edit s4t5u6v
git revert --no-edit v7w8x9y
# 3. 方法 B:在一个命令中 revert 多个提交
git revert --no-edit p1q2r3s s4t5u6v v7w8x9y
# 4. 如果遇到冲突,按照提示解决
# 假设在 revert s4t5u6v 时出现冲突
git status
# 编辑冲突文件
vim src/conflict-file.js
# 解决冲突后
git add src/conflict-file.js
git revert --continue
# 5. 推送所有 revert 提交
git push origin main
# 6. 验证结果
git log --oneline -15
# 应该看到 3 个新的 revert 提交
最佳实践:
--no-commit 一次性处理# 撤销单个提交
git revert <commit-hash>
# 撤销多个不连续的提交
git revert <commit-1> <commit-2> <commit-3>
# 撤销一系列连续的提交(从旧到新指定,但按从新到旧的顺序执行)
git revert <oldest-commit>^..<newest-commit>
# 或
git revert <oldest-commit>..<newest-commit> # 不包含 oldest-commit
# 撤销最近的提交
git revert HEAD
# 撤销最近的第 N 个提交
git revert HEAD~3
# 撤销但不自动提交
git revert -n <commit-hash>
git revert --no-commit <commit-hash>
# 跳过编辑提交信息
git revert --no-edit <commit-hash>
# 撤销提交并编辑提交信息(默认行为)
git revert -e <commit-hash>
git revert --edit <commit-hash>
# 查看 merge commit 的父提交信息
git show <merge-commit-hash>
# 撤销 merge commit(保留第一个父提交)
git revert -m 1 <merge-commit-hash>
# 撤销 merge commit(保留第二个父提交)
git revert -m 2 <merge-commit-hash>
# 撤销 merge commit 并编辑提交信息
git revert -m 1 --edit <merge-commit-hash>
# 重新合并之前被 revert 的分支
# 必须先 revert 那个 revert commit
git revert <revert-of-merge-commit>
# 当 revert 发生冲突时
# 1. 查看冲突文件
git status
# 2. 查看冲突详情
git diff
# 3. 手动解决冲突
# 编辑冲突文件,解决冲突标记
# 4. 标记冲突已解决
git add <resolved-files>
# 5. 继续 revert 过程
git revert --continue
# 或者,跳过当前 revert
git revert --skip
# 或者,放弃整个 revert 操作
git revert --abort
# 退出 revert 但保留工作目录的更改
git revert --quit
# 方法 1:使用提交范围(会按相反顺序执行)
git revert HEAD~5..HEAD
# 方法 2:使用多个提交哈希
git revert commit-1 commit-2 commit-3
# 方法 3:使用循环批量处理
for commit in commit-1 commit-2 commit-3; do
git revert --no-edit "$commit" || break
done
# 方法 4:从文件读取提交列表
cat commits-to-revert.txt | xargs git revert --no-edit
# 方法 5:批量 revert 但不自动提交(手动处理冲突后一次性提交)
git revert -n commit-1 commit-2 commit-3
# 解决所有冲突
git add .
git commit -m "Revert multiple problematic commits"
# 查看某个提交的详细信息
git show <commit-hash>
# 查看某个提交引入的变更
git diff <commit-hash>^ <commit-hash>
# 查看 revert 后的效果(不实际执行)
git show <commit-hash>
git diff <commit-hash>^ <commit-hash>
# 反向理解这些变更就是 revert 后的效果
# 查看提交历史(包含 revert 提交)
git log --oneline --graph --all
# 查找所有 revert 提交
git log --oneline --grep="Revert"
# 查看当前是否在 revert 过程中
cat .git/REVERT_HEAD 2>/dev/null && echo "In revert" || echo "Not in revert"
# 使用 reflog 查看 revert 历史
git reflog
# 如果 revert 还未完成(有冲突时)
git revert --abort
# 如果 revert 已完成但未推送,使用 reset 撤销
git reset --hard HEAD~1 # 删除最后一次 revert 提交
# 如果 revert 已推送,使用 revert of revert
git revert <revert-commit-hash>
# 或者使用 reflog 恢复到 revert 之前的状态
git reflog
git reset --hard HEAD@{5} # 假设 revert 之前是 HEAD@{5}
# 查看 reset 后的状态
git log --oneline -5
# Revert 时使用策略选项(自动解决冲突)
git revert -X theirs <commit-hash> # 优先使用被 revert 的版本
git revert -X ours <commit-hash> # 优先使用当前版本
# 显示 revert 过程的详细信息
git revert --verbose <commit-hash>
# Revert 时使用不同的合并策略
git revert --strategy=recursive -X patience <commit-hash>
# Revert 时保留特定的提交信息格式
git revert --edit <commit-hash>
# 在编辑器中自定义提交信息
# 在 revert 后立即创建一个修复提交
git revert <commit-hash>
# 然后立即修复问题并提交
git add fixed-files
git commit -m "fix: 修复 revert 后的相关问题"
# 使用 --mainline 参数的简写
git revert -m 1 <merge-commit> # 等同于 --mainline 1
# 场景:撤销最近 3 个提交但不自动提交,手动调整后再提交
git revert -n HEAD~2..HEAD
git status
# 手动调整代码
vim src/adjusted-file.js
git add .
git commit -m "Revert recent changes with adjustments"
# 场景:撤销提交并立即进行相关修复
git revert --no-edit <commit-hash>
git add fixed-files
git commit -m "fix: 相关修复"
# 场景:撤销 merge 后重新合并修复版本
git revert -m 1 <merge-commit>
git push origin main
# 在 feature 分支修复问题
git checkout feature-branch
# ... 修复 ...
git commit -m "fix: 问题已修复"
git checkout main
# 先 revert 之前的 revert
git revert <revert-commit-hash>
# 再重新 merge
git merge feature-branch
git revert -e <commit-hash>
# 在编辑器中提供详细信息:
# Revert "feat: 添加实时通知功能"
#
# This reverts commit a1b2c3d.
#
# Reason: 实时通知功能导致服务器负载过高,
# 在高峰期出现响应延迟。需要优化后再上线。
#
# Related issue: #1234
# Rollback approved by: @tech-lead
# 1. 查看要 revert 的提交内容
git show <commit-hash>
# 2. 评估影响范围
git log --all --source --full-history -- <affected-file>
# 3. 检查是否有其他提交依赖此提交
git log --oneline --grep="related keyword"
# 4. 在本地分支先测试 revert
git checkout -b test-revert
git revert <commit-hash>
# 运行测试
npm test
# Revert 后的测试清单
git revert <commit-hash>
# 1. 运行单元测试
npm test
# 2. 运行集成测试
npm run test:integration
# 3. 本地验证功能
npm run dev
# 手动测试相关功能
# 4. 检查代码质量
npm run lint
# 5. 确认无误后推送
git push origin main
# 执行 revert 前
# 1. 在团队频道通知
# 2. 说明 revert 的原因和影响范围
# 3. 建议其他成员暂停相关工作
git revert <commit-hash>
git push origin main
# 推送后
# 1. 再次通知团队 revert 已完成
# 2. 说明后续的修复计划
# 3. 更新相关的 issue 或 ticket
# 好的做法:每次 revert 一个逻辑完整的提交
git revert commit-1
git revert commit-2
git revert commit-3
# 不推荐:将多个不相关的 revert 合并
git revert -n commit-1 commit-2 commit-3
git commit -m "Revert multiple changes" # 难以追踪和理解
# 对于明显的 revert,使用 --no-edit 节省时间
git revert --no-edit <commit-hash>
# 对于需要解释的情况,使用 --edit 添加上下文
git revert --edit <commit-hash>
Revert 会创建新的提交,原始提交仍然存在:
# Revert 前
A - B - C - D (HEAD)
# Revert C 后
A - B - C - D - C' (HEAD)
# C' 是撤销 C 的新提交
# 历史中仍然可以看到 C
git log --all --oneline
影响:
git filter-branch 或 git filter-repo 彻底删除Revert merge commit 后,再次合并同一分支会遇到问题:
# 1. 合并 feature 分支
git merge feature
# 创建 merge commit M
# 2. Revert merge
git revert -m 1 M
# 创建 revert commit R
# 3. 后续在 feature 分支修复问题
git checkout feature
git commit -m "fix: 修复问题"
# 4. 尝试再次合并 feature(问题!)
git checkout main
git merge feature
# Git 认为 feature 的所有提交已经合并(被 M 合并),
# 只会合并修复提交,之前的所有提交都不会被包含!
正确做法:
# 在重新合并前,先 revert 那个 revert commit
git revert R
git merge feature
Revert 多个提交时注意顺序:
# 提交历史
A - B - C - D (HEAD)
# C 依赖 B,D 依赖 C
# 错误:只 revert C
git revert C
# D 的功能可能会出问题,因为它依赖 C
# 正确:按依赖顺序 revert
git revert D # 先 revert 依赖它的提交
git revert C # 再 revert 被依赖的提交
# 如果后续提交修改了相同的代码,revert 会产生冲突
A (修改 file.js 第 10 行) - B (修改 file.js 第 10 行) - C (HEAD)
# Revert A 时会冲突,因为 B 也修改了同一位置
git revert A
# CONFLICT: ...
# 需要手动解决冲突
vim file.js
# 决定保留 B 的修改还是完全撤销
git add file.js
git revert --continue
# 使用 Revert(安全,推荐):
# - 已推送到远程的提交
# - 多人协作的分支
# - 需要保留历史记录
git revert <commit-hash>
# 使用 Reset(危险,慎用):
# - 仅在本地的提交
# - 个人开发分支
# - 确定不会影响其他人
git reset --hard <commit-hash>
# 二进制文件(图片、PDF 等)冲突时无法手动合并
git revert <commit-with-binary>
# CONFLICT in image.png
# 选择完全保留当前版本
git checkout --ours image.png
git add image.png
git revert --continue
# 或选择完全使用 revert 的版本
git checkout --theirs image.png
git add image.png
git revert --continue
# 不推荐:一次性 revert 大量提交
git revert HEAD~100..HEAD # 可能很慢,且难以处理冲突
# 推荐:分批处理或考虑其他方案
# 方案 1:分批 revert
git revert HEAD~10..HEAD
git revert HEAD~20..HEAD~10
# ...
# 方案 2:创建新分支从已知良好的提交开始
git checkout -b hotfix <known-good-commit>
git cherry-pick <needed-commits>
当遇到 revert 冲突时,按以下步骤处理:
# 查看冲突文件
git status
# 查看冲突详情
git diff
# 理解为什么会冲突
git log --oneline --all --graph -- <conflict-file>
# 策略 A:完全接受当前版本(保留最新的代码)
git checkout --ours <conflict-file>
# 策略 B:完全接受 revert 的版本(完全撤销)
git checkout --theirs <conflict-file>
# 策略 C:手动合并
vim <conflict-file>
# 仔细编辑,保留正确的代码
# 标记冲突已解决
git add <resolved-files>
# 检查是否还有其他冲突
git status
# 继续 revert
git revert --continue
# 运行测试验证
npm test
# 在提交信息中说明如何解决冲突
git revert --continue
# 编辑器中添加:
#
# Conflicts resolved by:
# - Kept current implementation in file1.js
# - Reverted changes in file2.js
# - Manually merged changes in file3.js
# 团队 Revert 流程示例
# 1. 识别问题提交
# 2. 在团队频道通知
# 3. 评估影响范围
# 4. 获得 tech lead 批准
# 5. 执行 revert 并推送
# 6. 更新相关文档和 issue
# 7. 安排修复计划
# 在项目文档中记录重要的 revert
# docs/rollback-log.md
#
# ## 2024-01-15: Revert 实时通知功能
# - Commit: a1b2c3d
# - Reason: 性能问题
# - Revert commit: x1y2z3w
# - Fix plan: Issue #1234
# Revert 操作也应该经过审查
git revert <commit-hash>
git push origin revert-branch
# 创建 PR
gh pr create --title "Revert: 撤销有问题的提交" --body "..."
# 使用 Git hooks 自动通知团队
# .git/hooks/post-commit
#!/bin/bash
if git log -1 --pretty=%B | grep -q "^Revert"; then
# 发送通知到 Slack/Teams
curl -X POST $WEBHOOK_URL -d "{\"text\":\"Revert detected: $(git log -1 --oneline)\"}"
fi
在部署重要功能前,准备回滚方案:
# deployment-plan.md
#
# ## Rollback Plan
# If deployment fails:
# 1. git revert <deployment-merge-commit> -m 1
# 2. git push origin main
# 3. Trigger rollback deployment pipeline
# 4. Notify stakeholders
| 特性 | Revert | Reset |
|---|---|---|
| 历史记录 | 保留所有历史,添加新提交 | 删除或移动提交 |
| 安全性 | ✅ 安全,适合共享分支 | ⚠️ 危险,仅用于本地 |
| 是否可逆 | ✅ 可以 revert revert | ❌ 丢失的提交难以恢复 |
| 远程分支 | ✅ 可以直接推送 | ⚠️ 需要强制推送 |
| 团队协作 | ✅ 不影响他人 | ❌ 可能破坏他人工作 |
| 使用场景 | 已推送的提交 | 仅在本地的提交 |
# Revert 示例
git revert abc123
# A - B - C - C' (新提交撤销 C)
# Reset 示例
git reset --hard abc123
# A - B (C 被删除)
| 特性 | Revert | Cherry-pick |
|---|---|---|
| 目的 | 撤销更改 | 复制提交 |
| 变更方向 | 反向应用(撤销) | 正向应用(应用) |
| 使用场景 | 移除错误提交 | 迁移特定功能 |
| 历史影响 | 增加 revert 提交 | 增加新的提交副本 |
# Revert - 撤销提交
git revert abc123
# 创建反向变更的新提交
# Cherry-pick - 应用提交
git cherry-pick abc123
# 创建相同变更的新提交
| 特性 | Revert | Rebase |
|---|---|---|
| 历史处理 | 保留完整历史 | 重写历史 |
| 公开分支 | ✅ 安全使用 | ❌ 避免使用 |
| 操作复杂度 | 简单,单个命令 | 复杂,交互式操作 |
| 撤销能力 | 撤销特定提交 | 整理整个分支历史 |
# Revert - 保留历史
git revert abc123
# Rebase - 重写历史
git rebase -i HEAD~3
# 在编辑器中删除或修改提交
# 📋 选择流程图
# 提交是否已推送到远程?
# ├─ 是 → 使用 git revert(安全)
# └─ 否 → 继续判断
# ├─ 只想删除最近的提交?
# │ └─ 是 → 使用 git reset(快速)
# └─ 想整理多个提交的历史?
# └─ 是 → 使用 git rebase -i(灵活)
# 是否在团队共享分支?
# ├─ 是 → 使用 git revert(安全)
# └─ 否 → git reset 或 rebase(可以)
# 需要保留审计轨迹?
# ├─ 是 → 使用 git revert(可追溯)
# └─ 否 → 根据情况选择
# 是否要撤销特定的某个提交?
# ├─ 是 → 使用 git revert(精确)
# └─ 否 → 使用 git reset 回到某个点
git revert 是一个安全、可靠的撤销工具,特别适合在生产环境和团队协作中使用。正确使用它可以:
但也要注意:
| 场景 | 推荐命令 | 原因 |
|---|---|---|
| 已推送到远程 | git revert |
安全,不破坏历史 |
| 仅在本地 | git reset |
简单快速 |
| 生产环境回滚 | git revert |
可追溯,可恢复 |
| 撤销 merge | git revert -m 1 |
专门处理合并 |
| 多人协作分支 | git revert |
不影响他人 |
| 个人开发分支 | git reset |
更灵活 |
掌握 git revert 的正确用法,能够让你在面对错误提交时从容应对,保持代码仓库的健康和团队协作的顺畅!