在大屏或电脑上体验更佳——动画和操作区域更宽敞。
输入

三方合并

三方合并用一个共同的旧快照比较两个分支末端。当前检出侧叫 ours,命令指定的源侧叫 theirs,最佳共同祖先叫 merge base。基点提供修改前的状态,让 Git 判断哪一侧改过、哪一侧没改。

提交图负责选择基点并判断末端关系;三个快照负责逐路径决定内容。所有路径都得到无冲突结果后,引擎才写入结果树、双父提交,并推进当前分支。若冲突,这些最终写入都不会发生。

四条路径规则

对一个路径,比较 base、ours、theirs:

条件结果
ours == theirs两侧相同,取任一侧
ours == base只有源侧修改,取 theirs
theirs == base只有当前侧修改,取 ours
三者互不相同冲突,停止

两侧独立改成相同值时,即使与 base 不同,第一条也直接接受。本课用两个平面文本文件演示整文件值比较。真实 Git 还处理目录、模式、删除、重命名、二进制、驱动和策略选项,但 base 始终提供共同的修改前状态。

三方合并逐步演示

默认轨迹如下:

commit snapshot "init":         app.py=print("v1"), README.md=hello
git branch feature
commit snapshot "main-api":     app.py=print("v2"), README.md=hello
git switch feature
commit snapshot "feature-docs": app.py=print("v1"), README.md=quickstart
git switch main
git merge feature

使用固定身份和时间的 Git 格式 SHA-1 对象,让变化集中在树和父提交。

  1. 根快照形成树 a7c4b8a... 和提交 6bb158e...,main 首次指向它。
  2. 创建 feature,在 6bb158e... 新增引用,不复制提交,HEAD 仍在 main。
  3. main-api 只改 app.py,形成提交 fb177c6...,父为 6bb158e...,只推进 main。
  4. HEAD 切到仍在基点的 feature。feature-docs 改 README,app.py 与 base 相同,形成兄弟提交 87fb417...。
  5. 切回 main,合并固定 ours = fb177c6...、theirs = 87fb417...,比较期间不移动输入。
  6. 祖先探测先标记 ours 可达提交,再遍历源历史,得到唯一最佳共同祖先 6bb158e...。
  7. base 不等于任一末端,既不是已包含,也不能快进,载入三个快照。
  8. app.py 的 theirs 与 base 都为 print("v1"),按第三条取 ours 的 print("v2")。
  9. README 的 ours 与 base 都为 hello,按第二条取 theirs 的 quickstart。
  10. 结果为 app.py=print("v2")、README=quickstart,序列化成树 2bcb0a8...。
  11. 提交正文引用结果树,第一父为旧 HEAD fb177c6...,第二父为源末端 87fb417...,得到提交 73bb817...。
  12. 最后 main 进到 73bb817...,feature 留在 87fb417...。新增一个提交对象,当前引用写一次,源引用不写。

父顺序记录命令的视角。即使结果树相同,交换两条 parent 行也会改变提交 ID,因为有序头部参与哈希;first-parent 历史遍历也会选择不同主线。

为什么必须选最佳共同祖先

遍历先遇到的共同祖先不一定最佳。一个共同祖先可能还有更新的共同后代,这时应使用后者。本引擎收集共同祖先,删除那些仍是其他共同候选祖先的旧候选。

普通分叉只剩一个最佳基点;交错合并可能留下多个。本可视化在此报告模型边界,而非说历史非法。真实 Git 的 ort 等策略可以把多个基点合成为虚拟基点后,再合并末端。使用过旧基点会把错误的“之前状态”交给路径规则。

边界情况

  • base 等于 ours:当前为源祖先,移动当前引用即可快进,不需要三快照协调或合并提交,工作台不打开。
  • base 等于 theirs:当前已包含源,报告已是最新;即使名称指向不同提交,也不写对象或引用。
  • 两侧同改:ours 与 theirs 相同,第一条接受,base 可不同。
  • 同一路径三值不同:本引擎在结果树写入前停止。真实 Git 为该路径存索引 stage 1、2、3,设置 MERGE_HEAD 等合并状态,工作文件可能写入冲突标记;解决并提交后才有合并提交。
  • 多个最佳基点:交错历史可能发生,本模型停止,真实策略可合成虚拟基点。
  • 八提交上限:只是布局限制。轨迹报告超出范围,不存拟议提交,不表示真实 Git 拒绝大仓库。

常见错误

  • “三方是三条分支。” 三方是 base、ours、theirs 三个快照,命令只涉及两个末端。
  • “第一个共同祖先就是基点。” 还要排除有更新共同后代的旧候选。
  • “ours 总赢。” theirs 未改才取 ours;ours 未改就取 theirs;两侧相同则接受共同值。
  • “冲突会创建冲突提交。” 合并停在未解决索引状态,解决所有路径后才创建正常结果提交。
  • “合并推进两条分支。” 只推进当前检出的目标分支,源不动。
  • “合并提交存补丁。” 它引用完整结果树和两个父提交,差异由快照之间计算。
  • “父顺序只影响显示。” 顺序属于对象字节,会影响 ID 和第一父历史。

模型范围

本课有干净本地仓库、附着 HEAD、两个 100644 平面文件、确定性 SHA-1 对象,最多三个分支和一个唯一最佳基点。路径合并按完整字符串值比较。省略干净合并中的工作区和索引、重命名、删除、模式、二进制驱动、自定义属性、钩子、签名、标签、远程、子模块、章鱼合并、策略选项、reflog、并发引用更新及递归虚拟基点构造。

实际行为见 git merge、git merge-base、git read-tree。