因为最近帮别人解决了一次git merge丢代码的情况,当时处理地简单粗暴,reset到merge前一次commit后重来一遍。后来又设想了一下,如果merge后又commit很多次,这样岂不会很麻烦?如果这个分支不允许force push,那这样做也不行。于是就去查了一些资料,自己尝试并总结了一下几个方法。
用git reset重新来过
这种方法很简单,就是git reset
到merge之前的一次commit,然后重新merge,然后如果之前在merge之后还有commit的话利用git cherry-pick <commit start>^...<commit end>
apply到当前分支。最后git push --force
到远端分支(如果之前已经push了的话)
这种方法适合你在push之前就发现了错误的情况,因为用了git reset
后当前分支就必须要force push了。
force push有什么问题?
一般来说不应该在非私人分支上force push,因为这有可能会覆盖掉其他人在该分支上的commit。而且远端的protected branch是不能直接force push的。
更好的方法?
首先解决第一个问题,如何不用force push?
如果我们不修改要push分支的历史的话是不需要force push的,那么很简单了,只要把需要修改历史的reset操作在另外一个不需要push的分支上操作就行了。
实际操作就是从当前分支拉一条新分支出来,然后进行上面reset的方法,最后再rebase或merge回当前分支。
既然这里也提到了rebase,其实我们还可以简化一下上面的方法。
# 假设我们这里merge feature到master
# 使用第一种方法
git checkout -b fix-merge
git reset <commit before merge> --hard
git merge feature # fix conflict
git cherry-pick <commit start>^...<commit end> # maybe need fix conflict
git checkout master
git merge fix-merge # fix conflict
# 用rebase简化一下,因为rebase也是一个会修改历史的操作,所以还是需要新开分支
git checkout -b fix-merge
# 用rebase代替了reset > merge > cherry-pick的操作
git rebase -i <commit before merge> # fix conflict
git checkout master
git merge fix-merge # fix conflict
这里在rebase时添加了-i
的参数,可以清楚地展示和操作每次会应用的commit。如果不懂rebase可以看看官方文档。
用上面的方法要处理的冲突太多了怎么办?
无论是cherry-pick还是rebase都是将commit一次次给apply,所以merge之后的commit如果有冲突,那也得一次次解决,有时候一直要改同一个文件的话,那解决冲突就是很痛苦的一件事了。
这里有两个方案可以考虑一下:
其实在第一种方法中完全没必要用cherry-pick,新分支在重新merge后可以直接merge到当前分支的。当然这样还是一样要处理很多冲突,只不过可以一次处理完了。
git checkout -b fix-merge git reset <commit before merge> --hard git merge feature # fix conflict git checkout master git merge fix-merge # fix large conflict
如果是丢了代码并知道丢的是哪次提交的代码,那么可以直接
git cherry-pick <commit id>
。直接改代码。
git revert可以解决问题吗?
我在网上也看到很多用git revert
来解决错误merge的方法,但是这样做只能撤销merge而无法去修复merge,而且就算提交了revert commit,当前分支也不能重新merge,因为revert只是普通的commit而不能改变已经存在的merge commit。