【Git】git revert 與 git cherry-pick

採櫻桃囉

Johnny Fang
9 min readDec 31, 2023

最後更新時間:2024–07–28

話說華盛頓砍倒的是櫻桃樹還是蘋果樹?| Photo by sajeesh Gangadharan on Unsplash

前言

這次不只壓線而且還是壓線中的壓線,壓了 12 月的線,也壓了 2023 年的線。你是不是以為我現在要回顧今年以來的成長、感謝東感謝西之類?沒有,這篇就只是一篇正常文章,不會因為年底而特別不一樣,沒想到吧~

說文解字

沒錯,只要出現新名詞不管三七二十一,一律先說文解字一下,這樣才符合本部落格拖台錢的步調。沒有啦,其實我自己覺得養成這個習慣還不錯,常常都會因為去查這些字才發現原本沒想過的內容進而學到新知識,即使學到的東西跟軟體技術無關也增加了其他領域的新知(或冷知識)。

看到標題那兩個 git 指令,不知道在座的各位有沒有人是 git 大師?至少我不是,還記得以前在 AC 學的時候基本上只會用到 git push,做完就 push、做完就 push,頂多到了做 Twitter 專案時因為需要跟另一名前端夥伴協作所以多用了 git pull 來更新本地 repo 進度而已,因此一堆 git 指令都是上工後才邊弄邊學。

那就來看看這兩個字吧:

1️⃣ revert

動詞,使回復原狀。請注意,不是刪除的意思哦,在英文你不會這樣覺得,然而在 git 可能會誤以為 revert 指令把 commit 刪掉了,但其實沒有

2️⃣ cherry-pick

這個就有意思了,第一次看到這個字是在 Azure DevOps 上面發完 PR 後系統會顯示 Cherry-pick 這個按鈕可以按,從來沒按過也從未去查這什麼意思,直到這次。先來看看 Cambridge Dictionary 怎麼說:

to pick only the best people or things from a group, so that only people or things that are less good remain

咦?去 google 又發現這篇:FB 貼文:【Cherry-pick 摘櫻桃?】裡面寫道:

cherry-pick 就像是果農摘櫻桃一樣,只把品項最好的櫻桃挑出來,有著精挑細選的意思。在這樣的狀況之下,看到挑選結果的人可能會以為所有櫻桃都是好的,但事實上他所看到的櫻桃是經過有意挑選出來的,並不能代表全部的真實狀況,因此這樣的行為讓 cherry-pick 這個字大多時候是帶有貶義的。

而且文內以政客造的例句很到位,歡迎去那篇文看看

所以看到這邊你有跟我一樣驚訝嗎?原本以為 cherry-pick 可能就只是 pick 這個動詞比較俏皮的用法,單純是指摘或挑東西,沒想到還有這層含義。

所以我遇到什麼情境?

還記得那是個愉快的週五午後,暖陽灑在窗台上,微風徐徐吹拂著路人臉龐,藍天點綴著幾顆雲朵恰到好處,以上關於天氣的描述純屬唬爛,但差不多就是愉快午後的意象。當天在家工作,準備一下班就去搭機捷到桃園與朋友會合一起嘿皮歡度週末,一切規劃看似美好,而往往故事轉折點都接在「看似」這兩個字之後。

請容我簡述當下程式碼的狀態,遠端 repo 上有 develop 分支,本地端 repo 也有 develop 分支,兩邊同樣進度,我原本打算新功能做完後從本地端 develop 分支開一個新的 feature 分支(姑且稱之為 A 分支),再把 A 分支推到遠端去,然後再發 PR 將 A 分支合併到 develop 分支,這樣同事們才能看到我做了哪些變動,而且團隊也規定 PR 要有兩人以上 review & approve 才能合併。

結果,一個手殘毀了美好週末,不誇張,我在桃園都無法專心玩,腦中時不時就會想起這件事。如何手殘呢?我忘了開新分支就直接在 develop 分支 git push 了-.-,這什麼意思?意思就是還沒經過兩位同事 review 就直接更新了遠端 repo 上的 develop 分支(事後想想怎麼沒有設定一些機制來保護 develop 分支,印象中 master 分支好像有受保護),這是個超白癡的舉動,只因自己指令按太快忘了要先開新分支再推。

由於事發當下為下午三點多,原本預計五點準時結束工作開始整理行囊準備出門,於是已經沒時間慢慢查了,直接問 ChatGPT 該怎麼處理,結果它說可以用 git revert,為了保險起見,我在用 git revert 前就先開好原本要開的 A 分支,再返回 develop 分支操作,算是備份的概念把東西備份到 A 分支。於是我在本地端 develop 分支針對剛剛發的 PR 中包含到的所有 commit 用 git revert 之後再一次 git push,這樣遠端 develop 分支上的更新就都會被復原了。正當慶幸事情沒有變得太複雜,覺得反正遠端都 revert 掉了此時只要把本地端剛剛開好的 A 分支再推一次到遠端並發 PR 就 ok 了吧?

令人嚇出一身冷汗的事情來了,當我這樣發 PR 時,Azure DevOps 跟我說這個 PR 並沒有做任何變動,所以無法發 PR。

