[譯] 使用 `-force` 被認爲是有害的;瞭解 Git 的 `-force-with-lease` 命令

Git 的 push --force 具備破壞性,由於它無條件地覆蓋遠程存儲庫,不管你在本地擁有什麼。使用這個命令,可能覆蓋團隊成員在此期間推送的全部更改。然而,有一個更好的辦法,當你須要強制推送,但仍需確保不覆蓋其餘人的工做時,-force-with-lease 這條指令選項能夠幫助到你。前端

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

衆所周知,git 的 push -force 指令是不推薦被使用的,由於它會破壞其餘已經提交到共享庫的內容。雖然這不老是徹底致命的(若是那些修改的內容仍在某些同事的本地工做域中,那以後他們能被從新合併),可是這樣的作法很欠考慮,最糟糕的狀況會形成災難性的損失。這是由於 --force 指令選項迫使分支的頭指針指向你我的的修改記錄,而忽略了那些其餘和你同時進行地更改。react

強制推進最多見的緣由之一是當咱們被迫 rebase 一個分支的時候。爲了說明這一點,咱們來看一個例子。咱們有一個項目,其中有一個功能分支,Alice 和 Bob 要同時在這個分支上工做。他們都 git clone... 了這個倉庫,並開始工做。android

最初,Alice 完成了她負責的功能,並將其 push 到主倉庫。這都沒啥問題。ios

Bob 也完成了他的工做,但在 push 以前,他注意到一些變化已被合併到了 master 分支。想要保持一棵整潔的工做樹,他會對主分支執行一個 rebase。固然,當他 push 這個通過 rebase 的分支的時候將被拒絕。然而,Bob 沒有意識到 Alice 已經 push 了她的工做。Bob 執行了 push --force 命令。不幸的是,這將清除 Alice 在遠程主倉庫的全部更改和記錄。git

這裏的問題是,進行強制推送的 Bob 不知道爲何他的 push 會被拒絕,因此他認爲這是 rebase 形成的,而不是因爲 Alice 的變化。這就是爲何 --force 在同一個分支上協做的時候要杜絕的;而且經過遠程主倉庫的工做流程,任何分支均可以被共享。github

可是 --force 有一個不爲衆人所知的親戚,它在必定程度上能防止強制更新操做帶來的結構性破壞;它就是 --force-with-lease後端

--force-with-lease 是用於拒絕更新一個分支,除非該分支達到咱們指望的狀態。即沒有人在上游更新分支內容。 實際上,經過檢查上游引用是咱們所指望的,由於引用是散列,並將父系鏈隱含地編碼成它們的值。安全

你能夠告訴 --force-with-lease 究竟要檢查什麼,默認狀況下會檢查當前的遠程引用。這在實踐中意味着,當 Alice 更新她的分支並將其推送到遠程倉庫時,分支的引用指針將被更新。如今,除非 Bob從遠程倉庫 pull 一下,不然本地對遠程倉庫的引用將過時。當他使用 --force-with-lease 推送時,git 會檢查本地與遠程的引用是否對應,並拒絕 Bob 的強制推送。--force-with-lease 有效地只在沒有人在上游更新分支內容的時候容許你強制推送。就像是一個帶有安全帶的 --force。它的一個快速演示可能有助於說明這一點:bash

Alice 已經對該分支進行了一些更改,並已推送到了遠程主倉庫。Bob 如今又對遠程倉庫的 master 分支進行了 rebases 操做:服務器

ssmith$ git rebase master
First, rewinding head to replay your work on top of it...
Applying: Dev commit #1
Applying: Dev commit #2
Applying: Dev commit #3複製代碼

rebase 以後,他試圖將本身的更改 push 上去,但服務器拒絕了,由於這會覆蓋 Alice 的工做:

ssmith$ git push
To /tmp/repo
 ! [rejected]        dev -> dev (fetch first)
