Revert的奇妙技巧
21年了,不来篇文章,都对不起新的一年。
正常流程,先来个3连问。【请读完这个文章,绝对有你想要的】
首先来了解下revert是干嘛用的?revert的用法都有神马? revert之后的代码如何再次使用?
1. 了解revert
git revert
撤销 某次操作,此次操作之前和之后的commit和history都会保留,并且把这次撤销
作为一次最新的提交
新增一個 Commit 來反轉(或說取消)另一個 Commit 的內容,原本的 Commit 依舊還是會保留在历史记录中。虽然会因此而增加 Commit 數,但通常比较適用於已经推出去的 Commit,或是不允許使用 Reset 或 Rebase 之修改历史记录的指令的場合。
会记录曾经的提交历史
2.用法
2.1 简单的回滚策略
- git revert HEAD 撤销前一次 commit
- git revert HEAD^ 撤销前前一次 commit
- git revert commit (比如:fa042ce57ebbe5bb9c8db709f719cec2c58ee7ff)撤销指定的版本,撤销也会作为一次提交进行保存。
git revert是提交一个新的版本,将需要revert的版本的内容再反向修改回去,
版本会递增,不影响之前提交的内容【可以理解为撤销某次合并后,重新生成了一个版本(commit hash)】
2.2 revert 其他用法
git revert [--[no-]edit] [-n] [-m parent-number] [-s] [-S[<keyid>]] <commit>…
git revert --continue
git revert --quit
git revert --abort
说明:放弃一个或多个提交,并生成一个或多个新的提交来记录这些放弃操作。
git revert [--[no-]edit] [-n] [-m parent-number] [-s] [-S[<keyid>]] <commit>…
1. --edit or --no-edit是否弹出commit message窗口
2. -n 是 --no-commit的缩写
3. -m parent-number 存在merge是,指定父系分支号。
举个栗子:
案例一:
主线: A -> B -> C -> D
目前HEAD 指向 D,如果想回滚到C的状态
git revert D
说明: 直接回滚即可,此时会生成一个新的commit 号,那么此时索引是:A->B->C->D->CV1
案例二:
如果是单个主线可以这么解决,如果多个分支协同开发呢
主线:【借用下网图吧】
image.png
场景:
- dev1 提交了C1,C2, C3 ,C4 然后merge了master 生成了m5
- dev2 提交了C3,[应该是B3,dev2分支那个线],然后和合并了master 生成了m6
问题: 此时dev1 上有bug ,需要回滚怎么做呢
方案1:
直接在master上进行reset,然后 -f 提交完美解决。
这种方案可以,不过不建议用,首先master上一般不会让你-f, 而且master也尽可能的保持最新,最干净的代码
方案二
本节主要的git revert
来拯救世界了【用好了可以造世界,用不好可要受罚的哦】
step1 : git revert commitHash
这里呢,可以加一个参数 -m
-m 后面还需要加一个整数,且只能指定1或者2,否则会报错
commit 893247dca92dae929cf6255ad5985df25dbcfdac is a merge but no -m option was given.
fatal: revert failed
-m
标记指出 “mainline” 需要被保留下来的父结点。 为什么要写1或者2 呢,不是直接反向执行下这个commit进行的修改吗,commit加一行revert就减一行吗?为什么还要选1还是2模式?
回答:【谷歌是个好东西,大佬的解析很明白,这段说明原文:https://www.cnblogs.com/bellkosmos/p/11409904.html】
git并不是基于diff进行管理的(有这样的版本管理系统),git的每个commit都是一个当前版本的快照,简单说每个commit都是一个完整的仓库版本,所以当你需要revert某个commit的时候,GIT需要知道你到底是希望revert哪个commit与这个commit间的改动
不过其实并没有那么复杂,你要revert一个commit,就是revert掉这个commit和它上个commit间的改动,所以大部分时候,你直接revert就好了,不用指定-m参数
不过当你要revert的的commit的上面有两个commit节点的时候,问题就来了
A -> B ->
E -> F
C -> D ->
比如这里的E节点,它是AC两个分支合并的节点,这里假设是你在A分支使用命令merge C,那么E就有两个上游节点了,当你在新的分支F(其实就是之前的A分支)revert E 时,你就需要加上-m参数了,当你指定1时,就是revert 掉 B到E的改动,当你指定2时,你也可以revert 掉 D到E的改动,其实大部分时候我们都是选1就好了~
3. revert 掉的代码,如果想继续用怎么办呢
来个主线任务
image.pngstep1: 按照前文这个log, revert了m5,
stop2: dev2 的c3 merge 到了master生成了新的m6
当你认为是bug回滚完之后, 某位产品大大说,那个不是bug, 是我们的新方案,你有没有看需求文档,
你仔细看了下需求文档,一排脑门,对啊,这不是bug,那么少年想既然没问题,那么我在合并一次master就可以完美上线了,然后你开开心心的进行merge 代码了
git checkout dev1 # 查看分支,很完美,代码都还在,很是庆幸
git merge dev1 # 继续十分完美,没有冲突,但是在看一眼代码,dev1代码的改动被master给冲掉了,dev1代码没了
哦,no,怎么办?
百度吧,百度的方案其实类似我第二个模块方案所介绍的,回到master 上进行再次revert,
- 回到gitlab 上页面revert,兄弟你此时可是在master上操作啊,小心腿打折啊。
那么如果我在 master 新开分支test_2021
, 在当时那个rever的分支上进行revert 然后在和test_2021 进行merge 是否可以呢?
如果你幸运是没是的,不过你很不幸,在那次revert之后有过新的提交
那么你会很惊喜的收到如下报错
Sorry, we cannot revert this merge request automatically. This merge request may already have been
reverted, or a more recent commit may have updated some of its content.
- 命令执行,命令行执行有什么区别吗?
说明,如果你无法在master的当前HEAD指向下进行revert 到当时的c4点,那么你是无法在git上在此使用dev1代码的,甚至你把dev1的代码随便往任意一个基于master新建的分支,都是无法把dev1的改动弄过去的。因为git认为你已经提交过了。
现在来介绍一个中重要的方式
step1:
git checkout revert-commitHash
切换分支到当时revert出来的新版本上【可以先回到master,然后 git pull 就可以down下所有的branch 了】
step2:
假如你这个项目叫做 fileProject [项目文件夹],cp出一个新的副本
cp -r fileProject fileProjectV1
cd fileProjectV1
git checkout -b feature_revert
此时fileProjectV1 项目只是fileProject 一个副本,代码,git仓库信息都一致
step3:
第一个小重点来了
将fileProjectV1 文件夹中,曾经涉及dev1代码改动的文件删掉,当然如果文件很少,如果很多,不想分辨了,
像楼主一样,找到文件所在的文件夹,把文件夹彻底干掉,[主要不是改那个删那个]
cd fileProjectV1
rm -rf fileirectory
删完世界都干净了,有没有
step4:
打开fileProject,切换分支到 dev1
及revert 玩,还想要继续用的
cd fileProject
git checkout dev1
step5:
注意了啊,第二个重点来了
将你在dev1 分支上,当时修改的文件夹或者理解为在 step3 删除了那些文件,就把那些文件复制下来
复制,复制完cp到fileProjectV1中
不要cp git 相关文件
不要cp git 相关文件
不要cp git 相关文件
cp fileProject_fileirectory fileProjectV1_fileirectory #这是伪命令,相信你可以懂
step6:
此时fileProjectV1 项目是不是就有 dev1 代码了,且分支还是feature_revert
进行代码梳理提交
所在项目:fileProjectV1
git diff
git add
git commit
git push
step7
所在项目:fileProjectV1
git checkout master
git pull
git checkout feature_revert
git merge master
#不出意外有冲突,那么解决冲突
git add
git commit
git push
完美收官!
no!!! 这个办法有个后遗症,master原有代码可能会丢失,那么问题来了?
到底怎么解决呢??
终极奥义 - R 闪 马氏三角杀: cherry-pick
来上主线图
image.png
因为此时你想要dev1 的代码,但是这个分支又不能用了
上操作完事了
step1: 回master新开分支
git checkout master
git checkout -b dev_revert
step2: 查看log, 查找你想要的代码
git log # 根据commit msg 找到commitHash
git cherry-pick commitHash
step3:如果遇到冲突,自己解决冲突,然后执行git add, git push
即可
cherry-pick
是自动提交的 如果不想自动提交,加参数-n
git cherry-pick commitHash -n
==================华丽分割线=====================
如果你想要D2
哪里的这个dev分支代码怎么操作呢?
直接 cherry-pick 是错误的,因为那是2个分支的交点,
如果原始提交是一个合并节点,来自于两个分支的合并,那么 Cherry pick 默认将失败,因为它不知道应该采用哪个分支的代码变动
问题又来了,每日三省吾身,这个问题怎么解决??到底怎么解决呢?
继续看招 ------- eqr闪
git cherry-pick -m 1 <commitHash>
熟悉不,和上面的方式一样-m
含义也一样
-m配置项告诉 Git,应该采用哪个分支的变动。它的参数parent-number是一个从1开始的整数,代表原始提交的父分支编号。
上面命令表示,Cherry pick 采用提交commitHash来自编号1的父分支的变动。
一般来说,1号父分支是作为变动来源的分支(the branch being merged from),
2号父分支是接受变动的分支(the branch being merged into).
来下具体的操作,干说不操作假把式
看个分支树节点信息
[Merge branch 'master' into dev1]
对应的commitHash : qwer1234
如果你要dev1 的这个节点全部代码
git cherry-pick -m 2 qwer1234
那么执行完如果有冲突,会终止合并的, 你要先解决冲突,
然后 git add
之后 ,
执行git cherry-pick --continue
最后git push
记住这个方式 也是自动提交的【即不需要git commit,你可以add 完,git status 看下,这是个小秘密哦】
当然,如果你想放弃此次cherry-pick
,那么狠遗憾的告诉你,
执行这个就可以了git cherry-pick --abort
.
传送门cherry-pick :http://www.ruanyifeng.com/blog/2020/04/git-cherry-pick.html
4.加餐:revert 和reset 区别
- git revert是用一次新的commit来回滚之前的commit,git reset是直接删除指定的commit。
- 在回滚这一操作上看,效果差不多。但是在日后继续merge以前的老版本时有区别。因为git revert是用一次逆向的commit“中和”之前的提交,因此日后合并老的branch时,导致这部分改变不会再次出现,但是git reset是之间把某些commit在某个branch上删除,因而和老的branch再次merge时,这些被回滚的commit应该还会被引入。
- git reset 是把HEAD向后移动了一下,而git revert是HEAD继续前进,只是新的commit的内容和要revert的内容正好相反,能够抵消要被revert的内容。