跳转至

合并冲突

很多人把 merge 想成“把两个文件拼起来”。
这太粗糙了。真正的 merge 至少同时涉及两层问题:

  • 提交图问题;
  • 文件内容问题。

merge 的第一步不是拼文件,而是找 base

假设你有两条分支:

  • 当前分支 ours
  • 目标分支 theirs

在比较两边内容之前,必须先找到它们的最近公共祖先,也就是 base。
只有拿到这三个点:

  • base
  • ours
  • theirs

你才能做真正的三方合并。

快进合并是什么

如果当前分支其实没有自己的额外提交,而只是落后于目标分支,那么 merge 甚至不需要创建新的 merge commit。

这种情况叫 fast-forward。
本质上只是把当前分支 ref 向前推进到目标提交。

这说明 merge 不一定总要“合并内容”,有时只是“移动引用”。

三方合并怎么看

真正的三方合并不是简单比较 ours 和 theirs。
它要比较的是:

  • base 到 ours 发生了什么;
  • base 到 theirs 发生了什么;
  • 这些变化是否互相兼容。

常见情况:

  • 只有一边改了:直接采用那一边;
  • 两边改了不同位置:通常可自动合并;
  • 两边在同一位置做了不兼容修改:产生冲突。

冲突为什么会出现

冲突的根本原因不是“Git 笨”,而是:

系统无法确定哪一边的修改才是你真正想保留的结果。

当两边都对同一段内容做了重叠但不同的修改时,自动决策就会变得不可靠。这时候必须把决定权交还给用户。

工作区中的冲突标记

最常见的冲突表现,是 Git 在工作区文件里插入类似这样的隔离带:

<<<<<<< HEAD
ours 的内容
=======
theirs 的内容
>>>>>>> target

这只是冲突的可视化表现,方便用户手工编辑。

冲突不只存在于工作区文本里

很多初学者以为冲突只是一段文件文本。实际上,Git 还会在 index 里记录冲突状态。

典型做法是:

  • Stage 1 记录 base;
  • Stage 2 记录 ours;
  • Stage 3 记录 theirs。

这意味着冲突是“工作区 + index 元数据”共同表达的状态,不是单靠文本分隔符在工作。

为什么冲突没解决前不能 commit

如果冲突状态还保留着,系统就无法确定“当前仓库快照到底是什么”。
因此,合理实现会在未解决冲突时拒绝继续 commit。

只有用户:

  1. 手工编辑冲突文件;
  2. 确认最终内容;
  3. 重新 add

冲突才算真正解决。

python-git-reproduction 中的对应实现

这个项目要求 merge 至少实现这些能力:

  • DAG 遍历;
  • 最近公共祖先搜索;
  • fast-forward;
  • 三方合并;
  • 冲突标记;
  • index stage 1/2/3;
  • 冲突未解决时禁止 commit。

这也是为什么 merge 在任何 Git 复现项目里都属于难点之一。它同时要求你理解图、状态和内容。