當你決定去 revert 一個merge commit

本文首發於我的 Github,歡迎 issue / fxxk。git

前言

本文將結合一個標準的git工做流,以及具體的例子,來引出何種條件,會觸發 git revert 的失敗。經過本文,你講學到程序員

  • 鞏固 git 的使用和理解;
  • 出現問題可以放心大膽並以正確的姿式來 revert。

準備

爲了更好地描述本文,咱們將從頭開始進行一些準備工做。github

首先,建立一個repo:shell

mkdir git-revert-merge-commit
cd git-revert-merge-commit
git init
複製代碼

首先建立兩個 commit 來模擬 master 上現有的 commit 記錄:bash

echo 'file1' > file1 
git add . && git commit -m 'commit 1'
echo 'file2' > file2
git add . && git commit -m 'commit 2'
複製代碼

在項目不斷的演進過程當中,咱們會不斷地作新功能。假設如今咱們要作一個新 feature,爲了避免 block master 的 CI pipeline,全部的 feature 應該基於新的 branch 來開發。因而,咱們建立一個 branch, 並切換到該 branch:ide

git branch feature
git checkout feature # 或者使用 git checkout -b feature 一鍵完成上述兩步
複製代碼

首先建立兩個 commit 來模擬咱們完成了這個 feature:測試

echo 'file3' > file3
git add . && git commit -m 'feature - commit 1'
echo 'file4' > file4
git add . && git commit -m 'feature - commit 2'
複製代碼

在開發 feature 的階段中,master 上一般會有其餘人新進入的 commit,因而咱們回到 master,來模擬一下這些 commit:ui

git checkout master
echo 'file5' > file5
git add . && git commit -m 'commit 3'
echo 'file6' > file6
git add . && git commit -m 'commit 4' 
複製代碼

此時,QA們測完了你的 feature,給了你 thumbup (測試經過) 的 tag,模擬一下這個tag:spa

git ck -
git tag thumbup-feature
複製代碼

而後,你就能夠用這個 tag merge 了:翻譯

git checkout master
git pull origin master # 拉取遠程 master 合併到本地 master
git merge thumbup-feature --no-ff # --no-ff 是爲了不 fast-forward
複製代碼

image

在 Webstorm 中查看一下 git log 圖:

image

後來,master 上又多了一些 commit:

echo 'file7' > file7
git add . && git commit -m 'commit 5'
複製代碼

好了,到這裏,不少開發者以爲本身的事已經作完了,不,此時生產環境的 UT 因爲你的 merge 掛了,因爲不能 quick fix,也不能 block release,因此只好暫時決定 revert 你的 change,好吧,那就來 revert 吧。

首先,看一下 git log:

image

找到 merge 的 commit id, 來 revert 吧:

git revert cae5381
複製代碼

哈哈,問題就來了:

image

終於出現了該問題,nice。

固然,因爲程序員的最強本性是懶,因此我也爲想動手的同窗準備好了一鍵運行腳本:

git init 後 copy 到 iTerm 中運行如下腳本,最後 找到你的 merge commit id 嘗試 git revert。

echo 'file1' > file1; \
git add . && git commit -m 'commit 1'; \
echo 'file2' > file2; \
git add . && git commit -m 'commit 2'; \
git branch feature; \
git checkout feature; \
echo 'file3' > file3; \
git add . && git commit -m 'feature - commit 1'; \
echo 'file4' > file4; \
git add . && git commit -m 'feature - commit 2'; \
git checkout master; \
echo 'file5' > file5; \
git add . && git commit -m 'commit 3'; \
echo 'file6' > file6; \
git add . && git commit -m 'commit 4'; \ 
git ck -; \
git tag thumbup-feature; \
git checkout master; \
git pull origin master; \
git merge thumbup-feature --no-ff; \
echo 'file7' > file7; \
git add . && git commit -m 'commit 5';
複製代碼

分析

再次給出錯誤信息:

error: commit cae5381823aad7c285d017e5cf7e8bc4b7b12240 is a merge but no -m option was given.

咱們來看看 -m 到底指的是什麼, 援引 官方文檔, 能夠看到:

Usually you cannot revert a merge because you do not know which side of the merge should be considered the mainline. This option specifies the parent number (starting from 1) of the mainline and allows revert to reverse the change relative to the specified parent.

Reverting a merge commit declares that you will never want the tree changes brought in by the merge. As a result, later merges will only bring in tree changes introduced by commits that are not ancestors of the previously reverted merge. This may or may not be what you want.