「%^&$%@#??????」中間可能夾了一堆髒話但我不會承認。

我們再來釐清一下,現在遠端 develop 分支上的變動都被 revert 掉了,照理說已經回到開發新功能前的狀態,而此時我把本地端新開且包含新功能的 A 分支 git push 上去並發 PR 預計將 A 分支合併到遠端的 develop 分支,聽起來沒毛病吧?為什麼系統會偵測到兩邊分支一模一樣?實際上去看兩邊分支裡面程式碼就是不同啊,到底發生什麼事?心急的我趕緊問了同為菜鳥的 DevOps 同事(問資深的太羞恥了)但仍來不及解決問題,拖到我後來換洗衣物亂拿一通隨便塞進包包就直接一路衝出門衝到機捷,一路上還幫我老婆提個行李包,一到機捷全身狼狽直接將行李包塞給我老婆拿XD,甚至到了晚上看夜景、聽 live band 都還是在想怎麼解決這件事,而這一切的一切就只是因為 git push 前忘了先開好分支,完美闡釋了何謂蝴蝶效應。

寧靜的週日夜晚

隨著夜幕低垂,終於有空可以專心處理這件事,就算不處理週一還是要面對,倒不如假日先弄一弄免得拖到進度,正當準備要來找解方時,盯著螢幕靈光一閃的我突然想到「啊,某位後端朋友應該對 git 不陌生」於是請教他之後就把問題解決了。

怎麼解決的呢?

先講結論,只要在本地端使用 git cherry-pick 把之前 revert 掉的那些 commit 撿回來,接著再按照正常程序 git push 並發 PR,系統就能抓到兩邊差異了。

具體而言發生什麼事?

git revert 的用法是:

git revert aaaa1234

aaaa1234 就是你想要 revert 的那筆 commit 的編號(SHA-1 值),實際上是一長串,但通常只要打出前面 8 位數即可,而這 8 位數看起來基本上也會是比較醜的東西例如 bee975d1 之類,這邊只是為了簡化所以寫成 aaaa1234。

這整件事的關鍵點在於,revert 是去「反做」某筆 commit,把該筆 commit 造成的變動復原,而非直接刪除該筆 commit。以 aaaa1234 為例,aaaa1234 這筆 commit 被 revert 掉之後並不會消失,而 revert 本身也會再產生一筆新的 commit 例如 bbbb1234,此時 commit 記錄會長得像這樣:

bbbb1234 Revert buy apple
aaaa1234 Buy apple

也因此,在原本情境中本地端的 A 分支本來就有 aaaa1234 這筆 commit,而遠端 develop 分支雖然已經使用 git revert 將更動復原,但原本的 aaaa1234 還是在啊,所以系統一比對發現兩邊都有這筆 commit,也就是說將本地端 A 分支推到遠端要發 PR 時系統抓不出 A 分支做了什麼事情是 develop 沒出現過的,因為系統是用 commit 去判斷而非根據實際程式碼。

怎麼解決呢?摘櫻桃囉

再複習一次當時情境,遠端 develop 分支已經使用 revert,而本地端 develop 分支跟遠端同步,同時有一個新開的 A 分支(沒有 revert 記錄,只有正常的 commit 記錄)。解決方法就是從本地端的 develop 分支再開另一個 B 分支出來(已包含 revert 記錄),直接在 B 分支使用 cherry-pick 把被 revert 的 commit 撿回來,然後再依正常程序將本地 B 分支推到遠端並發 PR。

git cherry-pick 的用法是:

git cherry-pick aaaa1234

採完櫻桃後 commit 記錄會變成這樣:

cccc1234 Buy apple
bbbb1234 Revert buy apple
aaaa1234 Buy apple

也就是說,把被 revert 掉的內容撿回來,並且產生一組新的編號,除了編號是新的外,程式碼內容跟 commit 說明都是舊的,所以你會看到 Buy apple 出現兩次但編號已經不同,而這樣就能解決問題了,因為系統已經可以辨識 B 分支裡面有筆 commit 叫 cccc1234,而這筆是 develop 分支所沒有的,也因此系統才可以看出檔案變動並列出差異的程式碼,這樣也才能發 PR 讓同事 review 我做了哪些事。

結尾

雖然當下很痛苦而且毀掉當週週末,但仔細想想如果一直都很小心那好像也不會學到 git revert 與 git cherry-pick。後來為了防止類似蠢事再度發生,我更改了自己的開發 SOP,統一都在開發新功能前就先開好新分支,這樣就算不小心 git push 也只會影響我自己這個分支而不會影響到其他同事,也希望這段經驗分享能在未來幫到某些遇到問題當下卻趕著出門的有緣人。

cherry-pick 這個 git 指令取名取得滿貼切的,「挑好的過來用」大概是這個意思。

不知道大家有沒有想要從 2023 年 cherry-pick 哪些東西到 2024 年繼續用呢?

--

--

Johnny Fang

把 Medium 當 Notion 用,寫一下 coding 學習筆記 | email: johnny781222@gmail.com | LinkedIn: www.linkedin.com/in/johnny-fang-9356b2156 | Discord 使用者名稱:johnnyfang