릴리스 브랜치에서 회귀 테스트를 진행하는 도중 운영 환경에서 발견된 문제를 해결하기 위해 이전 커밋을 기준으로 긴급 수정 브랜치를 만들어야 할 수 있습니다. 동시에 기능 브랜치의 빌드도 계속되어야 합니다. 같은 디렉터리에서 git switch를 반복하면 추적되지 않은 파일, 빌드 스크립트의 상태, Xcode 중간 산출물이 서로 뒤섞이기 쉽습니다. 더 안전한 방법은 전용 클라우드 Mac 한 대에 Git 객체 저장소 하나를 유지하면서 Git Worktree로 작업별 독립 디렉터리를 만드는 것입니다.
Git Worktree로 병렬 Xcode 브랜치를 안전하게 격리하기
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 Worktree를 사용합니다.
여러 Worktree가 하나의 DerivedData를 공유해도 되나요?
권장하지 않습니다. 동시 빌드가 인덱스, 모듈 캐시, 중간 산출물을 덮어쓸 수 있으므로 Worktree마다 별도 경로를 지정해야 합니다.
Worktree를 삭제하기 전에 무엇을 확인해야 하나요?
커밋하지 않은 변경과 실행 중인 xcodebuild가 없는지 확인하고, 필요한 아카이브와 테스트 결과 및 로그를 보존한 뒤 삭제합니다.
재현 가능한 환경을 같은 클라우드 Mac에 유지
고정된 기종, 리전 및 대여 기간을 선택하고 완전한 macOS 그래픽 인터페이스와 명령줄을 사용해 개발 또는 자동화 작업을 수행하세요.