无论采用哪种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]反转上一次的反转,用反反得正的方式恢复之前的变更。
  • 分类:
    前端
    标签: