- 原文地址:-force considered harmful; understanding git's --force-with-lease
- 原文做者:Steve Smith
- 譯文出自:掘金翻譯計劃
- 本文永久連接:github.com/xitu/gold-m…
- 譯者:LeviDing
- 校對者:yifili09
Git 的 push --force
具備破壞性,由於它無條件地覆蓋遠程存儲庫,不管你在本地擁有什麼。使用這個命令,可能覆蓋團隊成員在此期間推送的全部更改。然而,有一個更好的辦法,當你須要強制推送,但仍需確保不覆蓋其餘人的工做時,-force-with-lease
這條指令選項能夠幫助到你。前端
衆所周知,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
功能來建立一個新的引用,以保存遠程倉庫在任何 rebase
或 fetch
操做前的狀態。這有效地標記了咱們開始強制 push
到遠程的工做節點。在這裏,咱們將遠程分支 dev
的狀態保存到一個名爲 dev-pre-rebase
的新引用中:
ssmith$ git update-ref refs/dev-pre-rebase refs/remotes/origin/dev複製代碼
這時呢,咱們就能夠進行 rebase
和 fetch
操做,而後使用保存的 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
操做的全部風險來講,這並非萬能的,若是不瞭解它內部的工做及其注意事項,就不該該使用它。
可是,在最多見的用例中,開發人員只要按照正常的方式進行 pull
和 push
操做便可。偶爾使用下 rebase
,這個命令提供了一些咱們很是須要的,防止強制推送帶來破壞的保護功能。所以,我但願在將來版本的 git(但可能 3.0 之前都不會實現),它將成爲 --force
的默認行爲,而且當前的行爲將被降級到顯示其實際行爲的選項中,例如:--force-replace-remote
。
掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 Android、iOS、React、前端、後端、產品、設計 等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃。