While a release branch is undergoing regression testing, a production issue may require an urgent hotfix based on an older commit, while a feature branch still needs to keep building. Repeatedly running git switch in the same directory makes it easy for untracked files, build-script state, and Xcode intermediate artifacts to leak between tasks. A more reliable approach is to keep a single Git object database on a dedicated cloud Mac and use Git Worktree to create an isolated working directory for each task.
Worktree isolates state, not just branch switching
The usual way to maintain multiple working directories is to clone the repository repeatedly. For a large codebase, that duplicates Git objects, download time, and maintenance overhead. Worktrees share the underlying object database, while each directory has its own checked-out files, index, and HEAD. This makes them well suited to running release, hotfix, and feature development tasks in parallel.
Worktree does not isolate Xcode artifacts automatically. If multiple worktrees still write to the default DerivedData location, indexes, module caches, and intermediate files can continue to overwrite one another. Both the source directories and build outputs therefore need to be isolated.
| Item | Automatically isolated | Recommendation |
|---|---|---|
| Tracked source files | Yes | Use a separate branch for each task |
| Untracked files | Yes | Do not store credentials inside repository directories |
| Git objects | No | Sharing them reduces duplicate disk usage |
| DerivedData | No | Assign a separate path to each worktree |
| Logs and result bundles | No | Use directories identified by branch |
Worktree does not make an individual build faster. Its value lies in reducing invisible state contamination between concurrent tasks.
Define the directory layout before creating worktrees
Keep the main repository, worktrees, and build artifacts in three sibling locations. With this separation, removing a worktree will not accidentally delete test results that still need to be archived.
~/projects/mobile-app
~/worktrees/release-3.4
~/worktrees/hotfix-3.4.1
~/build-data/release-3.4
~/build-data/hotfix-3.4.1
First update the remote references in the main repository, then create the worktrees:
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
By default, git worktree add does not allow the same branch to be checked out in two worktrees at once. This is a safeguard and should not be bypassed with force options. If you only need to inspect a historical commit, create a detached worktree:
git worktree add --detach "$HOME/worktrees/audit-build" 8f32c1a
Map branch names to stable directory names
Branch names often contain /, so they cannot be used directly as single-level directory names. Automation scripts should replace slashes and spaces with hyphens while retaining the original branch name for Git operations. They should also reject empty arguments to prevent accidental writes to the root directory or a shared location.
Give every Xcode task an isolated output path
After entering a worktree, use -derivedDataPath to pin intermediate artifacts to a specific location and -resultBundlePath to save test or build diagnostics. A result bundle path must not already exist when the command starts, so a timestamp can be used to generate a unique name.
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"
Two jobs may also run concurrently within the same worktree. In that case, separating outputs only by branch is insufficient; include a job identifier as well, such as release-3.4/job-17/DerivedData. Archive jobs should also specify separate archive paths so that a later job cannot overwrite an earlier result.
Do not share module caches indiscriminately
A shared cache may appear to save space, but if two branches use different compiler options, toolchains, or generation scripts, diagnosing an invalid cache hit can be harder than rebuilding. Establish a stable baseline with complete isolation first. Only after confirming that the tool versions, dependency lockfiles, and build parameters match should you consider sharing read-only downloaded resources—not the entire DerivedData directory.
Complete four validation checks before running in parallel
Before using each worktree for its first build, record the commit, working tree status, and effective build settings:
git rev-parse HEAD
git status --porcelain
xcodebuild -version
xcodebuild \
-workspace MobileApp.xcworkspace \
-scheme MobileApp \
-showBuildSettings > "$OUTPUT/build-settings.txt"
git status --porcelain should produce no output. If generated files are present, first determine whether they should be ignored instead of hiding the issue with a cleanup command. Then start two low-risk Debug builds concurrently and verify that their logs, result bundles, and DerivedData are written to their respective directories.
At minimum, validate the following:
- The commit hash recorded by each job matches its target branch.
- Cleaning DerivedData for one job does not affect the other.
- Result bundle, log, and archive names cannot collide.
- A failed build log can be traced back to its worktree, commit, and build configuration.
- The system has enough free memory and disk space for concurrent jobs.
Safely remove worktrees and build artifacts
Before removal, enter the target worktree and run git status --short, then confirm that no active xcodebuild process is using it. Move any logs, result bundles, and archives that need to be retained out of the temporary directory. Then run the following commands from the main repository:
cd "$HOME/projects/mobile-app"
git worktree remove "$HOME/worktrees/hotfix-3.4.1"
git worktree prune
git worktree list
If the directory contains uncommitted changes, git worktree remove will refuse to proceed. Do not use --force as a routine cleanup option. Commit the changes, stash them in a clearly identified location, or explicitly confirm that they can be discarded. Build artifacts should also be deleted by task directory rather than with broad wildcards whose scope is unclear.
For long-running setups, add a worktree registry to the task-management system and record the branch, owner, creation time, output directory, and expected release date. This prevents abandoned directories from consuming SSD capacity while also protecting active hotfix environments from accidental deletion.
Frequently asked questions
Can the same Git branch be checked out in two worktrees?
Not by default. Use separate branches for concurrent edits, or create a detached worktree when the second directory is only needed for inspection.
Can multiple worktrees share one DerivedData directory?
They should not. Concurrent builds can overwrite indexes, module caches, and intermediate products, so each worktree needs a dedicated output path.
What should be checked before removing a worktree?
Verify that there are no uncommitted changes or active xcodebuild processes, then preserve required archives, result bundles, and logs before removal.
Keep a reproducible environment on the same cloud Mac
Choose a fixed model, region, and rental term, then use the full macOS graphical interface and command line for development or automation tasks.