转自:juejin.im/post/5e377e…git
从接触编程就开始使用 Git 进行代码管理,先是本身玩 Github,又在工做中使用 Gitlab,虽然使用时间挺长,但是也只进行一些经常使用操做,如推拉代码、提交、合并等,更复杂的操做没有使用过,看过的教程也逐渐淡忘了,有些对不起 Linus 大神。编程
出来混老是要还的,前些天就遇到了 Git 里一种十分糟心的场景,并为以前没有深刻理解 Git 命令付出了一下午时间的代价。bash
先介绍一下这种场景,咱们一个项目从 N 版本升到 A 版本时引入了另外一项目的 jar 包,又陆续发布了 B、C 版本app
但在 C 版本后突然发现了 A 版本引入的 jar 包有极大的性能问题,B、C 版本都是基于 A 版本发布的,要修复 jar 包性能问题,等 jar 包再发版还得几天,可此时线上又有紧急的 Bug 要修,因而就陷入了进退两难的境地。工具
最后决定先将代码回退到 A 版本以前,再基于旧版本修复 Bug,也就开始了五个小时的受苦之路。gitlab
首先确定的是 revert,git revert commit_id
能产生一个 与 commitpost
可是使用 git log
查看了提交记录后,我就打消了这种想法,由于提交次数太多了,中途还有几回从其余分支的 merge 操做。性能
”利益于”咱们不太干净的提交记录,要完成从 C 版本到 N 版本的 revert,我须要倒序执行 revert 操做几十次,若是其中顺序错了一次,最终结果可能就是不对的。学习
另外咱们知道咱们在进行代码 merge 时,也会把 merge 信息产生一次新的提交,而 revert 此次 merge commit
时须要指定 m 参数,以指定 mainline
ui
这个 mainline 是主线,也是咱们要保留代码的主分支,从 feature 分支往 develop 分支合并,或由 develop 分支合并到 master 的提交还好肯定,但 feature 分支互相合并时,我哪知道哪一个是主线啊。
因此 revert 的方案被废弃了。
而后就考虑 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 把多个提交合并成一个提交,再使用 revert 产生一次反提交,这种方法的思路很是清晰,把 revert 和 rebase 两个命令搭配得很好,至关于使用 revert 回退的升级版。
先说一下 rebase,rebase
是”变基”的意思,这里的”基”,在我理解是指[屡次] commit 造成的 git workflow,使用 rebase,咱们能够改变这些历史提交,修改 commit 信息,将多个 commit 进行组合。
介绍 rebase 的文档有不少,咱们直接来讲用它来进行代码回退的步骤。
git rebase -i N, -i
指定交互模式后,会打开 git rebase 编辑界面,形如: pick 6fa5869 commit1
pick 0b84ee7 commit2
pick 986c6c8 commit3
pick 91a0dcc commit4复制代码复制代码
在合并 commit 这个需求里,咱们能够选择 pick(p) 最旧的 commit1,而后在后续的 commit_id 前添加 squash(s) 命令,将这些 commits 都合并到最旧的 commit1 上。
若是出错了,也可使用 git rebase --abort/--continue/--edit-todo
对以前的编辑进行撤销、继续编辑。
而 F 分支上的提交记录是 older, commit5,因为 F 分支的祖先节点是 older,明显落后于主分支的 commit4,将 F 分支向主分支合并是不容许的
因此咱们须要执行 git merge master 将主分支向 F 分支合并,合并后 git 会发现 commit1 到 commit4 提交的内容和 F 分支上 commit5 的修改内容是彻底相同的,会自动进行合并,内容不变,但多了一个 commit5。
这种方法的取巧之处在于巧妙地利用了 rebase 操做历史提交的功能和 git 识别修改相同自动合并的特性,操做虽然复杂,但历史提交保留得还算完整。
rebase 这种修改历史提交的功能很是实用,可以很好地解决咱们遇到的一个小功能提交了好屡次才好使,而把 git 历史弄得乱七八糟的问题,只须要注意避免在多人同时开发的分支使用就好了。
遗憾的是,当天我并无理解到 rebase 的这种思想,又因为试了几个方法都不行太过于慌乱,在 rebase 完成后,向主分支合并被拒以后对这些方式的可行性产生了怀疑,又加上有同事提出听起来更可行的方式,就中断了操做。
这种更可行的方式就是对文件操做,而后让 git 来识别变动,具体是:
这种方式的巧妙之处在于利用 git 自己对文件的识别,不牵涉到对 workflow 操做。
最后终于靠着文件操做方式成功完成了代码回退,过后想来真是一把心酸泪。
为了让个人五个小时不白费,复盘一下当时的场景,学习并总结一下四种代码回退的方式:
git 真的是很是牛逼的代码管理工具,入手简单,三五个命令组合起来就足够完成工做需求,又对 geeker 们很是友好,你想要的骚操做它都支持,学无止境啊。