Git之master主干代码回滚

http://www.heartthinkdo.com/?p=1791
http://blog.psjay.com/posts/git-revert-merge-commit/

通过git revert来实现线主干代码的回滚。如下命令

  • 对于 merge类型的commit对象,还需要“-m”参数

git revert -m 1 commit-id

  • 对于普通的commtit对象

git revert commit-id

1 问题描述

一个同事不小把自己代码合并 到了master生成C2。但是在该同事还没有回滚之前,又有其他同事合并到master生成了 C3。现在问题是我们想要回滚到C1应该怎么做?

1

2 解决问题

1、选择git revert还是git reset?

git revert是生成新的commit对象,而git reset是删除commit对象,为了保留记录,使用git revert命令。

2、解决

按时间顺序,依次回滚每一个commit对象,直到自己想要的那个commit对象为准。如下:

  • 回滚C3 ,c3-id是C3对应的那个commit-id

git revert -m 1 c3-id

  • 回滚C2。其中c2-id是C2对应的那个commit-id

git revert -m 1 c2-id

3、上面的命令为什么有“-m 1”?

这是因为上面的C2和C3不是普通的commit对象,都是merge生成的commit对象。如下图,如果需要通过git revert回滚M3,那么此时会在M3后面生成一个新的commit节点R,那么这个新节点R是属于M分支(M1->M2->M3->R)还是D分支(D1->D2->D3->R)呢?此时可以通过“-m” 来指定,如果是1,表示的是当前所在的分支,如果是2表示的是另外的分支。

2

对于上面的问题,由于我们当前分支是master,我们希望revert之后生成的commit也在master这条分支上,所以指定“-m 1”。

这里需要注意的是,如果只是普通的commit 对象,不是merge类型commit对象(由merge产生commit对象),就不需要”-m 1″了

3 问题总结

后续再遇到代码回滚,通过哪些步骤来做呢?这里总结了一些步骤。

1、假设在master上C0后面有C1、C2、C3三次代码提交,此时需要回滚到C0。

3

2、回滚步骤如下

(1)第一步 切到master代码,使用git log,如下图,获取到每一个commit对象对应的commit-id和commit对象类型(是普通类型还是merge类型)。

commit ee1389bba4bfcaa0ddb850c6e58d1e982fdfcb4d
  Merge:  8397201  7dc873c
  Author:  jie01  <jie01@qq.com>
  Date: Thu Oct  12  20:27:33  2017  +0800
    Merge branch  'fweb_1-0-806_BRANCH'  into master

commit  2f315650dff28e94d654309ed3230c34d32f1000
  Author:  shan03  <shan03@qq.com>
  Date: Tue Oct  17  14:33:16  2017  +0800

commit  7dc873cd34b8bba1fab68635ddf8331ab2babc74
  Merge:  cbaf7d9 d24f133
  Author:  xu01  <xu01@qq.com>
  Date: Thu Oct  12  15:59:13  2017  +0800
    Merge branch  'web_1-0-790_BRANCH'  into financeweb_1-0-806_BRANCH

(2) 第二步 按时间倒序,依次执行”git revert”回滚每一个commit对象

  • 对于 merge类型的commit对象,需要“-m”参数
    git revert -m 1 commit-id
  • 对于普通的commtit对象
    git revert commit-id

所以回滚命令如下

  • 回滚C3,merge类型commit对象,需要-m

git revert -m 1 ee1389bba4bfcaa0ddb850c6e58d1e982fdfcb4d

  • 回滚C2,普通commit对象

git revert 2f315650dff28e94d654309ed3230c34d32f1000

  • 回滚C1,merge类型commit对象,需要-m

git revert -m 1 7dc873cd34b8bba1fab68635ddf8331ab2babc74

后续问题

比如说当M3执行了git revet生成RM3,此时在master上又有其他人提交了M4,我们还需要在dev上进行开发D4和D5,如下图

Snip20171203_2

此时怎么合并D5到master的M4

(1)第一步 master合并到D5

因为master执行了git revert撤销操作,所以此时D1和D2的代码会被删除?

  • 在master上执行git revert撤销 RM3
  • 将master合并到dev的D5

(2)第二步 将dev的合并到master

附1 其他方法-通过覆盖方法来实现回滚主干

步骤如下:

  • 第一步 分别拉两份代码。第一份代码上面基于master新建一个分支F1,作为发布分支;第二份代码可以通过git checkout切到自己想要回滚的那个版本上(切到某个commit对象上)。
  • 第二步 删除分支F1对应的代码,把第二份代码拷贝到F1上面,即使用第二份代码覆盖F1的代码。。
  • 第三步 提交F1代码,并合并到master。

附2 git revert和git reset区别

1、git revert 。只是撤销某一次commit的操作,并没有删除commit对象;并且会生成一个新的commit对象。

假设分支为m1->m2->m3。当我们”git revet m2″生成m4,此时在m4里面只是把m2的操作撤销了而已,并没有撤销m2和m3对象。此时分支为“m1->m2->m3->m4”

