[譯] 保護咱們的 Git Repos,馬上中止「狐步舞」

狐步舞舞者

舞者們正準備跳狐步舞。html

首先,什麼是「狐步舞」式的合併?

「狐步舞」式的合併是 git commit 的一個特別很差的具體順序。如同在戶外看到的「狐步舞」,這種commits序列像這個樣子:前端

「狐步舞」式的合併

但在公開場合不多會見到「狐步舞」。它們隱藏在樹冠之間,樹枝之間。我稱它們「狐步舞」式,是由於他們交叉的樣子,他們看起來像同步舞蹈的舞步順序:react

狐步示意圖

還有一些人也提到「狐步舞」式的合併,但它們歷來沒有直接說出它的名字。例如,Junio C. Hamano 的博客有有趣的 --first-parent,還有有趣的非快進方式(Non-Fast-Forward)。David Lowe 的 nestoria.com 有關於保持一致的線性歷史記錄的文章。此外還有告訴你要避免使用 git pull,而是使用 git pull –rebase。爲何?主要是爲了不通常的合併和提交時的錯誤,此外還能夠避免出現該死的「狐步舞」式的提交。linux

「狐步舞」式的合併真的很很差嗎?是的。android

水母

它們顯然不如僧帽水母那樣糟糕。可是「狐步舞」式的合併也是很差的,你不但願你的 git 倉庫裏有它們的身影。ios

「狐步舞」式的合併爲何很差?

「狐步舞」式的合併很差,由於它會改變 origin/master 分支的「第一父級」的地位。git

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

你能夠像下面這樣想:後端

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

若是你是 octopus 的說客:bash

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前端後端產品設計 等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃

相關文章
相關標籤/搜索