서론: 서브 저장소 관리의 두 가지 접근법
Git 프로젝트에서 다른 저장소를 하위 디렉터리로 포함해야 할 때, 주로 두 가지 방법을 사용합니다: submodule과 subtree. Git은 1.5.2 버전부터 공식적으로 Subtree를 권장하며, Submodule을 대체하는 방안으로 제시하고 있습니다.
1. Submodule vs Subtree 핵심 비교
1.1 Git Submodule
- 다른 저장소의 특정 커밋을 현재 프로젝트의 하위 디렉터리로 포함
- 클론 시 추가 작업(
init,update) 필요 .gitmodules파일을 생성하여 서브모듈의 경로와 URL, 커밋 해시 기록- 서브모듈 제거 과정이 복잡하고 번거로움
- 서브모듈 내부에서 변경 이력을 별도로 확인 가능 (외부 부모 저장소에는 보이지 않음)
1.2 Git Subtree
- Git 공식 권장 방식
.gitmodules와 같은 추가 파일을 만들지 않음- 관리 및 업데이트 흐름이 단순하며, 협업자에게 투명함 (Subtree 존재를 몰라도 됨)
- 서브트리 디렉터리는 일반 디렉터리처럼 취급되어 부모 프로젝트의 작업 방식에 영향을 주지 않음
- 서브트리의 개별 변경 이력을 별도로 확인할 수 없음 (모든 이력이 부모 저장소에 통합됨)
2. Subtree 상세 사용법
2.1 부모 저장소에 서브트리 추가하기
다음 명령어로 외부 저장소를 하위 디렉터리로 추가합니다. --squash 옵션은 서브트리의 전체 히스토리를 가져오지 않고, 단일 커밋으로 압축하여 추가합니다.
cd <부모-저장소>
git subtree add --prefix=<상대-경로> <서브트리-URL> <브랜치> --squash
예시:
cd temp-test-1
git subtree add --prefix=sub/temp-test-2 https://example.com/temp-test-2.git master --squash
이 명령을 실행하면 git log에 두 개의 커밋이 추가됩니다. 첫 번째는 서브트리 내용을 스쿼시한 커밋이고, 두 번째는 이를 병합한 커밋입니다.
참고: 서브트리 디렉터리 내부에서 변경이 발생하면, 부모 저장소에서 git status에 변경 사항이 표시됩니다. 따라서 서브트리 파일을 수정했다면 부모 저장소에서도 add, commit, push가 필요합니다.
2.2 서브트리 업데이트 가져오기
git subtree pull --prefix=sub/temp-test-2 https://example.com/temp-test-2.git master --squash
2.3 서브트리 변경사항 푸시하기
git subtree push --prefix=sub/temp-test-2 https://example.com/temp-test-2.git master
2.4 분할(split)로 푸시 성능 최적화
git subtree push는 매번 전체 히스토리를 탐색하여 서브트리에 해당하는 커밋만 걸러냅니다. 프로젝트가 커지면 이 과정이 느려질 수 있습니다. git subtree split 명령어로 분할 지점을 만들어, 이후 푸시는 해당 지점부터만 탐색하도록 최적화할 수 있습니다.
git subtree split --prefix=<서브트리-경로> --branch <임시-브랜치>
이 명령어는 서브트리의 현재 상태를 기준으로 분할 지점(브랜치)을 생성합니다. 이후 git subtree push를 실행하면 이 브랜치를 시작점으로 사용하여 효율적으로 동작합니다. 분할 지점은 업데이트가 필요할 때마다 다시 생성하여 덮어쓰면 됩니다.
주의사항: push 시 --squash를 사용했다면, split에서는 --rejoin 파라미터를 사용하지 않아야 합니다. 반대의 경우도 마찬가지입니다.
3. Submodule 상세 사용법
3.1 저장소 클론 (서브모듈 포함)
서브모듈이 포함된 저장소를 클론하는 방법은 두 가지입니다.
방법 1: 재귀적 클론
git clone <저장소-URL> --recursive
방법 2: 클론 후 초기화
git clone <저장소-URL>
git submodule update --init --recursive
3.2 새 서브모듈 추가하기
cd <부모-저장소>
git submodule add <서브모듈-URL> <상대-경로>
명령 실행 후 다음과 같은 변화가 생깁니다:
- 루트 디렉터리에
.gitmodules파일 생성 (서브모듈의 path와 URL 정보 포함, Git 추적 대상) - 부모 저장소의
.git/config에 서브모듈 관련 설정 추가 .git/modules디렉터리에 서브모듈의 모든 정보 저장
예시:
cd my-project
git submodule add https://example.com/lib.git vendor/lib
git add .
git commit -m "Add lib as submodule"
git push origin main
3.3 서브모듈 내부 변경 및 부모에 반영
서브모듈 디렉터리에서 변경 작업을 수행한 후, 다음 순서로 진행합니다:
cd my-project/vendor/lib
# 변경 작업 수행
git add .
git commit -m "Update lib code"
git push origin main
cd ..
git status # 서브모듈에 새 커밋이 있음을 표시
git add vendor/lib
git commit -m "Update submodule pointer"
git push origin main
3.4 서브모듈 업데이트 (협업자 입장)
부모 저장소를 풀(pull)한 후, 서브모듈이 변경되었다면 업데이트해야 합니다.
방법 1:
cd my-project
git pull
git submodule update
방법 2 (특정 브랜치 추적 시):
cd my-project/vendor/lib
git checkout main
cd ..
git submodule foreach git pull origin main
3.5 서브모듈 완전 제거
서브모듈 제거는 여러 단계로 이루어집니다.
방법 1 (권장):
git submodule deinit -f vendor/lib # 서브모듈 디렉터리 및 내용 제거
git rm --cached vendor/lib # .gitmodules 및 인덱스에서 제거
rm -rf .git/modules/vendor/lib # 로컬 Git 저장소 데이터 제거
git commit -m "Remove lib submodule"
방법 2 (수동):
git rm -rf vendor/lib
rm -rf .git/modules/vendor/lib
# .git/config 파일에서 관련 submodule 섹션 수동 삭제
git commit -m "Remove lib submodule"
4. 선택 가이드
| 상황 | 권장 방식 | 이유 |
|---|---|---|
| 외부 라이브러리 단순 포함 | Subtree | 설정이 간단하고, 협업자에게 투명함 |
| 서브 프로젝트를 독립적으로 개발/배포 | Submodule | 각 저장소의 독립성과 이력 관리가 용이 |
| 대규모 모노레포 전환 | Subtree | 관리 오버헤드가 적고, 전체 히스토리 통합 가능 |