美文网首页
Git之一次Push的回滚之旅

Git之一次Push的回滚之旅

作者: boboyuwu | 来源:发表于2017-12-06 14:35 被阅读69次

    git相信很多人都接触过,但从我身边人的例子来看,大部分人都在入门使用阶段,比如对中央仓库和本地仓库的大概理解、通常add、commit、push、pull等命令的使用,但是却没有对git形成一个整体深入的理解, 这样如果是小型团队的话,git当成一个SVN模型使用平时开发也不会出现多大问题,但是假如我们团队较大相互之间协作比较频繁时,就很容易出现这种情况。

    我们经常容易做出一个失误操作(比如下面案例不小心提交很多自动生成的文件污染了仓库),或者一些有问题的代码push上去之后才发现, 一般错误提交到远程的操作可以通过git一系列操作回滚还原回来,而回滚方式一般有以下几种。

    方法1.把本地的错误提交改正回来,然后提交一个commit再push到远程,这也是最最简单的一种方式了,但是就为了改正一个失误操作就要commit一次,未免有点不优雅.

    方法2.我们可以暴力点,使用git log查看这次错误提交的上一次提交的Head id,然后reset过去,这个时候我们本地的Head所指向的版本已经比远程要低一个版本了,正常情况下我们push时会提示提交失败,不要乖乖的用常规操作了,可以使用非主流操作 git push origin xxxx(你要推的分支名) -f 这个命令强制把本地的commit push 覆盖掉远程,如果这是次pr操作那么完全就没有问题这种方式再适合不过了,因为pr操作基本完全就是你一个人提交操作的,在对方没有review并且merge之前,这么回滚之后.pr上去的commit就会回到错误提交的上一次操作。假如平时你是一个人独立开发项目,你想回滚到某次提交也可以这么玩, 放弃某些commit是你主观上完全清楚并且可控的!

    虽然上面2中操作简单粗暴,我们都很喜欢,但是实际开发中往往不能如你所愿这么简单,团队也有可能不是你一个人独自开发, 那这个时候假如你要使用第2种方式的话,就比较麻烦了.

    比如我们用团队开发方式去模拟第二种方法的回滚操作情景

    1.png

    这里团队就模拟二个人,更多情景也是一样的,这里我们分别建立二个目录,里面代表feng和我二个成员的工作目录,里面放着我们的项目代码.此时feng和我的项目代码都是刚从git上拉下来的,我们是一致的,好这个时候项目经理启动新版本了,我和feng任务分工明确,feng开发闪屏界面SplashActivity,我开发MainActivity模块功能.

    3.png

    我完成了MainActivity这个界面所有功能然后提交代码,常规的add-> commit -> push操作, push之前记得pull下这是好习惯.

    4.png

    Ok我完成的模块已经提交上去啦,但这个时候我不小心把.gitignore文件删了,然后第二天上班,打开git习惯性的敲个git status命令看到


    111.png

    卧槽这么多未跟踪文件,赶紧git .add然后commit -m "提交未跟踪的文件",,,请不要纠结我这个时候已经迷糊的大脑,我想这个错误是很多新手都会很容易犯得,然后pull->push,

    22.png

    可以看到我push .iml文件之前我们之前项目是比较干净的, push之后。。。

    1234.png
    55.png

    很明显每个目录层级下都多了我电脑生成的.iml文件、.gradle目录等,这在有些人眼里是不能忍受的,因为如果不从远程中删除这些文件,那么feng以后每次pull代码时就会弹出我的.iml需要跟他自己生成.iml合并提示,假设我们.iml不一样还会有冲突!这看起来是非常烦的。

    好了,feng这个时候已经完成了他的splash界面模块,他习惯性的pull下代码,然后看到拉下来很多代码,心里不禁想,这小子开发速度还蛮快的嘛~,然后扶了扶眼镜仔细一看.

    feng1.png

    拉下来的全是这些玩意,于是瞬间就怒了,直接冲到我工位上喊起来,卧槽,你特么提交了一堆什么玩意上去,我全部拉下来就报错了,赶紧给我改回来,不然我砍死你哦,

    我一听然后才发现我的.ingnore被不小心删除了,自动生成的所有文件全部提交上去了,而feng的那些自动生成的文件全部被忽略所以没有被跟踪,导致pull下来的时候出现冲突,解决方法可以 git clear -d -fx强制删除本地工作目录下未被track的文件.然后再pull就可以拉下来了。

    但是代码已经push远程上去了怎么办呢?这样以后所有人拉下来不都带上这堆乱七八糟的文件了吗,这个我们先用第二种方式回滚操作下

    目前的工作情景 git log 状态是 :

    1 我commit了MainActivity模块代码然后push上去了
    2 我所有自动生成的乱七八糟玩意文件被全部add并commit进了工作目录push上去了
    3 feng commit了他的SplashActivity模块还没有push( 当然他这个时候也有可能push上去了不过这都无所谓了)

    现在使用第二种方法回滚思路就是 :

    1 我查看log日志,本地reset回滚到commit MainActivity那次操作,然后强制push上去,这个时候远程是没有这堆乱七八糟文件的
    2 feng需要reset到他commit SplashActivity那次操作并建个分支切换过去,然后切换回这个分支后reset到远程那个操作,也就是我push MainActivity那次操作,然后merge刚才创建的分支,这个时候feng的本地 commit 时间轴就是

    ① 我push MainActivity
    ② feng commit SplashActivity
    ③ 然后feng push下代码,我pull一下 远程和我本地都同步成上面的串了,一切看起来都是那么完美

    最后赶紧把.ignore忽略文件添加上

    思路有了我们实际操作下吧

    12121212.png

    可以看到这个时候已经回退到那个时候版本并且工作目录tree很干净,我们强制push下


    33333.png

    very good 远程已经跟我目录保持一致了哪些烦人的目录已经完全消失不见了,这个时候只需要操作feng目录然后push到远程就可以切掉中间那次乱七八糟文件的提交

    feng111.png

    创建个splash_branch 分支切换到提交SplashActivity那次操作然后切换回来,然后切换过来,这个时候我们切换到我提交MainActivity操作就不能通过查看feng本地log了, 因为feng本地目前生成的log全部是他在本地做的commit,这个时候需要使用git log origin/master查看远程所有人共同的commit才可以找到我的提交,好,reset过去。

    wuboi11.png

    ok,最后merge下我们刚才的splash_branch分支,然后依次add->commit->push

    44444.png
    21.png
    4444.png

    可以看到,feng的SplashActivity模块已经提交上去并且所有的目录中.iml、.gradle等目录已经全部消失,已经达到我们的目的


    dddddd.png
    远程的log上也可以看到,很干净的一条线,乱七八糟那次commit也没了这个时候我pull下代码,我的本地也同步了 4444.png

    pull后:


    zzzz.png timg.jpg

    这波操作是不是有点骚有点炫呢?不过可能feng会抱怨了,哥哥哎就因为你一次误交,我要做这么麻烦的操作,现在就我们2个开发,要是团队多了岂不是每个人都要这么操作,我想想也对哦不能因为我一个人的过错这么玩大家啊,万一团队有成员对git不是太了解,这么搞下来不是要疯掉了,那真的要砍死你了哟?

    于是乎第三种黑科技来了,我们的
    方法3.
    其实也不是算是黑科技,方法3只是git提供的一些相对于方法一优雅一些的撤销操作

    ① amend (修改)

    这个操作可以基于上一次commit修改我们提交的内容,我们知道每次commit都会生成一个新的快照,这个操作会把我们上一次提交的内容跟这次修改的内容合并到上一次commit中去,它比较适合你刚commit之后立马意识到自己的错误,马上改回来又不想为了这点错误重新commit一次这种情景。
    什么,你不理解快照是什么?好吧,你大概理解成快照就是git给我们生成的一个个commit后最新内容的副本,git把每次commit工作区的所有最新内容都制成一个快照。


    555555.png

    这玩意就是指向commit生成的快照的SHA1,就是git生成的指向快照的一个唯一标记~

    ,假如我们对上一次操作不满意或者失误提交了有问题的代码我们就可以修改暂存区内容后使用这个命令在不改变上次commit快照情况下修改我们的错误。

    我们实际操作下还是上面那个项目,我把我的目录重置到刚拉代码状态


    sss.png aaaa.png

    然后我在MainActivity里面打印一个Log 模拟提交一次错误代码,这个时候我稀里糊涂的就push上去了


    aaaaaaa.png dfdfdsf.png 可以看到错误代码已经提交上去了,log日志也已经生成了,可是提交上去之后我立马我就后悔了,擦刚才那个代码写的有问题,我怎么提交上去了啊啊啊,一万个后悔中,这个时候肿么办肿么办,不要急 rrrr.png

    我们把错误改掉,然后add 我们修改后的文件, 这个时候就不需要再commit一次了,我们调用git commit --amend

    ddddd.png

    然后会出现这个界面,我们保存退出后。

    fffffff.png

    查看log可以看到并没有增加一个新的commit


    cxxxx.png

    而本地我们错误已经改回来了,但是由于我们修改了提交的内容信息,而快照却没有前进,所以我们push到远程时还是需要强制push的否则会提示当前commit分支版本落后于远程的失败信息,所以一般我们如果已经把错误代码push上去了最好也不好用这种操作去还原,因为所有会造成快照版本不变或者后退的commit操作都不适合去恢复已经push的操作,请好好理解这句话。

    ② revert

    这个命令我试验了很久也查了很多资料,但是始终没有一个很好的文献可以通俗的去理解它,它的用法很简单,就是新增一个commit 然后跟我们指定的commit id做出完全相反的操作,然后提交。

    不太理解没关系,我们下面继续敲个案例体验一下,还是上面类似情景


    werrrrr.png 这里重新创建一个git远程仓库,然后新建一个项目我负责开发主界面,我跟feng都从仓库拉倒本地自己工作目录下,我完成了主界面的一个功能模块push,然后feng创建了AppDetailActivity界面push,我拉下来后我跟feng的工作目录里都有了我们二个人开发的代码,这个时候feng继续开发他的功能模块二了,而我不小心把.ignore删了然后把所有文件全部push上去了 eeeeee.png

    这里就随便拷贝一些乱七八糟的文件然后push上去,

    wewe.png

    可以看到乱七八糟文件已经被全部push上去, 然后feng习惯性pull下目录已经完全乱了


    ssss.png

    这个时候我们就不要用第二种方法了,因为可能会覆盖掉别人的提交,这种是有很大的安全隐患的,所以我们使用 git revert HEAD 撤销我们上一次的操作,如果撤销上上次的就用HEAD^,依次类推如果想撤销指定倒数第几个提交就用HEAD~ 0... 0表示倒数第一次,1表示倒数第二次以此类推。
    好了我们可以看到git已经把我们误提交的那堆文件全部删除了时候我们只需要add 、commit、push一套就可以还原我们push上去的操作拉


    ee.png ssss.png

    可以看到此时我们远程仓库那堆乱七八糟文件已经完全消失了,而我查看一下远程仓库的log。


    sssssssssssss.png

    可以看到我们是用新增一次commit记录来删除那堆文件而没有干掉任何历史记录。

    revert使用虽然很爽但是需要注意一点的是下面这种情况,这个情况我再网上查询很久也没有得到一个准确的答案,然后通过不断摸索实验,得到了一个验证的结果。


    1111.png
    这里我重新模拟一下, 11111.png
    feng在MainActivity里面增加了一行代码然后add ->commit->push

    然后我开始pull下来之后我同样在MainActivity里面增加一行代码


    33333.png

    然后我也add->commit->push,ok这个时候feng发现他push的那行代码有问题想撤销掉,然后他查看了下log状态


    2222222.png
    youxi~撤销倒数第二次提交代码就可以了,然后胸有成竹的敲上git revert HEAD^

    FUCK!


    66666.png

    直接蹦出个这个错,我们再打开MainActivity.java看一下

    aaaaa.png

    看到这里feng就开始懵逼了,这到底是什么情况啊.


    a6e79a5afec31d4e84689381d86d1295.jpg

    其实这个原因是这样, 只要你在同一个文件中连续commit提交,那么除非你进行revert撤销的操作是最后一次commit才会成功,否则都会出现冲突?

    b90e7bec54e736d1229ed58993504fc2d46269a5.jpg

    因为如果撤销的是之前的操作,那么git认为会影响到撤销后面的一系列的commit 链, 这就好比一条小溪我在下游用水,而你在上游,我用的水是基于你上游流下来的,我下游的水怎样弄脏了都不会影响到你上游的水,而你上游的水如果弄脏了就会影响到你下游所有用水的人, 上面同理,我在MainActivity.java中改动的代码是基于feng改动的MainActivity.java代码下面进行改动的,所以当feng要撤销这次操作时,git就认为会影响到下游我改动代码的这次提交。

    所以这种情况我们可操作情形只能有以下二种:
    ① 我们每次在同一个文件连续操作时,只能撤销最后一次操作,要不然就合并一些列冲突后再add ->commit不过这并没有意义对吧我们使用这个命令就是为了git帮我们自动进行撤销都要自己合并冲突还不如我们自己改完重新commit一次呢。
    ② 我们撤销不同文件之间有间隔的操作,这种情况git是允许的


    6666666666666666666666666.png

    比如这个完整的log图,可以清晰的看到 开发了AppDetail第二个功能模块跟新建AppDetailActivity界面并完成第一个模块是同一个文件中的连续操作,而开发了AppDetail第二个功能模块跟提交未跟踪代码又是不同文件之间有间隔的操作,那这个时候如果我要撤销新建AppDetailActivity界面并完成第一个模块这次操作就是影响到新建AppDetailActivity界面并完成第一个模块,git就会提示失败,但是我如果撤销开发了AppDetail第二个功能模块这次操作就完全没问题,
    我们操作一下看看是不是这样,撤销开发了AppDetail第二个功能模块这次操作


    ssssssqwq.png

    可以看到完全没问题


    000000.png
    这次操作已经完全被撤销了,但假如我撤销开发了AppDetail第二个功能模块跟新建AppDetailActivity界面这次操作看一下 vvvv.png

    可以看到git又提示错误了,但是当我们打开这个文件后并没有看到任何类似上面情况的提示合并信息


    5555555.png

    这是因为你这次撤销的操作是这个文件的首次提交操作, 包括你创建这个文件然后填写模块代码,如果撤销这次操作相当于撤销你所有写的模块代码然后再删除这个文件,所以git会冲突然后提示让你直接删除这个文件就行,我们打开git status查看是不是这样


    wwww.png
    可以看到结果确实是如上所示,我们删除掉这个文件并提交就可以完成这次撤销。

    相关文章

      网友评论

          本文标题:Git之一次Push的回滚之旅

          本文链接:https://www.haomeiwen.com/subject/azskixtx.html