클라우드 Mac CI에서 Git Submodule을 재현 가능하게 체크아웃하기

클라우드 Mac CI에서 Git Submodule을 재현 가능하게 체크아웃하기

개발 머신에서는 정상적으로 복제되는 iOS 프로젝트도 클라우드 Mac CI에 올리면 Submodule 단계에서 멈출 수 있습니다. 메인 저장소는 체크아웃되었지만 비공개 컴포넌트에서 권한 오류가 발생하거나, 중첩된 의존성이 이전 커밋에 머물거나, 얕은 복제로 인해 지정된 객체를 찾지 못하는 식입니다. 대개 문제는 Xcode가 아니라 체크아웃 과정에서 “메인 저장소의 현재 브랜치”와 “메인 저장소에 기록된 Submodule 커밋”을 명확히 구분하지 않은 데 있습니다.

먼저 재현 가능성의 경계 확인하기

Git Submodule은 메인 저장소에 경로, 원격 주소, 정확한 커밋 하나를 기록합니다. 재현 가능한 빌드의 목표는 동일한 메인 저장소 커밋에서 항상 동일한 Submodule 객체를 가져오는 것입니다. 빌드할 때마다 Submodule 브랜치의 최신 상태를 추적하는 것이 아닙니다.

먼저 개발 환경에서 기준 상태를 기록합니다.

git submodule status --recursive
git config --file .gitmodules --get-regexp 'submodule\..*\.path'
git config --file .gitmodules --get-regexp 'submodule\..*\.url'

상태 행 앞의 -는 아직 초기화되지 않았음을, +는 작업 트리의 커밋이 메인 저장소에 기록된 커밋과 일치하지 않음을 뜻합니다. U는 병합 충돌이 있음을 나타냅니다. CI에 들어가기 전에는 이 세 가지 상태를 모두 정상 결과로 간주해서는 안 됩니다.

빌드 작업에서 git submodule update --remote를 실행하지 마십시오. 이 명령은 설정된 브랜치의 변경을 따라가므로, 동일한 메인 저장소 커밋이 실행 시점에 따라 서로 다른 의존성을 가져올 수 있습니다.

상대 주소는 동일한 코드 호스팅 경계 안에서 저장소를 이전할 때 유용하지만, 메인 저장소의 원격 주소를 기준으로 해석됩니다. 작업에서 origin을 임시로 변경했다면 초기화 전에 git submodule sync --recursive를 실행하고, 민감 정보를 제거한 실제 주소를 출력하여 해석 결과를 확인해야 합니다.

체크아웃 진입점 통합하기

clone --recurse-submodules, Submodule 디렉터리에 직접 들어가 수행하는 pull, 빌드 스크립트에 넣은 복구 명령을 뒤섞지 마십시오. CI가 먼저 메인 저장소를 가져온 다음, 하나의 진입점에서 모든 계층을 처리하도록 구성하는 것이 좋습니다.

set -euo pipefail

git submodule sync --recursive
git submodule update --init --recursive --jobs 4

expected_file="$(mktemp)"
actual_file="$(mktemp)"
trap 'rm -f "$expected_file" "$actual_file"' EXIT

git ls-tree -r HEAD |
  awk '$2 == "commit" { print $3, $4 }' |
  sort > "$expected_file"

git submodule status --recursive |
  sed -E 's/^[ +\-U]([0-9a-f]+) ([^ ]+).*/\1 \2/' |
  sort > "$actual_file"

diff -u "$expected_file" "$actual_file"

여기서 --jobs 4는 동시 네트워크 요청 수를 제한할 뿐이며, 고정된 성능 기준을 뜻하지 않습니다. 노드의 네트워크 상태, 원격 서버의 요청 제한, Submodule 수가 서로 다르므로 낮은 동시성부터 시작한 뒤 실패율에 따라 조정해야 합니다.

또한 스크립트는 빌드 전에 git diff --submodule=log --exit-code를 실행해야 합니다. 설치 단계 중 하나가 몰래 Submodule 작업 트리를 변경했다면, 출처가 불분명한 소스 코드로 아카이브를 계속 생성하지 말고 작업을 즉시 실패 처리해야 합니다.

얕은 복제 신중하게 사용하기

얕은 복제는 전송량을 줄여 주지만, “원격에는 분명 커밋이 있는데 CI에서는 찾지 못하는” 문제의 흔한 원인이기도 합니다. 메인 저장소에 기록된 Submodule 객체가 현재 얕은 복제 경계보다 오래된 것일 수 있으며, 삭제된 브랜치의 기록을 통해서만 접근 가능한 객체일 수도 있습니다.

상황 권장 전략 실패 시 처리
Submodule 커밋이 브랜치 최상단에 가까움 제한된 깊이로 체크아웃 깊이를 늘린 후 재시도
필요한 기록 범위가 일정하지 않음 Submodule 전체 체크아웃 고정 깊이를 추측해 사용하지 않음
여러 계층으로 중첩된 Submodule 계층별로 커밋 검증 실패한 경로와 객체 ID 출력
원격 객체에 더 이상 접근할 수 없음 저장소 참조 수정 브랜치를 전환해 문제를 숨기지 않음

