引用评论区 “tomi21634” 同学的简短版总结: 1. git revert 后会多出一条commit,这里可进行回撤操作 2. git reset 直接把之前 commit 删掉,非 git reset --hard 的操作是不会删掉修改代码,如果远程已经有之前代码,需要强推 git push -f

如果简短版的你觉得还看得不是很明白,你可以往下在认真看图和说明。

git reset

develop ----1      3-----
             \   /
branch a       a

develop将a分支合并后,想要不留痕迹的撤回合并。这个时候用git reset就是很好的选择了。

具体的操作步骤如下:

  • 切换分支到develop
  • git log 查看当前分支日志
  • 例如我的日志是这样的:

    commit 3
    Merge 1 2
    Author: admin <admin@163.com>
    Date: Wed May 30 15:00:00 2018 +0800
        Merge branch 'feature/a' into 'develop'
        close a
        See merge request !20
    commit 2
    Author: admin <admin@163.com>
    Date: Wed May 30 14:00:00 2018 +0800
        close a
    commit 1
    Author: admin <admin@163.com>
    Date: Wed May 30 13:00:00 2018 +0800
        init project
    

    我要将develop回退到合并之前的状态,那就是退到 commit 1这了,将commit号复制下来。退出编辑界面。

  • 讲一下git reset参数定义,具体见官网
    --soft 回退后a分支修改的代码被保留并标记为add的状态(git status 是绿色的状态)
    --mixed 重置索引,但不重置工作树,更改后的文件标记为未提交(add)的状态。默认操作。
    --hard 重置索引和工作树,并且a分支修改的所有文件和中间的提交,没提交的代码都被丢弃了。
    --merge--hard类似,只不过如果在执行reset命令之前你有改动一些文件并且未提交,merge会保留你的这些修改,hard则不会。【注:如果你的这些修改add过或commit过,merge和hard都将删除你的提交】
    --keep--hard类似,执行reset之前改动文件如果是a分支修改了的,会提示你修改了相同的文件,不能合并。如果不是a分支修改的文件,会移除缓存区。git status还是可以看到保持了这些修改。
    

    a分支的代码我不需要了,以后应该也不需要了。因此:

    git reset 1(粘贴过来的commit号) --hard

    a分支代码我还是想要的,只是这个提交我不想要了:

    git reset 1

  • git log查看一下:
  • commit 1
    Author: admin <admin@163.com>
    Date: Wed May 30 13:00:00 2018 +0800
        init project
    

    是我想要的没错了,可以push到远端去了。(这一步很危险,一定要确认好reset的结果确实是你想要的结果,否则,丢失了代码就不要怨我了。同时,确定公司的代码部署可以自己过去重新拉代码,像一般公司自己搞的部署系统,应该是不支持的,很可能会搞出问题,像我就是了。。。)

    git push origin develop

    ![rejected] develop -> develop (non-fast-forward)
    error: 无法推送一些引用到 'git@github.cn:...'
    提示:更新被拒绝,因为您当前分支的最新提交落后于其对应的远程分支。
    

    好吧,我的分支确实落后于远程的develop分支。我需要--force

    git push origin develop --force

    Total 0 (delta 0), reused 0 (delta 0)
    To git@...
    + 83***...23***** develop -> develop (forced update)
    

    可以了,回到github查看network。嗯,就是想要这样的。

    git revert

    还是这个例子

    develop ----1      3-----
                 \   /
    branch a       a
    

    还是之前的需求,不想要合并a,只想要没合并a时的样子。

    操作步骤:

  • 切分支到develop:git checkout develop
  • 查看日志:git log ,还是上面的日志:
  • commit 3
    Merge 1 2
    Author: admin <admin@163.com>
    Date: Wed May 30 15:00:00 2018 +0800
        Merge branch 'feature/a' into 'develop'
        close a
        See merge request !20
    commit 2
    Author: admin <admin@163.com>
    Date: Wed May 30 14:00:00 2018 +0800
        close a
    commit 1
    Author: admin <admin@163.com>
    Date: Wed May 30 13:00:00 2018 +0800
        init project
    

    这次和git reset 不同的是我不能复制 commit 1这个commit号了,我需要复制的是commit 2的commit号。因为revert后面跟的是具体需要哪个已经合并了的分支,而并不是需要会退到哪的commit号。

  • 开始回退:git revert 2
  • Revert "close a"
    This reverts commit 2
    #.......
    

    这是相当于又新增了一个commit,把a分支上的所有修改又改了回去。

  • Ctrl+X离开编辑commit信息页面。
  • git log查看一下是不是我想的那样

    commit 4
    Author: admin <admin@163.com>
    Date: Wed May 30 17:00:00 2018 +0800
        Revert "close a"
        This reverts commit 2
    commit 3
    Merge 1 2
    Author: admin <admin@163.com>
    Date: Wed May 30 15:00:00 2018 +0800
        Merge branch 'feature/a' into 'develop'
        close a
        See merge request !20
    commit 2
    

    确实是新增加了一个commit,查看代码发现a分支的修改都不存在了,也达到了我想要的效果。

  • push的远程服务器上git push origin develop
  • 查看network,是这样的:

    develop ----1      3-----revert a------
                 \   /
    branch a       a
    

    如此看来,git resetgit revert都能实现我现在的需求,那这两个到底有什么区别呢,在网上查了这个问题,我觉得说的有些抽象,看的不是很明白,于是自己实践了之后才明白。

    git revert是用一次新的commit来回滚之前的commit,git reset是直接删除指定的commit。

    这个很好理解,在刚才的操作中我们看日志已经可以看到这个现象。

    git reset操作之后,我们查看上面例子的network已经可以看到network中只有commit 1,分支a合并分支后的commit 3都消失了;

    git revert操作之后,network中还是可以看到a分支和合并a分支的操作,只不过在其基础上又增加了一个revert的commit而已。

    git reset 是把HEAD向后移动了一下,而git revert是HEAD继续前进,只是新的commit的内容和要revert的内容正好相反,能够抵消要被revert的内容。

    这个也是可以清晰明了的看到,我就不做过多的解释了

    在回滚这一操作上看,效果差不多。但是在日后继续merge以前的老版本时有区别。因为git revert是用一次逆向的commit“中和”之前的提交,因此日后合并老的branch时,导致这部分改变不会再次出现,但是git reset是直接把某些commit在某个branch上删除,因而和老的branch再次merge时,这些被回滚的commit应该还会被引入。

    git revert

    现在的需求是我之前已经把a分支revert了,但是我现在又需要a分支的代码了,我之前都写过一遍总不能再重新写一遍了。我首先想到的方法,把a分支再merge到develop不就好了。

    git merge a
    
    Already up-to-date
    

    啥?因为我们之前提交合并的a分支的代码还在,因此我们并不能在重新合并a分支。

    解决办法:使用revert之前revert的commit号。在上面的例子中就是git revert 4。于是又新增了一个commit,把之前revert的代码又重新revert回来了。具体参考

    git reset

    依旧是上面的需求。

    develop -----1-----4-------5------6
                  \   / \     / \    /
    feature         b      c       d
    

    现在我将develop reset到commit 4这里,继续提交我以后的代码

    develop -----1-----4-------7------8
                  \   / \\     /
    feature         b     \ e
                            \------5----d
                              \-c-/
    

    我现在想重新合并d分支的代码

     develop -----1-----4-------7------8------9
                  \   / \\     /             /
    feature         b     \ e              /
                            \------5----d/
                              \-c-/
    

    合并过来大概是这个样子的。也就是说d分支之前已经合并过c分支,因此如果还想要合并d分支的话,c分支的代码就会同步的带过来。

    讲到这里大部分常用的应用场景也就应该是比较清晰的了。

  • 如果回退分支的代码以后还需要的话用git revert就再好不过了; 如果分支我就是提错了没用了还不想让别人发现我错的代码,那就git reset
  • 例如:develop分支已经合并了a、b、c、d四个分支,我忽然发现b分支没用啊,代码也没必要,这个时候就不能用reset了,因为使用reset之后c和d的分支也同样消失了。这时候只能用git revert b分支commit号,这样c和d的代码依然还在。
  • 我能想到的大概就是这些了,当然在具体项目上我们会遇到很多没遇到过的情形。我的建议是不要盲目的查找类似问题后就去修改,因为他的问题并不一定就等同于你的问题。我建议去创建测试的分支,等真正整明白了,再去整线上的分支也不迟。

    第一次写了这么详细的一篇文章,主要也是因为网上查到的知识,对git知识体系一般的人来说,看的比较费解。

    分类:
    后端
    标签:
  •