The Will Will Web

記載著 Will 在網路世界的學習心得與技術分享

使用 Git 的部分複製功能 (Partial Clones) 快速下載專案原始碼

由於 Git 分散式的特性,所以每個開發者都可以在自己的電腦上建立一個完整的 Git 儲存庫,這樣就可以在沒有網路的情況下進行開發,而且每個開發者都可以在自己的電腦上建立多個分支,這樣就可以在開發新功能時,不會影響到其他開發者的工作。不過,缺點就是在 git clone 的時候會大量的下載資料,尤其是專案很大的時候,這個問題就會特別明顯了!有沒有辦法可以解決呢?可以的,這篇文章我將分享 Git 的 Partial Clone (部分複製) 功能,讓你更有效率的下載專案原始碼!

image

簡介 Partial Clone 功能

在 Git 2.26 版本中,Git 團隊推出了一個新的功能,稱為 Partial Clones,這個功能它允許你只部分地複製一個遠端儲存庫,而不必將整個儲存庫全部下載。這在大型專案或具有龐大歷史記錄的專案中非常有用。

以下是 Partial Clones 的功能與特性介紹:

  1. 部分複製

    Partial Clones 允許你僅下載特定分支、歷史記錄的一部分或特定的檔案,而不需要完整複製整個儲存庫。這有助於節省頻寬和儲存空間。

  2. 增量下載

    當你需要存取更多的專案資訊時,Git 部分複製具有增量下載的能力,它可以在需要時逐步下載額外的資料,而不必一次性下載所有內容。

  3. 快速操作

    因為只下載了部分資料,Git 部分複製可以加快各種操作,例如複製(clone)、拉取(pull/fetch)、查看歷史記錄(Show log)等。這對於大型專案的快速開發和協作非常有利。

  4. 本地快取

    部分複製的儲存庫中包含本地快取,可以更快速地存取和操作頻繁存取的資料。這提高了性能並減少網路請求。

  5. 端到端完整性

    Git 部分複製確保複製的儲存庫具有端到端的完整性,因此你可以執行各種 Git 操作,而不必擔心資料一致性的問題。

  6. 配置彈性

    你可以使用 Git 的配置選項來自定義部分複製的行為,例如指定要下載的深度、分支或資料範圍。

  7. 支援廣泛

    Git 部分複製已廣泛支援,包括 GitHub、GitLab、Azure Repos 和其他主要 Git 儲存庫平台。這意味著你可以在不同的專案中利用這一功能。

部分複製的使用方式

