Git Submodule · Subtree · LFS — 저장소 안에 다른 저장소
Git Submodule · Subtree · LFS — 저장소 안에 다른 저장소
한 저장소가 다른 저장소의 코드를 함께 가져가야 할 때, 또는 큰 바이너리 파일을 저장소에 넣어야 할 때 떠오르는 옵션. Submodule · Subtree · Sparse Checkout · Git LFS 가 그것. 각자 강점이 다르고 함정도 다릅니다. 이 글은 네 도구의 출자 · 동작 · 자주 만나는 함정 · 모노레포에서 submodule 을 자주 피하는 이유.
1. 네 도구에 대한 이야기
| 도구 | 출자 | 해결하려는 문제 |
|---|---|---|
| Git Submodule | Git 1.5.3 (2007) | 외부 저장소를 특정 커밋으로 고정해 포함 |
| Git Subtree | git-subtree 가 2009 년 contrib 합류 | 외부 저장소를 한 디렉터리로 통합. 부모 히스토리에 병합 |
| Sparse Checkout | Git 1.7 (2010), v2.25 에서 큰 개선 | 큰 저장소에서 일부 디렉터리만 체크아웃 |
| Git LFS | GitHub, 2015 | 큰 바이너리 파일을 별도 저장소에 |
각각이 풀려는 문제가 다릅니다. 같은 자리에 모두 적용 가능한 도구는 거의 없음.
2. Submodule
부모 저장소 안에 자식 저장소의 참조 만 둠. 자식의 실제 파일은 자기 저장소에 있고, 부모는 "이 디렉터리는 자식 저장소의 SHA xxx 커밋이다" 라는 메타정보만 들고 있음.
# 추가
git submodule add https://github.com/foo/lib.git vendor/lib
# .gitmodules 파일이 생기고 vendor/lib 에 자식 저장소 클론
# 클론 시 함께 받기
git clone --recurse-submodules https://github.com/me/parent.git
# 이미 클론한 저장소에 submodule 받기
git submodule update --init --recursive
# 자식 갱신
git submodule update --remote vendor/lib
git add vendor/lib && git commit -m "bump lib"
.gitmodules 파일이 SSOT:
[submodule "vendor/lib"]
path = vendor/lib
url = https://github.com/foo/lib.git
branch = main
| 강점 | 약점 |
|---|---|
| 자식 저장소의 히스토리가 분리 | 클론 · 체크아웃 절차가 복잡 |
| 정확한 커밋 핀고정 | 새 작업자가 자주 헷갈림 |
| 권한 분리 (private / public 혼합) | CI 설정 추가 필요 |
3. Subtree
자식 저장소의 내용을 부모의 한 디렉터리로 물리적으로 합침. 부모 히스토리에 자식 커밋이 들어감.
# 추가
git subtree add --prefix=vendor/lib https://github.com/foo/lib.git main --squash
# 자식의 변경을 가져오기
git subtree pull --prefix=vendor/lib https://github.com/foo/lib.git main --squash
# 부모에서 한 변경을 자식에게
git subtree push --prefix=vendor/lib origin-lib main
| 강점 | 약점 |
|---|---|
| 클론한 사람이 추가 명령 없이 모든 코드 보유 | 부모 히스토리가 비대해짐 |
| 평범한 클론 · 풀이 그대로 동작 | 자식으로 변경을 보내는 절차가 헷갈림 |
.gitmodules 같은 별도 메타 없음 |
자식의 히스토리가 부모에 섞임 |
4. Sparse Checkout
큰 모노레포에서 일부 디렉터리만 받음. Git v2.25 의 git sparse-checkout 명령이 큰 개선:
git clone --filter=blob:none --no-checkout https://github.com/big/mono.git
cd mono
git sparse-checkout init --cone
git sparse-checkout set apps/web packages/ui
git checkout main
--cone 모드는 디렉터리 단위로만 가능하지만 빠르고 안전. 옛 non-cone 모드는 glob 패턴이지만 성능 저하 · 불안정.
서브모듈처럼 외부 저장소 포함이 아니라 같은 저장소에서 일부만 받는 도구.
5. Git LFS
큰 바이너리 (이미지 · 동영상 · 머신러닝 가중치) 가 매 커밋마다 압축 · 전송되면 저장소가 곧 GB 급으로 부풂. LFS 는 그 파일들을 별도 LFS 서버에 두고, 저장소에는 작은 포인터 파일만:
# 한 번 설치
git lfs install
# 추적 추가
git lfs track "*.psd"
git lfs track "models/*.safetensors"
# .gitattributes 가 갱신됨
git add .gitattributes
# 평소처럼 add · commit · push
git add design.psd
git commit -m "add design"
git push
.gitattributes 가 SSOT:
*.psd filter=lfs diff=lfs merge=lfs -text
models/*.safetensors filter=lfs diff=lfs merge=lfs -text
GitHub 는 무료 1 GB 저장 + 1 GB / 월 대역폭. 더 필요하면 데이터팩 구매.
6. 한눈에 비교
| 시나리오 | 추천 |
|---|---|
| 외부 라이브러리를 정확한 커밋으로 고정 | Submodule (또는 패키지 매니저로 충분하면 그것) |
| 외부 코드를 자기 저장소에 합쳐 fork 처럼 관리 | Subtree |
| 한 거대한 모노레포의 일부만 받기 | Sparse checkout |
| 이미지 · 동영상 · 모델 가중치 등 큰 바이너리 | LFS |
7. 다른 길
위 도구 외:
- 패키지 매니저 — 의존성 관리는 npm / pnpm / Cargo / Maven 같은 패키지 매니저가 더 자연스러운 자리가 많음. submodule 이 답인지 의심.
- 모노레포 도구 — Nx · Turborepo · Bazel · Buck. 한 저장소 안에 여러 패키지를 두되 빌드 · 캐시 관리.
- vendoring — 외부 코드를 통째로 복사해 자기 코드처럼. 자식 갱신을 손으로 추적해야 함.
- Workspaces — pnpm / npm / Yarn workspaces. 같은 저장소 안의 여러 패키지 의존성 자동 링크.
8. 자주 걸리는 자리
Submodule
- 새 작업자가
git clone만 하고 끝남 —vendor/lib가 빈 폴더. README 에--recurse-submodules또는git submodule update --init안내 필요. - 자식 변경 후
git push만 하고 부모 갱신을 안 함 — 동료 머신에서 자식이 옛 SHA 로 보임. 자식에서 push → 부모에서git add vendor/lib→ 부모 push 가 한 묶음. - CI 의 submodule 받기 — GitHub Actions 의
actions/checkout은 기본이submodules: false.submodules: recursive를 명시. - detached HEAD — submodule 디렉터리는 기본이 detached HEAD. 자식 작업 시 의도하고 들어가는 자리.
Subtree
- 부모 히스토리 비대화 —
--squash없이 자주 풀하면 히스토리가 빠르게 커짐. - 자식으로 push 가 익숙해지기 까다로움 — 한 사람이 책임지는 편이 안전.
Sparse checkout
- non-cone 모드의 함정 — 패턴이 의도와 어긋나 일부 파일만 표시되는 일.
--cone모드 권장. - CI 가 전체를 체크아웃 — 빌드는 로컬보다 CI 가 무거운 경우가 흔함.
LFS
- 첫 추적 후 기존 큰 파일 —
git lfs track만으로는 과거 커밋의 큰 파일은 LFS 로 안 옮겨짐.git lfs migrate import필요. - fork 시 LFS 객체 — 일부 호스팅은 fork 시 LFS 가 따라가지 않음.
- 무료 한도 — GitHub 의 1 GB / 월 대역폭은 동영상 한 번이면 소진. 사전 계산.
9. 모노레포에서 submodule 을 자주 피하는 이유
같은 회사 · 팀이 만든 여러 패키지를 한 저장소에 두는 모노레포에서는 submodule 이 자주 마찰:
- 한 PR 이 두 저장소 (부모 · 자식) 를 동시에 만져야 하는 절차 부담.
- CI 캐시 · 의존성 그래프가 분산.
- 새 사람의 진입 장벽 (clone, init, update 절차).
대신 패키지 매니저 워크스페이스 또는 Nx / Turborepo 가 자주 권장. submodule 은 외부 OSS 를 핀고정해 가져가는 자리, 또는 권한 · 라이선스가 분리되어야 하는 자리에 더 어울립니다.
하고픈 말
Submodule · Subtree · Sparse Checkout · LFS 넷은 같은 카테고리로 묶이지만 사실 각자 다른 문제를 풉니다. 모노레포라면 submodule 보다 워크스페이스 (pnpm · Yarn) · 모노레포 도구 (Turborepo · Nx) 가 마찰이 적은 자리. 큰 바이너리는 LFS 가 거의 표준. 도구를 고르기 전에 "이 자리에 진짜 필요한가" 부터 의심.
Next
- (tools 끝)
git-submodule · Pro Git Submodules · git-subtree · git-sparse-checkout · Git LFS · GitHub LFS 한도 · Atlassian — Git Submodules vs Subtree · GitHub Blog — partial clone and shallow clone · Nx · Turborepo · Bazel · pnpm Workspaces 를 참고합니다.