얕은 복제를 반드시 유지해야 한다면 먼저 다음 명령을 시도할 수 있습니다.

git submodule update --init --recursive --depth 50

실패하더라도 곧바로 --remote로 바꾸지 마십시오. 오류가 발생한 경로로 이동하여 git fetch --deepen=100을 실행한 다음, git cat-file -e <commit>^{commit}로 객체를 검증해야 합니다. 그래도 객체가 없다면 무작위 재시도를 계속 추가할 것이 아니라 원격 객체의 보존 상태나 메인 저장소에 기록된 커밋을 확인해야 합니다.

비공개 Submodule 자격 증명 격리하기

Submodule마다 권한 경계가 다를 수 있습니다. 가장 안전한 방법은 각 작업에 임시 Git 설정을 만들고 프로세스가 실행되는 동안에만 전달하여, 노드의 전역 설정을 변경하지 않는 것입니다.

git_config="$(mktemp)"
chmod 600 "$git_config"
trap 'rm -f "$git_config"' EXIT

git config --file "$git_config" credential.helper ""
git config --file "$git_config" protocol.version 2

GIT_CONFIG_GLOBAL="$git_config" \
git submodule update --init --recursive

인증 값은 CI의 보호된 변수나 단기 파일로 제공해야 하며, .gitmodules, 원격 주소, 명령 인수 또는 빌드 로그에 기록해서는 안 됩니다. 작업이 끝난 뒤에는 최소한 다음 항목을 확인합니다.

git config --global --list
git remote -v
git submodule foreach --recursive 'git remote -v'

출력을 확인하기 전에 민감 정보를 제거해야 합니다. 자격 증명을 URL에 포함한 적이 있다면 임시 파일을 삭제하는 것만으로는 부족합니다. 원격 주소도 복원하고 Shell 기록, 진단 패키지, 캐시 디렉터리를 확인해야 합니다.

실패 정보를 원인 추적이 가능한 증거로 만들기

Submodule 실패 기록을 단순히 “종료 코드 128” 한 줄로 남겨서는 안 됩니다. 메인 저장소 커밋, 실패 경로, 예상 객체, 실제 객체, 해석된 호스트 이름, 체크아웃 깊이, 실패 단계를 기록하되 사용자 이름, 토큰 또는 인증 정보가 포함된 전체 주소는 기록하지 않아야 합니다.

문제 해결 순서는 다음과 같이 고정할 수 있습니다.

  1. git ls-tree HEAD <path>로 메인 저장소가 기대하는 객체를 확인합니다.
  2. .gitmodules와 로컬 Git 설정에서 최종 주소를 확인합니다.
  3. git cat-file -e로 객체가 로컬에 있는지 판단합니다.
  4. 읽기 전용 인증으로 원격 접근 권한을 검증합니다.
  5. 얕은 복제 경계와 중첩된 Submodule 상태를 확인합니다.
  6. 작업 트리를 정리한 후 동일한 체크아웃 스크립트를 다시 실행합니다.

마지막으로 git submodule status --recursive 출력을 빌드 메타데이터로 보관합니다. 크기는 작지만 해당 빌드에서 정확히 어떤 의존성 커밋을 사용했는지 확인할 수 있습니다. SDKMac 노드에서 실행하는 작업도 같은 원칙을 따라야 합니다. 먼저 소스 입력을 고정한 다음 Xcode 또는 다른 툴체인을 시작하여 체크아웃 오류와 컴파일 오류의 경계를 명확하게 유지하십시오.

자주 묻는 질문

Submodule 커밋을 고정했는데도 체크아웃이 실패하는 이유는 무엇인가요?

상위 저장소는 객체 ID만 기록합니다. Submodule URL, 접근 권한, 복제 깊이와 원격 저장소에 해당 객체가 실제로 남아 있는지 별도로 확인해야 합니다.

CI에서 git submodule update --remote를 사용해도 되나요?

재현 가능한 빌드에는 사용하지 않는 것이 맞습니다. 설정된 브랜치를 따라가므로 같은 상위 커밋도 실행 시점에 따라 다른 의존성을 받을 수 있습니다.

작업이 끝난 뒤 Submodule 자격 증명을 어떻게 정리하나요?

작업 전용 임시 설정이나 제한된 파일로만 주입하고 trap으로 삭제한 뒤, Git 전역 설정과 원격 URL 및 로그에 민감한 값이 남지 않았는지 검사합니다.

독점 물리 노드

지속적 빌드를 위한 클라우드 Mac을 선택하세요

SDKMac M4와 SDKMac M4 Pro의 구성, 리전, 네 가지 결제 주기를 비교한 후 주문을 생성하세요.

대여 플랜 선택