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

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

![image](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/499a2d24d6d7cc359c6c-b5afa98d-54ea-4a25-9a1f-7bff1e08df2e.webp)

## 簡介 Partial Clone 功能

在 Git 2.26 版本中，Git 團隊推出了一個新的功能，稱為 [Partial Clones](https://git-scm.com/docs/partial-clone)，這個功能它允許你只部分地複製一個遠端儲存庫，而不必將整個儲存庫全部下載。這在大型專案或具有龐大歷史記錄的專案中非常有用。

以下是 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 原始碼回來：
    
    ```bash
    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` 參數(部分複製)來下載專案：
    
    ```bash
    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 資料結構 - 物件結構](https://github.com/doggy8088/Learn-Git-in-30-days/blob/master/zh-tw/06.md#%E7%89%A9%E4%BB%B6%E7%9A%84%E9%A1%9E%E5%9E%8B) 文章。透過 `--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`，他會自動排除 `tree` 與 `blob` 物件，只下載需要的物件回來。
    
    命令如下：
    
    ```bash
    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` 查看歷史紀錄。例如：
    
    ```bash
    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` (淺層複製)，你可以排除 `tree` 與 `blob` 物件，而且連大部分的 `commit` 物件都排除，只下載需要的物件回來。
    
    我們在做 CI 的時候，通常都會取出某個分支的程式碼，如果你連 `git log` 都不需要，就可以不用取回 `commit` 物件，那麼你的下載量就可以更小，速度也更快。
    
    命令如下：
    
    ```bash
    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` 方法：
    
    ```bash
    # 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](/post/2022/05/17/Down-size-your-Monorepo-with-Git-Sparse-checkouts) 功能只取出自己要的檔案就好，例如：
    
    ```bash
    git sparse-checkout init --cone
    git sparse-checkout set "src"
    git checkout main
    ```
    
    以上就是我的終極 Partial Clone 方法，你可以參考看看！😎
    

### 補充說明

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

```bash
# 取回所有物件
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](https://developercommunity.visualstudio.com/) 網站提出了[這個需求](https://developercommunity.visualstudio.com/t/git-clone---filterblob:none---no-checko/899519)，但直到 2023/10/31 的「今天」才部署到正式環境，所以大家現在就都可以開始用這個技巧了！👍

### 相關連結

-   [Get up to speed with partial clone and shallow clone - The GitHub Blog](https://github.blog/2020-12-21-get-up-to-speed-with-partial-clone-and-shallow-clone/)
-   [git clone --filter=blob:none --no-checkout - Developer Community](https://developercommunity.visualstudio.com/t/git-clone---filterblob:none---no-checko/899519)
-   [Azure DevOps Release Notes - Azure Repos Sprint 227 Update | Microsoft Learn](https://learn.microsoft.com/en-us/azure/devops/release-notes/2023/repos/sprint-227-update?WT.mc_id=DT-MVP-4015686#blobless-and-treeless-filter-support)
-   [如何在 Monorepo 透過 Git 的 Sparse Checkout 取得部分 Repo 內容](/post/2022/05/17/Down-size-your-Monorepo-with-Git-Sparse-checkouts)
-   [Git - git-rev-list Documentation](https://git-scm.com/docs/git-rev-list#Documentation/git-rev-list.txt---filterltfilter-specgt)
