21年了,不來(lái)篇文章,都對(duì)不起新的一年。
正常流程,先來(lái)個(gè)3連問(wèn)。【請(qǐng)讀完這個(gè)文章,絕對(duì)有你想要的】
首先來(lái)了解下revert是干嘛用的?revert的用法都有神馬? revert之后的代碼如何再次使用?
1. 了解revert
git revert 撤銷(xiāo) 某次操作,此次操作之前和之后的commit和history都會(huì)保留,并且把這次撤銷(xiāo)
作為一次最新的提交
新增一個(gè) Commit 來(lái)反轉(zhuǎn)(或說(shuō)取消)另一個(gè) Commit 的內(nèi)容,原本的 Commit 依舊還是會(huì)保留在歷史記錄中。雖然會(huì)因此而增加 Commit 數(shù),但通常比較適用于已經(jīng)推出去的 Commit,或是不允許使用 Reset 或 Rebase 之修改歷史記錄的指令的場(chǎng)合。
會(huì)記錄曾經(jīng)的提交歷史
2.用法
2.1 簡(jiǎn)單的回滾策略
- git revert HEAD 撤銷(xiāo)前一次 commit
- git revert HEAD^ 撤銷(xiāo)前前一次 commit
- git revert commit (比如:fa042ce57ebbe5bb9c8db709f719cec2c58ee7ff)撤銷(xiāo)指定的版本,撤銷(xiāo)也會(huì)作為一次提交進(jìn)行保存。
git revert是提交一個(gè)新的版本,將需要revert的版本的內(nèi)容再反向修改回去,
版本會(huì)遞增,不影響之前提交的內(nèi)容【可以理解為撤銷(xiāo)某次合并后,重新生成了一個(gè)版本(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
說(shuō)明:放棄一個(gè)或多個(gè)提交,并生成一個(gè)或多個(gè)新的提交來(lái)記錄這些放棄操作。
git revert [--[no-]edit] [-n] [-m parent-number] [-s] [-S[<keyid>]] <commit>…
1. --edit or --no-edit是否彈出commit message窗口
2. -n 是 --no-commit的縮寫(xiě)
3. -m parent-number 存在merge是,指定父系分支號(hào)。
舉個(gè)栗子:
案例一:
主線(xiàn): A -> B -> C -> D
目前HEAD 指向 D,如果想回滾到C的狀態(tài)
git revert D
說(shuō)明: 直接回滾即可,此時(shí)會(huì)生成一個(gè)新的commit 號(hào),那么此時(shí)索引是:A->B->C->D->CV1
案例二:
如果是單個(gè)主線(xiàn)可以這么解決,如果多個(gè)分支協(xié)同開(kāi)發(fā)呢
主線(xiàn):【借用下網(wǎng)圖吧】

場(chǎng)景:
- dev1 提交了C1,C2, C3 ,C4 然后merge了master 生成了m5
- dev2 提交了C3,[應(yīng)該是B3,dev2分支那個(gè)線(xiàn)],然后和合并了master 生成了m6
問(wèn)題: 此時(shí)dev1 上有bug ,需要回滾怎么做呢
方案1:
直接在master上進(jìn)行reset,然后 -f 提交完美解決。
這種方案可以,不過(guò)不建議用,首先master上一般不會(huì)讓你-f, 而且master也盡可能的保持最新,最干凈的代碼
方案二
本節(jié)主要的git revert 來(lái)拯救世界了【用好了可以造世界,用不好可要受罰的哦】
step1 : git revert commitHash
這里呢,可以加一個(gè)參數(shù) -m
-m 后面還需要加一個(gè)整數(shù),且只能指定1或者2,否則會(huì)報(bào)錯(cuò)
commit 893247dca92dae929cf6255ad5985df25dbcfdac is a merge but no -m option was given.
fatal: revert failed
-m 標(biāo)記指出 “mainline” 需要被保留下來(lái)的父結(jié)點(diǎn)。 為什么要寫(xiě)1或者2 呢,不是直接反向執(zhí)行下這個(gè)commit進(jìn)行的修改嗎,commit加一行revert就減一行嗎?為什么還要選1還是2模式?
回答:【谷歌是個(gè)好東西,大佬的解析很明白,這段說(shuō)明原文:原文參考】
git并不是基于diff進(jìn)行管理的(有這樣的版本管理系統(tǒng)),git的每個(gè)commit都是一個(gè)當(dāng)前版本的快照,簡(jiǎn)單說(shuō)每個(gè)commit都是一個(gè)完整的倉(cāng)庫(kù)版本,所以當(dāng)你需要revert某個(gè)commit的時(shí)候,GIT需要知道你到底是希望revert哪個(gè)commit與這個(gè)commit間的改動(dòng)
不過(guò)其實(shí)并沒(méi)有那么復(fù)雜,你要revert一個(gè)commit,就是revert掉這個(gè)commit和它上個(gè)commit間的改動(dòng),所以大部分時(shí)候,你直接revert就好了,不用指定-m參數(shù)
不過(guò)當(dāng)你要revert的的commit的上面有兩個(gè)commit節(jié)點(diǎn)的時(shí)候,問(wèn)題就來(lái)了
A -> B ->
E -> F
C -> D ->
比如這里的E節(jié)點(diǎn),它是AC兩個(gè)分支合并的節(jié)點(diǎn),這里假設(shè)是你在A分支使用命令merge C,那么E就有兩個(gè)上游節(jié)點(diǎn)了,當(dāng)你在新的分支F(其實(shí)就是之前的A分支)revert E 時(shí),你就需要加上-m參數(shù)了,當(dāng)你指定1時(shí),就是revert 掉 B到E的改動(dòng),當(dāng)你指定2時(shí),你也可以revert 掉 D到E的改動(dòng),其實(shí)大部分時(shí)候我們都是選1就好了~
3. revert 掉的代碼,如果想繼續(xù)用怎么辦呢
來(lái)個(gè)主線(xiàn)任務(wù)

step1: 按照前文這個(gè)log, revert了m5,
stop2: dev2 的c3 merge 到了master生成了新的m6
當(dāng)你認(rèn)為是bug回滾完之后, 某位產(chǎn)品大大說(shuō),那個(gè)不是bug, 是我們的新方案,你有沒(méi)有看需求文檔,
你仔細(xì)看了下需求文檔,一排腦門(mén),對(duì)啊,這不是bug,那么少年想既然沒(méi)問(wèn)題,那么我在合并一次master就可以完美上線(xiàn)了,然后你開(kāi)開(kāi)心心的進(jìn)行merge 代碼了
git checkout dev1 # 查看分支,很完美,代碼都還在,很是慶幸
git merge dev1 # 繼續(xù)十分完美,沒(méi)有沖突,但是在看一眼代碼,dev1代碼的改動(dòng)被master給沖掉了,dev1代碼沒(méi)了
哦,no,怎么辦?
百度吧,百度的方案其實(shí)類(lèi)似我第二個(gè)模塊方案所介紹的,回到master 上進(jìn)行再次revert,
- 回到gitlab 上頁(yè)面revert,兄弟你此時(shí)可是在master上操作啊,小心腿打折啊。
那么如果我在 master 新開(kāi)分支test_2021, 在當(dāng)時(shí)那個(gè)rever的分支上進(jìn)行revert 然后在和test_2021 進(jìn)行merge 是否可以呢?
如果你幸運(yùn)是沒(méi)是的,不過(guò)你很不幸,在那次revert之后有過(guò)新的提交
那么你會(huì)很驚喜的收到如下報(bào)錯(cuò)
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.
- 命令執(zhí)行,命令行執(zhí)行有什么區(qū)別嗎?
說(shuō)明,如果你無(wú)法在master的當(dāng)前HEAD指向下進(jìn)行revert 到當(dāng)時(shí)的c4點(diǎn),那么你是無(wú)法在git上在此使用dev1代碼的,甚至你把dev1的代碼隨便往任意一個(gè)基于master新建的分支,都是無(wú)法把dev1的改動(dòng)弄過(guò)去的。因?yàn)間it認(rèn)為你已經(jīng)提交過(guò)了。
現(xiàn)在來(lái)介紹一個(gè)中重要的方式
step1:
git checkout revert-commitHash
切換分支到當(dāng)時(shí)revert出來(lái)的新版本上【可以先回到master,然后 git pull 就可以down下所有的branch 了】
step2:
假如你這個(gè)項(xiàng)目叫做 fileProject [項(xiàng)目文件夾],cp出一個(gè)新的副本
cp -r fileProject fileProjectV1
cd fileProjectV1
git checkout -b feature_revert
此時(shí)fileProjectV1 項(xiàng)目只是fileProject 一個(gè)副本,代碼,git倉(cāng)庫(kù)信息都一致
step3:
第一個(gè)小重點(diǎn)來(lái)了
將fileProjectV1 文件夾中,曾經(jīng)涉及dev1代碼改動(dòng)的文件刪掉,當(dāng)然如果文件很少,如果很多,不想分辨了,
像樓主一樣,找到文件所在的文件夾,把文件夾徹底干掉,[主要不是改那個(gè)刪那個(gè)]
cd fileProjectV1
rm -rf fileirectory
刪完世界都干凈了,有沒(méi)有
step4:
打開(kāi)fileProject,切換分支到 dev1及revert 玩,還想要繼續(xù)用的
cd fileProject
git checkout dev1
step5:
注意了啊,第二個(gè)重點(diǎn)來(lái)了
將你在dev1 分支上,當(dāng)時(shí)修改的文件夾或者理解為在 step3 刪除了那些文件,就把那些文件復(fù)制下來(lái)
復(fù)制,復(fù)制完cp到fileProjectV1中
不要cp git 相關(guān)文件
不要cp git 相關(guān)文件
不要cp git 相關(guān)文件
cp fileProject_fileirectory fileProjectV1_fileirectory #這是偽命令,相信你可以懂
step6:
此時(shí)fileProjectV1 項(xiàng)目是不是就有 dev1 代碼了,且分支還是feature_revert
進(jìn)行代碼梳理提交
所在項(xiàng)目:fileProjectV1
git diff
git add
git commit
git push
step7
所在項(xiàng)目:fileProjectV1
git checkout master
git pull
git checkout feature_revert
git merge master
#不出意外有沖突,那么解決沖突
git add
git commit
git push
完美收官!
no!!! 這個(gè)辦法有個(gè)后遺癥,master原有代碼可能會(huì)丟失,那么問(wèn)題來(lái)了?
到底怎么解決呢??
終極奧義 - R 閃 馬氏三角殺: cherry-pick
來(lái)上主線(xiàn)圖

