全部產品
Search
文件中心

Alibaba Cloud DevOps:部分複製(Partial clone)介紹

更新時間:Apr 19, 2025

本文主要介紹了什麼是部分複製、使用情境,以及如何使用部分複製。進而闡述了提升大倉庫體驗的其他方案-Git LFS。

部分複製功能簡介

什麼是部分複製?

眾所周知,Git是一個分布式的版本控制系統,當在預設情況(例如不帶任何參數情況下使用git clone命令)下複製倉庫時,Git會自動下載倉庫所有檔案的所有歷史版本。

如此設計一方面帶來了分布式的代碼協同能力, 但在另一方面, 隨著開發人員持續向倉庫中提交代碼,倉庫的體積會不可避免的變得越來越大, 因為遠端倉庫體積迅速膨脹, 帶來clone後本地後磁碟空間的迅速增長以及clone耗時的不斷增加。Git的部分複製(partial-clone)特性可以最佳化和解決這些問題。目前,部分複製已經在阿里雲Codeup上線, 使用者可以試用該功能體驗新特性帶來的研發效率的提升

部分複製允許您在複製代碼倉庫時,通過添加--filter選項來配置需要過濾的對象,從而達到按需下載的目的,這種按需下載大大減少了傳輸的資料量和過程的耗時,同時還可以降低本地磁碟空間的佔用。在後續工作中需要用到這些缺失檔案的時候,Git會自動地按需下載這些檔案,在不必進行任何額外配置的情況下,使用者仍然可以正常開展工作。

部分複製的適用情境

在許多情境下,都可以用部分複製來提升您的效率,以幾個典型情境為例:

大倉庫

當倉庫體積較大時,就可以考慮使用部分複製來提升開發過程中的效率及體驗。例如,Linux的核心,目前有100萬以上的提交,整個倉庫包含了830萬+的對象,體積約在3.3GB左右。

要全量複製這樣一個倉庫,複製速度以2MB/s 來算,需要約26分鐘的時間。在網路條件不佳的情況下,複製可能還會耗費更久的時間。

$ git clone --mirror gi*@codeup.aliyun.com:6125fa3a03f23adfbed12b8f/linux.git linux
複製到純倉庫 'linux'...
remote: Enumerating objects: 8345032, done.
remote: Counting objects: 100% (8345032/8345032), done.
remote: Total 8345032 (delta 6933809), reused 8345032 (delta 6933809), pack-reused 0
接收對象中: 100% (8345032/8345032), 3.26 GiB | 2.08 MiB/s, 完成.
處理 delta 中: 100% (6933809/6933809), 完成.

下面是開啟部分複製blob:none選項,使用部分複製後的效果:

$ git clone --filter=blob:none  --no-checkout git@codeup.aliyun.com:6125fa3a03f23adfbed12b8f/linux.git
正複製到 'linux'...
remote: Enumerating objects: 6027574, done.
remote: Counting objects: 100% (6027574/6027574), done.
remote: Total 6027574 (delta 4841929), reused 6027574 (delta 4841929), pack-reused 0
接收對象中: 100% (6027574/6027574), 1.13 GiB | 2.71 MiB/s, 完成.
處理 delta 中: 100% (4841929/4841929), 完成.

可以看到,使用了blob:none選項後,需要下載的對象由834萬左右減少至602萬左右,需要下載的資料量更是由3.26GB下降到了1.13GB,還是以2MB/s速度來計算,部分複製時間僅需9分鐘左右,與原來的全量複製相比,時間僅為原來的三分之一左右。

如果使用treeless模式的部分複製,需要下載的對象、耗費的時間還將進一步減少。但是,treeless模式的複製在開發情境下會更加頻繁地觸發缺失對象的下載,不推薦使用。使用部分複製,花費了更少的時間,複製了更少的對象,帶來的最佳化是顯著的。

微服務單根代碼倉

