↓快轉到主要內容
  1. 教學文章/

Git sparse-checkout 實戰:大型 Monorepo 的部分取出與安全切換

·7 分鐘· loading · loading · ·
Git Sparse-Checkout Monorepo Cone Mode Partial Clone Worktree Developer-Tools
每日拍拍
作者
每日拍拍
科學家 X 科技宅宅
目錄
版本控制: Git - 本文屬於一個選集。
§ 17: 本文

Monorepo 很方便:API、前端、CLI、部署設定和共用 library 都能在同一份歷史裡協作。直到某天,你只想改 services/payments/,卻得面對幾十萬個完全用不到的檔案。

IDE 索引慢、檔案監看器一直忙、搜尋結果被別的團隊淹沒,連 git status 都像在巡視一座城市。

這時候,拍拍君會拿出 Git 內建的 sparse-checkout:Repository 仍然是完整的 Git repository,但 working tree 只展開你真正需要的部分。

這篇把它整理成可以安全切換、檢查與復原的日常工作流。

一. sparse-checkout 到底省了什麼?
#

先把三件容易混在一起的事分開:

技術 主要減少 沒有保證減少
sparse-checkout Working tree 裡展開的檔案 .git 裡既有的物件
shallow clone 下載的歷史深度 當前版本的檔案數量
partial clone 一開始下載的 Git objects Working tree 自動變小

sparse-checkout 的核心問題是:「這個 commit 裡有哪些路徑,應該出現在我的 working tree?」它不是權限系統,也不會把其他目錄從 commit 歷史中刪掉。

只要物件已經在本機,你仍然可以用 Git 指令查看未展開路徑的內容:

git show HEAD:services/catalog/README.md

因此,不要拿 sparse-checkout 隔離機密;它是開發體驗與 I/O 範圍工具,不是安全邊界。

二. 建立可重現的示範 Monorepo
#

Git 已經內建 sparse-checkout,不必另外安裝套件。先確認版本與子命令:

git --version
git sparse-checkout -h

接著建立小型示範 repo:

mkdir pypy-monorepo
cd pypy-monorepo
git init

mkdir -p services/api services/worker apps/admin docs tools
printf '# API\n' > services/api/README.md
printf '# Worker\n' > services/worker/README.md
printf '# Admin\n' > apps/admin/README.md
printf '# Docs\n' > docs/README.md
printf 'workspace = true\n' > workspace.conf

git add .
git commit -m '建立 monorepo 骨架'

目前 working tree 會看到全部內容;等等只保留 services/api/ 與 docs/。

三. Cone Mode:目錄導向的安全預設
#

大多數人的需求都是「我要這幾個目錄」,而不是手寫複雜 pattern;這就是 cone mode 適合的情境。

git sparse-checkout init --cone
git sparse-checkout set services/api docs

現在檢查頂層:

find . -maxdepth 3 -type f -not -path './.git/*' | sort

你會看到指定目錄,也會看到根目錄的 workspace.conf。

這不是漏網之魚。

Cone mode 會保留 repository 根目錄的檔案,並展開所選目錄以及必要的祖先結構。

對許多 monorepo 而言,根目錄的 lockfile、格式設定和 workspace config 本來就需要一起存在。

可以用這個指令查看目前選了什麼:

git sparse-checkout list

輸出應該是:

docs
services/api

這份清單比直接偷看內部 pattern 更適合作為日常檢查入口。

四. set 與 add:替換範圍還是擴大範圍?
#

兩個指令長得很像,語意卻不同。

set 會把選取範圍替換成新的集合:

git sparse-checkout set services/worker

此時 services/api 與 docs 不再屬於目標範圍。

add 則會保留既有選擇,再加上新目錄:

git sparse-checkout add tools
git sparse-checkout list

結果會包含:

services/worker
tools

拍拍君的記法很簡單:

  • 換一個任務上下文:用 set
  • 臨時多碰一個相依模組:用 add
  • 不確定目前狀態:先用 list

在自動化腳本裡,通常應該偏好 set。

因為它描述的是完整期望狀態,多跑一次仍得到同一個結果;一直 add 則可能讓範圍愈來愈大。

五. 切換前先保護本機修改
#

最危險的不是 sparse-checkout 本身,而是你忘了 working tree 還有未提交內容。

先建立一個修改:

git sparse-checkout set services/api docs
printf '\nlocal note\n' >> services/api/README.md
git status --short

