提前声明,这个问题你在公司可能一辈子碰不上,只有很多野路子协同开发可能会遇到这个问题。不过,这个对于理解revert的本质确实很有帮助。
一、实验复现问题
在feature分支上开发了一个新功能,其中有部分带点bug
恰巧,代码评审时没有发现这个问题,将这个feature开发的新功能合并到了master上
自然而然的,master在线上运行的过程中出现了问题,这个时候我们选择了通过revert回滚
按照正常流程,有bug就要修复,于是在feature上修复了这个bug
经过更加严格的代码评审,这个代码没问题,于是又将修复后的feature合并到了master
这个时候问题出现了,master上feture1和feature2的文件没有了,仅留下feature3
二、分析下问题如何造成的
在我们将feature
的修复代码合并到master
之前,master
和feature
的分支如下:
此时按照常理来说,合并时会进行差异检查 。两个分支会因为revert feature
和fix
两个commit出现了冲突需要进行合并。但是,事实上并没有。
非常反直觉的是,在操作合并时revert feature
这个记录像失效了一样,fix
直接合并了上来。
此时fix
仅变更了含有bug的feature3 而revert feature
中没有任何fearue的部分。
三、出现问题的原因
对此,Linus早就给出了解释:
Reverting a regular commit just effectively undoes what that commit did, and is fairly straightforward. But reverting a merge commit also undoes the data that the commit changed, but it does absolutely nothing to the effects on history that the merge had.
So the merge will still exist, and it will still be seen as joining the two branches together, and future merges will see that merge as the last shared state - and the revert that reverted the merge brought in will not affect that at all.
So a "revert" undoes the data changes, but it's very much not an "undo" in the sense that it doesn't undo the effects of a commit on the repository history.
So if you think of "revert" as "undo", then you're going to always miss this part of reverts. Yes, it undoes the data, but no, it doesn't undo history.
简单的梳理下:
- 我们在feature分支上revert一个常规commit,就仅仅是撤销这个commit做的改动
- 但是如果我们在master分支上revert一个
merged commit
(合并上来的commit),也仅仅撤销这个commit的改动,而不会对master分支产生任何对这个merge状态的影响 - 这就意味着意味着在master分支上
merge feature
的状态仍然存在 - 我们在feature修复后再发起merge,检查时会看到master分支上存在merged状态
- 因此只会带上merge状态之后(也就是feature之后)的改动,即
fix
,之前被revert的改动(即feature)不会再被merge到master分支上
最后总结下核心原因:revert撤回了代码的变更,但是没有撤回合并的记录,在master分支上慎用此方法。
四、如何解决这个问题
- 在master上复原feature的改动 ,也就是
revert
revert feature
, 让master和feature分支上的代码一致,然后再进行合并。
- 在feature上同步master,然后在进行修复,最后合并也能解决这个问题。
这就是为什么这个问题很难在公司遇到的原因,正经人谁不先看一下master直接提merge啊。但是这样也会把feature分支上有用的代码全部弄没。
- 虽然,revert没有撤回合并的状态,但是那个合并的状态绑定的是原有的
commit ID
,我们可以从feature将原commit的内容带到master,但是需要产生和原merged commit不同的状态(不同的commit SHA ID)
在feature分支reset + commit,产生新的commits:
feature-copy
修复后再merge到master
参考文章
Git revert 某次merge后再重新 merge代码被丢失(第一次想merge的代码再也merge不上了)_git revert 后 再merge-CSDN博客
git 分回滚后无法合并代码问题 - 赵坤坤 - 博客园 (cnblogs.com)