合并冲突¶
很多人把 merge 想成“把两个文件拼起来”。
这太粗糙了。真正的 merge 至少同时涉及两层问题:
- 提交图问题;
- 文件内容问题。
merge 的第一步不是拼文件,而是找 base¶
假设你有两条分支:
- 当前分支
ours - 目标分支
theirs
在比较两边内容之前,必须先找到它们的最近公共祖先,也就是 base。
只有拿到这三个点:
baseourstheirs
你才能做真正的三方合并。
快进合并是什么¶
如果当前分支其实没有自己的额外提交,而只是落后于目标分支,那么 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。
只有用户:
- 手工编辑冲突文件;
- 确认最终内容;
- 重新
add;
冲突才算真正解决。
在 python-git-reproduction 中的对应实现¶
这个项目要求 merge 至少实现这些能力:
- DAG 遍历;
- 最近公共祖先搜索;
- fast-forward;
- 三方合并;
- 冲突标记;
- index stage 1/2/3;
- 冲突未解决时禁止 commit。
这也是为什么 merge 在任何 Git 复现项目里都属于难点之一。它同时要求你理解图、状态和内容。