近年來,越來越多專案選擇了使用微服務的架構,將大單體服務拆分為若干個內聚化的微型服務,每一個服務由一個微型團隊進行維護,團隊間開發可以並行、互不干擾,團隊間協同複雜度大幅降低。但是,這也將帶來公用代碼更難重用、不同倉庫之間依賴混亂、團隊之間流程規範難以協同等問題。

因此,微服務單根代碼倉模式被提出,在這種模式下,子服務使用Git來進行管理,並且由一個根倉庫來統一管理所有的服務:

使用單根代碼倉,公用代碼更易於共用,專案文檔、流程規範可以集中於一處,也更加易於實施持續整合。但是,這種模式也有缺點,對於一名開發人員來說,即使他只關注專案中某一部分,他也不得不複製整個倉庫。

部分複製配合稀疏檢出特性,可以解決這一問題,可以首先啟用部分複製,並指定--no-checkout選項來指定複製完成後不執行自動檢出,避免檢出時自動下載當前分支下的所有檔案。之後,再通過稀疏檢出功能,只按需下載並檢出指定目錄下的檔案。

例如,建立了一個專案,具有如下結構:

monorepo
├── README
├── backend
│   └── command
│       └── command.go
├── docs
│   └── api_specification
├── frontend
│   ├── README.md
│   └── src
│       └── main.js
└── libraries
    └── common.lib

現在,作為一名後端開發人員,我只關心backend下的代碼,並且也不想花費時間下載其他目錄下的代碼,那麼就可以執行:

$ git clone --filter=blob:none  --no-checkout https://codeup.aliyun.com/61234c2d1bd96aa110f27b9c/monorepo.git
正複製到 'monorepo'...
remote: Enumerating objects: 24, done.
remote: Counting objects: 100% (24/24), done.
remote: Total 24 (delta 0), reused 0 (delta 0), pack-reused 0
接收對象中: 100% (24/24), 2.62 KiB | 2.62 MiB/s, 完成.

然後,進入該專案,開啟稀疏檢出,並配置為只下載backend下的檔案:

$ cd monorepo
$ git config core.sparsecheckout true
$ echo "backend/*" > .git/info/sparse-checkout

最後執行git checkout,並執行tree命令觀察目錄結構。可以看到,只有backend目錄下的檔案被下載了。

$ tree .
.
└── backend
    └── command
        └── command.go

2 directories, 1 file

應用構建

在構建情境下,構建伺服器首先需要從Git倉庫擷取代碼,並執行構建,最後發布應用。在構建的過程中,並不需要倉庫中的歷史代碼,而是根據代碼的最新版本來構建應用。此時,可以用部分複製的tree:0選項,最大程度減少需要下載的對象數量。

對於構建情境來說,還可以使用Git淺複製特性,進一步的過濾歷史的commit對象。關於Git淺複製,請參考git-clone

image

部分複製使用及原理簡介

Git底層物件類型簡介

在使用部分複製前,還需要瞭解一些Git的底層儲存原理,以便更好地理解各個選項的含義,主要涉及blob對象,tree對象,以及commit對象。

下圖以Git底層對象的形式,展示了一個Git倉庫的結構。其中:

  • 圓形,代表了commit對象,commit對象用於儲存提交資訊,並指向了父commit(如果存在)以及根tree對象。通過commit對象,我們可以回溯代碼的歷史版本。

  • 三角形,代表一個tree對象,tree對象用於隱藏檔名及目錄結構資訊,並且指向blob對象或其他tree對象,由此組成嵌套的目錄結構。

  • 方塊,代表了blob對象,儲存了檔案的實際內容。

3

部分複製使用限制

  • 用戶端限制:本地的Git版本在2.22.0或更高。

  • 服務端filter限制:目前,Codeup支援指定兩種--filter

    • Blobless複製:--filter=blob:none

    • Treeless複製:--filter=tree:<depth>

在本地用戶端支援的情況下,就可以使用部分複製來提升研發效率了。

如何使用部分複製

