如何保留必要修复并安全删除长期未合并且存在冲突的 Git 分支?
建议从最新的 origin/dev 创建新分支,按依赖顺序 cherry-pick 仍需保留的提交。完成测试和代码审查、合入 dev 后,再删除旧分支。
如果只有两个提交需要保留,这通常是合适的做法,前提是它们能够独立工作,或者所需依赖已经存在于当前 dev。如果修复与旧代码耦合很深,在新分支上重新实现可能更清楚。
git branch -D sql-bugfixes 和 git push origin --delete sql-bugfixes 可以留到最后清理分支时使用。这两个命令不会检查修复是否完整,也不会替你保存旧分支的历史。
先确认当前冲突发生在哪里
如果你在 sql-bugfixes 分支上执行了:
git pull origin dev
Git 会获取远程 dev,再根据配置将它合并或 rebase 到当前分支,不会直接修改远程 dev。
先检查仓库状态:
git status
如果冲突处理尚未结束,根据 git status 的提示选择对应命令:
# 正在进行 merge 时
git merge --abort
# 正在进行 rebase 时
git rebase --abort
只执行与当前状态对应的一条。如果操作前已有未提交改动,先确认这些内容已妥善保存。工作区不干净时,Git 不一定能完整恢复操作前的状态,不要用 git reset --hard 草率清理。
继续之前,确认没有正在进行的合并、变基或 cherry-pick,且工作区干净。
1. 更新远程信息,并给旧分支留一个备份
先获取最新的远程引用:
git fetch origin --prune
这不会把远程代码合入当前分支,也不会改写工作区文件。
给本地旧分支创建备份标签:
git tag archive/sql-bugfixes sql-bugfixes
标签会保留该提交及其可达历史。以后即使删除了 sql-bugfixes 分支,也能从标签恢复。如果标签名已存在,换一个名字,不要强制覆盖。
如果远程旧分支仍然存在,检查它与本地分支是否一致:
git log --left-right --oneline sql-bugfixes...origin/sql-bugfixes
< 表示仅本地一侧可达的提交,> 表示仅远程一侧可达的提交。如果远程有本地没有的提交,先审查这些内容,必要时另建备份标签:
git tag archive/sql-bugfixes-remote origin/sql-bugfixes
本地标签只保存在当前仓库。如果需要共享备份或在异地保留,可以按团队约定推送指定标签:
git push origin refs/tags/archive/sql-bugfixes
备份标签保留的是已提交历史,不包含未提交文件。
2. 找出候选提交,检查修复是否仍然需要
列出旧分支中尚未出现在 origin/dev 提交历史里的提交:
git log --reverse --topo-order --oneline origin/dev..sql-bugfixes
这里按提交历史判断。同一修复即使已经通过 cherry-pick、squash 或重新实现进入 dev,旧提交仍可能出现在结果中。
还可以辅助检查补丁是否等价:
git cherry -v origin/dev sql-bugfixes
通常,- 表示上游已有等价补丁,+ 表示没有找到等价补丁。不过,标记为 + 的修复也未必还需要,当前代码可能已经用另一种方式解决了同一问题。
逐个查看候选提交。下面的哈希只是示例,需要替换为实际提交:
git show --stat a1b2c3d
git show a1b2c3d
git show --stat d4e5f6a
git show d4e5f6a
重点检查:
- 当前
dev是否还存在原来的 bug,最好能通过回归测试复现。 - 提交是否依赖旧分支中的其他改动,例如新增函数、配置、表字段或数据库迁移。
- 提交里是否混入无关重构、调试代码或过时逻辑。
- 后续提交是否修正或撤销过这次修改。
也可以查看旧分支相对共同祖先的整体改动,了解上下文:
git diff origin/dev...sql-bugfixes
三点比较展示的是从共同祖先到旧分支的变化,不能据此判断这些改动能否直接用于当前 dev。
3. 在最新 dev 上应用选定提交
直接从远程跟踪分支创建新分支,可以避免本地 dev 尚未更新的问题:
git switch -c recover-sql-fixes origin/dev
确认两个提交的依赖关系后,按顺序执行:
git cherry-pick -x a1b2c3d d4e5f6a
-x 会在提交说明中记录来源提交,方便追溯。
Cherry-pick 会把选中提交引入的改动应用到当前代码上,通常会生成新的提交哈希。它不会自动带上旧分支中更早的依赖提交,因此需要事先检查依赖。
出现冲突时,先查看状态:
git status
结合当前 dev 的实现解决冲突,再暂存处理过的文件:
git add path/to/resolved-file
git cherry-pick --continue
将 path/to/resolved-file 替换为实际路径。处理冲突时,要保留当前代码中仍然需要的行为,并补入修复。不要用整份旧文件覆盖当前实现,也不要批量接受所有 ours 或 theirs。
如果发现选中的提交不合适,可以撤销本次 cherry-pick 序列:
git cherry-pick --abort
如果 Git 提示某个提交应用后为空,先核对修复是否已经存在,或是否在解决冲突时被意外丢弃。确认无需保留该提交后,再使用 git cherry-pick --skip。
这些示例适用于普通提交。如果目标是 merge commit,需要先弄清要应用的是它相对哪个父提交的变化,不能随意添加 -m 1。
4. 检查最终差异,验证后再合入
即使应用成功且没有冲突,也只说明 Git 完成了补丁处理,业务逻辑是否正确仍需验证。
检查新分支实际引入的全部改动:
git log --oneline origin/dev..HEAD
git diff --stat origin/dev...HEAD
git diff origin/dev...HEAD
git diff --check origin/dev...HEAD
git diff --check 主要检查空白错误等问题,不能替代测试。
围绕修复本身进行验证:
- 增加或恢复能复现原 bug 的回归测试,确认修复有效。
- 运行受影响模块的测试,以及项目要求的构建、静态检查和 CI。
- 检查与当前接口、数据结构和调用方的兼容性。
如果涉及 SQL,还要检查当前数据库结构、迁移顺序和事务行为,以及查询结果或性能是否发生变化。
准备就绪后,推送新分支:
git push -u origin recover-sql-fixes
通过正常的 PR/MR 流程合入 dev。审查期间如果 dev 有变化,按团队约定更新新分支,并重新验证受影响部分。
5. 确认修复已进入 dev,再删除旧分支
删除前,确认必要修复已合入 dev 并通过验证,旧分支的其余提交也已审查并决定舍弃。
再获取一次远程状态:
git fetch origin --prune
检查远程旧分支是否有新提交,避免删掉其他人刚推送的工作。如果还有协作者在使用这个分支,先协调。
然后切换到其他分支:
git switch dev
可以先尝试普通删除:
git branch -d sql-bugfixes
经过 cherry-pick 后,原提交通常不会成为 dev 的祖先,因此即使修复已经迁移,-d 仍可能拒绝删除。它检查的是提交合并关系,无法判断代码改动是否已完整迁移。
完成审查、备份和验证后,可以强制删除本地分支:
git branch -D sql-bugfixes
再删除远程分支:
git push origin --delete sql-bugfixes
这两个操作不会撤销已进入 dev 的修复,也不会删除其他人的本地分支。不过,-D 会跳过未合并检查。如果旧分支是某些提交的唯一引用,删除分支后,这些提交日后可能被清理。
可以用保留的备份标签恢复分支:
git switch -c sql-bugfixes-restored archive/sql-bugfixes
reflog 有保留期限,也不能保证能从远程找回已删除的历史,不要把它当作长期备份。
什么时候应该重新实现修复
分支存在六个月,本身不代表合并一定有问题。Git 合并通常会结合共同祖先和两侧改动,不会简单地用旧分支快照覆盖新代码。需要评估的是旧分支的全部变化是否仍适用于当前系统。
如果两个提交边界清楚、依赖较少,cherry-pick 很合适。如果相关模块已经重写,或者提交混入大量过时代码,更适合保留原提交作为参考,在最新 dev 上重新编写修复和回归测试,并在提交说明中记录来源。
只有旧分支的大部分工作都值得保留时,才有必要考虑整体 merge 或 rebase。如果只需保留少量修复,迁移经过审查的改动更容易控制范围。
备注:内容仅供参考。
