repo 설치 및 사용법

AOSP(Android Open Source Project)처럼 수백 개의 Git 저장소로 쪼개진 대형 프로젝트를 다뤄본 적이 있다면, 저장소 하나하나를 git clone하고 브랜치를 맞추고 동시에 pull하는 일이 얼마나 번거로운지 알 것이다. 저장소마다 리비전이 어긋나거나, 몇 개만 깜빡하고 동기화를 빼먹으면 빌드 전체가 깨지기도 한다. Google은 이 문제를 풀기 위해 repo라는 도구를 만들었다. 여러 Git 저장소를 하나의 매니페스트(manifest) 파일로 묶어 마치 단일 저장소처럼 다룰 수 있게 해주는 래퍼(wrapper) 도구다. 이 글에서는 repo의 설치 방법과 init/sync/start/upload 같은 기본 사용법을 정리한다.

repo란 무엇인가

repo는 AOSP를 받아오기 위해 만들어진 Python 스크립트로, 내부적으로는 Git 명령을 감싸는 형태로 동작한다. 핵심은 매니페스트(manifest.xml)다. 매니페스트에는 프로젝트를 구성하는 각 Git 저장소의 원격 URL, 로컬 경로, 기준 리비전(브랜치·태그·커밋)이 XML로 정의돼 있다. repo는 이 매니페스트를 읽어 수십~수백 개 저장소를 한 번의 명령으로 clone하거나 동기화한다.

Git의 submodule과 목적이 비슷해 보이지만 동작 방식은 다르다. submodule은 상위 저장소 안에 하위 저장소의 특정 커밋 하나를 고정 참조로 박아 넣는 구조라, 하위 저장소를 독립적으로 작업하고 리뷰를 올리기가 번거롭다. 반면 repo는 각 저장소를 처음부터 독립된 Git 저장소로 유지하면서, 매니페스트가 정의한 리비전 기준으로 여러 저장소를 나란히 동기화한다. 저장소별로 별도 브랜치를 만들고(repo start), 개별적으로 커밋하고 리뷰 서버(Gerrit 등)에 업로드하는 워크플로우가 자연스럽다. AOSP에서 출발했지만 LineageOS 등 AOSP 파생 프로젝트나, 사내에서 여러 Git 저장소를 함께 버전 관리해야 하는 멀티 리포지토리 프로젝트에도 그대로 쓸 수 있다.

설치

Ubuntu 18.04 이상이라면 APT 저장소에 repo 패키지가 포함돼 있어 가장 간단하게 설치할 수 있다.

sudo apt-get update
sudo apt-get install repo

배포판 저장소에 없거나 최신 버전이 필요하면, Google이 배포하는 launcher 스크립트를 GPG 서명까지 검증해서 직접 설치할 수 있다.

mkdir -p ~/bin
export REPO=$(mktemp /tmp/repo.XXXXXXXXX)
curl -o ${REPO} https://storage.googleapis.com/git-repo-downloads/repo
gpg --recv-keys 8BB9AD793E8E6153AF0F9A4416530D5E920F5C65
curl -s https://storage.googleapis.com/git-repo-downloads/repo.asc | gpg --verify - ${REPO} \
  && install -m 755 ${REPO} ~/bin/repo

~/binPATH에 없다면 셸 설정 파일에 추가한다.

echo 'export PATH="$HOME/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc

설치가 끝나면 버전을 확인한다. 2.4 이상이면 정상이다.

