一、背景:
今天将一段代码合入了master,上线的时候有问题,而不能很快解决掉,为了不影响其他同事合入master或将有问题的commit带上线,因此我将我的commit revert掉了。
同时,当天晚上的时候有同事刚好问我git revert后自己代码消失的问题,当时思考不清晰,也比较少用revert,因此现在来复盘下。
commit提交流程示意图如下:
其中A是有问题的commit,R是revert的commit,M是master,序号N代表master的流向
如果我在commitA上rebase M3的代码时,会发现代码没有变动,或者说在commitA中修改了部分内容再提交,A中的代码合入master后还是消失了。
二、原因:
git合并历史commit
如果合并一个历史commit,会以master的git历史图为准。
原因是无论是merge还是rebase,都会先找到两个分支的公共节,再进行相应的合并操作。但是历史commit肯定是master的commit的父节点,所以两个分支的公共节点必定就是历史commit本身,所以rebase跟merge只会合并公共节点之后的commit,HEAD就只进行FastForward,最终跟master保持一致。
三、解决办法
1. git revert VS git reset
当想要修改有问题的远程分支时,我们需要回退到没有合入主分支的版本,该怎么做呢?
git revert与git reset都能帮助我们解决问题,但是适用的场景不尽相同。
(1)git reset
git reset的作用就在于重新设置HEAD,由于每次提交都会产生一个commit,我们用reset的时候可以制定我们要回退的历史版本commit,如:
git reset --hard commitA
git reset --soft commitB
- hard 参数代表强重置,废弃所有的提交,因此会有代码消失的风险,使用时需谨慎
- soft 参数代表弱重置,保留指定历史commit之后所有的修改,并存放在暂存区(相当于git add .)
(2) git revert
git revert实际就是将你merge到master的所有commit进行反向操作,并将所有的操作合并成一个commit,无论原本commit有多少,都只会产生一个commit,命令如下:
git revert commitA
操作完成之后再次合入到主分支即可"抵消"掉你原本merge的代码
2. 解决revert带来的代码消失问题
回退到原来版本后,如何将A的代码中再次合入master呢?
中心思想就是保留A代码且需要一个新的分支。
比较方便的做法是:在新的master开一个新branch,在新branch中再将revert掉你revert掉的代码(有点像负负得正的意思)
git checkout -b new_branch
git revert commitR // git revert是会触发生成一个commit的
// 此处bugfix
git revert:
- 优点:能够快速回滚代码,并且可以保留revert记录,建议master分支回退代码使用git revert
- 缺点:需要重新生成个新的分支并生成新的commit才能够再次合入到master
3. 解决非master的回退
我们可以使用git reset --soft来回退到指定的版本,并进行bugfix,再推到远程仓库来修改提交历史,但由于本地仓库的HEAD与远程仓库的HEAD历史产生了分叉,因此推到远程分支的时候会报错:
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
为了解决历史分叉问题,并且保留现有新的提交,我们可以强行将本地commit推到远程仓库,如:
git push origin --force branch
git push origin -f branch // -f是--force的缩写
PS: !!! 使用 -f 的时候需要特别小心,因为你一旦将现有的提交强推上去了,可能会导致原有的有效代码下掉,所以使用的时候切记当心,必须清楚自己在干什么。
git reset:
- 优点:能够在同一个分支里面进行处理,分支合并操作次数较少
- 缺点:操作不会出现在历史图中,开发分支回滚建议使用reset。由于master是保护分支,不建议使用在master分支上
具体使用哪个命令来处理建议结合具体场景分析,并没有绝对的好坏。