What is the submodule

아래의 내용은 정리하면서 검증한 부분도 있지만, 검증이 되지 않고 정리된 내용도 있을 수 있으므로, 반드시 관련된 정보를 추가로 검색하여 검증해야 한다.

Submodule?

Git 프로젝트는 하나의 독립된 라이브러리라고 생각할 수 있다. 따라서 특정 프로젝트를 진행할 때 또 다른 프로젝트를 내부적으로 사용해야하는 경우가 빈번하게 발생할 수 있다. 일반적으로 이런 상황에서는 필요한 라이브러리의 특정 버전을 설치하거나 소스를 복사해서 사용하는 방법을 사용하는데 이에 따른 버전관리나 소스의 변경등에 대한 대처가 만만치 않다.

Git와 Submodule을 사용하면 Git Repository 내에 또 다른 독립적인 Repository를 운영하는 구조를 만들 수 있다. 당연히 각 Repository는 독립적으로 관리된다.

이런 상황이 새로운 것일까?
Node 환경이라면 이미 npm을 이용해서 package를 관리하며 사용하고 있다. 그런데 가져다 쓸 패키지가 git에 repository로만 존재하는 경우라면 어떻게 해야할 까?
이런 상황에 대체하기 위한 것이 submodule이고 정리하면 “submodule은 git repository 하위에 다른 git repository를 관리하기 위한 도구” 라고 생각하면 된다.

자세한 내용은 Pro Git의 내용을 참고!

사용법 정리

기본 옵션들

git submodule에 대한 공식 옵션은 다음의 명령으로 확인 가능하다.

git submodule 도움말
$ git submodule --help

main project와 submodule project 관계

main project에 submodule을 추가하면 submodule들을 관리하기 위한 .gitmodules 파일이 생성되고 여기에 submoudle에 대한 폴더와 repository 정보가 관리되며 main project는 추가된 submodule에 대한 commit point를 바라보고 있게 된다.

따라서 main project와 submodule 간의 처리는 아래와 같은 순서에 주의해야 한다.

  • clone / pull / update : 리모트에서 로컬로 데이터를 가져올 경우는 Parent 먼저 처리 후 submodule을 처리해야 한다.
  • commit / push : 로컬 commit과 리모트로 데이터를 전송하는 경우는 submodule 먼저 처리 후 Parent를 처리해야 한다.

main project가 submodule에 대한 commit point를 연계하고 있기 떄문에 위의 순서가 맞지 않으면 잘못된 submodule의 commit point를 바라보게 되므로 문제가 발생할 수 있다.

submodule 추가

submodule 추가 (Main Project Root 폴더)
$ git submodule add [-b branch_name] <repository address> [path]
# path를 생략하면 현재 경로 밑으로 repository 이름으로 생성된다.

정상적으로 추가되면 main project 경로에 .gitmodules 파일이 생성되며, 내용은 아래와 같이 구성된다.

.gitmodules 파일 내용 (Main Project Root 폴더)
[submodule "themes/hugo-theme-docdock"]
path = themes/hugo-theme-docdock
url = https://github.com/vjeantet/hugo-theme-docdock.git

위와 같이 Root Repository (여기서는 Hugo로 만든 사이트) 를 기준으로 경로와 git repository url이 관리되고 있는 것을 확인할 수 있다.

submodule이 추가된 것은 main project가 변경된 것을 의미하기 때문에 commit을 수행해 줘야 한다. 특정 branch로 지정할 경우는 -b <branch name>을 사용하면 된다. 또한 나중에 submodule에 대한 branch를 설정할 경우는 아래와 같이 처리할 수도 있다.

submodule에 대한 Branch 설정 (Main Project Root 폴더)
$ git config -f .gitmodules submodule.<submodule path>.branch <branch name>

submodule 상태 확인

기본적인 git 상태 확인은 아래의 명령을 사용하며 submodule과 연동해서 사용할 수 있다.

submodule 상태 확인 (Main Project Root 폴더)
# main project 상태 확인
$ git status

# submodule 상태 확인
$ git submodule status

# main project 상태확인에 submodule 정보 추가해서 보기 설정
$ git config status.submodulesummary 1

main project에서 바라보는 submodule 상태

main project에서 바라보는 submodule 상태 (Main Project Root 폴더)
$ git diff --cached <submodule path>
new file mode 160000
index 0000000..51dbdcf
--- /dev/null
+++ b/themes/hugo-theme-learn
@@ -0,0 +1 @@
+Subproject commit 51dbdcf4aaef01d02e78a6ea76b2a6fde5842b55

위의 내용을 검토해 보면 다음과 같은 특징이 존재한다.

  • mode 160000은 일반적인 파일이나 디렉토리가 아니라는 의미를 가진다.
  • Subproject commit 51dbdcf4aaef01d02e78a6ea76b2a6fde5842b55는 main project가 submodule repository의 51dbdcf4aaef01d02e78a6ea76b2a6fde5842b55 commit 정보를 바라보고 있다는 의미를 가진다.

Detached HEAD 문제

Detached HEAD 문제란 submodule update 명령이 수행되면 해당 submodule이 어떤 branch에도 속하지 않는 분리된 HEAD 정보를 가지는 상태를 말하는 것으로 branch를 지정해 줘야 한다. 따라서 아래와 같이 각 단계별 명령을 수행할 때 지정해 주는 것이 좋다.

Detach HEAD 방지 처리 (Main Project Root 폴더)
# 처음 clone할 때
$ git submodule init
$ git submodule udpate
$ git submodule foreach git checkout <branch name>

# pull/update할 때
$ git pull
$ git submodule update --remote --merge

