Appearance
撤销合并的完整方案
合并错误的分支是开发中常见的意外。根据合并是否已推送、是否有他人拉取,处理方案各有不同。
未 push:reset --hard
如果合并还没有推送到远程,使用 git reset --hard 是最简单的方案:
bash
# 查看合并前的提交(合并提交的第一父提交)
git log --oneline
# abc123 (HEAD -> main) Merge branch 'wrong-feature' ← 要撤销的合并
# def456 feat: 上一个正常提交
# 回退到合并前的状态(完全丢弃合并提交)
git reset --hard HEAD~1
# 或者
git reset --hard def456
# 验证
git log --oneline
# def456 (HEAD -> main) feat: 上一个正常提交如果 ORIG_HEAD 存在(通常合并后会设置):
bash
# Git 在合并前会保存 HEAD 到 ORIG_HEAD
git reset --hard ORIG_HEAD警告:
--hard会丢弃合并提交中的所有未提交改动。确认后再执行。
已 push:revert -m 1
如果合并已经推送到远程分支(尤其是共享分支),不能使用 reset(会需要 force push 破坏他人工作),应使用 revert:
bash
# 找到合并提交的 SHA-1
git log --oneline --merges
# abc123 Merge branch 'wrong-feature'
# revert 合并提交,保留主线(第一父提交)
git revert -m 1 abc123
# 推送(不需要 force push)
git push-m 1 的含义:
-m 1:保留第一父提交(合并时所在的 main 分支)-m 2:保留第二父提交(被合并的 feature 分支)
通常情况下,撤销合并时使用 -m 1。
选择性撤销合并中的部分内容
如果不想完全撤销合并,只想撤销合并中某些具体的改动:
bash
# 方案1:revert 合并中的某些具体提交(而非整个合并提交)
git revert abc123 # revert 具体的功能提交
# 方案2:cherry-pick 保留需要的提交,revert 不需要的
# 先 revert 整个合并
git revert -m 1 <merge-commit>
# 再 cherry-pick 需要保留的部分
git cherry-pick <good-commit-from-feature>revert 合并后再次合并的问题与解决
这是一个重要的陷阱,前一节已提到:
问题链:
1. feature 合并到 main → 合并提交 M
2. revert M → revert 提交 R(main 恢复)
3. feature 修复后想再次合并 → 直接 merge feature
问题:步骤3中,Git 认为 feature 上 M 之前的提交已经被引入过,
只会引入 M 之后的新提交,导致之前的功能代码丢失!解决方案一:revert the revert
bash
# 在 feature 准备好后,先 revert 掉之前的 revert
git switch main
git revert R # R 是之前 revert 合并的提交
# 这会重新引入 feature 的原始改动
# 然后再 merge feature 的新改动
git merge feature解决方案二:在 feature 分支上变基
bash
# 在 feature 分支上,以 revert 后的 main 为基础重建提交
git switch feature
git rebase main # 变基到 revert 后的 main
# feature 上的所有提交都变成新的提交(新的 SHA-1)
# 再次合并时,Git 会正确识别所有改动
git switch main
git merge feature推荐方案二,因为更简洁,不会在主线留下额外的 revert 提交。
使用 squash 避免历史污染
当合并的分支有大量混乱的提交时,使用 squash 合并可以保持主线历史整洁:
bash
# Squash 合并(将 feature 的所有提交压缩为一个改动,不创建合并提交)
git merge --squash feature/login
git commit -m "feat: 添加用户登录功能"Squash 的好处:
- 主线历史简洁清晰
- 功能分支的混乱提交不污染主线
- 撤销时只需 revert 这一个 squash 提交
Squash 后 revert 更简单:
bash
# Squash 提交是普通提交,直接 revert 即可
git revert <squash-commit-sha>重建分支历史的方案
极端情况下,如果主线历史已经非常混乱,可以考虑重建:
bash
# 方案:创建孤立提交,重新开始
# (适用于项目早期或所有人都同意的情况)
git switch --orphan clean-main
git add .
git commit -m "feat: 从某个稳定状态重新开始"更常见的是:使用 git filter-repo 或 BFG Repo-Cleaner 清理历史(见性能优化章节)。
完整决策流程
发现合并了错误的分支
↓
是否已推送到共享分支?
/ \
否 是
↓ ↓
reset --hard revert -m 1 <merge-sha>
HEAD~1 ↓
git push实际操作示例
完整示例:线上撤销错误合并
bash
# 1. 发现线上问题,找到错误合并提交
git log --oneline main | head -5
# abc123 (HEAD -> main, origin/main) Merge branch 'feature/broken'
# def456 feat: 上一个稳定版本
# 2. 立即 revert 合并提交
git revert -m 1 abc123
# 编辑器打开,写入 revert 原因
# revert: 撤销 feature/broken 的合并,该功能存在严重性能问题
# 3. 推送到远程
git push
# 4. 在 feature/broken 分支上修复问题
git switch feature/broken
# ... 修复 ...
git commit -m "fix: 修复性能问题"
# 5. 在 feature 分支上 rebase(重建提交)
git rebase main
# 6. 再次合并
git switch main
git merge feature/broken
git push总结
| 场景 | 方案 | 命令 |
|---|---|---|
| 合并后立即发现,未 push | reset | git reset --hard ORIG_HEAD |
| 已 push 到共享分支 | revert | git revert -m 1 <sha> |
| 保持历史清洁 | squash 后 revert | git merge --squash |
| 再次合并时避免问题 | feature 变基 | git rebase main(在 feature 上) |
撤销合并是 Git 中较复杂的操作之一,理解各种方案的原理和适用条件,才能在紧急情况下做出正确选择。