git revert merge完全指南

3,510 阅读6分钟

前言

无论采用哪种git分支管理策略,master分支一般都作为主分支并设置为保护分支。开发人员往master分支提交代码只能通过合并动作。开发者发起一个pull request(或者merge request)动作将代码合入master,而万一合并的代码有问题影响正常的上线,就需要回滚代码。一般的回滚方法有git resetgit revert。关于两者的比较已经有很多文章进行了详细的讲解,本文将重点讨论如何使用git revert回滚合并到master分支的代码以及回滚之后的后续动作。

revert 一个 merge

git revert会生成一个“反向操作”,动过动作反转实现代码回滚。这也正是git revertgit reset的最大区别。

需要注意的是git revert动作没有删除已经提交的commit,只是用一套反转动作将其覆盖,所以从语义上来讲开发者之前提交的commit已经完全合入master。这就为后续二次合入带来了一定的困扰,会出现commit"丢失"问题。怎么解决改问题呢?请继续往下看~

准备动作

声明:本文操作均在github上进行。

先建一个测试分支 feature-A:

git checkout -b feature-A
git push origin feature-A:feature-A

在feature-A分支上提交更新:

image.png

使用git log查看提交记录:

image.png

在github上发起 Pull Request动作,合并代码到master分支:

image.png

查看master分支的git 日志,可以看到Merge已经成功发起:

image.png

当然,不想在页面可视化操作的也可以用命令行执行merge:

git checkout master
git merge feature-A
git push origin master

这样,我们成功完成了一次标准merge操作。接下来,我们进行merge的回滚。

执行revert

还是在刚才的Pull Request结束页面,可以看到有一个Revert按钮,点击该按钮就可以完成撤销merge:

image.png

对应的命令格式是:

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

image.png

接下来呢?大家一般的做法肯定是在feature-A分支上继续开发,嗯...我们先按照这种方式来。但是我想先做一件事,同步master分支到feature-A。

开发过程中不断同步master分支代码是GitFlow、GitHubFlow以及GitLabFlow三种分支策略都要求的规范,在日常开发中应该严格遵循Git工作流规范。

错误示范

执行同步:

git checkout feature-A
git pull origin master --rebase

看下日志:

image.png

看着好像不对劲啊,这个文件怎么添加了两行呢,期望的内容明明只有一行啊?打开README.md文件,确认下文件内容:

image.png

what?feature-A分支的文件内容怎么被还原到修改前的状态了?!
github 出bug?
不,出bug的是你自己,我们看下feature-A的git log:

image.png

红框标记的这一条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分支代码是不可行,首先我们要明确两点目标:

  1. 同步master分支代码到feature-A
  2. 保证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 日志:

image.png

看到日志中多了一条revert记录。此时,查看README.md文件可以发现feature-A的修改被保留了下来:

image.png

如此,我们即回滚了master分支上merge,有获得了保留着之前变更的新开发分支feature-B。两个目标完美达成!

上述内容就是本文要给大家呈现的一次完整git revert merge流程,喜欢的同学多多点赞哈。

总结

  1. 在前端主流的git分支管理策略中,master分支一般作为保护分支。开发分支的代码通过merge动作合入master;
  2. 开发分支的生命周期从创建起,截止到合入master为止;
  3. 一旦开发分支被合入master,无论何种原因都不应该在该开发分支上继续开发,应该从以master为source重新创建开发分支;
  4. GitHub/GitLab上的revert merge操作其实是生成一个新的revert commit,然后将该commit合入master;
  5. 在revert merge后,为了恢复之前开发分支提交的内容,可以使用git revert [revert_id]反转上一次的反转,用反反得正的方式恢复之前的变更。