[譯] 保護我們的 Git Repos,立刻停止“狐步舞”

LeviDing發表於2017-07-31

狐步舞舞者

舞者們正準備跳狐步舞。

首先,什麼是“狐步舞”式的合併?

“狐步舞”式的合併是 git commit 的一個特別不好的具體順序。如同在戶外看到的“狐步舞”,這種commits序列像這個樣子:

“狐步舞”式的合併

但在公開場合很少會見到“狐步舞”。它們隱藏在樹冠之間,樹枝之間。我稱它們“狐步舞”式,是因為他們交叉的樣子,他們看起來像同步舞蹈的舞步順序:

狐步示意圖

還有一些人也提到“狐步舞”式的合併,但它們從來沒有直接說出它的名字。例如,Junio C. Hamano 的部落格有有趣的 --first-parent,還有有趣的非快進方式(Non-Fast-Forward)。David Lowe 的 nestoria.com 有關於保持一致的線性歷史記錄的文章。此外還有告訴你要避免使用 git pull,而是使用 git pull –rebase。為什麼?主要是為了避免一般的合併和提交時的錯誤,此外還可以避免出現該死的“狐步舞”式的提交。

“狐步舞”式的合併真的很不好嗎?是的。

水母

它們顯然不如僧帽水母那樣糟糕。但是“狐步舞”式的合併也是不好的,你不希望你的 git 倉庫裡有它們的身影。

“狐步舞”式的合併為什麼不好?

“狐步舞”式的合併不好,因為它會改變 origin/master 分支的“第一父級”的地位。

合併提交記錄的父級是有序的。第一個父級是 HEAD。第二個父級是用 git merge 命令提交的。

你可以像下面這樣想:

git checkout 1st-parent
git merge 2nd-parent
複製程式碼

如果你是 octopus 的說客:

git merge 2nd-parent 3rd-parent 4th-parent ... 8th-parent etc...
複製程式碼

這意味著父級的記錄就像它聽起來一樣。當你提交新的程式碼的時候,忽略第一個父級以外的父級,從而得到一個新的程式碼記錄。對於常規的 commit(非 merge),第一個父級是唯一的父級,並且對於 merge 來說,它是你在輸入 git merge 時所產生的記錄。這種父級概念是直接植入到 Git 裡的,並且在很多命令列中都有所體現,例如,git log –-first-parent

“狐步舞”式的合併問題在於,它使得 origin/master 由第一父級變成了第二父級。

除了 Git 在評估提交是否有資格進行 fast-forward 時,Git 並不關心父級的先後次序。

當然你很不希望這樣。你不希望“狐步舞”式的合併通過 fast-forward 的方式更新你的 origin/master,使得 origin/master 第一父級的地位不穩定。

看一下當“狐步舞”式的合併被 push 上去的時候會發生什麼:

“狐步舞”式的合併被 push

可以使用手指從 origin/master 開始沿著圖形往下,在每個分叉的地方選擇左邊的分支,從而知道當前的第一父級的變更歷史。

問題是,最初的第一個父級提交次序(從 origin/master 開始)是這樣的:

B, A.

但是當“狐步舞”式的合併被 push 之後,父級的次序變成這樣了:

D, C, A.

這時,B 節點已從 origin/master 第一父級中消失,事實上,B在它的第二父級上。當然,不會有任何資料的丟失,並且 B 節點仍然是 origin/master 的一部分。

但是,這樣父級節點就會有錯綜複雜的關係。你是否知道,tilda 符號(例如 ~N)指定從第 N 個提交的節點到第一個父節點間的路徑?

你有沒有想要看看你的分支上的每個提交記錄之間的差異,但是使用 git log -p 顯然會漏掉一些資訊,使用 git log -p -m 能獲取更多的資訊嗎?

嘗試使用 git log -p -m –first-parent 吧。

你想過要還原一個合併的分支嗎?那你需要為 git revert 提供 -m parent-number 選項,這時候你就很不希望自己提供的 parent-number 是錯的。

和我一起工作的人,大多數都將第一個父級作為真正的 master 分支。有意識或無意識地,人們將 git log –first-parent origin/master 視為重要事物的順序。 至於任何其他合併進來的分支?嗯,你應該知道他們會怎麼說:

topic

但是“狐步舞”式的合併把這些都混在了一起。請考慮下面的例子,其中 origin/master 分支的一系列的重要提交資訊,與你自己的稍微不那麼重要的提交併行:

topic escaped

現在,你終於準備把你的工作併入到 master 中。你輸入 git pull,或者可能你在一個主題分支上使用 git merge master 命令。那這樣發生了什麼?一個“狐步舞”式的合併就這麼出現了。

