簡體中文 ▾ 主題 ▾ 最新版本 ▾ partial-clone 上次更新於 2.49.0

"部分克隆"功能是Git的一種效能最佳化,它允許Git在沒有完整的倉庫副本的情況下執行。這項工作的目標是讓Git能夠更好地處理極其龐大的倉庫。

在克隆和抓取操作期間,Git會下載倉庫的完整內容和歷史記錄。這包括倉庫整個生命週期中的所有提交、樹和二進位制大物件(blobs)。對於極其龐大的倉庫,克隆可能需要數小時(甚至數天),並佔用100+GiB的磁碟空間。

這些倉庫中通常有許多使用者不需要的二進位制大物件和樹,例如:

  1. 樹中位於使用者工作區之外的檔案。例如,在一個每個提交有50萬個目錄和350萬個檔案的倉庫中,如果使用者只需要源樹的一個狹窄的“錐形”,我們可以避免下載許多物件。

  2. 大型二進位制資產。例如,在一個將大型構建產物檢入樹中的倉庫中,我們可以避免下載這些不可合併的二進位制資產的所有先前版本,而只下載實際引用的版本。

部分克隆允許我們在克隆和抓取操作期間提前避免下載這些不需要的物件,從而減少下載時間和磁碟使用。缺失的物件可以在需要時進行“按需抓取”。

可以稍後提供缺失物件的遠端倉庫被稱為“承諾遠端倉庫”(promisor remote),因為它承諾在請求時傳送物件。最初Git只支援一個承諾遠端倉庫,即使用者從中克隆的源遠端倉庫,它在"extensions.partialClone"配置選項中配置。後來實現了對多個承諾遠端倉庫的支援。

使用部分克隆要求使用者線上,並且源遠端倉庫或其他承諾遠端倉庫可用於按需抓取缺失物件。這對於使用者來說可能存在問題,也可能沒有問題。例如,如果使用者可以停留在源樹預選的子集中,他們可能不會遇到任何缺失物件。或者,如果使用者知道自己將離線,他們可以嘗試預先抓取各種物件。

非目標

部分克隆是一種限制給定提交範圍內下載的二進位制大物件和樹數量的機制——因此它獨立於現有的DAG級別機制,不打算與它們衝突,以限制請求的提交集(即淺克隆、單分支或抓取 <refspec>)。

設計概述

部分克隆在邏輯上由以下部分組成:

  • 客戶端向伺服器描述不需要或不想要的物件的一種機制。

  • 伺服器從傳送給客戶端的 packfile 中省略這些不想要的物件的一種機制。

  • 客戶端優雅地處理缺失物件(先前已被伺服器省略)的一種機制。

  • 客戶端根據需要回填缺失物件的一種機制。

設計細節

  • 在 fetch-pack 和 upload-pack 協商中添加了一個新的 pack-protocol 能力“filter”。

    這使用了現有的能力發現機制。請參閱 gitprotocol-pack[5] 中的“filter”部分。

  • 客戶端將“filter-spec”傳遞給克隆和抓取操作,該“filter-spec”再傳遞給伺服器,以請求在 packfile 構建期間進行過濾。

    有各種過濾器可用於適應不同的情況。請參閱 Documentation/rev-list-options.adoc 中的“--filter=<filter-spec>”部分。

  • 在伺服器端,pack-objects 在為客戶端建立“過濾”packfile 時應用請求的 filter-spec。

    這些過濾後的 packfile 在傳統意義上是不完整的,因為它們可能包含引用了 packfile 中未包含且客戶端尚未擁有的物件的物件。例如,過濾後的 packfile 可能包含引用缺失的二進位制大物件(blobs)的樹或標籤,或者引用缺失的樹的提交。

  • 在客戶端,這些不完整的 packfile 被標記為“承諾 packfile”(promisor packfiles),並由各種命令以不同方式處理。

  • 在客戶端,本地配置中添加了一個倉庫擴充套件,以防止舊版 Git 因無法處理的缺失物件而在操作中失敗。請參閱 git-config[1] 中的 extensions.partialClone

