# 如何在 Monorepo 透過 Git 的 Sparse Checkout 取得部分 Repo 內容

我們現在有越來越多的專案都是前後端分離的架構，所以在一個 Git Repo 裡面同時放前後端程式碼是很常見的狀況。不過，問題來了，如果前後端分離的很乾淨，若是前端人員不想要看到「後端」的原始碼，那 Git 有辦法做到「部分取出」的功能嗎？是的，還真的有！這篇文章我就來說說這個好用的功能。

### 簡介 Sparse checkouts 功能

[Sparse checkouts](https://git-scm.com/docs/git-sparse-checkout) 是一個從 [Git 2.25](https://github.blog/2020-01-13-highlights-from-git-2-25/) 才開始支援的功能，主要用途就是幫助你取得一個 Repo 的部分內容，大幅減少本機磁碟空間的佔用，也可以幫助你更加專注在當前的開發項目。這樣的機制尤其對目前 Monorepo 正夯的時候，提供一個絕佳的解決方案。

英文的 `Sparse` 是一個形容詞，表示「稀疏的」、「零落的」的意思，代表你僅會從 Repo 取出「零星的」檔案！我們從 Git 官網的 [git-sparse-checkout](https://git-scm.com/docs/git-sparse-checkout) 文件可以看到他的定義：

> Reduce your working tree to a subset of tracked files

### 基本使用方式

而 Sparse checkouts 的使用方式，其實也就是兩個步驟而已：

1.  先透過 `git clone` 取得一個 Repo 並啟用 Sparse checkouts 功能
    
    這個動作是在複製遠端儲存庫內容時，直接啟用 Sparse checkouts 功能並套用預設**樣式清單**，預設只會取出**根目錄**下的檔案：
    
    ```bash
    git clone --filter=blob:none --sparse https://github.com/doggy8088/MonorepoAspNetCoreWithAngular.git
    cd MonorepoAspNetCoreWithAngular
    ```
    
    > 注意: 這個命令依然會下載完整的 Git Repo 內容，只是預設**不會取出**所有檔案到**工作目錄**而已！
    
2.  啟用圓錐模式 (Cone mode)
    
    目前 Git 的 Sparse checkouts 支援兩種模式，預設為 **非圓錐模式** (**non-cone mode**)，但建議改用 **圓錐模式** (**cone mode**) 效能比較好！
    
    ```bash
    git sparse-checkout init --cone
    ```
    
3.  接著你可以透過 `git sparse-checkout` 明確指定你想要取得的資料夾名稱
    
    這個命令會指定取得**指定資料夾**下的所有檔案，但在**圓錐模式**下，預設會包含根目錄下的所有檔案喔！
    
    ```bash
    git sparse-checkout set "MonorepoAspNetCoreWithAngular/ClientApp"
    ```
    

### TIPS: 快速下載超大 Repo 的小技巧

你可以在 `git clone` 的時候套用 `--depth 1` 命令，僅取得最近 1 個版本的相關 Blob 物件，這樣就可以大幅縮短 `git clone` 的時間。我以 ASP.NET Core 原始碼專案為例，正常的 `git clone` 在高速網路下也要十幾秒才能下載完畢，但透過以下指令就只要 2 秒就可以複製完畢：

```bash
git clone --depth 1 --filter=blob:none --sparse https://github.com/dotnet/aspnetcore.git
cd aspnetcore
```

> 注意: 使用 `--sparse` 預設就只會 **取出** (Checkout) 根目錄下的所有檔案而已，但沒有任何「子資料夾」喔。

然而，你若只想取回 `/docs` 資料夾的話，就只要用以下命令即可**即時下載**這個資料夾下的用到的 Blob 物件，速度極快：

```bash
git sparse-checkout set "/docs"
```

### 簡介圓錐模式

Git 的 Sparse checkouts 支援兩種模式， **非圓錐模式** (**non-cone mode**) 與 **圓錐模式** (**cone mode**)，主要差別在於解析 **路徑樣式清單** (Pattern Set) 的格式不同！

事實上，你使用 `git sparse-checkout` 所做的任何操作，都會記錄在一個 `.git/info/sparse-checkout` 設定檔中，這是一個**文字檔** (Text file)，內容跟 `.gitignore` 的格式有點相似，但你要特別注意在不同的模式下，其內容所代表的意義是不同的！

-   **非圓錐模式** (**non-cone mode**)
    
    先說，雖然這是 Git 的預設值，不過**不建議使用**，主要是**效能考量**！🔥
    
    在 **非圓錐模式** 下，這個 `.git/info/sparse-checkout` 設定檔的內容會採用 `FULL PATTERN SET` 語法，其內容格式與 `.gitignore` 完全一樣。
    
    那什麼是 `FULL PATTERN SET` 呢？簡單來說，就是這些 Patterns 是對整個 Repo 的檔案路徑進行比對，所以你可以用「正向比對」，也可以用 `!` 進行「反向比對」，設定上比較靈活。不過，這層靈活性的唯一的缺點就是**效能較差**，因為當 Git 在做 Checkout 動作時，每一個檔案在取出時，每一條規則都要判斷，所以複雜度是 `O(M*N)` 這麼多，這意味著**檔案越多**或**規則集越多**，**取出**(Checkout)的速度就越慢！
    
    我舉個簡單的例子，以下列命令為例，該專案預設使用了**非圓錐模式**，然後指定了 `MonorepoAspNetCoreWithAngular/ClientApp` 路徑。事實上，這個 `MonorepoAspNetCoreWithAngular/ClientApp` 所代表的意義，是指「**所有目錄與子目錄下有包含 `MonorepoAspNetCoreWithAngular/ClientApp` 的路徑，都在取出的範圍內**」。
    
    ```bash
    git sparse-checkout set "MonorepoAspNetCoreWithAngular/ClientApp"
    ```
    
    你如果設定 `/MonorepoAspNetCoreWithAngular/ClientApp` 路徑，就代表著只有「**位於根目錄下的 `/MonorepoAspNetCoreWithAngular/ClientApp` 路徑，才包含在取出範圍內**」。
    
    ```bash
    git sparse-checkout set "/MonorepoAspNetCoreWithAngular/ClientApp"
    ```
    
-   **圓錐模式** (**cone mode**)
    
    在 **圓錐模式** 下，這個 `.git/info/sparse-checkout` 設定檔的內容會採用 `CONE PATTERN SET` 語法，這個語法只能說近似於 `.gitignore` 的格式，但比對上稍微再嚴格一些，而且有些特殊的規則。
    
    基本上圓錐模式下的 `CONE PATTERN SET` 規則集有兩大特性：
    
    1.  `Recursive`: 你所指定的路徑，預設包含所有的子目錄下的所有檔案路徑。
    2.  `Parent`: 你所指定的路徑所在的目錄下的所有檔案也都包含在內！(有點繞口，需要思考一下)
    
    我舉個簡單的例子，以下列命令為例，我將該專案啟用了**圓錐模式**，然後指定了 `MonorepoAspNetCoreWithAngular/ClientApp` 路徑：
    
    ```bash
    git clone --filter=blob:none --sparse https://github.com/doggy8088/MonorepoAspNetCoreWithAngular.git
    cd MonorepoAspNetCoreWithAngular
    git sparse-checkout init --cone
    git sparse-checkout set "MonorepoAspNetCoreWithAngular/ClientApp"
    ```
    
    這個 Pattern 將會包含：
    
    1.  `Recursive`: 包含所有子目錄所有路徑 (`MonorepoAspNetCoreWithAngular/ClientApp/**`)
    2.  `Parent`: 包含上層的 `/MonorepoAspNetCoreWithAngular/*` 與 `/*` 目錄下的所有檔案
    
    所以我們可以說，以下**圓錐模式**的樣式集(CONE PATTERN SET)：
    
    ```
    MonorepoAspNetCoreWithAngular/ClientApp
    ```
    
    會等同於以下**非圓錐模式**的樣式集(FULL PATTERN SET)：
    
    ```
    /*                                          根目錄所有檔案
    !/*/                                        排除根目錄下的所子目錄 (沒有排除檔案)
    /MonorepoAspNetCoreWithAngular/             包含這個子目錄以及底下所有路徑(目錄+檔案)
    !/MonorepoAspNetCoreWithAngular/*/          排除這個子目錄下的所有子目錄 (沒有排除檔案)
    /MonorepoAspNetCoreWithAngular/ClientApp/   包含這個子目錄以及底下所有路徑(目錄+檔案)
    ```
    
    > 在**圓錐模式**下，你**無法排除**已經包含在這些 Pattern Set 在內的所有檔案。
    
    > 如果你真的想要**精準的包含與排除檔案**，只能使用**非圓錐模式** (**non-cone mode**)！
    

### 為什麼要取名叫**圓錐模式**？

我從 [Bring your monorepo down to size with sparse-checkout](https://github.blog/2020-01-17-bring-your-monorepo-down-to-size-with-sparse-checkout/) 文章中擷取兩張圖片來說明。

假設你的 Monorepo 有以下目錄結構：

![](https://stwillblogassets.blob.core.windows.net/files/images/external/user-images.githubusercontent.com/55beb5a9d4bddeb141b3-168781791-5a082a47-5f4e-4de8-b574-85470f583c8f.png)

若你只想取出 `/client/android` 目錄下的原始碼，那麼你若用**圓錐模式**的話，就只要用一條規則就可以取出你要的檔案：

```bash
git sparse-checkout set "client/android"
```

![image](https://stwillblogassets.blob.core.windows.net/files/images/external/user-images.githubusercontent.com/62fb479f5e0b30080c79-168781900-de9ac775-6638-4135-b74b-c496d61faf20.png)

你從這個檔案取出的模式來看，整個資料夾結構就好像一個「圓錐體」，而取得檔案的方式是從上到下以「圓錐狀」的方式取出，圓錐的頂端就是專案的「根目錄」，而之後每一層的檔案也都需要取出，一直取到某一層之後，就會取出以下的所有檔案。

> 我只能說取名是一種藝術，大家要多發揮想像力！ 😄

### 常用 Sparse checkouts 命令與參數

1.  啟用 Sparse checkouts 功能
    
    ```bash
    # 啟用非圓錐模式 (non-cone mode)
    git sparse-checkout init
    
    # 啟用圓錐模式 (cone mode)
    git sparse-checkout init --cone
    ```
    
2.  取得 Sparse checkouts 的**路徑樣式**清單
    
    ```bash
    git sparse-checkout list
    ```
    
    > 注意: 如果你的 Repo 並沒有啟用過 Sparse checkouts，第一次使用會出現 `fatal: this worktree is not sparse` 錯誤訊息。
    
3.  設定 Sparse checkouts 的**路徑樣式**清單 (`.git/info/sparse-checkout`)
    
    使用 `git sparse-checkout set` 命令會直接覆蓋現有的路徑清單：
    
    ```bash
    git sparse-checkout set "/MonorepoAspNetCoreWithAngular/ClientApp"
    git sparse-checkout list
    ```
    
    > 執行完 `git sparse-checkout set` 命令之後，你的工作目錄下所有檔案就會立即反應出結果，執行前請先確認擁有一個乾淨的工作目錄！
    
    你可以使用以下命令調整 Sparse checkouts 回覆到預設值：
    
    ```bash
    git sparse-checkout set ""
    git sparse-checkout list
    ```
    
    > 無論你使用哪種 Sparse 模式，執行完之後預設都只會剩根目錄下的檔案！
    
4.  一次設定多組 Sparse checkouts 的**路徑樣式**清單
    
    設定多組 Sparse checkouts 路徑樣式時，套用順序很重要！
    
    ```bash
    git sparse-checkout set "!/*" "/MonorepoAspNetCoreWithAngular/ClientApp" ".git*"
    git sparse-checkout list
    ```
    
    > 此範例僅適用於**非圓錐模式**！(`FULL PATTERN SET`)
    
5.  加入新路徑到 Sparse checkouts 的**路徑樣式**清單
    
    ```bash
    git sparse-checkout set "!/*" "/MonorepoAspNetCoreWithAngular/ClientApp"
    git sparse-checkout add ".git*"
    git sparse-checkout list
    ```
    
    > 此範例僅適用於**非圓錐模式**！(`FULL PATTERN SET`)
    
6.  重新套用 Sparse checkouts 的**路徑樣式**清單
    
    這個命令僅用於你手動調整 `.git/info/sparse-checkout` 設定檔內容之後執行。
    
    ```bash
    git sparse-checkout reapply
    ```
    
7.  停用 Sparse checkouts 功能
    
    這個命令會將所有工作目錄中的檔案完整取出：
    
    ```bash
    git sparse-checkout disable
    ```
    
    > 注意: 此命令不會更動 `.git/info/sparse-checkout` 設定檔中的**路徑樣式**清單。
    
8.  將 Sparse checkouts 功能切換至**圓錐模式** (**cone mode**)
    
    ```bash
    git sparse-checkout disable
    git sparse-checkout init --cone
    #git sparse-checkout set
    git sparse-checkout list
    ```
    
    > 由於**圓錐模式**與**非圓錐模式**的 Patterns 並不相容，切換過程記得要重新檢視過！
    
9.  將 Sparse checkouts 功能切換至**非圓錐模式** (**non-cone mode**)
    
    ```bash
    git sparse-checkout disable
    git sparse-checkout init
    #git sparse-checkout set
    git sparse-checkout list
    ```
    
    > 由於**圓錐模式**與**非圓錐模式**的 Patterns 並不相容，切換過程記得要重新檢視過！
    

### 相關連結

-   [Git - git-sparse-checkout Documentation](https://git-scm.com/docs/git-sparse-checkout)
-   [Bring your monorepo down to size with sparse-checkout | The GitHub Blog](https://github.blog/2020-01-17-bring-your-monorepo-down-to-size-with-sparse-checkout/)
-   [Highlights from Git 2.25 | The GitHub Blog](https://github.blog/2020-01-13-highlights-from-git-2-25/)
-   [Partial clone | GitLab](https://docs.gitlab.com/ee/topics/git/partial_clone.html)
-   [Monorepo vs. Polyrepo](https://github.com/joelparkerhenderson/monorepo-vs-polyrepo)
