Posts Git - Submodule 알아보기
Post
Cancel

Git - Submodule 알아보기

배경


AWS 환경에서 운영시 사내 nexus를 사용할 수 없어서, common 모듈 같은 공통 모듈을 별도 프로젝트로 관리하고, git submodule로 포함시킨다는데 이게 어떤 의미일까 ?

  • 보통 사내에서 빌드된 공통 라이브러리(common module)는 Nexus (또는 Artifactory) 같은 사설 Maven Repository에 업로드
  • 다른 프로젝트에서는 의존성으로 이렇게 불러온다.
1
2
3
dependencies {
    implementation 'com.company:common:1.2.3'
}
  • 이렇게 하면 빌드 시 Gradle/Maven이 Nexus에서 jar를 받아온다.
  • 그런데 AWS 환경에서는 종종 다음과 같은 이유로 사내 Nexus에 접근이 불가능할 수 있다:
    • AWS VPC 내부망에서 사내망(Nexus 서버) 으로 직접 접근이 차단됨
    • VPN 연결 없이 외부망(AWS)에서 내부망(Nexus)에 접근 불가
    • Nexus 서버가 프라이빗 네트워크에 있고, 인터넷에 노출되지 않음
  • 즉, 의존성 아티팩트를 원격 Repository에서 받을 수 없는 상황이 된다.

※ Nexus ?

Sonatype Nexus Repository는 빌드 결과물(예: jar, war, npm 패키지, Docker 이미지 등)을 중앙에서 저장하고 배포할 수 있게 해주는 Artifact Repository(아티팩트 저장소) 즉, Maven Central이나 npmjs.org 같은 공용 저장소의 사내 버전

  • 필요성
    • 여러 프로젝트에서 공통 라이브러리(core, util 등) 사용
    • 외부 네트워크 접근이 제한된 환경 (AWS VPC, 내부망 등)
    • 회사 내부 전용 라이브러리 (보안상 외부에 공개 불가)
    • 그래서 “사내 전용 Maven Central” 같은 서버가 필요
역할설명
Artifact 저장소빌드 결과물(JAR, WAR, ZIP, Docker 이미지 등)을 업로드(Publish)하고 다른 프로젝트에서 다운로드(Fetch) 가능
사설 Repository 관리외부 공개 불가능한 내부용 라이브러리를 보관
Proxy CacheMaven Central, npm registry 등의 캐시 프록시 역할 (외부 패키지를 한 번 받아두면 내부에서 빠르게 재사용 가능)
버전 관리아티팩트의 버전을 1.0.0, 1.0.1, SNAPSHOT 등으로 관리
권한 관리LDAP, SSO 연동으로 팀별 접근 권한 제어 가능
  • AWS 환경에서는 Nexus 대신 AWS CodeArtifact(AWS 관리형 Nexus 같은 서비스)가 같은 역할을 한다.

Git Submodule


하나의 Git 저장소 안에 다른 Git 저장소를 하위 디렉터리로 포함시키는 기능

1
2
3
4
main-repo/
├── .gitmodules
├── app/
└── common-lib/   ← 다른 Git 저장소 (submodule)

주요 특징

항목설명
독립 버전 관리Submodule은 자체 Git 히스토리를 가지고 있어요. 부모 저장소와 별도로 커밋, 푸시 가능.
특정 커밋 고정부모 저장소는 submodule의 “브랜치”가 아니라 “특정 커밋 SHA”를 참조합니다.
업데이트는 수동submodule 쪽에서 변경이 있어도, 부모 저장소에서는 git submodule update로 직접 갱신해야 반영됩니다.
.gitmodules 파일submodule 경로와 원격 저장소 URL을 관리하는 설정 파일입니다.

멀티모듈 프로젝트와의 차이

비교 항목Git Submodule멀티모듈 프로젝트 (예: Gradle, Maven 등)
소스 관리 단위각 모듈이 서로 다른 Git 저장소하나의 Git 저장소 안에 여러 모듈
버전 관리각 모듈은 자체 버전, 커밋 히스토리를 가짐전체가 한 커밋 단위로 버전 관리됨
의존성 연결 방식Git 커밋 기반 링크 (.gitmodules)빌드 시스템(Gradle, Maven)의 settings.gradle이나 pom.xml로 연결
업데이트 방식수동으로 git submodule update자동으로 같은 브랜치/커밋 기준에서 빌드
빌드 통합성독립적 빌드, 통합 자동화 어려움전체 프로젝트를 한 번에 빌드 가능
주 사용 목적외부 공용 라이브러리나 공통 모듈 재사용대규모 단일 프로젝트를 논리적으로 분리

장/단점