要使用部分複製,有如下幾種方式:

  • 使用 git clone 命令啟用部分複製:

    git clone --filter=blob:none <倉庫地址>
  • 使用 git fetch 命令啟用部分複製

    git init .
    git remote add origin <倉庫地址>
    git fetch --filter=blob:none origin
    git switch master
  • 使用 git config 設定倉庫啟用部分複製

    git init .
    git remote add origin <倉庫地址>
    git config remote.origin.promisor true
    git config remote.origin.partialclonefilter blob:none
    git fetch origin
    git switch master

這三種方式達到效果是一樣的,您可以自行選擇喜歡的方式。下面,我們用git clone的方式來分別介紹blobless複製和treeless複製的使用以及基本原理。

Blobless複製

在複製時使用--filter=blob:none選項,即可開啟blobless模式複製。在這種情況下,倉庫中歷史committree會被下載,blob則不會。下面用一個例子來更好地說明使用此選項複製時倉庫的結構。

首先,建立一個測試倉庫。

  • 在第一個提交中,建立檔案hello.txt,內容為hello world!。

  • 在第二個提交中,建立檔案src/hello.go,內容為列印"hello world"。

  • 在第三個提交中,修改檔案src/hello.go,修改輸出內容為“hello codeup"。

整個倉庫結構看起來像是這樣:

1

然後,執行以下命令,來執行一次blobless模式的部分複製

git clone --filter=blob:none \
https://codeup.aliyun.com/61234c2d1bd96aa110f27b9c/partial-clone-tutorial.git

複製完成後,進入此倉庫,並執行git rev-list命令來檢視倉庫中的對象,得到輸出為:

$ git rev-list --missing=print --objects HEAD
18990720b6e55a70ba9f9877213dad948e0973a2
e18cc4e7890e6ec832f683c1a0f58412b4a37964
2f7478bda13e73e1e1eaab6fae3d0dfd35e50b32
e7c719df0874ebd3b2ec02666d65879e986d537d
a0423896973644771497bdc03eb99d5281615b51 hello.txt
98a390b9c8b5ba25e9444c8b5a487634795d7c72 src
02a9d16faa87c68bd6fc2af27cbe3714e53af272 src/hello.go
b7458566de2bf5e1011142ef5fe81ccaa4c9e73e
3f2157b609fb05814ba0a45cf40a452640e663c3 src
6009101760644963fee389fc730acc4c437edc8f
?f2482c1f31b320e28f0dea5c4e7c8263a0df8fec

注意最後一行ID,第一個字元是問號,這也就意味著這個對象在本地其實是不存在的。為什麼會出現這種情況呢?其實這正是部分複製所要達到的效果。

下面來看看f2482c1f31b320e28f0dea5c4e7c8263a0df8fec這個對象是什麼,執行:

$ git cat-file -p f2482c1f31b320e28f0dea5c4e7c8263a0df8fec
remote: Enumerating objects: 1, done.
remote: Counting objects: 100% (1/1), done.
remote: Total 1 (delta 0), reused 0 (delta 0), pack-reused 0
接收對象中: 100% (1/1), 109 位元組 | 109.00 KiB/s, 完成.
package main
import "fmt"
func main() {
    fmt.Println("hello world")
}

第五行,接收對象意味著這個對象實際上是剛被下載下來的,其中內容為fmt.Println("hello world"),也就是第二個提交的版本。

通過以上分析,可以知道,使用部分複製blob:none選項複製倉庫後,只有第二個提交中的hello.go檔案不存在。

將圖形上色,空心代表對象不存在,實心代表對象存在,那麼倉庫結構可以表示成這個樣子:

1

可以看到,倉庫中歷史committree對象都存在,歷史blob對象則不存在。把這個觀察推廣到更複雜一些的倉庫,就可以總結出以blobless模式複製倉庫的一般形式:

1

需要注意的是,在當前HEAD分支下,所有的treeblob對象都存在,這是由於在複製之後自動執行了一次檢出。在此基礎之上,可以修改,提交代碼,展開工作。對於歷史提交來說,committree對象都存在,僅有blob對象未被下載。通過不下載這些歷史blob對象,達到了節省複製時間,節省磁碟佔用空間的目的。