$ repo version
repo launcher version 2.4
       (from https://gerrit.googlesource.com/git-repo)
repo Version v2.45
       (from https://gerrit.googlesource.com/git-repo)

기본 사용법

가장 먼저 작업 디렉터리를 만들고 repo init으로 매니페스트를 받아온다. -u로 매니페스트 저장소 URL을, -b로 브랜치를 지정한다.

mkdir my-workspace && cd my-workspace
repo init -u https://android.googlesource.com/platform/manifest -b android-latest-release

이 명령은 저장소 전체를 내려받지 않고, .repo/ 디렉터리에 repo 자체와 매니페스트 저장소만 clone한다. 매니페스트는 대략 이런 구조다.

<manifest>
  <remote name="origin" fetch="https://android.googlesource.com" />
  <default remote="origin" revision="main" sync-j="4" />

  <project name="platform/build" path="build" />
  <project name="platform/frameworks/base" path="frameworks/base" />
  <project name="platform/packages/apps/Settings" path="packages/apps/Settings" />
</manifest>

project 태그 하나가 실제 Git 저장소 하나에 대응하고, path는 로컬에 clone될 경로다. 초기화가 끝나면 repo sync로 매니페스트에 나열된 모든 저장소를 실제로 내려받는다.

repo sync -c -j8

-c는 매니페스트에 지정된 현재 브랜치만 받아 내려받는 양을 줄이고, -j8은 8개 스레드로 병렬 동기화해 속도를 높인다. 처음 실행하는 저장소는 git clone과 동일하게 동작하고, 이미 받아둔 저장소는 git remote updategit rebase origin/<branch>를 수행하는 것과 같다.

특정 저장소에서 작업을 시작할 때는 repo start로 새 로컬 브랜치를 만든다. 매니페스트에 지정된 리비전을 기준으로 브랜치가 생성된다.

cd frameworks/base
repo start my-feature .

마지막 인자 .은 “현재 디렉터리의 프로젝트”를 뜻한다. 여러 프로젝트에 걸쳐 동시에 브랜치를 만들고 싶다면 워크스페이스 루트에서 프로젝트 이름을 여러 개 나열하면 된다. 커밋 후 어떤 저장소에 어떤 변경이 있는지 한눈에 보려면 repo status를, 실제 diff 내용을 보려면 repo diff를 워크스페이스 루트에서 실행한다.

$ repo status
project frameworks/base/                              branch my-feature
 -m     core/java/android/app/Activity.java
project packages/apps/Settings/                        branch my-feature
 -m     src/com/android/settings/Settings.java

작업이 끝나면 git commit으로 커밋한 뒤, repo upload로 Gerrit 같은 리뷰 서버에 올린다. --cbr 옵션은 현재 브랜치만 업로드하도록 제한한다.

git commit -am "Fix null check in Activity.onResume"
repo upload --cbr .

여러 저장소에 걸쳐 같은 명령을 반복 실행하고 싶을 때는 repo forall이 유용하다. 예를 들어 모든 저장소의 최근 커밋 로그를 한 줄씩 보려면 다음과 같이 쓴다.

repo forall -c 'echo "$REPO_PATH:" && git log -1 --oneline'

-c 뒤의 명령은 각 프로젝트 디렉터리 안에서 실행되며, REPO_PROJECT(매니페스트상 프로젝트 이름), REPO_PATH(워크스페이스 루트 기준 상대 경로) 같은 환경 변수를 셸 스크립트에서 그대로 참조할 수 있다.

주의사항

배포판 저장소의 repo는 버전이 낮을 수 있다: APT로 설치한 repo가 오래된 버전이면 최신 매니페스트 문법(예: 일부 project 속성)을 지원하지 않을 수 있다. repo version으로 확인하고, 필요하면 GPG 검증 방식의 수동 설치로 최신 launcher를 받는 편이 낫다.

-j 값을 무작정 높이지 않는다: repo sync -j의 스레드 수를 과도하게 높이면 네트워크 대역폭을 다 써버리거나 서버 쪽에서 동시 연결을 제한(rate limit)해 오히려 동기화가 실패할 수 있다. 저장소 개수와 회선 상황을 보고 4~8 정도에서 시작하는 것이 안전하다.

repo start 없이 바로 커밋하지 않는다: repo sync 직후의 저장소는 detached HEAD 상태인 경우가 많다. 이 상태에서 바로 커밋하면 브랜치에 연결되지 않은 커밋이 만들어져 repo sync 한 번에 그대로 날아갈 수 있다. 작업 전에는 항상 repo start <branch> .로 로컬 브랜치부터 만든다.

매니페스트 브랜치를 바꾸면 대규모 재동기화가 발생한다: repo init -b로 다른 브랜치를 지정하면 다음 repo sync에서 프로젝트 구성 자체가 달라져 상당한 양의 데이터를 다시 받아야 할 수 있다. 브랜치를 자주 오가는 작업이라면 워크스페이스를 분리하는 편이 낫다.

마무리

repo는 결국 “매니페스트로 정의한 여러 Git 저장소를 하나처럼 다룬다”는 아이디어 하나로 요약된다. init으로 매니페스트를 받고, sync로 저장소들을 동기화하고, start로 브랜치를 만들어 작업한 뒤 upload로 리뷰에 올리는 흐름만 익히면 AOSP 같은 대형 멀티 리포지토리 프로젝트도 개별 git clone을 반복하지 않고 다룰 수 있다. AOSP 전용 도구처럼 보이지만, 매니페스트 XML만 직접 작성하면 사내 멀티 리포 프로젝트에도 그대로 응용할 수 있다.

참고

Establishing a Build Environment – Install Repo (Android Open Source Project)

Repo command reference (Android Open Source Project)

Source control tools (Android Open Source Project)

답글 남기기