因?yàn)榇藭r(shí)你想要dev1 的代碼,但是這個(gè)分支又不能用了
上操作完事了
step1: 回master新開(kāi)分支
git checkout master
git checkout -b dev_revert
step2: 查看log, 查找你想要的代碼
git log # 根據(jù)commit msg 找到commitHash
git cherry-pick commitHash
step3:如果遇到?jīng)_突,自己解決沖突,然后執(zhí)行git add, git push即可
cherry-pick 是自動(dòng)提交的 如果不想自動(dòng)提交,加參數(shù)-n
git cherry-pick commitHash -n
==================華麗分割線(xiàn)=====================
如果你想要D2哪里的這個(gè)dev分支代碼怎么操作呢?
直接 cherry-pick 是錯(cuò)誤的,因?yàn)槟鞘?個(gè)分支的交點(diǎn),
如果原始提交是一個(gè)合并節(jié)點(diǎn),來(lái)自于兩個(gè)分支的合并,那么 Cherry pick 默認(rèn)將失敗,因?yàn)樗恢缿?yīng)該采用哪個(gè)分支的代碼變動(dòng)
問(wèn)題又來(lái)了,每日三省吾身,這個(gè)問(wèn)題怎么解決??到底怎么解決呢?
繼續(xù)看招 ------- eqr閃
git cherry-pick -m 1 <commitHash>
熟悉不,和上面的方式一樣-m含義也一樣
-m配置項(xiàng)告訴 Git,應(yīng)該采用哪個(gè)分支的變動(dòng)。它的參數(shù)parent-number是一個(gè)從1開(kāi)始的整數(shù),代表原始提交的父分支編號(hào)。
上面命令表示,Cherry pick 采用提交commitHash來(lái)自編號(hào)1的父分支的變動(dòng)。
一般來(lái)說(shuō),1號(hào)父分支是作為變動(dòng)來(lái)源的分支(the branch being merged from),
2號(hào)父分支是接受變動(dòng)的分支(the branch being merged into).
來(lái)下具體的操作,干說(shuō)不操作假把式
看個(gè)分支樹(shù)節(jié)點(diǎn)信息
[Merge branch 'master' into dev1]
對(duì)應(yīng)的commitHash : qwer1234
如果你要dev1 的這個(gè)節(jié)點(diǎn)全部代碼
git cherry-pick -m 2 qwer1234
那么執(zhí)行完如果有沖突,會(huì)終止合并的, 你要先解決沖突,
然后 git add之后 ,
執(zhí)行git cherry-pick --continue
最后git push
記住這個(gè)方式 也是自動(dòng)提交的【即不需要git commit,你可以add 完,git status 看下,這是個(gè)小秘密哦】
當(dāng)然,如果你想放棄此次cherry-pick,那么狠遺憾的告訴你,
執(zhí)行這個(gè)就可以了git cherry-pick --abort.
4.加餐:revert 和reset 區(qū)別
- git revert是用一次新的commit來(lái)回滾之前的commit,git reset是直接刪除指定的commit。
- 在回滾這一操作上看,效果差不多。但是在日后繼續(xù)merge以前的老版本時(shí)有區(qū)別。因?yàn)間it revert是用一次逆向的commit“中和”之前的提交,因此日后合并老的branch時(shí),導(dǎo)致這部分改變不會(huì)再次出現(xiàn),但是git reset是之間把某些commit在某個(gè)branch上刪除,因而和老的branch再次merge時(shí),這些被回滾的commit應(yīng)該還會(huì)被引入。
- git reset 是把HEAD向后移動(dòng)了一下,而git revert是HEAD繼續(xù)前進(jìn),只是新的commit的內(nèi)容和要revert的內(nèi)容正好相反,能夠抵消要被revert的內(nèi)容。