處理缺失物件

  • 物件可能因部分克隆或抓取而缺失,也可能因倉庫損壞而缺失。為了區分這些情況,本地倉庫特意將從承諾遠端倉庫獲取的此類過濾 packfile 標記為“承諾 packfile”。

    這些承諾 packfile 除了其“<name>.pack”和“<name>.idx”檔案外,還包含一個“<name>.promisor”檔案,其中包含任意內容(類似於“<name>.keep”檔案)。

  • 本地倉庫將“承諾物件”視為它知道(盡其所能)承諾遠端倉庫已承諾擁有的物件,這可能是因為本地倉庫在其中一個承諾 packfile 中擁有該物件,或者因為另一個承諾物件引用了它。

    當 Git 遇到缺失物件時,Git 可以檢視它是否是承諾物件並進行適當處理。如果不是,Git 可以報告損壞。

    這意味著客戶端不需要明確維護一個修改成本高昂的缺失物件列表。[a]

  • 由於幾乎所有 Git 程式碼目前都期望任何引用的物件都在本地存在,並且我們不想強制每個命令先進行一次試執行,因此添加了一個回退機制,允許 Git 嘗試從承諾遠端倉庫動態抓取缺失物件。

    當正常的查詢物件失敗時,Git 呼叫 promisor_remote_get_direct() 嘗試從承諾遠端倉庫獲取物件,然後重試查詢物件。這允許在沒有複雜預測演算法的情況下“載入”物件。

    出於效率考慮,不檢查缺失物件是否實際是承諾物件。

    動態物件抓取通常很慢,因為物件是逐個抓取的。

  • checkout(以及任何其他使用 unpack-trees 的命令)已被修改,以在單個批處理中批次預抓取所有必需的缺失二進位制大物件(blobs)。

  • rev-list 已被修改,以列印缺失物件。

    這可以被其他命令用來批次預抓取物件。例如,“git log -p A..B”可能內部首先想要做類似“git rev-list --objects --quiet --missing=print A..B”的操作,並批次預抓取這些物件。

  • fsck 已更新,完全支援承諾物件。

  • GC 中的 repack 已更新,完全不觸及承諾 packfile,僅重打包其他物件。

  • 全域性變數 "fetch_if_missing" 用於控制物件查詢是否會嘗試動態抓取缺失物件或報告錯誤。

    我們對這個全域性變數不滿意,並希望將其刪除,但這需要對物件程式碼進行大量的重構以傳遞一個額外的標誌。

抓取缺失物件

  • 物件的抓取透過呼叫“git fetch”子程序來完成。

  • 本地倉庫傳送包含所有請求物件雜湊的請求,並且不執行任何 packfile 協商。然後它接收一個 packfile。

  • 因為我們重用了現有的抓取機制,所以目前抓取會抓取所有被請求物件引用的物件,即使它們不是必需的。

  • 使用 --refetch 抓取將從遠端倉庫請求一個完整的新的過濾 packfile,這可以用於更改過濾器而無需動態抓取缺失物件。

使用多個承諾遠端倉庫

可以配置和使用多個承諾遠端倉庫。

例如,這允許使用者擁有多個地理位置相近的快取伺服器來抓取缺失的二進位制大物件(blobs),同時繼續從中心伺服器執行過濾後的 git-fetch 命令。

抓取物件時,承諾遠端倉庫會一個接一個地嘗試,直到所有物件都被抓取完畢。

被認為是“承諾”遠端倉庫的是由以下配置變數指定的倉庫:

  • extensions.partialClone = <name>

  • remote.<name>.promisor = true

  • remote.<name>.partialCloneFilter = ...

使用 extensions.partialClone 配置變數只能配置一個承諾遠端倉庫。在抓取物件時,此承諾遠端倉庫將是最後一個嘗試的。

我們決定將其設為最後嘗試的一個,因為使用多個承諾遠端倉庫的人很可能是因為其他承諾遠端倉庫在某種程度上(也許它們對於某些型別的物件更近或更快)比源遠端倉庫更好,而源遠端倉庫很可能是由 extensions.partialClone 指定的遠端倉庫。

這個理由不是很充分,但必須做出一個選擇,而且長期的計劃應該是讓順序完全可配置。

但目前,其他承諾遠端倉庫將按照它們在配置檔案中出現的順序進行嘗試。

當前限制

  • 除了它們在配置檔案中出現的順序之外,無法以其他方式指定嘗試承諾遠端倉庫的順序。

    當從一個遠端倉庫抓取時,也無法指定要使用的順序;當從另一個遠端倉庫抓取時,也無法指定不同的順序。

  • 無法僅將特定物件推送到承諾遠端倉庫。

    無法以特定順序同時推送到多個承諾遠端倉庫。

  • 動態物件抓取將只向承諾遠端倉庫請求缺失物件。我們假設承諾遠端倉庫對倉庫有完整的檢視,並且可以滿足所有此類請求。

  • 重新打包(Repack)基本上將承諾 packfile 和非承諾 packfile 視為兩個不同的分割槽,並且不混合它們。

  • 動態物件抓取會為每個專案呼叫一次 fetch-pack,因為大多數演算法在遇到缺失物件時會中斷,需要解決該物件才能繼續工作。如果需要許多物件,這可能會導致顯著的開銷——以及多次身份驗證請求。

  • 動態物件抓取目前使用現有的 pack 協議 V0,這意味著每個物件都透過 fetch-pack 請求。當連線建立時,伺服器將傳送一組完整的 info/refs。如果 ref 數量龐大,這可能會導致顯著的開銷。

