發佈分支正在進行迴歸測試時,線上問題突然要求從舊提交建立緊急修正;同一時間,功能分支仍須繼續編譯。如果在同一目錄反覆執行 git switch,未追蹤檔案、建置腳本狀態與 Xcode 中間產物很容易互相干擾。更穩妥的方式,是在一台專屬雲端 Mac 上保留單一 Git 物件庫,再透過 Git Worktree 為每項任務建立獨立工作目錄。
Worktree 解決的不是切換分支速度,而是狀態隔離
一般的多目錄做法通常會重複複製儲存庫。程式碼庫較大時,不僅會重複占用 Git 物件空間,也會增加下載時間與維護成本。Worktree 共用底層物件庫,但每個目錄都有獨立的簽出內容、暫存區與 HEAD,很適合並行處理發佈、緊急修正與功能開發。
不過,Worktree 不會自動隔離 Xcode 產物。如果多個工作樹仍將資料寫入預設 DerivedData,索引、模組快取與中間檔案依然可能彼此覆寫。因此,原始碼目錄與建置輸出都必須分開。
| 項目 | 是否自動隔離 | 建議 |
|---|---|---|
| 已追蹤原始碼 | 是 | 每項任務使用獨立分支 |
| 未追蹤檔案 | 是 | 不要將憑證寫入儲存庫目錄 |
| Git 物件 | 否 | 共用可減少重複占用磁碟空間 |
| DerivedData | 否 | 依工作樹指定獨立路徑 |
| 日誌與結果套件 | 否 | 使用包含分支識別資訊的目錄 |
Worktree 的價值不在於加快單次建置,而是減少並行任務之間難以察覺的狀態污染。
先固定目錄規則,再建立工作樹
建議將主要儲存庫、工作樹與建置產物分別放在三個同層級區域。分開管理後,即使移除工作樹,也不會誤刪仍需封存的測試結果。
~/projects/mobile-app
~/worktrees/release-3.4
~/worktrees/hotfix-3.4.1
~/build-data/release-3.4
~/build-data/hotfix-3.4.1
先在主要儲存庫中同步遠端參照,再建立工作樹:
cd "$HOME/projects/mobile-app"
git fetch --prune
mkdir -p "$HOME/worktrees" "$HOME/build-data"
git worktree add "$HOME/worktrees/release-3.4" release/3.4
git worktree add -b hotfix/3.4.1 \
"$HOME/worktrees/hotfix-3.4.1" origin/release/3.4
git worktree list
git worktree add 預設不允許同一分支同時出現在兩個工作樹中。這是一項保護機制,不應使用強制參數繞過。如果只是要檢查某個歷史提交,可以建立 detached 工作樹:
git worktree add --detach "$HOME/worktrees/audit-build" 8f32c1a
為目錄名稱建立穩定映射
分支名稱經常包含 /,不能直接用來組成單層目錄名稱。自動化腳本應將斜線與空格轉換為連字號,同時保留原始分支名稱供 Git 操作使用。腳本也必須拒絕空白參數,避免意外寫入根目錄或共用目錄。
為每項 Xcode 任務指定獨立輸出路徑
進入工作樹後,透過 -derivedDataPath 固定中間產物的位置,並以 -resultBundlePath 儲存測試或建置診斷資訊。結果套件路徑在執行前不得已經存在,因此可使用時間戳記產生唯一名稱。
set -euo pipefail
BRANCH="${1:?branch required}"
SAFE_NAME="$(printf '%s' "$BRANCH" | tr '/ ' '--')"
WORKTREE="$HOME/worktrees/$SAFE_NAME"
OUTPUT="$HOME/build-data/$SAFE_NAME"
STAMP="$(date '+%Y%m%d-%H%M%S')"
mkdir -p "$OUTPUT/logs" "$OUTPUT/results"
cd "$WORKTREE"
xcodebuild \
-workspace MobileApp.xcworkspace \
-scheme MobileApp \
-configuration Debug \
-derivedDataPath "$OUTPUT/DerivedData" \
-resultBundlePath "$OUTPUT/results/$STAMP.xcresult" \
build 2>&1 | tee "$OUTPUT/logs/$STAMP.log"
即使在同一工作樹內,也可能同時執行兩項任務。此時只按分支劃分仍不足夠,還應加入任務編號,例如 release-3.4/job-17/DerivedData。封存任務也應另行指定封存路徑,避免較晚啟動的任務覆寫先前的結果。
不要任意共用模組快取
共用快取看似能節省空間,但當兩個分支採用不同的編譯參數、工具鏈或產生腳本時,錯誤命中既有狀態往往比重新編譯更難排查。應先以完全隔離的設定建立穩定基準;只有在確認工具版本、相依套件鎖定檔與建置參數一致後,才評估共用唯讀下載資源,而不是直接共用整個 DerivedData。
並行執行前完成四項驗收
每個工作樹首次投入建置前,都應記錄提交、工作區狀態與實際建置設定:
git rev-parse HEAD
git status --porcelain
xcodebuild -version
xcodebuild \
-workspace MobileApp.xcworkspace \
-scheme MobileApp \
-showBuildSettings > "$OUTPUT/build-settings.txt"
git status --porcelain 應該沒有任何輸出。如果存在產生的檔案,應先確認是否需要將它們加入忽略規則,不要直接用清理命令掩蓋問題。接著同時啟動兩項低風險 Debug 建置,檢查日誌路徑、結果套件與 DerivedData 是否都寫入各自的目錄。
驗收時至少應核對以下項目:
- 兩項任務記錄的提交雜湊均與目標分支一致。
- 任一任務清理 DerivedData 時,不會影響另一項任務。
- 結果套件、日誌與封存名稱不會重複。
- 可從失敗日誌反查工作樹、提交與建置設定。
- 系統記憶體與磁碟剩餘空間足以承載並行任務。
安全回收工作樹與建置產物
刪除前,先進入目標工作樹執行 git status --short,並確認沒有仍在執行的 xcodebuild。需要保留的日誌、結果套件與封存檔應先移出暫存目錄。之後再從主要儲存庫執行:
cd "$HOME/projects/mobile-app"
git worktree remove "$HOME/worktrees/hotfix-3.4.1"
git worktree prune
git worktree list
如果目錄中包含尚未提交的修改,git worktree remove 會拒絕執行。不要將 --force 當成日常清理方式;應先提交、暫存至明確位置,或由人工確認可以捨棄。建置產物也應按任務目錄刪除,避免使用作用範圍不明確的萬用字元。
長期運作時,可將工作樹登記表納入任務系統,記錄分支、負責人、建立時間、輸出目錄與預計釋放時間。這樣既能避免廢棄目錄持續占用 SSD,也能防止仍在使用中的緊急修正環境遭到誤刪。
常見問題
同一個 Git 分支可以同時出現在兩個工作樹嗎?
預設不行。同一分支只能由一個工作樹簽出;若要並行修改,應建立不同分支,唯讀檢查則可使用 detached 工作樹。
不同工作樹可以共用 DerivedData 嗎?
不建議。並行建置可能互相覆寫索引、模組快取與中間產物,應為每個工作樹指定獨立目錄。
移除工作樹前應完成哪些檢查?
先確認沒有未提交變更與執行中的 xcodebuild,再保存所需的封存、測試結果和記錄,最後執行 git worktree remove。
讓可重現的環境固定在同一台雲端 Mac 上
選擇固定的機型、地區與租用期間,透過完整的 macOS 圖形介面與命令列完成開發或自動化工作。