同一个冲突解到第三次,说明漏掉了什么
一句话总结
rerere 会记录你是如何解决冲突的,同样的冲突再次出现时就原样应用。merge.conflictStyle=zdiff3 会在冲突标记中同时显示共同祖先,这样就不必再猜“谁改了什么”。-X ours 和 -s ours 只是名字相似,结果却完全不同。
为什么需要它
如果你把一个长期存在的功能分支不断 rebase 到 main 之上,同一个位置会反复出现同样的冲突。对十个提交做 rebase,这个冲突就要解十遍。不小心 --abort 后重来,又是十遍。人一累,手就容易滑,而滑了手的解决方案会被悄悄提交。
而且冲突标记本身信息不足。默认的标记是这样的。
<<<<<<< HEAD
b by main
=======
b by topic
>>>>>>> topic
这里有一样东西看不出来——原来是什么。无法区分是一方删除了,还是双方改成了不同的内容,还是一方保持原样而只有另一方修改了。于是人们只好选“看起来安全的一侧”,而这个判断一旦错了,别人的修改就消失了。
工作原理
zdiff3——同时显示共同祖先
git config merge.conflictStyle zdiff3
这样冲突标记中就会多出中间一栏。
<<<<<<< HEAD
b by main
||||||| 75364cd
b
=======
b by topic
>>>>>>> topic
||||||| 之下是共同祖先的内容。现在一眼就能看出“原来是 b,双方改成了不同的内容”。旧的配置值 diff3 也做同样的事,不过 zdiff3(git 2.35 及以上)会把双方共有的行挪到冲突之外,所以标记更短。
rerere——记住解决方案
git config rerere.enabled true
打开之后,发生冲突时 git 会把冲突前的样子(preimage)记录到 .git/rr-cache,提交时再把解决后的结果(postimage)附在同一个位置。下次出现完全相同的 preimage 时,它会这样说。
Resolved 'handler.txt' using previous resolution.
有三点要注意。第一,rerere 只应用解决方案,不会暂存。看过结果后执行 git add 是人的职责(打开 rerere.autoUpdate 会自动暂存,但那样就会不经确认而放过,所以不推荐)。第二,记录保存在 .git 中,所以只留在我自己的机器上。第三,解错了的方案也会被同样记住——这时必须用 git rerere forget <경로>(占位符为路径)把那条记录删掉。
-X ours 和 -s ours 完全不同
名字相似,经常被混淆。
| 旋钮 | 是什么 | 结果 |
|---|---|---|
-X ours |
传给默认合并策略的选项 | 只在冲突的位置选我方的,没有冲突的对方改动照常合入 |
-s ours |
策略本身 | 整个丢弃对方的树。结果树与合并前我方的树一个字符也不差 |
-s ours 的用途,是在历史中留下“决定不合并这个分支”这一事实。对方分支会作为祖先挂上去,所以从此以后该分支不再属于合并对象。如果出于想带入内容的目的使用它,就会出现这样的状态:历史中写着已经合并,代码里却什么也没进来——而且以后没有人会怀疑这一点。
在现场相遇的样子
- 定期把发布分支合回
main时,版本号那一行每次都会出现同样的冲突。打开 rerere 之后,从第二次起,只看一眼标记就能过去。 - 对经常需要中断 rebase 再重新开始的人,rerere 的价值最大,因为他们会反复遇到同样的冲突。
- 如果生成文件(锁文件、打包产物)每次都冲突,与其让它记住解决方案,不如在
.gitattributes中指定合并方式,或者干脆不再跟踪它们。 - 把
merge.conflictStyle=zdiff3作为团队规则推荐的地方越来越多。因为只需一行配置,就不用再猜“原来是什么”。
下一项实验要做什么
创建两个在同一位置做了不同修改的分支,并排查看默认标记和 zdiff3 标记。打开 rerere,解决一次之后,撤销合并再重新合并,确认 git 自己应用了相同的解决方案。最后分别使用一次 -X ours 和 -s ours,通过内容来确认结果文件里留下了什么、丢失了什么。
官方文档是 git-rerere、git-merge、git-config。