The Will Will Web

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

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

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

簡介 Sparse checkouts 功能

Sparse checkouts 是一個從 Git 2.25 才開始支援的功能,主要用途就是幫助你取得一個 Repo 的部分內容,大幅減少本機磁碟空間的佔用,也可以幫助你更加專注在當前的開發項目。這樣的機制尤其對目前 Monorepo 正夯的時候,提供一個絕佳的解決方案。

英文的 Sparse 是一個形容詞,表示「稀疏的」、「零落的」的意思,代表你僅會從 Repo 取出「零星的」檔案!我們從 Git 官網的 git-sparse-checkout 文件可以看到他的定義:

Reduce your working tree to a subset of tracked files

基本使用方式

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

  1. 先透過 git clone 取得一個 Repo 並啟用 Sparse checkouts 功能

    這個動作是在複製遠端儲存庫內容時,直接啟用 Sparse checkouts 功能並套用預設樣式清單,預設只會取出根目錄下的檔案:

    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) 效能比較好!

    git sparse-checkout init --cone
    
  3. 接著你可以透過 git sparse-checkout 明確指定你想要取得的資料夾名稱

    這個命令會指定取得指定資料夾下的所有檔案,但在圓錐模式下,預設會包含根目錄下的所有檔案喔!

    git sparse-checkout set "MonorepoAspNetCoreWithAngular/ClientApp"
    

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

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

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

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

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

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 的路徑,都在取出的範圍內」。

    git sparse-checkout set "MonorepoAspNetCoreWithAngular/ClientApp"
    

    你如果設定 /MonorepoAspNetCoreWithAngular/ClientApp 路徑,就代表著只有「位於根目錄下的 /MonorepoAspNetCoreWithAngular/ClientApp 路徑,才包含在取出範圍內」。

    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 路徑:

    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 文章中擷取兩張圖片來說明。

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

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

git sparse-checkout set "client/android"

image

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

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

常用 Sparse checkouts 命令與參數

  1. 啟用 Sparse checkouts 功能

    # 啟用非圓錐模式 (non-cone mode)
    git sparse-checkout init
    
    # 啟用圓錐模式 (cone mode)
    git sparse-checkout init --cone
    
  2. 取得 Sparse checkouts 的路徑樣式清單

    git sparse-checkout list
    

    注意: 如果你的 Repo 並沒有啟用過 Sparse checkouts,第一次使用會出現 fatal: this worktree is not sparse 錯誤訊息。

  3. 設定 Sparse checkouts 的路徑樣式清單 (.git/info/sparse-checkout)

    使用 git sparse-checkout set 命令會直接覆蓋現有的路徑清單:

    git sparse-checkout set "/MonorepoAspNetCoreWithAngular/ClientApp"
    git sparse-checkout list
    

    執行完 git sparse-checkout set 命令之後,你的工作目錄下所有檔案就會立即反應出結果,執行前請先確認擁有一個乾淨的工作目錄!

    你可以使用以下命令調整 Sparse checkouts 回覆到預設值:

    git sparse-checkout set ""
    git sparse-checkout list
    

    無論你使用哪種 Sparse 模式,執行完之後預設都只會剩根目錄下的檔案!

  4. 一次設定多組 Sparse checkouts 的路徑樣式清單

    設定多組 Sparse checkouts 路徑樣式時,套用順序很重要!

    git sparse-checkout set "!/*" "/MonorepoAspNetCoreWithAngular/ClientApp" ".git*"
    git sparse-checkout list
    

    此範例僅適用於非圓錐模式!(FULL PATTERN SET)

  5. 加入新路徑到 Sparse checkouts 的路徑樣式清單

    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 設定檔內容之後執行。

    git sparse-checkout reapply
    
  7. 停用 Sparse checkouts 功能

    這個命令會將所有工作目錄中的檔案完整取出:

    git sparse-checkout disable
    

    注意: 此命令不會更動 .git/info/sparse-checkout 設定檔中的路徑樣式清單。

  8. 將 Sparse checkouts 功能切換至圓錐模式 (cone mode)

    git sparse-checkout disable
    git sparse-checkout init --cone
    #git sparse-checkout set
    git sparse-checkout list
    

    由於圓錐模式非圓錐模式的 Patterns 並不相容,切換過程記得要重新檢視過!

  9. 將 Sparse checkouts 功能切換至非圓錐模式 (non-cone mode)

    git sparse-checkout disable
    git sparse-checkout init
    #git sparse-checkout set
    git sparse-checkout list
    

    由於圓錐模式非圓錐模式的 Patterns 並不相容,切換過程記得要重新檢視過!

相關連結