대규모 멀티 Git 저장소 프로젝트의 구원투수: Git Repo 완벽 가이드 (vs 일반 Git 비교)
수십, 수백 개의 독립된 Git 저장소로 이루어진 대규모 프로젝트(예: 안드로이드 오픈소스 프로젝트 - AOSP)를 관리해 본 적이 있다면? 각 서비스나 모듈이 개별 저장소로 분리되어 있을 때, 일반적인 Git 명령어만으로는 전체 코드베이스의 일관성을 유지하고 협업하는 것이 매우 번거롭고 복잡해진다.
이번 글에서는 구글(Google)이 개발한 멀티 저장소 관리 도구인 Repo의 개념과 일반 Git과의 차이점, 그리고 실제 프로젝트에 적용하는 Manifest 설정 방법까지 완벽하게 정리해 본다.
1. Git만으로는 부족한 순간: 멀티 Repo 관리의 어려움
현대 대규모 소프트웨어 아키텍처는 코드베이스를 여러 개의 독립된 Git 저장소로 쪼개어 관리하는 경우가 많다. 이때 Git의 기본 기능(Submodule, Subtree 등)을 활용할 수 있지만, 다음과 같은 한계에 부딪히게 된다.
- 동기화의 어려움: 수십 개의 저장소에서 각각 특정 브랜치나 커밋을 일일이 git pull하고 상태를 확인하는 작업은 피로감을 준다.
- 원자적(Atomic) 작업의 부재: 여러 저장소에 걸쳐 코드를 수정하고 동시에 브랜치를 생성하거나 커밋/푸시를 수행할 때 휴먼 에러가 발생하기 쉽다.
- 버전 관리의 파편화: 어떤 조합의 커밋들이 정상적으로 빌드되는 버전인지(Manifest) 전체적인 스냅샷을 관리하기 어렵다.
이러한 문제를 해결하기 위해 등장한 것이 바로 Repo다.
2. Google Repo란 무엇인가?
Repo는 Git을 대체하는 도구가 아니라, 여러 개의 Git 저장소를 상위 레벨에서 통합 관리하기 위해 구글이 만든 파이썬 기반의 툴이다.
Repo는 Manifest(매니페스트) 파일을 기반으로 작동한다. Manifest 파일은 XML 형식으로 작성되며, 프로젝트 내에 어떤 Git 저장소들이 포함되어 있고 각각 어떤 브랜치나 리비전(Revision)을 바라봐야 하는지를 정의한다.
핵심 동작 방식
- repo init: 프로젝트의 기준이 되는 Manifest 저장소를 클론하고 작업 디렉터리를 초기화한다.
- repo sync: Manifest 파일에 명시된 모든 하위 Git 저장소를 한 번에 클론하거나 최신 상태로 동기화한다.
3. Git vs Repo 핵심 비교
| 구분 | Git (단일 저장소) | Repo (멀티 저장소 관리자) |
| 관리 단위 | 단일 Git 저장소 (Repository) | 수십~수백 개의 Git 저장소 집합 |
| 핵심 목적 | 개별 소스코드의 버전 관리 및 이력 추적 | 여러 저장소 간의 버전 동기화 및 협업 워크플로우 조율 |
| 설정 관리 | .git 디렉터리 및 .gitmodules | XML 기반의 Manifest 파일 |
| 주요 작업 방식 | git clone, git pull, git push 등 개별 실행 | repo sync, repo start, repo upload 등 일괄 처리 |
| 대상 프로젝트 | 단일 애플리케이션, 라이브러리 등 | 안드로이드 OS, 대규모 임베디드 시스템, 마이크로서비스 아키텍처 등 |
4. Repo의 핵심: Manifest 파일 (default.xml) 예제
Repo를 작동시키기 위해 가장 중요한 것은 저장소 구조를 정의하는 Manifest 파일이다.
Manifest 기본 구조 예제
<?xml version="1.0" encoding="UTF-8"?>
<manifest>
<!-- 1. 원격 저장소 서버(Remote) 정의 -->
<remote name="origin"
fetch="https://github.com/my-organization"
review="https://review.my-organization.com" />
<!-- 2. 기본값(Default) 설정 -->
<default revision="main"
remote="origin"
sync-j="4" />
<!-- 3. 개별 Git 저장소(Project) 목록 정의 -->
<!-- 메인 프레임워크 저장소 -->
<project path="src/framework" name="core-framework.git" revision="release/v1.0" />
<!-- 모듈/라이브러리 저장소 -->
<project path="src/modules/auth" name="module-auth.git" />
<project path="src/modules/payment" name="module-payment.git" />
<!-- 외부 오픈소스 라이브러리 (다른 원격 서버 사용 예시) -->
<remote name="external-git" fetch="https://github.com/external-lib" />
<project path="src/vendor/libfoo" name="libfoo.git" remote="external-git" revision="v2.1.0" />
</manifest>
Manifest 주요 태그 설명
- <remote>: Git 서버의 Base URL(fetch)을 정의한다.
- <default>: <project> 태그에서 별도 지정이 없을 때 사용할 기본 브랜치(revision)와 서버(remote)를 지정한다.
- <project>: 실제 로컬로 가져올 개별 Git 저장소를 정의한다. path는 로컬 폴더 경로, name은 서버의 저장소 이름이다.
TIP: 프로젝트 규모가 너무 커지면 <include name="other_manifest.xml"/> 태그를 활용해 Manifest 파일 자체를 여러 개로 분할해 관리할 수도 있다.
5. 실무에서 주로 사용하는 Repo 명령어 워크플로우
- Manifest 저장소 초기화
repo init -u https://github.com/my-org/manifests.git -b main - 전체 저장소 일괄 동기화 (다운로드)
repo sync - 전체 저장소 동시 브랜치 생성
repo start feature/new-login --all - 전체 작업 상태 확인
repo status
6. 결론: 어떤 프로젝트에 도입해야 할까?
- 일반 Git이 적합한 경우: 하나의 저장소에서 개발이 이루어지거나, Git Submodule만으로도 충분히 관리가 가능한 중소규모 프로젝트.
- Repo가 적합한 경우: 수십 개 이상의 독립된 저장소가 유기적으로 엮여 있어 전체 코드베이스의 일괄 동기화와 스냅샷 관리가 필수적인 대규모 프로젝트.
프로젝트의 규모가 커져서 여러 Git 저장소를 유기적으로 묶어 관리해야 하는 골칫거리가 생겼다면, Repo 도입을 진지하게 고려해 볼 만하다. 개발 생산성을 극적으로 끌어올려 줄 것이다.