Git 三方合并策略详解
Git 三方合并策略详解一、什么是合并Merge在 Git 中合并是将两条独立的开发线分支的修改整合到一起的操作。当你执行git merge时Git 需要决定如何将两个分支的代码变更合并为一个最终结果。二、快进合并Fast-Forward Merge在了解三方合并之前先了解最简单的合并方式。场景A --- B --- C (main) \ D --- E (feature)如果 main 分支在你创建 feature 分支后没有任何新提交合并时 Git 只需要把 main 的指针直接移动到 feature 的最新提交即可A --- B --- C --- D --- E (main, feature)这就是快进合并不会产生新的合并提交。特点不产生合并提交merge commit历史记录是一条直线只有在目标分支没有新提交时才会发生三、三方合并Three-Way Merge什么时候触发当两个分支都有各自的新提交时Git 无法简单地快进必须执行三方合并A --- B --- C --- F --- G (main) \ D --- E (feature)此时 main 有了 F、G 两个新提交feature 有了 D、E 两个新提交Git 需要把两边的修改合在一起。三方合并的三方是什么三方合并涉及三个版本角色说明示例共同祖先Merge Base两个分支最近的共同提交提交 C当前分支Ours你当前所在的分支的最新状态提交 G目标分支Theirs你要合并进来的分支的最新状态提交 E为什么需要共同祖先假设某个文件的某一行在共同祖先中是x 10在 main 中是x 10没改在 feature 中是x 20改了如果没有共同祖先作为参照Git 只看到 main 是x 10feature 是x 20无法判断应该用哪个。有了共同祖先Git 的判断逻辑就很清晰共同祖先是x 10main 也是x 10→ main 没改共同祖先是x 10feature 是x 20→ feature 改了结论采用 feature 的修改四、共同祖先Merge Base定义共同祖先是两个分支在提交历史中最近的共同节点。它代表了两个分支分道扬镳的那个时间点。图示E --- F (feature-A) / A --- B --- C --- D (main) \ G --- H (feature-B)feature-A和main的共同祖先是Bfeature-B和main的共同祖先是Bfeature-A和feature-B的共同祖先也是B查找共同祖先gitmerge-basebranch1branch2这个命令会输出共同祖先的 commit SHA。复杂场景多次合并后的共同祖先A --- B --- C --- D --- M1 --- E (main) \ / F --- G --- H (feature)如果 feature 曾经被合并到 mainM1之后 feature 又继续开发那么再次合并时共同祖先不再是 B而是H上次合并时 feature 的状态Git 会自动找到最近的共同祖先确保只合并上次合并之后的新变更。五、三方合并的决策规则对于文件中的每一处差异Git 按以下规则决定最终结果共同祖先Ours当前分支Theirs目标分支Git 的决策AA未改B改了采用 BAB改了A未改采用 BAB改了C也改了但不同冲突AB改了B改了且相同采用 BAA未改A未改保持 A规则总结只有一方修改→ 自动采用修改方的版本双方做了相同修改→ 自动采用无冲突双方做了不同修改→ 产生冲突需要人工解决双方都没改→ 保持原样六、冲突Conflict什么时候产生冲突当同一处代码两个分支都做了不同的修改时Git 无法自动决定用哪个版本就会标记为冲突。冲突标记 HEAD // 当前分支ours的代码 int count getCount(); // 目标分支theirs的代码 long count getCount(); feature解决冲突手动编辑文件选择保留哪个版本或合并两者删除冲突标记、、git add file标记为已解决git commit完成合并七、合并提交Merge Commit三方合并完成后Git 会创建一个特殊的合并提交它有两个父提交A --- B --- C --- F --- G --- M (main) \ / D --- E ---- (feature)M 就是合并提交它的两个父提交分别是 Gmain 的最新和 Efeature 的最新。查看合并提交的父提交gitlog-1--format%Pmerge_commit输出两个 SHA第一个是当前分支的父提交ours第二个是被合并分支的父提交theirs。注博客https://blog.csdn.net/badao_liumang_qizhi八、实际场景分析场景从旧分支合并到已更新的目标分支时间线 1月dev 上有人把 Integer 改为 Long 3月你从 master还是 Integer拉出 feature 分支 5月你把 feature 合并到 dev合并时的三方对比共同祖先master 3月状态你的分支devInteger count ...Integer count ...Long count ...Git 判断你没改这行dev 改了 → 采用 dev 的Long。场景你修改了同一行附近的代码如果你在 feature 分支修改了Integer count ...这行附近的代码比如上下几行Git 可能会把你修改的部分保留把 dev 修改的部分也保留如果两者修改了同一行 → 产生冲突场景你的分支不是从 dev 拉的如果你从 master 拉分支而 master 和 dev 的代码状态不同合并到 dev 时共同祖先可能是很早之前的提交导致大量差异需要合并增加了自动合并出错的风险。九、合并策略选项recursive默认Git 默认使用 recursive 策略进行三方合并。当存在多个共同祖先时它会递归地合并这些祖先来构造一个虚拟祖先。oursgitmerge-sours feature完全忽略对方的修改保留当前分支的所有内容。theirs通过选项实现gitmerge-Xtheirs feature冲突时自动选择对方的版本。十、最佳实践频繁同步定期将目标分支dev/main合并到你的 feature 分支减少最终合并时的差异从正确的分支拉取如果要合并到 dev就从 dev 拉分支而不是从 master小步提交每次提交的改动尽量小且聚焦减少冲突范围合并前先拉取最新git fetchgit merge确保本地是最新状态合并后立即验证编译、运行测试确保合并结果正确十一、总结三方合并的核心思想通过共同祖先作为参照基准判断每一处差异是谁改的从而自动决定最终结果。只有当双方都改了同一处且改法不同时才需要人工介入。理解了这个原理你就能理解为什么合并后代码会自动变化——不是 Git 出了问题而是它正确地执行了三方合并的逻辑。