TT Lab
开始
学习 学习路径 课程

Git 实战

同一个冲突解到第三次,说明漏掉了什么

在 TT Lab 中继续学习

一句话总结

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 的用途,是在历史中留下“决定不合并这个分支”这一事实。对方分支会作为祖先挂上去,所以从此以后该分支不再属于合并对象。如果出于想带入内容的目的使用它,就会出现这样的状态:历史中写着已经合并,代码里却什么也没进来——而且以后没有人会怀疑这一点。

在现场相遇的样子

下一项实验要做什么

创建两个在同一位置做了不同修改的分支,并排查看默认标记和 zdiff3 标记。打开 rerere,解决一次之后,撤销合并再重新合并,确认 git 自己应用了相同的解决方案。最后分别使用一次 -X ours 和 -s ours,通过内容来确认结果文件里留下了什么、丢失了什么。

官方文档是 git-rerere、git-merge、git-config。