发布分支正在做回归时,线上问题突然要求从旧提交拉出热修复;与此同时,功能分支还要继续编译。若在同一目录反复执行 git switch,未跟踪文件、构建脚本状态和 Xcode 中间产物很容易串线。更稳妥的做法,是在一台独享云端 Mac 上保留一个 Git 对象库,再用 Git Worktree 为每条任务创建独立工作目录。
Worktree 解决的不是切分支速度,而是状态隔离
普通的多目录方案通常是重复克隆仓库。代码库较大时,这会重复占用 Git 对象、下载时间和维护成本。Worktree 共享底层对象库,但每个目录拥有独立的检出内容、暂存区和 HEAD,适合并行处理发布、热修复与功能开发。
它不能自动隔离 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 Worktree 能让同一个分支同时出现在两个目录吗?
默认不能。同一分支只能被一个工作树检出;需要并行修改时,应创建不同分支,或为只读检查使用 detached 工作树。
不同工作树可以共用同一个 DerivedData 目录吗?
不建议。并行构建可能覆盖索引、模块缓存和中间产物,应按工作树分配独立的 DerivedData、结果包与日志目录。
删除工作树前最少要检查什么?
确认没有未提交修改、没有正在运行的 xcodebuild 进程,并保存需要的归档、测试结果与日志,然后使用 git worktree remove 清理。
把可复现环境保留在同一台云端 Mac
选择固定机型、区域和租期,使用完整 macOS 图形界面与命令行完成开发或自动化任务。