共计 3266 个字符,预计需要花费 9 分钟才能阅读完成。
从接触编程就开始应用 Git 进行代码治理,先是本人玩 Github,又在工作中应用 Gitlab,尽管应用工夫挺长,可是也只进行一些罕用操作,如推拉代码、提交、合并等,更简单的操作没有应用过,看过的教程也逐步淡忘了,有些对不起 Linus 大神。
进去混总是要还的,前些天就遇到了 Git 里一种非常糟心的场景,并为之前没有深刻了解 Git 命令付出了一下午工夫的代价。
先介绍一下这种场景,咱们一个我的项目从 N 版本升到 A 版本时引入了另一项目标 jar 包,又陆续公布了 B、C 版本,但在 C 版本后突然发现了 A 版本引入的 jar 包有极大的性能问题,B、C 版本都是基于 A 版本公布的,要修复 jar 包性能问题,等 jar 包再发版还得几天,可此时线上又有紧急的 Bug 要修,于是就陷入了进退维谷的地步。
最初决定先将代码回退到 A 版本之前,再基于旧版本修复 Bug,也就开始了五个小时的受苦之路。
根底试探
revert
首先必定的是 revert,git revert commit_id 能产生一个 与 commit_id 齐全相同的提交,即 commit_id 里是增加,revert 提交里就是删除。
然而应用 git log 查看了提交记录后,我就打消了这种想法,因为提交次数太多了,中途还有几次从其余分支的 merge 操作。”利益于”咱们不太洁净的提交记录,要实现从 C 版本到 N 版本的 revert,我须要倒序执行 revert 操作几十次,如果其中程序错了一次,最终后果可能就是不对的。
另外咱们晓得咱们在进行代码 merge 时,也会把 merge 信息产生一次新的提交,而 revert 这次 merge commit 时须要指定 m 参数,以指定 mainline,这个 mainline 是主线,也是咱们要保留代码的主分支,从 feature 分支往 develop 分支合并,或由 develop 分支合并到 master 的提交还好确定,但 feature 分支相互合并时,我哪晓得哪个是主线啊。
所以 revert 的文案被废除了。
Reset
而后就思考 reset 了,reset 也能使代码回到某次提交,但跟 revert 不同的是,reset 是将提交的 HEAD 指针指到某次提交,之后的提交记录会隐没,就像从没有过这么一次提交。
但因为咱们都在 feature 分支开发,我在 feature 分支上将代码回退到某次提交后,将其合并到 develop 分支时却被提醒报错。这是因为 feature 分支回退了提交后,在 git 的 workflow 里,feature 分支是落后于 develop 分支的,而合并向 develop 分支,又须要和 develop 分支放弃最新的同步,须要将 develop 分支的数据合并到 feature 分支上,而合并后,原来被 reset 的代码又回来了。
这个时候另一个可选项是在 master 分支上执行 reset,应用 –hard 选项齐全摈弃这些旧代码,reset 后再强制推到远端。
master> git reset --hard commit_id
master> git push --force origin master
然而还是有问题,首先,咱们的 master 分支在 gitlab 里是被爱护的,不能应用 force push,毕竟危险挺大了,万一有人 reset 到最开始的提交再强制 push 的话,尽管能够应用 reflog 复原,但也是一番折腾。
另外,reset 毕竟太横蛮,咱们还是想能保留提交历史,当前排查问题也能够参考。
降级交融
rebase
只好用搜索引擎持续搜寻,看到有人提出能够先应用 rebase 把多个提交合并成一个提交,再应用 revert 产生一次反提交,这种办法的思路十分清晰,把 revert 和 rebase 两个命令搭配得很好,相当于应用 revert 回退的升级版。
先说一下 rebase,rebase 是”变基”的意思,这里的”基”,在我了解是指 [屡次] commit 造成的 git workflow,应用 rebase,咱们能够扭转这些历史提交,批改 commit 信息,将多个 commit 进行组合。
介绍 rebase 的文档有很多,咱们间接来说用它来进行代码回退的步骤。
- 首先,切出一个新分支 F,应用 git log 查问一下要回退到的 commit 版本 N。
-
应用命令 git rebase -i N,-i 指定交互模式后,会关上 git rebase 编辑界面,形如:
pick 6fa5869 commit1 pick 0b84ee7 commit2 pick 986c6c8 commit3 pick 91a0dcc commit4
- 这些 commit 自旧到新由上而下排列,咱们只须要在 commit_id 前增加操作命令即可,在合并 commit 这个需要里,咱们能够抉择 pick(p) 最旧的 commit1,而后在后续的 commit_id 前增加 squash(s) 命令,将这些 commits 都合并到最旧的 commit1 上。
- 保留 rebase 后果后,再编辑 commit 信息,使这次 rebase 生效,git 会将之前的这些 commit 都删除,并将其更改合并为一个新的 commit5,如果出错了,也能够应用 git rebase –abort/–continue/–edit-todo 对之前的编辑进行撤销、持续编辑。
- 这个时候,主分支上的提交记录是 older, commit1, commit2, commit3, commit4,而 F 分支上的提交记录是 older, commit5,因为 F 分支的先人节点是 older,显著落后于主分支的 commit4,将 F 分支向主分支合并是不容许的,所以咱们须要执行 git merge master 将主分支向 F 分支合并,合并后 git 会发现 commit1 到 commit4 提交的内容和 F 分支上 commit5 的批改内容是完全相同的,会主动进行合并,内容不变,但多了一个 commit5。
- 再在 F 分支上对 commit5 进行一次 revert 反提交,就实现了把 commit1 到 commit4 的提交全副回退。
这种办法的取巧之处在于奇妙地利用了 rebase 操作历史提交的性能和 git 辨认批改雷同主动合并的个性,操作尽管简单,但历史提交保留得还算残缺。
rebase 这种批改历史提交的功十分实用,可能很好地解决咱们遇到的一个小性能提交了好屡次才好使,而把 git 历史弄得乌七八糟的问题,只须要留神防止在多人同时开发的分支应用就行了。
遗憾的是,当天我并没有了解到 rebase 的这种思维,又因为试了几个办法都不行太过于慌乱,在 rebase 实现后,向主分支合并被拒之后对这些形式的可行性产生了狐疑,又加上有共事提出听起来更可行的形式,就中断了操作。
文件操作
- 这种更可行的形式就是对文件操作,而后让 git 来辨认变更,具体是:
- 从主分支上切出一个跟主分支完全相同的分支 F。
- 从文件管理系统复制我的项目文件夹为 bak,在 bak 内应用 git checkout N 将代码切到想要的历史提交,这时候 git 会将 bak 内的文件复原到 N 状态。
- 在从文件管理系统内,将 bak 文件夹下 除了 .git 文件夹下的所有内容复制粘贴到原我的项目目录下。git 会纯从文件级别辨认到变更,而后更新工作区。
- 在原我的项目目录下执行 add 和 commit,实现反提交。
- 这种形式的奇妙之处在于利用 git 自身对文件的辨认,不牵涉到对 workflow 操作。
小结
最初终于靠着文件操作形式胜利实现了代码回退,预先想来真是一把心酸泪。
为了让我的五个小时不徒劳,复盘一下过后的场景,学习并总结一下四种代码回退的形式:
- revert 适宜须要回退的历史提交不多,且无合并抵触的情景。
- 如果你能够向 master 强推代码,且想让 git log 里不再呈现被回退代码的痕迹,能够应用 git reset –hard + git push –force 的形式。
- 如果你有些 geek,谋求用”正规而正统”的形式来回退代码,rebase + revert 满足你的需要。
- 如果你不在乎是否优雅,想用最简略,最间接的形式,文件操作正合适。
git 真的是十分牛逼的代码管理工具,动手简略,三五个命令组合起来就足够实现工作需要,又对 geeker 们十分敌对,你想要的骚操作它都反对,学无止境啊。
起源:占小狼的博客