error: failed to push some refs to '/tmp/repo'
hint: Updates were rejected because the remote contains work that you do
hint: not have locally. This is usually caused by another repository pushing
hint: to the same ref. You may want to first integrate the remote changes
hint: (e.g., 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.複製代碼

但 Bob 認爲這是 rebase 操做形成的,並決定強制 push

ssmith$ git push --force
To /tmp/repo
 + f82f59e...c27aff1 dev -> dev (forced update)複製代碼

然而,若是他使用了 --force-with-lease,則會獲得不一樣的結果,由於 git 會檢查遠程分支,發現 從上一次 Bob 使用 fetch 到如今,實際上並無被更新:

ssmith$ git push -n --force-with-lease
To /tmp/repo
 ! [rejected]        dev -> dev (stale info)
error: failed to push some refs to '/tmp/repo'複製代碼

固然,在這有一些關於 git 的注意事項。上面展現的,只有當 Alice 已經將其更改推送到遠程存儲庫時,它纔有效。這不是一個嚴重的問題,可是若是她想修改她提交的東西,那她去 pull 分支時,會被提示合併被更改。

一個更微妙的問題是,咱們有方法去騙 git,讓 git 認爲這個分支沒有被修改。在正常使用狀況下,最常發生這種現象的狀況是,Bob 使用 git fetch 而不是git pull`來更新他的本地副本。fetch將從遠程倉庫拉出對象和引用,但沒有匹配的merge則不會更新工做樹。這將使本地倉庫看起來已經與遠程倉庫進行了同步更新,但實際上本地倉庫並無進行更新,並欺騙--force-with-lease` 命令,成功覆蓋遠程分支,就像下面這個例子:

ssmith$ git push --force-with-lease
To /tmp/repo
 ! [rejected]        dev -> dev (stale info)
error: failed to push some refs to '/tmp/repo'

ssmith$ git fetch
remote: Counting objects: 3, done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 3 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
From /tmp/repo
   1a3a03f..d7cda55  dev        -> origin/dev

ssmith$ git push --force-with-lease
Counting objects: 9, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (6/6), done.
Writing objects: 100% (9/9), 845 bytes | 0 bytes/s, done.
Total 9 (delta 0), reused 0 (delta 0)
To /tmp/repo
   d7cda55..b57fc84  dev -> dev複製代碼

這個問題的最簡單的答案就是,簡單的說「不要在沒有合併的狀況下 fetch 遠程該分支」(或者更經常使用的方法是 pull,這個操做包含了前面的兩個),可是若是因爲某種緣由你但願在用 --force-with-lease 進行代碼上傳以前進行 fetch,那麼這有一種比較安全的方法。像 git 那麼多的屬性同樣,引用只是對象的指針,因此咱們能夠建立咱們本身的引用。在這種狀況下,咱們能夠在進行 fetch 以前,爲遠程倉庫引用建立「保存點」的副本。而後,咱們能夠告訴 --force-with-lease 將此做爲引用值,而不是已經更新的遠程引用。

爲了作到這一點,咱們使用 git 的 update-ref 功能來建立一個新的引用,以保存遠程倉庫在任何 rebasefetch 操做前的狀態。這有效地標記了咱們開始強制 push 到遠程的工做節點。在這裏,咱們將遠程分支 dev 的狀態保存到一個名爲 dev-pre-rebase 的新引用中:

ssmith$ git update-ref refs/dev-pre-rebase refs/remotes/origin/dev複製代碼

這時呢,咱們就能夠進行 rebasefetch 操做,而後使用保存的 ref 來保護遠程倉庫,以防有人在工做時作了更改:

ssmith$ git rebase master
First, rewinding head to replay your work on top of it...
Applying: Dev commit #1
Applying: Dev commit #2
Applying: Dev commit #3

ssmith$ git fetch
remote: Counting objects: 3, done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 3 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
From /tmp/repo
   2203121..a9a35b3  dev        -> origin/dev

ssmith$ git push --force-with-lease=dev:refs/dev-pre-rebase
To /tmp/repo
 ! [rejected]        dev -> dev (stale info)
error: failed to push some refs to '/tmp/repo'複製代碼

咱們能夠看到 --force-with-lease 對於有時須要進行強制推送的 git 用戶來講,是一個頗有用的工具。可是,對於 --force 操做的全部風險來講,這並非萬能的,若是不瞭解它內部的工做及其注意事項,就不該該使用它。

可是,在最多見的用例中,開發人員只要按照正常的方式進行 pullpush 操做便可。偶爾使用下 rebase,這個命令提供了一些咱們很是須要的,防止強制推送帶來破壞的保護功能。所以,我但願在將來版本的 git(但可能 3.0 之前都不會實現),它將成爲 --force 的默認行爲,而且當前的行爲將被降級到顯示其實際行爲的選項中,例如:--force-replace-remote


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

相關文章
相關標籤/搜索