如果此時檢出歷史提交,那麼Git用戶端會自動地批量下載這些缺失的blob對象。此外,當需要使用到檔案內容時,就會觸發blob下載,當只需要檔案的OID時,就不需要了。這也就意味著可以運行git merge-basegit log等命令,效能與完全複製模式相同。

Treeless複製

在複製時使用--filter=tree:<depth>選項,就開啟了無tree複製,其中depth是一個數字,代表了從commit對象開始的深度。在這種模式下,只有給定深度內的tree以及blob對象會被下載。

回到測試倉庫,這次利用--filter=tree:0來啟用treeless複製,並用rev-list來檢視本機物件:

$ git clone --filter=tree:0 \
https://codeup.aliyun.com/61234c2d1bd96aa110f27b9c/partial-clone-tutorial.git
$ cd partial-clone-tutorial

$ git rev-list --missing=print --objects HEAD
18990720b6e55a70ba9f9877213dad948e0973a2
e18cc4e7890e6ec832f683c1a0f58412b4a37964
2f7478bda13e73e1e1eaab6fae3d0dfd35e50b32
e7c719df0874ebd3b2ec02666d65879e986d537d
a0423896973644771497bdc03eb99d5281615b51 hello.txt
98a390b9c8b5ba25e9444c8b5a487634795d7c72 src
02a9d16faa87c68bd6fc2af27cbe3714e53af272 src/hello.go
?b7458566de2bf5e1011142ef5fe81ccaa4c9e73e
?6009101760644963fee389fc730acc4c437edc8f

現在問號出現在兩個對象ID前,來看看這些對象是什麼:

$ git cat-file -p HEAD^^
tree 6009101760644963fee389fc730acc4c437edc8f
author yunhuai.xzy <yunhuai.***@alibaba-inc.com> 1631697940 +0800
committer yunhuai.xzy <yunhuai.***@alibaba-inc.com> 1631697940 +0800

first commit
$ git cat-file -p HEAD^
tree b7458566de2bf5e1011142ef5fe81ccaa4c9e73e
parent 2f7478bda13e73e1e1eaab6fae3d0dfd35e50b32
author yunhuai.xzy <yunhuai.***@alibaba-inc.com> 1631698032 +0800
committer yunhuai.xzy <yunhuai.***@alibaba-inc.com> 1631698032 +0800

add hello.go

注意其中第二行,可以發現b74585b74585這兩個對象,正好是第一第二個提交所指向的根樹。畫出倉庫結構如下:

1

推廣到更一般的Git倉庫,則如下圖所示:

1

可以看到,擁有所有提交資訊,以及在HEAD分支下的所有對象(還是由於自動檢出),但不包含任何歷史提交中的treeblob

與blobless模式相比,treeless模式需要下載的對象更少了,複製時間會更短,磁碟佔用空間也會更少。但是在後續工作中,treeless模式的複製會更加頻繁的觸發資料的下載,並且代價也更為昂貴。例如,Git用戶端會向服務端請求一顆樹及其所有的子樹,在這個過程中,用戶端不會告訴服務端本地已有一些樹,服務端不得不把所有的樹都發送給用戶端,然後用戶端可以對缺失的blob對象發起批量請求。

為了更好理解treeless複製,下圖是一個使用了--filter=tree:1選項的例子。可以看到,深度為1的tree對象也被下載了,更深的tree或者blob對象則沒有。

1

在日常開發過程中,不建議使用treeless模式的複製,treeless複製更加適用於自動構建的情境,快速的複製倉庫,構建,然後刪除。

部分複製的其他選項

部分複製還可以使用其他選項,完整選項請參考https://git-scm.com/docs/git-rev-list中的--filter=<filter-spec>節,正在逐步支援其他選項。

部分複製的效能陷阱

部分複製通過只下載部分資料方式,在首次複製時減輕了需要傳輸的資料量,降低了複製需要的時間。但是,在後續過程中如果需要使用到這些歷史資料,就會觸發對象的按需下載。根據執行的命令不同,效能可能好也可能壞。

