以 Git Worktree 隔離雲端 Mac 上的並行 Xcode 分支

CI/CD 實踐 ·約 8 分鐘閱讀

以 Git Worktree 隔離雲端 Mac 上的並行 Xcode 分支

發佈分支正在進行迴歸測試時,線上問題突然要求從舊提交建立緊急修正;同一時間,功能分支仍須繼續編譯。如果在同一目錄反覆執行 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 是否都寫入各自的目錄。

驗收時至少應核對以下項目:

  1. 兩項任務記錄的提交雜湊均與目標分支一致。
  2. 任一任務清理 DerivedData 時,不會影響另一項任務。
  3. 結果套件、日誌與封存名稱不會重複。
  4. 可從失敗日誌反查工作樹、提交與建置設定。
  5. 系統記憶體與磁碟剩餘空間足以承載並行任務。

安全回收工作樹與建置產物

刪除前,先進入目標工作樹執行 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 圖形介面與命令列完成開發或自動化工作。

立即租用雲端 Mac