bigywater.tech
Git · 46

冲突标记:merge、diff3、zdiff3 的区别

冲突标记 merge、diff3、zdiff3 有什么区别?这篇用同一个冲突、三种样式并排看清,再看 checkout --conflict、combined diff、rebase 时 ours/theirs 调转,以及 log --merge 怎么帮你找相关的提交。

3 种样式
merge.conflictStyle 的 merge、diff3、zdiff3
|||||||
diff3 / zdiff3 多出的一段:基础版本,能看出两边各自改了什么
rebase
rebase 冲突时 <<<<<<< HEAD 一侧是上游的新基础,不是你自己的提交

merge 样式

实测 · 本机执行输出
  $ git merge theirs >/dev/null 2>&1; sed -n 3,12p f.txt
  第 3 行
  <<<<<<< HEAD
  第 4 行 ours 的修改
  =======
  第 4 行 theirs 的修改
  >>>>>>> theirs
  共同新增的一行
  第 5 行
  第 6 行
  第 7 行

默认样式只有 ours 和 theirs 两段。两边完全相同的开头或结尾行已经被挪到冲突块外面。

diff3

实测 · 本机执行输出
  $ git merge theirs >/dev/null 2>&1; sed -n 3,12p f.txt
  第 3 行
  <<<<<<< HEAD
  第 4 行 ours 的修改
  共同新增的一行
  ||||||| 01b0b82
  第 4 行
  =======
  第 4 行 theirs 的修改
  共同新增的一行
  >>>>>>> theirs

多出 ||||||| 一段,显示基础版本,便于看出两边各自的改动。缺点是共同的行被重复写进两侧,块更长。

zdiff3

实测 · 本机执行输出
  $ git merge theirs >/dev/null 2>&1; sed -n 3,12p f.txt
  第 3 行
  <<<<<<< HEAD
  第 4 行 ours 的修改
  ||||||| 01b0b82
  第 4 行
  =======
  第 4 行 theirs 的修改
  >>>>>>> theirs
  共同新增的一行
  第 5 行

在 diff3 基础上把两边共同的行挪出冲突块,块里只留真正有分歧的部分。信息量和简洁兼得,merge.conflictStyle=zdiff3 是常见的推荐配置。

换样式

实测 · 本机执行输出
  $ git checkout --conflict=diff3 f.txt; grep -n -E "^(<<<<<<<|\|\|\|\|\|\|\||=======|>>>>>>>)" f.txt
  4:<<<<<<< ours
  7:||||||| base
  9:=======
  12:>>>>>>> theirs
  Recreated 1 merge conflict
  $ git checkout --conflict=merge f.txt; grep -c "^|||||||" f.txt || true
  0
  Recreated 1 merge conflict

git checkout --conflict=diff3 文件 用 index 里的三个阶段重新生成标记,但会覆盖你已经做的手工编辑。重新生成的标签是 ours / base / theirs。

combined diff

实测 · 本机执行输出
  $ git diff | sed -n 1,16p
  diff --cc f.txt
  index 5142d45,b88af78..0000000
  --- a/f.txt
  +++ b/f.txt
  @@@ -1,7 -1,7 +1,11 @@@
    第 1 行
    第 2 行
    第 3 行
  ++<<<<<<< HEAD
   +第 4 行 ours 的修改
  ++=======
  + 第 4 行 theirs 的修改
  ++>>>>>>> theirs
    共同新增的一行
    第 5 行
    第 6 行
  $ git diff --base f.txt | grep -E "^[+-][^+-]" | head -4; echo "--- --ours:"; git diff --ours f.txt | grep -E "^[+-][^+-]" | head -4; echo "--- --theirs:"; git diff --theirs f.txt | grep -E "^[+-][^+-]" | head -4
  -第 4 行
  +<<<<<<< HEAD
  +第 4 行 ours 的修改
  +=======
  --- --ours:
  +<<<<<<< HEAD
  +=======
  +第 4 行 theirs 的修改
  +>>>>>>> theirs
  --- --theirs:
  +<<<<<<< HEAD
  +第 4 行 ours 的修改
  +=======
  +>>>>>>> theirs

冲突期间 git diff 输出 diff --cc,每行前面两列分别表示相对 ours 与 theirs 的变化;--base、--ours、--theirs 分别与 stage 1/2/3 比较。

rebase

实测 · 本机执行输出
  $ git rebase main 2>&1 | head -2; sed -n 4,10p f.txt
  Rebasing (1/1)
  Auto-merging f.txt
  CONFLICT (content): Merge conflict in f.txt
  <<<<<<< HEAD
  第 4 行 ours 的修改
  =======
  第 4 行 theirs 的修改
  >>>>>>> 8008ba9 (我的提交)
  共同新增的一行
  第 5 行

rebase 是把你的提交一个个重放到新基础上,所以冲突标记里 HEAD 一侧是新基础(上游),另一侧才是你的提交。--ours/--theirs 的含义也因此颠倒。

log --merge

实测 · 本机执行输出
  $ git log --merge --oneline --left-right | sed -E "s/^(.)[0-9a-f]{7} /\1 /"
  < 26e6c7d ours
  > 94d02b5 theirs

git log --merge --left-right 只列出触及冲突文件、两边各自的提交,快速定位谁改了什么。

分步演示

冲突标记
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / git 2.54.0(Apple Git-157);每个实验在临时目录里新建仓库,HOME 和全局配置指向实验专用的空目录(不读用户自己的 git 配置),提交的作者与时间固定以便复现;需要网络协议的实验用本机的 file:// 或 git daemon 回环端口(9800 以上),不碰任何远程仓库;计时用 time.perf_counter,数字每次运行会有小幅抖动。三种样式用同一个冲突在三个仓库里重做;标记里基础提交的缩写 hash 在输出中省略;rebase 一节的冲突提交 hash 做了固定时间,可复现
    • 没有验证:merge.conflictStyle 的取值以外的 mergetool 工作流;冲突标记长度(conflict-marker-size 属性);二进制文件的冲突;git rerere 记录与重放冲突(见 rerere 一篇);图形化合并工具;多于两方的 octopus 合并冲突
    京ICP备2026067502号-1京公网安备11010502063487号