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种方式的话,就比较麻烦了.

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

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

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

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

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

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


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

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

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

我一听然后才发现我的.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忽略文件添加上

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

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

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

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

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



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

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

pull后:

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

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

① amend (修改)

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

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

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

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


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

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

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


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


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

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

② revert

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

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

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

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


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

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

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

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

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

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

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

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

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

FUCK!

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

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

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

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

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

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

可以看到完全没问题

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

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

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

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

③ rebase -i (交互式变基)

rebase这个命令呢翻译起来就是变基的意思,额是变基!!不是搞基….,这个命令很强大,它后面直接带上分支名参数就可以将一个分支的基点变动到另一个分支上,这样我们merge代码时,log历史就不会出现乱七八糟的交叉线而是一条笔直的水平线,看起来比较简洁清晰,如果 rebase命令后面带上 -i就是交互式变基

这里说明是让用户edit 编辑commit列表去变基
可能看到这里你还是一头雾水,没关系,老规矩还是敲案例

这里我第一次在MainActivity中添加一个Toast然后提交 ,第二次创建SecondActivity并添加一个Toast后提交,我们看看log

这里我们敲命令
git rebase -i HEAD^^^

然后我们就会进入这个界面,这个界面会列出我们最近三次的提交。这里我们修改下倒数第二次也就是MainActivity这次提交的信息

我们把pick改成edit然后提交,可以看到此时我们倒数第二次后面提交后面创建的SecondActivity文件消失了,其实是这样的,不仅这个文件会消失,所有在我们edit 的那个HEAD的后面的所有commit此时都会消失,因为我们已经把HEAD暂时变基到了edit的那个HEAD, 而这个HEAD后面所有的commit也并不是彻底消失了,而是git暂时以一种方式帮我们保存起来,等我们修改好edit这次提交后再使用
git rebase –continue命令就可以把git帮我们保存的commit合并到edit的那次commit中并把HEAD移动指向到末尾。

此时rebase变基到指定的HEAD后,这个HEAD就是最后一次commit提交了,我们使用上面amend命令去修改这次提交

我们修改了MainActivity代码后然后使用amend一系列操作

git add . -> git commit –amend

然后进入编辑页面然后编辑修改后的commit信息然后amend修改

最后我们只需要调用git rebase –continue命令就可以合并变基啦~

可以看到我们消失不见的SecondActivity文件已经回来啦,并且MainActivity中的代码也已经修改了,而此时我们使用
git log去查看时也会发现我们只是修改了那次commit操作并没有重新创建一个修改的commit。

这里特别需要注意的一点是,交互式变基与上面revert情况一样,因为我们修改的操作分别是在MainActivity和SecondActivity二个不同间隔文件之中进行的,如果我们把交互式变基操作在相同文件的连续commit上就会发生下面的冲突。

而原因在上面revert操作中已经分析的很透彻了,就是git会认为你这次修改可能会对相同文件后面一系列的commit产生影响从而把冲突标记出来。

Git之一次Push的回滚之旅相关推荐

  1. Git push之后回滚

    Git在push之后该怎么回滚呢?分为下面两个步骤 1.本地回退 回退到上次提交: git reset --hard HEAD^ 2.远程回退 然后强制推送当前这个版本到云端 git push ori ...

  2. git add . 之后 想执行回滚操作(git add 到本地仓库的代码回滚到没有add 之前的操作)

    git add 到本地仓库的代码回滚到没有add 之前的操作 ,如果git 非常熟悉临时忘记了命令 回滚单个文件的命令:git restore --staged  xx文件名 回滚所有add 的文件直 ...

  3. git:如何做代码回滚

    代码回滚的场景:当别人把一些文件上传到服务器,你更新代码之后,出现了本地代码无法运行的情况:产品上线之后,出现了紧急bug,一时无法修复时,为了保证线上稳定,需要做代码回滚. 回退的步骤如下: 1)查 ...

  4. git 几种情况的回滚

    增加了几个文件, 没有 add, 不想保存文件, 怎么快速清除文件? 增加了 add 了文件, 不想保存文件, 怎样清理? 修改了文件, 没有 add, 怎样清理? 修改了文件, 已经 add, 怎样 ...

  5. git idea 本地历史版本回滚_如何为IDEA项目创建GitHub存储库和本地Git存储库

    有几种方法可以使用IntelliJ IDEA来发布我们在GitHub上编写的代码.在此博客中,我们将介绍两种方法.第一个步骤是最快的一步,您可以在其中创建本地和远程存储库.第二种方法是多步骤,当您要为 ...

  6. Git学习总结(19)——Git代码回滚总结

    一.Git代码回滚分为两种情况,分别是push前回滚和push后回滚. 二.push前代码回滚 push前代码回滚使用reset命令,形如:git reset [--soft | --mixed | ...

  7. 使用GIT不小心merge后的回滚操作

    虽然有四个方法,但我们项目中使用方法一和二比较合适,不会产生新的commit,并且简单粗暴 方法一,新分支覆盖 ①首先两步保证当前工作区是干净的,并且和远程分支代码一致方法一,删除远程分支再提交 $ ...

  8. GIt版本回滚的两种方法reset、revert

    在学习关于git版本回滚操作之前我们应该需熟练使用git log查看Git提交日志,同时也应学会使用git reflog查看Git提交日志. 可参考博客:git log的使用_WEB前端李志杰的博客- ...

  9. git回滚代码版本 强制提交代码

    git reset --hard [commit id] 回滚代码 git push --force 如果强制提交失败可以试试 git push origin 分支名 --force

最新文章

  1. jQuery基础知识准备
  2. 如何自学python爬虫-怎样入门学习Python爬虫?
  3. Ajax框架DWR入门
  4. javascript中的命名规则和方法
  5. cmake find_package路径详解
  6. oracle11gr2配置监听,Windows环境配置Oracle 11gR2 Listener
  7. Oracle dbms_job管理
  8. Numpy——数组分割
  9. Java打印菱形源码及介绍
  10. android 不限速迅雷,安卓iOS,Windows和Mac四大系统迅雷不限速神器,今天全部解决了...
  11. Java基础 - 网络编程 - netstat指令,UDP通信编程和网络编程总练习
  12. 算法分析与设计实验报告——0-1背包问题的动态规划算法实现
  13. 微信公众号获取openid流程
  14. net.reflector8.5.0.179过了试用期,要求输入序列号怎么办 注册机 破解
  15. 一文带你学明白java虚拟机:C1编译器,HIR代码优化
  16. 性别计算机英语怎么说,性别教育用英语怎么说?
  17. 网站运营活跃用户、流失用户、流失率、新用户流失率定义以及诠释
  18. roguelike2d 摄像机参数设置
  19. 基于视频点播上传、多分辨率转码-搭建自己的短视频|教学视频播放系统
  20. 利用计算机设计比率分析模型,整理的答案——财务模型分析与设计的复习思考题...

热门文章

  1. java仪表盘_GitHub - Jensenczx/Dashboard: 通过Java实现的仪表盘
  2. 50部经典影片,你看过哪些
  3. android辅助功能手势,Android辅助功能——全局手势放大实现原理
  4. 模板解析原理,脱裤子放屁
  5. 程序员自我修养——提问的智慧
  6. 《Cloud Native Data Center Network》读书笔记-1
  7. 英文版sketch如何转为中文版?sketch英文转为中文教程!
  8. 常用的hook js
  9. 单片机课程设计:基于STM32的温湿度检监测报警系统的设计
  10. mysql查找附近算法_Java+MySQL实现附近功能