topic escaped b

一切都沒有什麼大問題,除了當你鍵入 git push,讓你的遠端倉庫接受它時,你的歷史記錄看起來像這樣:

topic escaped c

topic escaped lego

對於已經混入了“狐步舞”式的合併的 git 專案應該怎麼做?

啥招都沒有,隨它們去吧。除非你重寫 master 分支的歷史而惹怒其他人,那麼就去這麼瘋吧。

事實上,不要這樣做。

如何防止未來“狐步舞”式的合併出現在我的 git 專案中?

這有幾個方法。我最喜歡的的方式是下面的四步:

  1. 為你的團隊安裝 Atlassian Bitbucket 伺服器。

  2. 安裝我為 Bitbucket 伺服器寫的外掛,名字叫“Bit Booster Commit Graph and More”。 你可以在下面的連結中找到他們:marketplace.atlassian.com/plugins/com…marketplace.atlassian.com/plugins/com…

  3. 在你所有專案中,都點選 “Protect First Parent Hook” 上的 “Enabled” 按鈕,也就是“啟用”按鈕:

hook enabled

  1. 你可以在試用許可結束前免費使用31天。(感覺它好用的話,可以在試用期後進行購買)。

這是我最喜歡的方式,因為它杜絕了“狐步舞”的出現。每當有一個“狐步舞”式的合併被阻擋時,它會列印一隻牛:

$ git commit -m 'my commit'
$ git pull
$ git push

remote:  _____________________________________________
remote: /                                             \
remote: | Moo! Your bit-booster license has expired!  |
remote: \                                             /
remote:  ---------------------------------------------
remote:         \   ^__^
remote:          \  (oo)\_______
remote:             (__)\       )\/\
remote:                 ||----w |
remote:                 ||     ||
remote:
remote: *** PUSH REJECTED BY Protect-First-Parent HOOK ***
remote:
remote: Merge [da75830d94f5] is not allowed. *Current* master
remote: must appear in the 'first-parent' position of the
remote: subsequent commit.
複製程式碼

還有其他的方法。你可以禁止直接向 master 分支進行推送,並保證不在 fast-forward 的情況下合併 pull-requests。或者培訓你的員工使用 git pull –rebase 命令,並且永遠不要使用 git merge master。並且一旦你培訓完你的員工,就不要再招聘其他員工了。

如果你可以直接訪問遠端倉庫,則可以設定 pre-receive hook。 以下的 bash 指令碼可以幫助你開始這項設定:

#/bin/bash

# Copyright (c) 2016 G. Sylvie Davies. http://bit-booster.com/
# Copyright (c) 2016 torek. http://stackoverflow.com/users/1256452/torek
# License: MIT license. https://opensource.org/licenses/MIT
while read oldrev newrev refname
do
if [ "$refname" = "refs/heads/master" ]; then
   MATCH=`git log --first-parent --pretty='%H %P' $oldrev..$newrev |
     grep $oldrev |
     awk '{ print \$2 }'`

   if [ "$oldrev" = "$MATCH" ]; then
     exit 0
   else
     echo "*** PUSH REJECTED! FOXTROT MERGE BLOCKED!!! ***"
     exit 1
   fi
fi
done
複製程式碼

我不小心建立了一個“狐步舞”式的合併,但我還沒有 push 上去。我該怎麼解決?

假設你安裝了預先接收的鉤子,並且阻止你“狐步舞”式的合併。你下一步做什麼?你有三種可能的補救辦法:

  1. 普通的 rebase

使用 `rebase` 進行補救

  1. 撤銷你之前的合併,使你的 origin/master 分支成為第一父級:

撤銷 merge

  1. 在“狐步舞”式的合併後建立第二個合併並提交,以恢復 origin/master 的第一父級的地位。

補救

但請不要使用上面的第三種方法,因為最後的結果被稱為“僧帽水母”式的合併,這種合併甚至比“狐步舞”式的合併更糟糕。

總結

在最後,其實“狐步舞”式的合併也像其他的合併那樣。兩個(或多個)提交到一起融合成一個新的記錄節點。就你的程式碼庫而言,沒有任何區別。無論 commit A 合併到 commit B 中還是反過來 commit B 合併到 commit A,從程式碼的角度來看最終結果是相同的。

但是,當涉及到你的倉庫的歷史記錄時,以及有效地使用 git 工具集時,“狐步舞”式的合併會有一定的破壞性。通過設定相應的策略來防止其出現,可以使你倉庫的歷史記錄更加清晰明瞭,並減少了需要記住的 git 命令選項的範圍。


掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 AndroidiOSReact前端後端產品設計 等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃

相關文章