Monorepo 的 Git 挑戰

repo size:Google 的 monorepo 有 80TB+,但大多數公司的 monorepo 在幾 GB 到幾十 GB。幾 GB 的 repo clone 一次要幾分鐘,每次 CI 都 full clone 就是浪費。

大檔案:設計素材、訓練資料、binary artifacts——如果這些放進 git,repo 會快速膨脹而且 git 的 diff/merge 對 binary 沒有意義。

只改一個服務卻要跑所有測試:monorepo 裡有 10 個服務,只改了 orders/ 卻要等所有服務的 CI 跑完,浪費 CI 資源。


Sparse Checkout:只 checkout 你需要的目錄

# 初始化 sparse checkout
git clone --filter=blob:none --sparse https://github.com/company/monorepo.git
cd monorepo
 
# 只 checkout orders 服務
git sparse-checkout init --cone
git sparse-checkout set services/orders shared/common
 
# 現在工作目錄只有這兩個目錄,其他服務的檔案不在本機

--filter=blob:none(Partial Clone,見下節)配合使用,效果最好。


Partial Clone:只下載你需要的 object

傳統 git clone 會下載整個 repo 的所有 object(所有版本的所有檔案)。Partial Clone 讓 git 延遲下載——你實際 checkout 的檔案才下載。

# blobless clone:有所有的 commit 和 tree,但 blob(檔案內容)延遲下載
git clone --filter=blob:none https://github.com/company/monorepo.git
 
# treeless clone:更激進,tree object 也延遲下載(適合 CI 環境)
git clone --filter=tree:0 https://github.com/company/monorepo.git

CI 環境配合 --depth 1(shallow clone)可以把 clone 時間從幾分鐘降到幾十秒:

git clone --filter=blob:none --depth 1 --sparse https://github.com/company/monorepo.git

Git LFS:把大檔案存在別的地方

Git LFS(Large File Storage)讓大檔案存在外部 storage(GitHub LFS、GitLab LFS、S3 + git-lfs server),git repo 裡只存一個 pointer。

# 追蹤所有 png 和 psd 檔
git lfs track "*.png" "*.psd" "*.mp4"
 
# 這會在 .gitattributes 加上 filter 設定
# 之後 add / commit 就跟普通操作一樣
git add design-mockup.psd
git commit -m "add design mockup"

LFS 的代價:clone 要有 LFS 支援、LFS storage 通常額外收費(GitHub 免費 1GB)。適合:設計素材、音影片、ML 訓練資料。不適合:頻繁變動的 binary 檔案(每次 push 都要上傳新版本)。


CI 的 Affected 策略

Monorepo 裡只改了一個服務,只需要跑那個服務的測試。工具:

Nx(JavaScript/TypeScript monorepo):nx affected --target=test 只跑受影響的 project。

Turborepo(JavaScript/TypeScript):類似 Nx,有 remote cache(跑過的 task 結果共用)。

Bazel(多語言,Google 開源):依賴圖的極致版,只 rebuild 和 retest 真正 affected 的部分。學習曲線陡。

自製 diff-based CI

# 找出和 main 有差異的服務
CHANGED_SERVICES=$(git diff --name-only origin/main | grep -o 'services/[^/]*' | sort -u)
 
# 只跑有改變的服務的測試
for SERVICE in $CHANGED_SERVICES; do
  cd $SERVICE && npm test && cd -
done

Monorepo 的 git 策略最終是在「開發體驗(不要下載整個 repo)」和「CI 效率(不要跑不相關的測試)」之間取得平衡。Sparse checkout + Partial clone 解前者,Affected task 解後者。