bigywater.tech
Git · 45

三路合并:ort 如何处理冲突

git merge 在底层做的就是三方合并:基础、ours、theirs。这篇用小例子把它的行为一项项对出来:什么时候自动合并、什么时候冲突、冲突时 index 和 .git 里留下什么、改名会不会被识别、-X 和 -s 到底差在哪里。

3 个阶段
冲突文件在 index 里的 stage 1(基础)、2(ours)、3(theirs)
相邻即冲突
改动相邻的两行(只隔 0 行)冲突,中间隔一行就不冲突
-X vs -s
-X ours 只在冲突块选 ours;-s ours 整个忽略对方的内容但仍产生合并提交

三方合并

实测 · 本机执行输出
  $ git merge theirs 2>&1 | head -3; git diff HEAD~1 --stat | tail -1
  Auto-merging f.txt
  Merge made by the 'ort' strategy.
   f.txt | 2 +-
   1 file changed, 1 insertion(+), 1 deletion(-)
  git merge-file -p ours base theirs 的输出与合并结果一致: True

对每个文件,git 取 merge-base 里的版本为基础,与 ours、theirs 各做一次比较,不重叠的改动两边都收。底层命令是 git merge-file,实测它的输出与合并结果完全一致。

冲突时留下什么

实测 · 本机执行输出
  $ git merge theirs 2>&1 | head -3; git status --short; sed -n 4,8p f.txt
  Auto-merging f.txt
  CONFLICT (content): Merge conflict in f.txt
  Automatic merge failed; fix conflicts and then commit the result.
  UU f.txt
  第 4 行
  <<<<<<< HEAD
  第 5 行 ours 改
  =======
  第 5 行 theirs 改
  $ git ls-files -u | sed -E 's/^([0-9]+) [0-9a-f]{40} ([123])/\1 <sha> stage\2/'
  100644 <sha> stage1	f.txt
  100644 <sha> stage2	f.txt
  100644 <sha> stage3	f.txt
  $ for s in 1 2 3; do printf "stage $s: "; git show :$s:f.txt | sed -n 5p; done
  stage 1: 第 5 行
  stage 2: 第 5 行 ours 改
  stage 3: 第 5 行 theirs 改
  $ cat .git/MERGE_HEAD | cut -c1-7; cat .git/MERGE_MSG | head -3; ls .git | grep -E "^MERGE_"
  72fdde5
  Merge branch 'theirs'
  
  # Conflicts:
  MERGE_HEAD
  MERGE_MODE
  MERGE_MSG

工作区文件里有冲突标记;index 里同一路径保留三个阶段(1 基础、2 ours、3 theirs),可以用 git show :1:文件 取出;.git 里出现 MERGE_HEAD、MERGE_MODE 和 MERGE_MSG。

什么算冲突

实测 · 本机执行输出
  $ git merge theirs 2>&1 | head -2
  Merge made by the 'ort' strategy.
  $ git merge theirs 2>&1 | head -2
  Auto-merging f.txt
  CONFLICT (content): Merge conflict in f.txt
  $ git merge theirs 2>&1 | head -2
  Auto-merging f.txt
  Merge made by the 'ort' strategy.

相同的改动不冲突;改动块相邻就算冲突(即使改的是不同的行);中间隔一行以上则能自动合并。

文件级冲突

实测 · 本机执行输出
  $ git merge theirs 2>&1 | head -3; git status --short
  CONFLICT (modify/delete): f.txt deleted in theirs and modified in HEAD.  Version HEAD of f.txt left in tree.
  Automatic merge failed; fix conflicts and then commit the result.
  UD f.txt
  $ git merge theirs 2>&1 | head -3; git status --short; cat new.txt
  Auto-merging new.txt
  CONFLICT (add/add): Merge conflict in new.txt
  Automatic merge failed; fix conflicts and then commit the result.
  AA new.txt
  <<<<<<< HEAD
  ours 新增的 new
  =======
  theirs 新增的 new
  >>>>>>> theirs

一边删除一边修改是 modify/delete(状态 UD),两边各自新增同名文件内容不同是 add/add(状态 AA)。

改名

实测 · 本机执行输出
  $ git merge theirs 2>&1 | head -4; ls; sed -n 5p renamed.txt
  Merge made by the 'ort' strategy.
   old.txt => renamed.txt | 0
   1 file changed, 0 insertions(+), 0 deletions(-)
   rename old.txt => renamed.txt (100%)
  renamed.txt
  第 5 行 ours 改

一边改名、一边修改内容时,ort 检测到改名,把修改应用到新名字上。检测依据见下一篇。

-X 与 -s

实测 · 本机执行输出
  git merge -X ours:第 2 行='第 2 行 ours 改'         第 5 行='第 5 行 ours 改'         第 9 行='第 9 行 theirs 改'       x.txt='x theirs 改'
  git merge -X theirs:第 2 行='第 2 行 ours 改'         第 5 行='第 5 行 theirs 改'       第 9 行='第 9 行 theirs 改'       x.txt='x theirs 改'
  git merge -s ours:第 2 行='第 2 行 ours 改'         第 5 行='第 5 行 ours 改'         第 9 行='第 9 行'                x.txt='x'
  (-X 只在冲突块选边,不冲突的块两边都收;-s ours 不收对方的任何改动,但仍产生一个有两个 parent 的合并提交)
  $ git cat-file -p HEAD | grep -c "^parent"
  2

-X ours/-X theirs 只在冲突块上选边,其余改动都收;-s ours 不收对方任何改动,但仍记录一次有两个 parent 的合并。

取消

实测 · 本机执行输出
  $ git merge --abort; git status --short; ls .git | grep -E "^MERGE_" || echo "(MERGE_HEAD 等文件都没了)"; sed -n 5p f.txt
  (MERGE_HEAD 等文件都没了)
  第 5 行 ours 改

git merge --abort 回到合并之前,清除 MERGE_HEAD 等文件。

分步演示

三方合并与冲突
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / git 2.54.0(Apple Git-157);每个实验在临时目录里新建仓库,HOME 和全局配置指向实验专用的空目录(不读用户自己的 git 配置),提交的作者与时间固定以便复现;需要网络协议的实验用本机的 file:// 或 git daemon 回环端口(9800 以上),不碰任何远程仓库;计时用 time.perf_counter,数字每次运行会有小幅抖动。所有例子在每个场景单独的仓库里重做;作者与时间固定;冲突阶段的 hash 做了省略
    • 没有验证:diff3 / zdiff3 样式的冲突标记(见后面冲突标记一篇);rerere 记住冲突解法(见后面);目录改名检测(见 rename 检测一篇之后);criss-cross 合并时虚拟基础的构造;-X patience、-X ignore-space-change 等其它策略选项;二进制文件的冲突;合并子模块
    京ICP备2026067502号-1京公网安备11010502063487号