# push할 때
$ git push --recurse-submodules=check

git detached HEAD에 대한 내용은 정보를 참고하고, submodule과 detached HEAD에 대한 내용은 정보를 참고하면 된다.

단축 명령 및 Config 설정

위에서 알아본 명령들을 별칭(Alias) 등록을 통해서 단축 명령으로 수행할 수도 있다.

Alias 기준 단축 명령 등록 (Main Project Root 폴더)
# git sdiff 단축 정의
$ git config alias.sdiff '!'"git diff && submodule foreach 'git diff'"
# git spush 단축 정의
$ git config alias.spush 'push --recurse-submodules=check'
# git supdate 단축 정의
$ git config alias.supdate 'submodule update --remote --merge'

main project에 아래와 같이 정보를 설정해 두면 활용하기 쉽다.

main project에 submodule연계 환경 설정 (Main Project Root 폴더)
# login
$ git config credential.helper cache
# status
$ git config status.submodulesummary 1
# diff
$ git config diff.submodule log

이미 submodule을 포함하고 있는 Repository 활용하기

clone and update submodules

  • Parent git project clone : .gitmodules 파일이 존재하며, 파일내에 어떤 폴더에 어떤 git repository를 사용하는지에 대한 정보를 알 수 있다.

    Parent 프로젝트 Clone
      $ git clone <main project repository>
    

  • submodule clone

    submodule 프로젝트들 Clone (Clone된 Main Project Root 폴더)
    $ git submodule init      # submodule 초기화
    $ git submodule update    # submodule 갱신(다운로드 - Detached HEAD 상태로 어떤 branch에도 속하지 않는 상태)
    $ git submodule foreach git checkout <branch name>   # 지정한 branch로 설정
    

위의 clone 과정을 한번에 처리할 수도 있다.
$ git clone –recurse-submodules <main project repository>
$ git submodule foreach git checkout

이미 로컬에 submodule이 존재하는 경우 갱신

이미 존재하는 submodule 갱신 (Main Project Root 폴더)
$ git submodule update --remote --merge

commit and push submodules

  • 만일 새로운 branch로 작업하는 경우

    새로운 Branch로 작업 (Main Project Root 폴더)
    $ git submodule foreach git checkout -b <feature name>
    

  • 변경된 코드들 commit

    변경된 코드들 Commit (Main Project Root 폴더)
    $ git submodule foreach git add -A .
    $ git submodule foreach git commit -am "commit message"
    $ git submodule foreach git checkout <branch name>
    $ git submodule foreach git merge <feature name>
    

  • push submodules

    Submodule Push (Main Project Root 폴더)
    $ git submodule foreach git push
    

  • commit and push main project

    Main Project Push (Main Project Root 폴더)
    $ git commit -am "commit message"
    $ git push –recurse-submodules=check
    

위에서 언급한 것과 같이 push를 할 때는 반드시 submodule들을 먼저 처리하고 main project를 처리 해야 한다. 따라서 submodule들이 모두 push되었는지를 확인해야 한다.

Main Project Push 전에 모든 Submodule들이 Push된 상태인지 확인 (Main Project Root 폴더)
$ git push --recurse-submodules=check     # submodule들이 모두 push된 상태인지를 검사하고 확인되면 push 처리
$ git push --recurse-submodules=on-demand # submodule들을 모두 push하고 성공하면 push 처리

매번 명령어를 지정해서 처리하는 것을 항상 처리할 수 될 수 있도록 하기 위해서는 아래와 같이 설정으로 처리해 두면 된다.

항상 처리될 수 있도록 설정 (Main Project Root 폴더)
$ git config push.recurseSubmodules check
$ git config push.recurseSubmodules on-demand

submodule 제거

  1. .gitmodules 파일에서 대상 submodule 정보를 삭제한다.
  2. 변경된 내용을 stage 처리 한다.

    변경사항 Stage 처리 (Main Project Root 폴더)
    $ git add .gitmodules
    

  3. .git/config 파일에서 대상 submodule 정보를 삭제한다.

  4. git 에서 cached 인덱스 정보를 제거한다.

    Cached Index 삭제 (Main Project Root 폴더)
    $ git rm –cached <path to submodule>   # 경로 맨 뒤의 '/'는 사용하지 않는다.
    

  5. ./git/modules 폴더에서 대상 submodule 폴더 정보를 삭제한다.

    git modules 에서 Submodule 경로 삭제 (Main Project Root 폴더)
    $ rm .git/modules/<path to submodule>   # 경로 맨 뒤의 '/'는 사용하지 않는다.
    

  6. 변경된 내용을 commit 한다.

  7. 제거된 submodule 폴더를 삭제한다.

    제거된 Submodule 폴더 삭제 (Main Project Root 폴더)
    $ rm -rf <path to submodule>
    

위의 내용은 기본적인 git 명령을 사용한 것이고 submodule 명령을 통하면 다음과 같이 더 간단하게 처리가 가능할 수 있다.

  1. git에서 submodule 관련 정보 삭제

    Git의 Submodule 관련 정보 삭제 (Main Project Root 폴더)
    $ git submodule deinit <path to submodule>
    

  2. git에서 submodule 삭제

    Git의 Submodule 삭제 (Main Project Root 폴더)
    $ git rm <path to submodule>
    

  3. 변경 내용 commit

  4. submodule 폴더 삭제

    git modules 에서 Submodule 경로 삭제 (Main Project Root 폴더)
    $ rm .git/modules/<path to submodule>   # 경로 맨 뒤의 '/'는 사용하지 않는다.
    

참고자료