未來工作

  • 改進指定嘗試承諾遠端倉庫順序的方式。

    例如,這可以允許明確指定類似:“從這個遠端倉庫抓取時,我希望按這個順序使用這些承諾遠端倉庫;然而,當推送到或抓取到那個遠端倉庫時,我希望按那個順序使用那些承諾遠端倉庫。”

  • 允許推送到承諾遠端倉庫。

    使用者可能希望採用三角工作流,使用多個承諾遠端倉庫,每個遠端倉庫對倉庫都有不完整的檢視。

  • 允許非基於路徑名的過濾器利用 packfile 點陣圖(如果存在)。這只是初始實現時的遺漏。

  • 研究使用長時間執行的程序動態抓取一系列物件,例如 [5,6] 中提出的方法,以減少程序啟動和開銷成本。

    如果 pack 協議 V2 能夠允許長時間執行的程序透過單個長時間連線進行一系列請求,那將是很好的。

  • 研究 pack 協議 V2,以避免在與伺服器的每次連線上廣播 info/refs 來動態抓取缺失物件。

  • 研究處理鬆散承諾物件的必要性。

    承諾 packfile 中的物件被允許引用可以從伺服器動態抓取的缺失物件。我們曾假設鬆散物件只在本地建立,因此不應引用缺失物件。我們可能需要重新審視這個假設,例如,如果我們動態抓取一個缺失的樹並將其儲存為鬆散物件而非單個物件 packfile。

    這不一定意味著我們需要將鬆散物件標記為承諾物件;放寬物件查詢或 is-promisor 函式可能就足夠了。

非任務

  • 每次提到“按需載入二進位制大物件”時,似乎總有人建議允許伺服器“猜測”併發送可能與請求物件相關的額外物件。

    我們沒有為此做任何實際工作;我們只是記錄這是一個常見的建議。我們不確定它將如何運作,也沒有計劃在這方面開展工作。

    伺服器傳送比請求更多的物件是有效的(即使是動態物件抓取),但我們並未以此為基礎進行構建。

腳註

[a] 修改成本高昂的缺失物件列表:在部分克隆的早期設計中,我們討論了需要一個單一的缺失物件列表。這本質上是一個排序的 OID 線性列表,這些 OID 在克隆或後續抓取期間被伺服器省略了。

該檔案需要在每次物件查詢時載入到記憶體中。它需要被讀取、更新和重寫(像 .git/index 檔案一樣),在每次顯式的“git fetch”命令任何動態物件抓取時都如此。

如果存在大量缺失物件,讀取、更新和寫入此檔案的成本可能會為每個命令增加顯著的開銷。例如,如果存在 1 億個缺失二進位制大物件,此檔案在磁碟上將至少佔用 2GiB。

有了“承諾”概念,我們根據引用它的 packfile 型別推斷缺失物件。

[0] https://crbug.com/git/2 Bug#2: 部分克隆

[1] https://lore.kernel.org/git/20170113155253.1644-1-benpeart@microsoft.com/
主題:[RFC] 新增按需下載二進位制大物件(blobs)的支援
日期:2017年1月13日 星期五 10:52:53 -0500

[2] https://lore.kernel.org/git/cover.1506714999.git.jonathantanmy@google.com/
主題:[PATCH 00/18] 部分克隆(從克隆到懶惰抓取,共18個補丁)
日期:2017年9月29日 星期五 13:11:36 -0700

[3] https://lore.kernel.org/git/20170426221346.25337-1-jonathantanmy@google.com/
主題:Git 倉庫中缺失二進位制大物件支援的提案
日期:2017年4月26日 星期三 15:13:46 -0700

[4] https://lore.kernel.org/git/1488999039-37631-1-git-send-email-git@jeffhostetler.com/
主題:[PATCH 00/10] RFC 部分克隆和抓取
日期:2017年3月8日 星期三 18:50:29 +0000

[5] https://lore.kernel.org/git/20170505152802.6724-1-benpeart@microsoft.com/
主題:[PATCH v7 00/10] 將過濾器處理程式碼重構為一個可重用模組
日期:2017年5月5日 星期五 11:27:52 -0400

[6] https://lore.kernel.org/git/20170714132651.170708-1-benpeart@microsoft.com/
主題:[RFC/PATCH v2 0/1] 新增按需下載二進位制大物件(blobs)的支援
日期:2017年7月14日 星期五 09:26:50 -0400

scroll-to-top