天天看點

[譯] 使用 `-force` 被認為是有害的;了解 Git 的 `-force-with-lease` 指令

<b>本文講的是[譯] 使用 `-force` 被認為是有害的;了解 Git 的 `-force-with-lease` 指令,</b>

<b></b>

Git 的 <code>push --force</code> 具有破壞性,因為它無條件地覆寫遠端存儲庫,無論你在本地擁有什麼。使用這個指令,可能覆寫團隊成員在此期間推送的所有更改。然而,有一個更好的辦法,當你需要強制推送,但仍需確定不覆寫其他人的工作時,<code>-force-with-lease</code> 這條指令選項可以幫助到你。

我不經常使用 push --force...

衆所周知,git 的 <code>push -force</code> 指令是不推薦被使用的,因為它會破壞其他已經送出到共享庫的内容。雖然這不總是完全緻命的(如果那些修改的内容仍在某些同僚的本地工作域中,那之後他們能被重新合并),但是這樣的做法很欠考慮,最糟糕的情況會造成災難性的損失。這是因為 <code>--force</code> 指令選項迫使分支的頭指針指向你個人的修改記錄,而忽略了那些其他和你同時進行地更改。

強制推動最常見的原因之一是當我們被迫 <code>rebase</code> 一個分支的時候。為了說明這一點,我們來看一個例子。我們有一個項目,其中有一個功能分支,Alice 和 Bob 要同時在這個分支上工作。他們都 <code>git clone...</code> 了這個倉庫,并開始工作。

最初,Alice 完成了她負責的功能,并将其 <code>push</code> 到主倉庫。這都沒啥問題。

Bob 也完成了他的工作,但在 <code>push</code> 之前,他注意到一些變化已被合并到了 master 分支。想要保持一棵整潔的工作樹,他會對主分支執行一個 <code>rebase</code>。當然,當他 <code>push</code> 這個經過<code>rebase</code> 的分支的時候将被拒絕。然而,Bob 沒有意識到 Alice 已經 <code>push</code> 了她的工作。Bob 執行了 <code>push --force</code> 指令。不幸的是,這将清除 Alice 在遠端主倉庫的所有更改和記錄。

這裡的問題是,進行強制推送的 Bob 不知道為什麼他的 <code>push</code> 會被拒絕,是以他認為這是<code>rebase</code> 造成的,而不是由于 Alice 的變化。這就是為什麼 <code>--force</code> 在同一個分支上協作的時候要杜絕的;并且通過遠端主倉庫的工作流程,任何分支都可以被共享。

但是 <code>--force</code> 有一個不為衆人所知的親戚,它在一定程度上能防止強制更新操作帶來的結構性破壞;它就是 <code>--force-with-lease</code>。

<code>--force-with-lease</code> 是用于拒絕更新一個分支,除非該分支達到我們期望的狀态。即沒有人在上遊更新分支内容。 實際上,通過檢查上遊引用是我們所期望的,因為引用是散列,并将父系鍊隐含地編碼成它們的值。

你可以告訴 <code>--force-with-lease</code> 究竟要檢查什麼,預設情況下會檢查目前的遠端引用。這在實踐中意味着,當 Alice 更新她的分支并将其推送到遠端倉庫時,分支的引用指針将被更新。現在,除非 Bob從遠端倉庫 <code>pull</code> 一下,否則本地對遠端倉庫的引用将過期。當他使用 <code>--force-with-lease</code> 推送時,git 會檢查本地與遠端的引用是否對應,并拒絕 Bob 的強制推送。<code>--force-with-lease</code> 有效地隻在沒有人在上遊更新分支内容的時候允許你強制推送。就像是一個帶有安全帶的 <code>--force</code>。它的一個快速示範可能有助于說明這一點:

Alice 已經對該分支進行了一些更改,并已推送到了遠端主倉庫。Bob 現在又對遠端倉庫的<code>master</code> 分支進行了 <code>rebases</code> 操作:

<code>rebase</code> 之後,他試圖将自己的更改 <code>push</code> 上去,但伺服器拒絕了,因為這會覆寫 Alice 的工作:

但 Bob 認為這是 <code>rebase</code> 操作造成的,并決定強制 <code>push</code>:

然而,如果他使用了 <code>--force-with-lease</code>,則會得到不同的結果,因為 git 會檢查遠端分支,發現 從上一次 Bob 使用 <code>fetch</code> 到現在,實際上并沒有被更新:

當然,在這有一些關于 git 的注意事項。上面展示的,隻有當 Alice 已經将其更改推送到遠端存儲庫時,它才有效。這不是一個嚴重的問題,但是如果她想修改她送出的東西,那她去<code>pull</code> 分支時,會被提示合并被更改。

一個更微妙的問題是,我們有方法去騙 git,讓 git 認為這個分支沒有被修改。在正常使用情況下,最常發生這種現象的情況是,Bob 使用 <code>git fetch</code> <code>而不是</code>git pull<code>`來更新他的本地副本。</code>fetch<code>将從遠端倉庫拉出對象和引用,但沒有比對的</code>merge<code>則不會更新工作樹。這将使本地倉庫看起來已經與遠端倉庫進行了同步更新,但實際上本地倉庫并沒有進行更新,并欺騙</code>--force-with-lease` 指令,成功覆寫遠端分支,就像下面這個例子:

這個問題的最簡單的答案就是,簡單的說“不要在沒有合并的情況下 <code>fetch</code> 遠端該分支”(或者更常用的方法是 <code>pull</code>,這個操作包含了前面的兩個),但是如果由于某種原因你希望在用 <code>--force-with-lease</code> 進行代碼上傳之前進行 <code>fetch</code>,那麼這有一種比較安全的方法。像 git 那麼多的屬性一樣,引用隻是對象的指針,是以我們可以建立我們自己的引用。在這種情況下,我們可以在進行 <code>fetch</code> 之前,為遠端倉庫引用建立“儲存點”的副本。然後,我們可以告訴 <code>--force-with-lease</code> 将此作為引用值,而不是已經更新的遠端引用。

為了做到這一點,我們使用 git 的 <code>update-ref</code> 功能來建立一個新的引用,以儲存遠端倉庫在任何 <code>rebase</code> 或 <code>fetch</code> 操作前的狀态。這有效地标記了我們開始強制 <code>push</code> 到遠端的工作節點。在這裡,我們将遠端分支 <code>dev</code> 的狀态儲存到一個名為 <code>dev-pre-rebase</code> 的新引用中:

這時呢,我們就可以進行 <code>rebase</code> 和 <code>fetch</code> 操作,然後使用儲存的 <code>ref</code> 來保護遠端倉庫,以防有人在工作時做了更改:

我們可以看到 <code>--force-with-lease</code> 對于有時需要進行強制推送的 git 使用者來說,是一個很有用的工具。但是,對于 <code>--force</code> 操作的所有風險來說,這并不是萬能的,如果不了解它内部的工作及其注意事項,就不應該使用它。

但是,在最常見的用例中,開發人員隻要按照正常的方式進行 <code>pull</code> 和 <code>push</code> 操作即可。偶爾使用下 <code>rebase</code>,這個指令提供了一些我們非常需要的,防止強制推送帶來破壞的保護功能。是以,我希望在未來版本的 git(但可能 3.0 以前都不會實作),它将成為 <code>--force</code> 的預設行為,并且目前的行為将被降級到顯示其實際行為的選項中,例如:<code>--force-replace-remote</code>。

<b>原文釋出時間為:2017年8月05日</b>

<b>本文來自雲栖社群合作夥伴掘金,了解相關資訊可以關注掘金網站。</b>