起因
我有一个开发分支antd3.x和一个主分支develop,我在合并antd3.x到develop的时候发现有些修改没有合并进来。
查找问题
然后就去网上查,发现这篇文章《git合并丢失代码问题分析与解决》给我了一些启发。
其中说到git merge的原理是三方合并,简单来说就是假设我有a和b两个分支,我要合并b到a,这个时候git 其实还会去找到a和b的最近的父节点c,将c作为基础的分支,然后对abc进行比较,如果有一个文件xxx.js,xxx.js的内容abc三个分支上同一行都不一样那么就会报conflict,因为git也不知道该保留谁的代码,就会让你自行决断;如果只有一个分支比如b上面的xxx.js的同一行代码和c分支上的不一样那么git就会自作主张的认为应该保留b上面的修改,看到这我就有了方向,我应该去找我此次合并的两个分支的那个最近的父节点,问题应该就出在他上面。
通过执行 git merge-base antd3.x develop 找到了他俩base节点2344a88,然后查看该节点的代码发现那些没有合并进来的修改已经存在于这个base节点上了,怪不得进行三方合并的时候没有合并进来,因为git发现develop分支上对比base分支没有这些修改,于是这些修改就被删除掉了。但是到这一步我又有疑问了,我没有手动的删除过develop上的这些修改,为啥这些修改会没有了呢,于是我查看log发现我曾经通过 git revert 撤销过一个合并,而这个合并恰巧就是那个修改的内容,到这块问题基本就清楚了,下面我总结一下。
总结
让我们从头捋一下这个问题的前因后果:
- 我有三个分支develop、antd3.x和std-08,其中antd3.x是根据develop拉取的,std-08是根据antd3.x拉取的
- 此时我想要合并antd3.x到develop分支上,执行代码 git merge --no-ff antd3.x
- 发现antd3.x上面的有些修改(x)没有合并到develop上
- 通过 git merge-base antd3.x develop 找到了他俩base节点2344a88
- 发现base节点上面存在修改x
- 通过 git log 发现develop分支上曾经执行过 git revert
- 本来当时是想把std-08合并到antd3.x上结果合并到了develop上,也就是此时修改x合并到了develop分支上
- 然后执行了 git revert 撤销了此次合并,并正确合并std-08到antd3.x上,这就导致了antd3.x和develop的共同父节点变成了std-08的最后一次提交2344a88
- 所以后续再想合并antd3.x到develop上时,进行三方合并,发现base节点上的修改在develop上面被删除了,所以合并的结果就是删除这些修改,但是实际上这些修改我们是想保留的
解决方案
解决方案有两个:
1.在develop上执行 git reset commitId 到合并std-08之前的那次提交然后 git push -f origin ,这样develop的commit历史就不包含那次合并了
2.在develop上执行 git revert HEAD~ 把之前revert的再revert掉,这个我没有试过
思考
其实这个教训充分暴露了我对 git revert 和 git reset 的区别不甚了解,可以看下这篇文章《git revert 用法》,对它俩的区别解释的很清楚,其实就是 git revert 的撤销原理就是删除掉之前的提交然后执行commit,这样所有的commits都会保留下来,也就埋下了隐患,在我的场景里就是在git 合并的时候寻找的base节点就是develop revert之前的那个commit,如果是 git reset 就不会保留这个commit,也就不会把它作为base节点,合并的时候就不会有问题。我看了下网上还有另一种解释就是执行完 git revert 后,git就认为你不在需要这些修改,以后再合并的时候如果有这样的修改要合并,git就会忽略,我没有验证过这种说法的真实性,如果有人清楚的话,还请不吝赐教。