接著若把範圍切到 services/worker,Git 不應該默默刪掉會造成資料損失的檔案。

但「Git 會保護我」不該取代自己的切換程序。

建議每次縮小範圍前先做:

git status --short
git diff --check
git diff --stat

然後選一種保存方式:

# 方法一:完成一個可理解的 commit
git add services/api/README.md
git commit -m '補充 API 說明'

# 方法二:工作尚未完成,先暫存並包含 untracked files
git stash push -u -m '切換 sparse scope 前保存 API 工作'

想深入 stash 的選擇,可以參考Git stash 實戰。

重點不是「永遠先 commit」,而是切換前必須知道哪些資料只存在 working tree。

六. Ignored 與 Untracked 檔案為什麼還在?
#

即使把 services/api 移出 sparse 範圍,Git 也不會任意刪除 ignored 或 untracked 資料。因此你可能看見一個「理論上沒有選,實際上仍存在」的目錄。

先查來源,不要直接判定 sparse-checkout 失效:

git status --short --ignored services/api
git check-ignore -v services/api/.cache/result.txt

如果確定內容可重建,再另外使用經過 dry run 的清理流程:

git clean -ndX services/api/.cache

-n 是預覽;沒有確認輸出前,不要拿掉它。清理 generated files 與調整 sparse scope 是兩個不同決策。

七. reapply:索引狀態跑掉時重新套用
#

某些 merge、rebase 或工具操作後,working tree 可能暫時出現 sparse 規則之外的 tracked files。

先檢查狀態,再重新套用:

git status --short
git sparse-checkout reapply

reapply 不是萬用修復按鈕。有修改或衝突時,Git 仍可能保留檔案;正確順序是:

  1. 閱讀 git status
  2. 解決衝突或保存修改
  3. 執行 reapply
  4. 再次用 list 與檔案清單驗證

最後用 git sparse-checkout list 與 git status --short 驗證,不要只相信畫面看起來乾淨。

八. Non-Cone Mode:真的需要 pattern 才使用
#

有時你不是想要整個目錄,而是想展開跨目錄的特定檔案,例如所有 Dockerfile 與部署 YAML。

這時可以改用 non-cone mode:

git sparse-checkout init --no-cone
git sparse-checkout set --no-cone '/*' '!/*/' '/**/Dockerfile' '/deploy/**/*.yaml'

Non-cone patterns 類似 .gitignore 語法,能力更自由,也更難推理。

它的成本包括:

  • Pattern 的先後與 negation 容易讓人誤判
  • 大型 repo 的匹配成本可能更高
  • 團隊成員不一定能從目錄清單理解範圍
  • 腳本與人工操作更難共享同一個心智模型

所以拍拍君的建議是:

能用目錄描述需求,就留在 cone mode。

若真的要用 non-cone,請把 pattern 檔納入團隊文件與測試,而不是只存在某個人的 shell history。

九. Partial Clone:少展開不等於少下載
#

對全新 clone,若遠端支援 partial clone,可以把兩者結合:

git clone \
  --filter=blob:none \
  --sparse \
  https://example.com/pypy/monorepo.git

cd monorepo
git sparse-checkout set services/api docs

--filter=blob:none 代表先不下載所有 blob,等實際需要時再向遠端取得。兩者分工是:

  • partial clone 控制 object 傳輸
  • sparse-checkout 控制 working tree 展開

某些歷史查詢、diff 或 scope 擴張可能觸發網路下載。離線工作前,至少跑一次任務需要的 build、test 與歷史查詢:

git rev-list --objects HEAD -- services/api >/dev/null
git log --oneline -- services/api | head

若 Git server 不支援 filter,不要假裝 partial clone 生效;先用實際 trace 與網路行為驗證。

十. 搭配 Worktree:每個工作區有自己的 Scope
#

Git worktree 解決的是「同時開多個 branch」,sparse-checkout 解決的是「每個 working tree 展開哪些路徑」。

兩者可以搭配,每個工作區保留自己的 scope:

git worktree add ../pypy-docs -b docs-refresh
cd ../pypy-docs
git sparse-checkout init --cone
git sparse-checkout set docs

現代 Git 會使用 worktree-specific configuration 管理 sparse 狀態,避免一個 worktree 的 scope 直接覆蓋另一個。

