Git三路合并的隐患
我觉得最近碰到的这个情况应该不算少见,做一点总结。
这个repository的流程是 remote master
分支受保护,不直接接受push,新增feature通过pull request合并到master
分支。
场景再现
某个组员自己的A分支中的P文件是旧版本,提交了pull request,由于种种原因没有发现P文件的问题,结果合并到master
分支后,master
分支的P文件部分的代码倒退回了N天前。我尝试通过B分支(最新P版本)发起一个pull request,合并到master
试图将P重新恢复到最新版本。但结果出乎意料,P仍然是旧版本。
分析
问题出在git自身的三路合并(three-way merge)机制,如果base和X分支的某一部分代码相同,但和Y分支不同,合并的结果就会采用Y分支不同的那部分
再加上之前我对git合并的一个误区,以为 merge conflicts 给出的结果涵盖了两个分支所有变动。事实上仅属于单一分支的变动不会显示。(本地除了用外部合并工具,还可以git difftool --ours
or git difftool --theirs
or git difftool --base
)
现在用图例来展示下合并的时间轴:
图示:假设P5版本比P2新, M1是引入旧版本的合并, M2是我尝试修复的合并:
B: P5-----------------P5 / \ master: ---P5(M1 base)---P5(M2 base)---P2(M1)---P2(M2) \ / A: P2----------------------P2
在M2合并的时候,Git(包括GitHub上处理合并冲突的webIDE)生成的冲突文件不会显示仅由P2改动的区域。
目前我发现的解决方法只能通过外部合并工具(git mergetool
)比如Kaleidoscope, Beyond Compare, vimdiff或者JetBrains家自带的冲突处理工具才能完整看到哪些是仅仅被P2改动的部分。
但似乎GitHub的pull request不支持用外部合并工具处理冲突。除非在GitHub处理冲突的webIDE上直接粘贴P5,想通过M2更新到P5无解。如果P2不仅仅包含旧版本,还有新增的feature,人工处理就会很麻烦。
后来我的解决方法是:在本地用外部合并工具解决冲突后,再提交pull request。
具体操作:从M1 pull到了B分支(这样一来base就变成M1),在本地通过外部合并工具修复回P5(或者git checkout --ours path/to/P
),再发pull request合并到master
。
还有种方法就是 pull master
后,在本地把B分支合并到master
(通过外部合并工具修复回P5),用管理员权限push到 remote master
。
总结
GitHub上pull request的Resolve conflicts
慎用
本地git生成的冲突文件慎用。
鉴于以上几点,今后pull request出现冲突时,可以采用如下流程:
(假设pull request对应的分支是feature
)
- 把 remote
feature
fetch到本地,把 remotemaster
合并到该分支(可以先fetchmaster
,再合并到本地feature
,或者直接把 remotemaster
pull到本地的feature
),再用外部合并工具解决冲突,然后push,这时候pull request上显示可以合并,直接点Merge按钮即可。 - 如果
master
不受保护或者可以使用管理员权限强行push,则还可以在本地 pullmaster
后,把feature
合并到master
,再 pushmaster
到GitHub。
Reference
https://www.git-tower.com/lea...
https://stackoverflow.com/que...
https://git-scm.com/docs/git-...