2、git reset。删除某一个commit之后所有commit提交对象;不会产生新的commit对象。

假设m1->m2->m3,当我们git reset m2,此时并没有生成新的commit对象,并且删除了m2和m3两个commit对象,此时分分支为”m1->m2″

撤销提交

Git 的 revert 命令可以用来撤销提交(commit),对于常规的提交来说,revert 命令十分直观易用,相当于做一次被 revert 的提交的「反操作」并形成一个新的 commit,但是当你需要撤销一个合并(merge)的时候,事情就变得稍微复杂了一些。

Merge Commit
在描述 merge commit 之前,先来简短地描述一下常规的 commit。每当你做了一批操作(增加、修改、或删除)之后,你执行 git commit 便会得到一个常规的 Commit。执行 git show <commit> 将会输出详细的增删情况。

Merge commit 则不是这样。每当你使用 git merge 合并两个分支,你将会得到一个新的 merge commit。执行 git show <commit> 之后,会有类似的输出:

commit 19b7d40d2ebefb4236a8ab630f89e4afca6e9dbe
Merge: b0ef24a cca45f9
......
其中,Merge 这一行代表的是这个合并 parents,它可以用来表明 merge 操作的线索。

举个例子,通常,我们的稳定代码都在 master 分支,而开发过程使用 dev 分支,当开发完成后,再把 dev 分支 merge 进 master 分支:

a -> b -> c -> f -- g -> h (master)
           \      /
            d -> e  (dev)

上图中,g 是 merge commit,其他的都是常规 commit。g 的两个 parent 分别是 f 和 e。

Revert a Merge Commit
当你使用 git revert 撤销一个 merge commit 时,如果除了 commit 号而不加任何其他参数,git 将会提示错误:

git revert g
error: Commit g is a merge but no -m option was given.
fatal: revert failed
在你合并两个分支并试图撤销时,Git 并不知道你到底需要保留哪一个分支上所做的修改。从 Git 的角度来看,master 分支和 dev 在地位上是完全平等的,只是在 workflow 中,master 被人为约定成了「主分支」。

于是 Git 需要你通过 m 或 mainline 参数来指定「主线」。merge commit 的 parents 一定是在两个不同的线索上,因此可以通过 parent 来表示「主线」。m 参数的值可以是 1 或者 2,对应着 parent 在 merge commit 信息中的顺序。

以上面那张图为例,我们查看 commit g 的内容:

git show g
commit g
Merge: f e
那么,$ git revert -m 1 g 将会保留 master 分支上的修改,撤销 dev 分支上的修改。

撤销成功之后,Git 将会生成一个新的 Commit,提交历史就成了这样:


a -> b -> c -> f -- g -> h -> G (master)
           \      /
            d -> e  (dev)

其中 G 是撤销 g 生成的 commit。通过 $ git show G 之后,我们会发现 G 是一个常规提交,内容就是撤销 merge 时被丢弃的那条线索的所有 commit 的「反操作」的合集。

Recover a Reverted Merging
上面的提交历史在实践中通常对应着这样的情况:

工程师在 master 分支切出了 dev 分支编写新功能,开发完成后合并 dev 分支到 master 分支并上线。上线之后,发现了 dev 分支引入了严重的 bug,而其他人已经在最新的 master 上切出了新的分支并进行开发,所以不能简单地在 master 分支上通过重置(git reset )来回滚代码,只能选择 revert 那个 merge commit。

但是事情还没有结束。工程师必须切回 dev 分支修复那些 bug,于是提交记录变成了这个样子:

a -> b -> c -> f -- g -> h -> G -> i (master)
           \      /
            d -> e -> j -> k (dev)

工程师返回 dev 分支通过 j,k 两个 commit 修复了 bug,其他工程师在 master 上有了新的提交 i。现在到了 dev 分支的内容重新上线的时候了。

直觉上来说,还是和之前一样,把 dev 分支合并到 master 分支就好了。于是:

$ git checkout master
$ git merge dev
得到的提交记录变成了这样:

a -> b -> c -> f -- g -> h -> G -> i -- m (master)
           \      /                    /
            d -> e -> j -> k ---------    (dev)

m 是新的 merge commit。需要注意的是,这不能得到我们期望的结果。因为 d 和 e 两个提交曾经被丢弃过,如此合并到 master 的代码,并不会重新包含 d 和 e 两个提交的内容,相当于只有 dev 上的新 commit 被合并了进来,而 dev 分支之前的内容,依然是被 revert 掉了。

所以,如果想恢复整个 dev 所做的修改,应该:

$ git checkout master
$ git revert G
$ git merge dev
于是,提交历史变成了这样:

a -> b -> c -> f -- g -> h -> G -> i -> G' -- m (master)
           \      /                          /
            d -> e -> j -> k ---------------    (dev)

其中 G' 是这次 revert 操作生成的 commit,把之前撤销合并时丢弃的代码恢复了回来,然后再 merge dev 分支,把解 bug 写的新代码合并到 master 分支。

现在,工程师可以放心地上线没有 bug 的新功能了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。