git worktree list
git sparse-checkout list
git config --get extensions.worktreeConfig

例如主 worktree 放常用服務、review worktree 放 PR 目錄、docs worktree 只放文件。Branch 流程仍是另一層問題,可再看Git Branch 策略完全攻略。

十一. 安全停用與完整復原
#

要回到完整 working tree,不要手動改內部 pattern,直接停用:

git sparse-checkout disable
git status --short
git sparse-checkout list

停用後,list 應提示目前不在 sparse checkout 狀態,而 tracked files 會重新展開。 遇到異常時先收集資訊:

git status
git rev-parse --show-toplevel
git config --show-origin --get core.sparseCheckout
git config --show-origin --get core.sparseCheckoutCone
git config --show-origin --get extensions.worktreeConfig

接著保存修改、解決衝突、嘗試 reapply,必要時 disable 後重新 init --cone。不要第一時間刪除 index、.git 或 sparse 設定檔,那只會把 scope 問題升級成 repository 修復問題。

十二. 團隊實務:把 Scope 變成可重現介面
#

別讓每位開發者各自背一串指令。把常見角色整理成小型腳本:

#!/usr/bin/env bash
set -euo pipefail

role="${1:-}"

case "$role" in
  api) paths=(services/api packages/contracts tools) ;;
  admin) paths=(apps/admin packages/ui packages/contracts) ;;
  docs) paths=(docs examples) ;;
  *) printf 'usage: %s {api|admin|docs}\n' "$0" >&2; exit 2 ;;
esac

if ! git diff --quiet || ! git diff --cached --quiet; then
  printf 'working tree 有修改,請先保存再切換 scope\n' >&2
  exit 1
fi

git sparse-checkout init --cone
git sparse-checkout set "${paths[@]}"
git sparse-checkout list

這讓角色只能映射到已審查的目錄,dirty tree 時拒絕切換,最後再印出 Git 接受的範圍。CI 也能做最小驗證:

./scripts/sparse-role.sh api
test -d services/api
test -d packages/contracts
test ! -e apps/admin
test -z "$(git status --porcelain=v1)"

若根目錄檔案仍存在,不要把它當失敗;cone mode 本來就會保留它們。

十三. 上線前檢查清單
#

導入團隊工作流前,至少確認:

  • Repository 的常用 scope 能用目錄表達
  • 根目錄設定檔保留是預期行為
  • Scope 切換前有 dirty-tree guard
  • Generated、ignored 與 untracked files 有獨立清理政策
  • set、add、reapply、disable 的用途有文件
  • Partial clone 與 worktree 的行為已測試
  • Build、test、IDE 與檔案監看器在縮小範圍後仍正常
  • 團隊知道 sparse-checkout 不是存取控制

結語
#

sparse-checkout 最有價值的地方,不是讓 repository 看起來比較小,而是把開發者每天需要注意的工作範圍縮到合理大小。

先用 cone mode 與少量目錄開始,把 status → 保存修改 → set → 驗證 變成固定流程。

當 clone 傳輸真的也是瓶頸,再評估 partial clone;當需要多分支並行,再搭配 worktree。

工具一層一層加,邊界一層一層驗證,大型 monorepo 也能保持安靜。🔭

延伸閱讀
#

版本控制: Git - 本文屬於一個選集。
§ 17: 本文

相關文章

Git Changelog 自動化:從 Commit、PR 到 Release Notes
·5 分鐘· loading · loading
Git GitHub Changelog Release Notes Automation GitHub Actions Developer-Tools
Git tag 與 Release 實戰:版本標記、SemVer 與安全發佈流程
·8 分鐘· loading · loading
Git Tag Release SemVer Versioning GitHub Developer-Tools
Git rerere 實戰:記住衝突解法,讓 Rebase 與 Merge 不再重做
·8 分鐘· loading · loading
Git Rerere Merge-Conflict Rebase Merge Version-Control Developer-Tools
Git reset、restore、revert 實戰:選對復原工具
·9 分鐘· loading · loading
Git Reset Restore Revert Undo Version-Control Developer-Tools
Git worktree 實戰:同時開多個分支、平行測試與安全清理完全攻略
·11 分鐘· loading · loading
Git Worktree Branch Workflow Version-Control
Git bisect 實戰:快速定位壞 commit 與除錯流程完全攻略
·9 分鐘· loading · loading
Git Bisect Debugging Regression Version-Control