命令

效能

說明

  • git checkout

  • git clone

  • git switch

  • git archive

  • git merge

  • git reset

這些命令支援批量下載缺失的對象,因此效能很好。

  • git log --stat

  • git cat-file

  • git diff

  • git blame

不好

這些命令會逐一下載需要的對象,效能很差。

另外,在執行git rev-parse --verify "<object-id>^{object}"命令校正某個對象是否存在的時候,如果該對象在倉庫中不存在,會執行git fetch對該對象按需擷取。如果這個缺失對象是一個提交對象,則擷取過程會將該提交關聯的所有歷史提交、樹對象等都重新下載,即使很多歷史提交已經在倉庫中了。這是因為部分倉庫按需擷取過程中執行git fetch命令使用了-c fetch.negotiationAlgorithm=noop參數,沒有在用戶端和伺服器之間進行提交資訊的協商。

如何避免效能問題

git clonegit checkoutgit switchgit archivegit mergegit reset等命令支援對缺失對象的批量下載,因此效能很好。

其他不支援批量下載的命令可以用如下方式最佳化:

  • 找到需要訪問且在倉庫中缺失的對象。

  • 啟動git fetch進程,通過標準輸入傳遞缺失對象列表,批量下載缺失對象。

那麼如何尋找缺失對象呢?可以使用git rev-list命令,通過參數--missing=print顯示缺失對象,缺失對象在列印時會以問號開頭。

如下命令顯示v1.0.0v2.0.0之間缺失的對象:

git rev-list --objects --missing=print v1.0.0..v2.0.0 | grep "^?"

將擷取到的缺失對象列表通過管道傳遞給下面的git fetch進程,實現缺失對象的批量擷取:

 git -c fetch.negotiationAlgorithm=noop \
     fetch origin \
     --no-tags \
     --no-write-fetch-head \
     --recurse-submodules=no \
     --filter=blob:none \
     --stdin

提升大倉庫體驗的其他方案

Git LFS(大檔案儲存體)

除了部分複製,Codeup 同時提供 Git LFS 大檔案儲存體能力。關於Git LFS,請參考Git-LFS 大檔案儲存體

部分複製與Git LFS的異同

方案

目標

應用情境

實現機制

優點

缺點

部分複製

主要解決由於倉庫歷史較長或檔案數量龐大導致的倉庫體積增大問題。

適用於倉庫歷史較長或檔案數量較多的情況,特別是只需要代碼最新版本的情境。

通過在複製倉庫時設定過濾器選項,只下載特定的歷史對象或檔案,減少複製時的傳輸串流量和本地磁碟佔用。

  • 減少複製時的傳輸串流量。

  • 縮短複製倉庫的時間。

  • 減輕倉庫對本地磁碟空間的佔用。

可能會導致某些記錄不完整,影響回溯和調試。

Git LFS

主要解決向Git倉庫中提交大量二進位檔案導致的倉庫體積膨脹問題。

適用於需要管理大量二進位檔案(如圖片、視頻、音頻、設計資源等)的倉庫。

通過將大檔案替換為指標檔案儲存體在倉庫中,實際的大檔案則儲存在第三方伺服器上。在檢出分支時,指標檔案會被替換為實際檔案。

  • 有效管理大檔案,避免倉庫體積迅速膨脹。

  • 提高複製速度和效率。

  • 保持倉庫的整潔和可維護性。

  • 需要額外的配置和管理。

  • 依賴第三方伺服器,可能增加成本。

部分複製與大檔案儲存體也不是割裂的,可以結合使用,以達到更好的代碼協同體驗。例如,使用部分複製來減少歷史對象的下載,同時使用Git LFS來管理大檔案。Codeup對這兩個特性都進行了支援,您可以根據實際情況合理選擇和結合使用部分複製和Git LFS,以有效最佳化大型倉庫的管理和使用體驗。

參考資料