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[]] …
git revert --continue
git revert --quit
git revert --abort

说明:放弃一个或多个提交,并生成一个或多个新的提交来记录这些放弃操作。

git revert [--[no-]edit] [-n] [-m parent-number] [-s] [-S[]] …
 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

场景:

  1. dev1 提交了C1,C2, C3 ,C4 然后merge了master 生成了m5
  2. 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模式?

回答:【谷歌是个好东西,大佬的解析很明白,这段说明原文:原文参考】
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.png

step1: 按照前文这个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,

  1. 回到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.
  1. 命令执行,命令行执行有什么区别吗?

说明,如果你无法在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
熟悉不,和上面的方式一样-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.

4.加餐:revert 和reset 区别

  1. git revert是用一次新的commit来回滚之前的commit,git reset是直接删除指定的commit。
  2. 在回滚这一操作上看,效果差不多。但是在日后继续merge以前的老版本时有区别。因为git revert是用一次逆向的commit“中和”之前的提交,因此日后合并老的branch时,导致这部分改变不会再次出现,但是git reset是之间把某些commit在某个branch上删除,因而和老的branch再次merge时,这些被回滚的commit应该还会被引入。
  3. git reset 是把HEAD向后移动了一下,而git revert是HEAD继续前进,只是新的commit的内容和要revert的内容正好相反,能够抵消要被revert的内容。

你可能感兴趣的:(Revert的奇妙技巧)