구분내용
장점- Nexus가 없어도 공통 모듈 사용 가능
- 각 모듈의 버전을 Git 커밋으로 고정 가능
- 별도 jar 배포 없이 코드 동기화 가능
단점- submodule 업데이트를 수동으로 해야 함 (git submodule update)
- 여러 프로젝트가 같은 submodule을 쓰면 버전 동기화가 번거로울 수 있음
- 빌드 시스템 통합 관리가 어려움

적용


  • 디렉토리 구조
1
2
3
4
5
6
repo1/
├── .gitmodules
├── api/
├── admin/
├── web/
└── core/   ← submodule (repo2)
  • 명령어
1
2
3
4
5
6
7
# repo1에서 core 모듈(submodule) 추가
# 즉, repo1은 core 저장소의 특정 커밋 SHA를 기록 (.gitmodules 파일과 내부 .git/config에 저장됨)
git submodule add https://github.com/company/repo2.git core

# 서브모듈 초기화 및 업데이트
git submodule init
git submodule update
  • repo1/settings.gradle
1
2
include(":api", ":admin", ":web", ":core")
project(":core").projectDir = file("core")
  • repo1/build.gradle
1
2
3
dependencies {
    implementation project(":core")
}
  • core 코드는 같은 워크스페이스에 존재하므로, AWS 환경에서도 별도 Nexus 없이 빌드 가능해진다.

  • 이때 common은 별도의 Git 레포지토리이며, main-project 안에는 그 레포지토리의 특정 커밋을 submodule로 연결
  • 빌드 시 common의 소스 코드가 로컬에 그대로 존재하므로, AWS 환경에서도 네트워크를 통한 Nexus 접근 없이 바로 빌드 가능.
  • 즉, common 코드를 라이브러리처럼 직접 포함시키는 형태가 되는 것
  • main-project/settings.gradle:
1
2
include(":app", ":common")
project(":common").projectDir = file("common")

실습


submodule을 참조하는 repo : repo1
submodule repo : repo2

repo2에서 반영한 최신 내역 repo1에서 가져오기

  • repo1에서는 foo3 호출중 img

  • repo2에서는 foo4로 메서드명 변경해서 반영한 상태 img

  • repo1에서 repo2의 변경사항 반영하기

    1
    2
    
    1. git submodule update --remote common
    2. push
    

img

img

repo1에서 repo2 코드 변경하고 반영하기

1
2
3
4
5
1. 코드 수정
2. cd common
3. git add .
4. git commit -m "foo5 반영"
5. git push origin master

img.png

  • 지금 오류 상황은 submodule을 detached HEAD 상태에서 작업했기 때문에 발생
    • HEAD : 현재 체크아웃된 브랜치 또는 커밋을 가리키는 포인터
    • detached HEAD : 브랜치가 아닌 특정 커밋 위에서 작업하는 상태
  • Git은 항상 HEAD → 브랜치 → 커밋 순으로 연결되어 있어야 한다.
    • ex : HEAD → master → (a1b2c3)
    • 하지만 특정 브랜치가 아니라, 특정 커밋(SHA)을 직접 checkout하면 이렇게 됨: HEAD → (a1b2c3)
      • master 같은 브랜치에 연결되지 않음 즉, HEAD가 “분리(detached)”된 상태.
  • foo5 반영 커밋(a33f7410)은 아직 사라지지 않았고, 브랜치에서 연결되지 않은 “떠 있는 커밋” 상태로 남아 있다.
    • Git에서는 이런 커밋을 dangling commit이라고 한다.
    • git fsck --lost-found : dangling commit 확인

img

  • 즉, a33f7410 커밋은 브랜치(master)가 아닌, detached HEAD에서 만든 커밋이었기 때문에 master 브랜치로 이동하면서 브랜치 히스토리에서 보이지 않게 된 것. (dangling commit)
  • dangling commit을 submodule master에 붙이고 싶으면
    • git checkout master
    • git cherry-pick a33f7410
    • git push origin master

img

submodule이 detached HEAD 되는 이유

  • 서브모듈은 항상 “커밋 SHA”를 기준으로 체크아웃됨.
  • 즉, 브랜치를 checkout 한 게 아니라 SHA를 checkout 함 → detached HEAD
  • 여기서 작업 후 커밋하면 “임시 커밋”이 됨
  • 다시 master checkout 하니까 foo5 커밋은 잃어버린 것처럼 보임
  • 그러니 push 시도해도 안 됐던 것

submodule 코드 수정할 때 올바른 절차

1
2
3
4
5
6
7
1. git submodule update --remote
2. cd common
3. git switch master # 이제 HEAD가 정상 브랜치에 연결됨: HEAD → master
4. 코드 수정
5. git add .
6. git commit -m "message"
7. git push

img.png

img.png

This post is licensed under CC BY 4.0 by the author.

AWS - 상품 살펴보기

IntelliJ에서 프로젝트 세팅하기 (Gradle, Spring Boot)