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到本地,把 remote master合并到该分支(可以先fetch master,再合并到本地feature,或者直接把 remote master pull到本地的feature),再用外部合并工具解决冲突,然后push,这时候pull request上显示可以合并,直接点Merge按钮即可。
  • 如果master不受保护或者可以使用管理员权限强行push,则还可以在本地 pull master后,把feature合并到master,再 push master到GitHub。

Reference

https://www.git-tower.com/lea...
https://stackoverflow.com/que...
https://git-scm.com/docs/git-...

相关推荐