git revert 可以讓開發者快速把已經 merge 到 develop branch 的 feature revert 掉。比如在發生緊急 bug 又沒辦法立刻解決時,透過退掉該 feature 來達成 work around,此時 git revert 將會是你最好的工具!立刻來看看如何使用!

準備練習用 repository

為了方便練習,可以 clone 這個事先準備好的 repo 來測試

https://github.com/Jim-Chang/git-revert-demo

git clone https://github.com/Jim-Chang/git-revert-demo.git

repo 目前有兩個 branch,分別是 master 和 feat,用來模擬當 feature branch merge 到 master 後要 revert 的過程(一般流程會是 develop,這邊方便起見以 master 為代表)。

是啥?快速理解原理與應用!

一開始在 repo 裡面新增一個檔案 demo.py 來做實驗,commit: add demo.py 如下

print('old 1')
print('old 2')
print('old 3')
print('old 4')
print('old 5')
print('old 6')
print('old 7') 

後續在 master 上每一個 commit 會改一行 print,將字串 old 改成 master,如 master: commit 1

是啥?快速理解原理與應用!

在 feat 上也是一樣方法,只是把 old 改成 feat 來區分,如 feat: commit 1

是啥?快速理解原理與應用!

最後 demo.py 如下,可以用 print 中的文字來區別是在哪一個 branch 中做的修改或增加

print('master 1')
print('master 2')
print('feat 1')
print('feat 2')
print('feat 3')
print('old 6')
print('old 7')
print('master 3')
print('master 4')

如何 revert 掉某一個 commit?

先從簡單的做起,如果今天我們要退掉某一個在 master branch 的 commit 該如何做?

假設我們今天要退掉 master: commit 4 這個 commit,其修改是在最後面增加一行 print('master 4')

是啥?快速理解原理與應用!

輸入以下指令做 revert

$ git revert 495a61c

[master a2e4445] Revert "master: commit 4"
 1 file changed, 1 insertion(+), 2 deletions(-)

git 會自動幫你提交一個 commit,把 master: commit 4 中的修改 revert 掉

是啥?快速理解原理與應用!

此時線圖長這樣

是啥?快速理解原理與應用!

實驗完畢,我們讓 repo 回到 revert 前的狀態,使用 git reset 往前 reset 一個 commit

$ git reset --hard @~1

HEAD is now at 495a61c master: commit 4

如何 revert 掉 feature branch 的修改?

今天因為 feat 裡面修改的地方壞了,需要整個 revert 掉,該怎麼處理?

首先找到 feat merge 到 master 的 commit: Merge branch ‘feat’ 78dfcc5,並輸入以下指令做 revert

$ git revert 78dfcc5

error: commit 78dfcc5f25971a9a8de95203451ee6800f786858 is a merge but no -m option was given.
fatal: revert failed

發現報錯了!怎麼會這樣呢?剛剛做 revert 不是也是這麼做嗎?

原來我們這次 revert 的 commit 是 merge commit,他往前有兩個 parent,分別是在 master 上的 master: commit 3 和 feat 上的 feat: commit 3

是啥?快速理解原理與應用!

git 不知道要用哪一個 commit 來和 merge commit 做 diff 因此報錯

此時只需要帶 -m 參數給 git,告訴他要用哪一個 parent commit 做 diff 即可。其中

  1. -m 1 表示用 parent 1 (master branch)
  2. -m 2 表示用 parent 2 (feat branch)。

現在需求是 revert 掉 feat branch 的修改,因此使用 parent 1 (master branch) 為基準做 diff

$ git revert 78dfcc5 -m 1

Auto-merging demo.py
[master 748343c] Revert "Merge branch 'feat'"
 1 file changed, 3 insertions(+), 3 deletions(-)

git 也自動幫你提交一個 commit,並把 feat branch 中做的修改都 revert 掉

是啥?快速理解原理與應用!

線圖如下

是啥?快速理解原理與應用!

實驗完畢,我們讓 repo 回到 revert 前的狀態,使用 git reset 往前 reset 一個 commit

$ git reset --hard @~1

HEAD is now at 495a61c master: commit 4

如果 revert 時對 feat branch 做 diff 會發生什麼事情?

剛剛是使用 -m 1 對 master branch 做 diff 做 revert,如果改用 -m 2 對 feat branch 做會發生什麼事情?

立刻來試試!

$ git revert 78dfcc55 -m 2

Auto-merging demo.py
CONFLICT (content): Merge conflict in demo.py
error: could not revert 78dfcc5... Merge branch 'feat'

回報發生 conflict!看一下目前 demo.py 檔案狀態

是啥?快速理解原理與應用!

看起來衝突是在 master 上修改的最後幾行,為什麼呢?

我們把 merge commitfeat: commit 3 的修改抓出來比較一下

是啥?快速理解原理與應用!

因為我們這次選的 parent 是 feat branch,從他的角度來看,master branch 在 merge commit 前增加了第八行 print('master 3'),所以要把他 revert 掉,但沒想到 merge commit 後面又有 commit 在第八行接續增加了第九行 print('master 4'),導致和 revert 衝突,只好報錯人工處理。

假設今天 merge commit 後面沒有 master: commit 4 的修改,是否還會有衝突呢?

實驗看看!先 hard reset 到 merge commit

$ git reset --hard 78dfcc5

HEAD is now at 78dfcc5 Merge branch 'feat'

此時線圖如下

是啥?快速理解原理與應用!

緊接著再做 revert

$ git revert 78dfcc5 -m 2

[master 4a53b4d] Revert "Merge branch 'feat'"
 1 file changed, 1 deletion(-)

果然安然無恙的 revert 了!revert 的修改如下,符合預期

是啥?快速理解原理與應用!

線圖如下

是啥?快速理解原理與應用!

最後重點複習

  1. 如果是一般 commit ( 1 parent) 時,使用 git revert <commit_id>
  2. 如果是 merge commit(2 parent),要帶 -m 參數,通常會帶 1 代表 merge 進去的 branch,使用指令 git revert <commit_id> -m 1

如果覺得我文章內容對你有幫助的話,請在文章後面幫我按 5 個讚!讓我知道大家都喜歡什麼內容哦!

延伸閱讀:
git 綜合技, 坐時光機解決 CI pre-commit 報錯問題
被新創公司裁員後,我學到的五件事

Written by J
雖然大學唸的是生物,但持著興趣與熱情自學,畢業後轉戰硬體工程師,與宅宅工程師們一起過著沒日沒夜的生活,做著台灣最薄的 intel 筆電,要與 macbook air 比拼。 離開後,憑著一股傻勁與朋友創業,再度轉戰軟體工程師,一手扛起前後端、雙平台 app 開發,過程中雖跌跌撞撞,卻也累計不少經驗。 可惜不是那 1% 的成功人士,於是加入其他成功人士的新創公司,專職開發後端。沒想到卻在採前人坑的過程中,拓寬了眼界,得到了深層的領悟。