翻譯過來就是:

一般狀況下,你沒法 revert 一個 merge,由於你不知道 merge 的哪一條線應該被視爲主線。這個選項(-m)指定了主線的 parent 的代號(從1開始),並容許以相對於指定的 parent 的進行 revert。

revert 一個 merge commit 意味着你將徹底不想要來自 merge commit 帶來的 tree change。 所以,以後的 merge 只會引入那些不是以前被 revert 的那個 merge 的祖先引入的 tree change,這多是也可能不是你想要的。

聽起來很繞口,簡單解釋一下,因爲 merge commit 是將兩條線合併到一條線上,所以,合併時的那個commit,將具備兩個祖先。因此 git 不知道 base 是選擇哪一個 parent 進行 diff,因此就抱怨了,因此你要用 -m 屬性顯示地告訴 git 用哪個 parent。

那麼,如何查看當前的commit有幾個祖先呢?

  1. git show cae5381
commit cae5381823aad7c285d017e5cf7e8bc4b7b12240
Merge: edf99ca 125cfdd
Author: ULIVZ <472590061@qq.com>
Date:   Thu Apr 12 18:27:21 2018 +0800

    Merge tag 'thumbup-feature'
複製代碼

能夠看到,Merge 這個字段便標明瞭當前的parent,分別是 edf99ca 和 125cfdd

  1. Github 上點開這個 commit 也能夠看到:

image

接下來,咱們用 git log 圖來描述一下這兩個 parent:

image

是否是很直觀?又有人會問了,爲何 master 是 parent1,而 branch 的最後一個 commit 是 parent2。是這樣的,當你在 B 分支上把 A merge 到 B 中,那麼 B 就是merge commit 的 parent1,而 A 是 parent2。

解決

有了上一節的分析,咱們能夠很直接地給出如下可用的代碼:

git revert cae5381 -m 1
複製代碼

輸出如下log:

Revert "Merge tag 'thumbup-feature'"

This reverts commit cae5381823aad7c285d017e5cf7e8bc4b7b12240, reversing
changes made to edf99ca31755a27b0a43b290263ed810833a95c4.
複製代碼

:wq 退出看到:

[master f0aac26] Revert "Merge tag 'thumbup-feature'"
 2 files changed, 2 deletions(-)
 delete mode 100644 file3
 delete mode 100644 file4
複製代碼

file3 和 file4 是 feature branch 上的 commit 引入的文件,被正確地刪掉了,cool。

那麼 git revert cae5381 -m 2 到底會發生什麼呢? 或許你已經有答案,仍是來試試看:

首先 reset hard 到 revert 前的最後一個commit,讓咱們能夠再次嘗試另外一種 revert,這裏,你能夠用更可愛的 git reflog。

git reset --hard d208cba 
git revert cae5381 -m 2
複製代碼
[master 2c5a0ee] Revert "Merge tag 'thumbup-feature'"
 2 files changed, 2 deletions(-)
 delete mode 100644 file5  
 delete mode 100644 file6
複製代碼

果真,這種 revert 把 master 在 feature branch 期間進行的 commit 都給幹掉了。哈哈,很正常,由於此時 diff 是基於 feature branch 的。

說到這裏,你是否是豁然開朗了呢?是否是也知道再遇到這種問題,內心有一股底氣去revert呢?

本文代碼均已放到這個倉庫上 git-revert-merge-commit

結論

結論以下:

  1. 對於單一 parent 的 commit,直接使用 git revert commit_id;
  2. 對於具備多個 parent 的 commit,須要結合 -m 屬性:git revert commit_id -m parent_id;
  3. 對於從 branch 合併到 master 的 merge commit,master 的 parent_id 是1,branch 的 parent_id 是2, 反之亦然;

最後,附上我自定義的可愛的 git log 的配置(copy到~/.gitconfig中的 [alias] 下便可):

lo = log --color --pretty=format:' %C(white bold dim) %C(cyan bold dim)[%h] %Creset %Cgreen%cn %C(white bold dim)| %C(yellow dim)%cr %n%Creset %C(white dim italic)%s%n%n%Creset'
複製代碼

話提及來,一年前我有一個如何用 shell 來 enhance git 工做流的庫 git-shell,當前,目前沒有任何文檔,有興趣的同窗能夠看一下思路,後面我會寫好文檔並另開博文。

本文就到這裏,謝謝閱讀。

相關文章
相關標籤/搜索