前言
无论采用哪种git分支管理策略,master分支一般都作为主分支并设置为保护分支。开发人员往master分支提交代码只能通过合并动作。开发者发起一个pull request
(或者merge request
)动作将代码合入master
,而万一合并的代码有问题影响正常的上线,就需要回滚代码。一般的回滚方法有git reset
和git revert
。关于两者的比较已经有很多文章进行了详细的讲解,本文将重点讨论如何使用git revert
回滚合并到master分支的代码以及回滚之后的后续动作。
revert 一个 merge
git revert
会生成一个“反向操作”,动过动作反转实现代码回滚。这也正是git revert
和git reset
的最大区别。
需要注意的是git revert
动作没有删除已经提交的commit
,只是用一套反转动作将其覆盖,所以从语义上来讲开发者之前提交的commit
已经完全合入master
。这就为后续二次合入带来了一定的困扰,会出现commit
"丢失"问题。怎么解决改问题呢?请继续往下看~
准备动作
声明:本文操作均在github上进行。
先建一个测试分支 feature-A:
git checkout -b feature-A
git push origin feature-A:feature-A
在feature-A分支上提交更新:
使用git log
查看提交记录:
在github上发起 Pull Request
动作,合并代码到master
分支:
查看master分支的git 日志,可以看到Merge已经成功发起:
当然,不想在页面可视化操作的也可以用命令行执行merge:
git checkout master
git merge feature-A
git push origin master
这样,我们成功完成了一次标准merge操作。接下来,我们进行merge的回滚。
执行revert
还是在刚才的Pull Request
结束页面,可以看到有一个Revert按钮,点击该按钮就可以完成撤销merge:
对应的命令格式是:
git revert -m 1 <merge commit hash> # -m 后面的是数字1, 表示要回滚的是一个merge动作且以主分支的提交为准
在本实例中,最终的revert命令为:
git revert -m 1 3d244c2
因为master分支是保护分支,所以为了规范,我使用了github提供的可视化revert功能,这里github的做法是生成一个新的revert commit
,然后将该commit
merge到master
:
接下来呢?大家一般的做法肯定是在feature-A分支上继续开发,嗯...我们先按照这种方式来。但是我想先做一件事,同步master分支到feature-A。
开发过程中不断同步master分支代码是GitFlow、GitHubFlow以及GitLabFlow三种分支策略都要求的规范,在日常开发中应该严格遵循Git工作流规范。
错误示范
执行同步:
git checkout feature-A
git pull origin master --rebase
看下日志:
看着好像不对劲啊,这个文件怎么添加了两行呢,期望的内容明明只有一行啊?打开README.md文件,确认下文件内容:
what?feature-A分支的文件内容怎么被还原到修改前的状态了?!
github 出bug?
不,出bug的是你自己,我们看下feature-A的git log:
红框标记的这一条commit就是还原文件的内容的“罪魁祸首”,还记得该commit是怎么来的吗?
是的,在上文中我们想要revert merge,然后点击了页面上的revert按钮,这个点击动作让github生成了这一条提交,之后当你点击confirm时,该commit被merge到了master分支。这样就完成了git merge的revert。
然后,当你同步master分支代码时(无论是merge同步 or pull 同步),这条revert commit就被拉到feature-A分支,回滚魔法生效,feature-A分支的README.md文件内容被回滚到了修改前。
正确示范
姿势一(不推荐)
从错误的示范可知,直接同步master分支代码是不可行,首先我们要明确两点目标:
- 同步master分支代码到feature-A
- 保证feature-A本身的修改不被错误回滚
实现上面两个目标可以分为两步,第一步是同步master分支代码,第二步同步后的代码以feature-A的提交为准,保证之前的commit不被回滚。如此便可得到下面组合命令:
git fetch
git rebase origin/master
git push -f
我们用git rebase
逐个同步master分支的commit,此时又可能出现文件冲突,需要正确解决冲突。之后,用git push -f
将同步到的commit提交到origin。使用-f
修饰符是为了强制以feature-A当前的变更为准。
同步完代码后就可以愉快的编码啦,但是上述组合命令存在一个很恶心的问题:
如果feature-A分支与master分支差异很大且存在冲突,那么执行git rebase origin/master过程中将会产生串联冲突,需要逐个commit进行解决。
所以姿势一大家了解就好,实际开发过程中不推荐采用,下面的姿势二食用起来更健康哦。
姿势二(推荐)
首先,澄清一个问题:当一个feature分支被merge到了master,该分支的有效期是否认为已经结束?
本文给出答案是:是的,feature分支的生命周期从创建开始,以合入到master那一刻为终止! 所以,既然feature-A分支生命周期已经结束,我们完全可以不用去管它,后续的开发应该从master创建一个新的分支开始,记为feature-B:
git checkout master
git pull origin master --rebase
git checkout -b feature-B # 创建feature-B分支
git push origin feature-B:feature-B
但是,此时feature-B是没有保留feature-A的提交内容的,好的我们实现了目标1(同步master分支commit),但是还没有实现目标2(保留feature-A的修改内容)接下里的动作很重要,我们在feature-B上执行一个命令:
git revert [revert_id] # 反转master分支的revert
在本实例中的完整命令是:
git revert 085afb3 # 085afb3 是Revert "feature-A add这commit的id
git push origin feature-B # 如果push失败可以添加 --set-upstream 参数
查看git 日志:
看到日志中多了一条revert记录。此时,查看README.md文件可以发现feature-A的修改被保留了下来:
如此,我们即回滚了master分支上merge,有获得了保留着之前变更的新开发分支feature-B。两个目标完美达成!
上述内容就是本文要给大家呈现的一次完整git revert merge流程,喜欢的同学多多点赞哈。
总结
- 在前端主流的git分支管理策略中,master分支一般作为保护分支。开发分支的代码通过merge动作合入master;
- 开发分支的生命周期从创建起,截止到合入master为止;
- 一旦开发分支被合入master,无论何种原因都不应该在该开发分支上继续开发,应该从以master为source重新创建开发分支;
- GitHub/GitLab上的revert merge操作其实是生成一个新的revert commit,然后将该commit合入master;
- 在revert merge后,为了恢复之前开发分支提交的内容,可以使用
git revert [revert_id]
反转上一次的反转,用反反得正的方式恢复之前的变更。