部分複製有許多明確的應用情境,以下我整理幾種常見的使用方式:

  1. 不想下載完整的 Repo 回來,尤其是超大的 Blob 物件能不下載就不下載

    這個技巧又稱 blobless clone,以下我就一個簡單的例子來比較一下差異。

    假設我們先下載 ASP.NET MVC 5 原始碼回來:

    git clone https://github.com/aspnet/AspNetWebStack.git
    

    下載時的訊息如下:

    Cloning into 'AspNetWebStack'...
    remote: Enumerating objects: 50283, done.
    remote: Counting objects: 100% (14945/14945), done.
    remote: Compressing objects: 100% (1870/1870), done.
    remote: Total 50283 (delta 13240), reused 13297 (delta 13065), pack-reused 35338
    Receiving objects: 100% (50283/50283), 14.86 MiB | 2.08 MiB/s, done.
    Resolving deltas: 100% (42518/42518), done.
    

    你可以從 Enumerating objects: 50283, done. 看的出來,目前在 Git 版本庫中總共有 50,283 個 Git 物件,而且總共佔用了 14.86 MiB 的空間。

    我們改用 --filter=blob:none 參數(部分複製)來下載專案:

    git clone --filter=blob:none https://github.com/aspnet/AspNetWebStack.git AspNetWebStack_partial
    

    下載時的訊息如下:

    Cloning into 'AspNetWebStack_partial'...
    remote: Enumerating objects: 21077, done.
    remote: Counting objects: 100% (614/614), done.
    remote: Compressing objects: 100% (206/206), done.
    remote: Total 21077 (delta 454), reused 517 (delta 398), pack-reused 20463
    Receiving objects: 100% (21077/21077), 2.91 MiB | 2.93 MiB/s, done.
    Resolving deltas: 100% (16355/16355), done.
    remote: Enumerating objects: 3367, done.
    remote: Counting objects: 100% (3076/3076), done.
    remote: Compressing objects: 100% (2338/2338), done.
    remote: Total 3367 (delta 1254), reused 812 (delta 738), pack-reused 291
    Receiving objects: 100% (3367/3367), 3.09 MiB | 3.11 MiB/s, done.
    Resolving deltas: 100% (1352/1352), done.
    Updating files: 100% (3402/3402), done.
    

    你可以看的出來,我們只需要下載 21,077 個 Git 物件而已,而且總共佔用了 2.91 MiB 的空間而已,相當有效率!👍

    另外,上面你也會看到 remote: Enumerating objects: 3367, done. 這段訊息,這段是因為我們 git clone 時,也會順便做一次 git checkout 將檔案取出到工作目錄的關係,所以會再次下載所需的相關物件。

    Git 有四種物件類型,分別是 blob, tree, commit, tag 這四種,詳細介紹可以參見我的 第 06 天:解析 Git 資料結構 - 物件結構 文章。透過 --filter=blob:none 參數來複製專案時,就會排除 Git 的 Blob 類型物件,大幅降低下載的物件數量。

    blob 物件:就是工作目錄中某個檔案的 "內容",且只有內容而已,當你執行 git add 指令的同時,這些新增檔案的內容就會立刻被寫入成為 blob 物件,檔名則是物件內容的雜湊運算結果,沒有任何其他其他資訊,像是檔案時間、原本的檔名或檔案的其他資訊,都會儲存在其他類型的物件裡 (也就是 tree 物件)。

    下載的物件中因為包含了 commit 物件,因此你可以透過 git log 指令來查看歷史記錄,這些物件內容都已經下載回來了。

    你可能會想問,那如果用 git diff 查詢歷史版本的檔案內容,或使用 git switch 切換分支時那怎麼辦?你放心,Git 會在需要 blob 物件時自動下載這些檔案回來,只是下載的時候就需要網路了。

  2. 不想下載完整的 Repo 回來,尤其是超大的 Blob 物件與檔案目錄資訊 Tree 物件也要盡可能的能不下載就不下載

    這個技巧又稱 treeless clone,他會自動排除 treeblob 物件,只下載需要的物件回來。

    命令如下:

    git clone --filter=tree:0 https://github.com/aspnet/AspNetWebStack.git AspNetWebStack_partial
    

    以下是執行時的訊息:

    Cloning into 'AspNetWebStack_partial'...
    remote: Enumerating objects: 2223, done.
    remote: Counting objects: 100% (33/33), done.
    remote: Compressing objects: 100% (31/31), done.
    remote: Total 2223 (delta 2), reused 18 (delta 2), pack-reused 2190
    Receiving objects: 100% (2223/2223), 590.81 KiB | 1.83 MiB/s, done.
    Resolving deltas: 100% (21/21), done.
    remote: Enumerating objects: 352, done.
    remote: Counting objects: 100% (171/171), done.
    remote: Compressing objects: 100% (164/164), done.
    remote: Total 352 (delta 0), reused 48 (delta 0), pack-reused 181
    Receiving objects: 100% (352/352), 129.83 KiB | 916.00 KiB/s, done.
    remote: Enumerating objects: 3367, done.
    remote: Counting objects: 100% (3076/3076), done.
    remote: Compressing objects: 100% (2338/2338), done.
    remote: Total 3367 (delta 1254), reused 812 (delta 738), pack-reused 291
    Receiving objects: 100% (3367/3367), 3.09 MiB | 4.19 MiB/s, done.
    Resolving deltas: 100% (1352/1352), done.
    Updating files: 100% (3402/3402), done.
    

    你可以看的出來,我們只需要下載 2,223 個 Git 物件而已,而且總共下載了 590.81 KiB 的空間而已,太讓人驚艷了!👍

    何時會想這樣用了?我們最終不是還是要 Checkout 嗎?最後還是要下載 Blob 與 Tree 物件回來啊?是的,沒錯,但如果你只是想看 Git Log 就好,就可以連工作目錄都不要取出,不用取出檔案就可以用 git log 查看歷史紀錄。例如:

    git clone --filter=tree:0 --no-checkout https://github.com/aspnet/AspNetWebStack.git AspNetWebStack_partial
    cd AspNetWebStack_partial
    git log
    

    是不是很棒!😎

  3. 在執行 CI 時僅下載特定分支的最新版,我連完整的 Commit 物件都不要

    這個技巧又稱 shallow clone (淺層複製),你可以排除 treeblob 物件,而且連大部分的 commit 物件都排除,只下載需要的物件回來。

    我們在做 CI 的時候,通常都會取出某個分支的程式碼,如果你連 git log 都不需要,就可以不用取回 commit 物件,那麼你的下載量就可以更小,速度也更快。

    命令如下:

    git clone --filter=tree:0 --depth=1 -b main https://github.com/aspnet/AspNetWebStack.git AspNetWebStack_partial
    

    這裡的 --depth=1 是取出的 commit 深度,而 -b main 是預設取出 main 分支到工作目錄。

  4. 最極端的 Partial Clone 方法

    例如我想取回 10 筆 commit 記錄、沒有 tree、沒有 blob、沒有 tags,只取得工作目錄中特定一個資料夾的檔案就好,那就需要這個超級極端的 git clone 方法:

    # Treeless clone + Shallow clone + No tags + No checkout + Sparse checkouts
    git clone --filter=tree:0 --sparse --depth=10 --no-checkout --no-tags https://github.com/aspnet/AspNetWebStack.git AspNetWebStack_partial
    

    以下是執行時的訊息:

    Cloning into 'AspNetWebStack_partial'...
    remote: Enumerating objects: 11, done.
    remote: Counting objects: 100% (11/11), done.
    remote: Compressing objects: 100% (11/11), done.
    remote: Total 11 (delta 0), reused 1 (delta 0), pack-reused 0
    Receiving objects: 100% (11/11), 8.11 KiB | 8.11 MiB/s, done.
    

    是不是很扯?才下載 11 個物件而已! XD

    然後你可以進入一個「空白」的工作目錄,透過 Sparse checkouts 功能只取出自己要的檔案就好,例如:

    git sparse-checkout init --cone
    git sparse-checkout set "src"
    git checkout main
    

    以上就是我的終極 Partial Clone 方法,你可以參考看看!😎

補充說明

如果一個已經是透過 shallow clone (淺層複製) 下載的專案,要取回所有的 Git 物件與分支,需要輸入以下命令:

# 取回所有物件
git fetch --unshallow

# 取回所有分支
git config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*"
git fetch origin

總結

這個技巧其實我幾年前就知道了,為什麼今天才寫這篇文章呢?因為 Azure DevOps Services 上面的 Azure Repos 一直都沒有提供 partial clones 能力,我從 Jan 28, 2020 11:00 PM 就在 Developer Community 網站提出了這個需求,但直到 2023/10/31 的「今天」才部署到正式環境,所以大家現在就都可以開始用這個技巧了!👍

相關連結

留言評論