摘要
长期分支反复合并或变基时,相同冲突可能被多次触发。Git rerere 能记录人工解决结果,在冲突再次出现时自动复用,减少重复操作,同时保留人工检查与回退空间。
问题背景
在短生命周期分支里,一次合并通常只需要解决一次冲突。
但真实项目经常不是这样。一个功能分支可能持续数周,中间要反复同步主分支;一个补丁系列也可能在评审期间多次执行 rebase。只要双方持续修改相同区域,同一组冲突就会重复出现。
常见过程大致是:
text
第一次 rebase:解决冲突,继续提交
主分支更新后再次 rebase:重新解决相似冲突
调整提交历史后再次 rebase:第三次处理同一组冲突
最让人浪费时间的不是冲突本身,而是开发者明明已经做过判断,Git 却不知道上一次是怎么解决的。
我最后采用的方案是启用 rerere,让 Git 记录冲突前后的内容,在相同冲突再次出现时复用之前的人工处理结果。
rerere 解决了什么问题
rerere 是 reuse recorded resolution 的缩写,可以理解为"复用已记录的冲突解决方案"。
第一次遇到冲突时,仍然需要开发者手动判断并修改文件。文件解决并加入暂存区后,Git 会记录这次处理结果。
之后如果出现相同的冲突形态,Git 会尝试把之前的结果重新应用到工作区。
它并不会替开发者理解业务,也不是一个通用的自动合并算法。它只是把已经做过的人工判断保存下来,避免重复劳动。
Git 官方文档对它的典型场景描述得很明确:长期存在的主题分支需要反复合并或变基时,开发者可能多次解决同样的冲突,而 rerere 可以记录并复用第一次的人工解决结果。
启用 rerere
可以只在当前仓库启用:
bash
git config rerere.enabled true
也可以全局启用:
bash
git config --global rerere.enabled true
检查当前配置:
bash
git config --get rerere.enabled
返回 true 就说明已经开启。
如果希望 Git 在复用解决结果后自动把文件加入暂存区,还可以启用:
bash
git config --global rerere.autoupdate true
我更倾向于一开始只开启 rerere.enabled,暂时不启用 rerere.autoupdate。这样 Git 可以帮忙恢复内容,但文件是否进入暂存区仍由开发者确认,第一次使用时更容易理解真实行为。
第一次冲突仍然需要手动处理
假设当前功能分支需要变基到主分支:
bash
git switch feature/order-flow
git fetch origin
git rebase origin/main
Git 报告冲突后,先检查状态:
bash
git status
再打开冲突文件,处理这些标记:
text
<<<<<<< HEAD
主分支内容
=======
功能分支内容
>>>>>>> feature/order-flow
修改完成后加入暂存区并继续:
bash
git add src/order/service.ts
git rebase --continue
启用 rerere 后,Git 会在这个过程中记录冲突内容和最终解决结果。第一次操作看起来和普通冲突处理没有明显区别,真正的差异会在下一次相同冲突出现时体现。
再次遇到冲突时会发生什么
当分支之后再次 rebase,并出现同样的冲突,Git 会尝试恢复上一次的结果。
终端通常会出现类似提示:
text
Resolved 'src/order/service.ts' using previous resolution.
这时不要直接认为任务已经完成,先检查工作区:
bash
git status
git diff
确认内容正确后再继续:
bash
git add src/order/service.ts
git rebase --continue
如果启用了 rerere.autoupdate,Git 可能已经把复用成功的文件加入暂存区。此时应该同时检查普通 diff 和暂存区 diff:
bash
git diff
git diff --cached
rerere 最有价值的地方不是让开发者跳过检查,而是把"重新手写同一个结果"变成"检查上一次结果是否仍然适用"。
几个有用的 rerere 命令
查看当前有哪些文件正在使用 rerere:
bash
git rerere status
查看 rerere 记录的解决差异:
bash
git rerere diff
查看仍未解决的路径:
bash
git rerere remaining
如果发现某个路径记录了错误的处理结果,可以让 Git 忘记它:
bash
git rerere forget src/order/service.ts
然后重新手动解决这次冲突并加入暂存区,Git 会记录新的结果。
清除当前冲突会话的 rerere 元数据可以使用:
bash
git rerere clear
这几个命令里,我最常用的是 status、diff 和 forget。前两个用于确认复用内容,后一个用于纠正错误记录。
一个更适合 rerere 的工作流
rerere 在频繁 rebase 的功能分支里尤其合适。
例如一个分支需要持续跟随主分支:
bash
git switch feature/order-flow
git fetch origin
git rebase origin/main
第一次遇到冲突:
text
手动理解双方改动
完成业务层面的合并
运行测试
继续 rebase
之后再次遇到相同冲突:
text
让 rerere 应用历史结果
检查 diff
运行测试
继续 rebase
真正节省的是第二次以及之后的重复处理成本。
如果冲突只会出现一次,rerere 的收益并不明显;如果同一分支会持续同步主线,它的价值会随着重复次数增加。
为什么不能完全相信自动复用
相同的文本冲突不一定代表相同的业务语义。
例如第一次冲突时,保留旧字段是正确的;两周后主分支已经重构了接口结构,即使 Git 识别出相同冲突,上一次的结果也可能不再适用。
因此每次复用后至少要做三类检查:
text
检查 diff 是否符合当前需求
运行受影响模块的测试
确认没有残留冲突标记
可以搜索冲突标记:
bash
git grep -n -e '<<<<<<<' -e '=======' -e '>>>>>>>'
如果项目支持定向测试,优先运行受影响模块,而不是只确认代码能够提交:
bash
npm test -- order
或者:
bash
./gradlew test
rerere 降低的是重复编辑成本,不应该降低验证标准。
适合使用的场景
rerere 比较适合这些工作流:
- 长期存在的功能分支持续同步主分支
- 大型重构期间反复执行 rebase
- 补丁系列根据评审意见多次重排提交
- 多个发布分支反复吸收同一批修复
- 合并结果需要先验证,再撤销并等待正式合入
它不太适合被包装成"所有冲突自动解决工具"。如果团队很少遇到重复冲突,或者每次冲突的业务上下文差异很大,收益会比较有限。
常见误区
误区一:启用后第一次冲突也会自动解决
不会。rerere 必须先看到一次人工解决结果,之后才能尝试复用。
误区二:出现 Resolved 提示就可以直接提交
不建议。复用结果仍然要经过 diff 和测试检查,尤其是在主分支持续演进时。
误区三:记录错误后只能关闭 rerere
不需要。可以使用 git rerere forget <path> 删除指定路径的错误记录,再重新解决。
误区四:rerere 记录会自动分享给团队
默认不会。相关记录保存在本地仓库的 Git 元数据中。它首先是个人工作流优化,不是团队冲突规则库。
最后总结
重复冲突最浪费时间的地方,是开发者已经做过一次正确判断,却不得不再次手动输入同样的结果。
rerere 的思路很克制:第一次仍由人解决,Git 只负责记录;之后出现相同冲突时,Git 复用历史结果,人再负责检查。
对长期分支和频繁 rebase 的项目来说,这比单纯加快冲突编辑更可靠。它不会消除冲突,但能把重复劳动压缩成一次可审查的复用过程。
参考资料:Git rerere 官方文档