[Git] Git Submodule에 대하여
Git

[Git] Git Submodule에 대하여

728x90

 

 

 개발을 하다 보면 같은 소스코드를 여러 프로젝트에서 공통으로 사용해야 하는 경우가 생긴다. A 프로젝트에서 쓰던 코드를 B 프로젝트에서도 사용해야 한다고 해보자. 간단한 경우에는 그냥 코드를 복붙 해서 사용할 수 있다. 하지만 공통으로 사용하는 코드가 많고 여러 군데에서 최신의 코드를 유지해야 한다면 단순 복붙으로는 관리하기가 힘들 것이다.

 이런 경우엔 Git의 서브모듈이라는 도구를 사용하면 관리가 수월해진다. Git 저장소 안에 다른 Git 저장소를 분리해 넣는 것이 서브모듈이다. 다른 독립된 Git 저장소를 Clone 해서 내 Git 저장소 안에 포함할 수 있으며 각 저장소의 커밋은 독립적으로 관리된다.

 

 

1. 서브모듈 시작하기

 서브모듈을 추가하기 위해선 미리 준비된 Git 저장소가 있어야 한다. 작업을 할 메인 프로젝트에서 서브모듈을 추가해보자. 서브모듈을 추가하는 명령어는 아래와 같다.

git submodule add [서브모듈 URL]
➜  Main (master) ✔ git submodule add https://github.com/tkdgusl94/lv-library.git
Cloning into '/Users/sanghyun/work/exercise/Main/lv-library'...
remote: Enumerating objects: 220, done.
remote: Counting objects: 100% (220/220), done.
remote: Compressing objects: 100% (92/92), done.
remote: Total 220 (delta 93), reused 216 (delta 89), pack-reused 0
Receiving objects: 100% (220/220), 99.43 KiB | 261.00 KiB/s, done.
Resolving deltas: 100% (93/93), done.

 서브모듈 URL 뒤에 원하는 이름을 넣어서 다른 디렉토리 이름으로 추가할 수도 있다.

 

ls -al 명령어로 서브모듈이 제대로 추가되었는지 확인해보자.

➜  Main (master) ✗ ls -al
total 8
drwxr-xr-x   5 sanghyun  staff  160 12 25 18:40 .
drwxr-xr-x  14 sanghyun  staff  448 12 25 18:33 ..
drwxr-xr-x  11 sanghyun  staff  352 12 25 18:40 .git
-rw-r--r--   1 sanghyun  staff   95 12 25 18:40 .gitmodules
drwxr-xr-x   9 sanghyun  staff  288 12 25 18:40 lv-library

 추가한 서브모듈이 정상적으로 추가된 것을 볼 수 있다. 그런데 추가한 서브모듈 이외의 .gitmodules라는 파일도 새롭게 생성되었다. .gitmodules 파일은 서브 저장소와 하위 프로젝트 URL의 매핑 정보를 담은 설정 파일이다. 이 프로젝트를 clone 하는 다른 사람은 .gitmodules 파일을 보고 어떤 서브모듈이 있는지 알 수 있다.

➜  Main (master) ✗ cat .gitmodules
[submodule "lv-library"]
	path = lv-library
	url = https://github.com/tkdgusl94/lv-library.git

 

 여기서 git diff 명령어로 어떤 부분이 바뀌었는지 확인해보자. 

➜  Main (master) ✗ git diff --cached lv-library
diff --git a/lv-library b/lv-library
new file mode 160000
index 0000000..6b2291e
--- /dev/null
+++ b/lv-library
@@ -0,0 +1 @@
+Subproject commit 6b2291ef48a52f46480aad3c2c7121d55fbddea0

 서브모듈은 다른 파일과는 다르게 해당 디렉토리 아래의 파일 수정사항을 직접 추적하지 않는다. 대신 서브모듈 디렉토리를 통째로 특별한 커밋으로 취급한다.

 서브모듈을 추가했으니 이제 커밋을 해보자.

➜  Main (master) ✗ git commit -am "add lv-library"
[master (root-commit) 1938c7b] add lv-library
 2 files changed, 4 insertions(+)
 create mode 100644 .gitmodules
 create mode 160000 lv-library

 .gitmodules 파일과는 다르게 서브모듈의 디렉토리 모드는 160000이다. 이는 Git에게 있어서 일반적인 파일이나 디렉토리가 아니라 특별하다는 의미다.

 

 

 이제 원격 저장소로 푸시를 해본다.

 github에 푸시를 하고 들어가서 보면 다른 디렉토리와는 다르게 뒤에 버전이 적혀있고, 링크가 걸려있는 것을 볼 수 있다. 이는 해당 서브모듈의 원격 저장소와 연결이 되어 있고, 적혀 있는 버전과 연결되어 있다는 뜻이다. 링크를 누르면 서브모듈의 원격 저장소의 해당 버전으로 이동된다.

 

 

2. 서브모듈을 포함한 프로젝트 clone

 이제 서브모듈이 들어있는 프로젝트를 클론 해보자.

➜  sources git clone https://github.com/tkdgusl94/Main.git
Cloning into 'Main'...
remote: Enumerating objects: 3, done.
remote: Counting objects: 100% (3/3), done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 3 (delta 0), reused 3 (delta 0), pack-reused 0
Unpacking objects: 100% (3/3), done.

➜  sources cd Main

➜  Main (master) ✔ cd lv-library
➜  lv-library (master) ✔ ls
➜  lv-library (master) ✔

  분명 서브모듈 디렉토리는 있지만 비어 있다. 서브모듈이 들어있는 프로젝트를 클론 할 때 서브모듈 디렉토리는 기본적으로 빈 디렉토리다. 만약 서브모듈이 수백 개가 들어 있는 프로젝트라고 하자. 이때 간단한 수정만 하기 위해 프로젝트를 클론 한다고 했을 때 모든 서브모듈의 내용물들을 가져온다고 하면 굉장히 비효율적일 것이다. 때문에 서브모듈이 들어있는 프로젝트를 클론 할 때는 추가적인 작업이 필요하다.

 먼저 git submodule init 명령어로 서브모듈 정보를 기반으로 로컬 환경설정 파일이 준비된다. 이후 git submodule update 명령으로 서브모듈의 리모트 저장소에서 데이터를 가져오고 서브모듈을 포함한 프로젝트의 현재 스냅샷에서 checkout 해야 할 커밋 정보를 가져와서 서브모듈 프로젝트에 대한 checkout을 한다.

➜  Main (master) ✔ git submodule init
Submodule 'lv-library' (https://github.com/tkdgusl94/lv-library.git) registered for path 'lv-library'

➜  Main (master) ✔ git submodule update
Cloning into '/Users/sanghyun/work/sources/Main/lv-library'...
Submodule path 'lv-library': checked out '6b2291ef48a52f46480aad3c2c7121d55fbddea0'

 

 위와 같은 방식으로 서브모듈을 포함하는 프로젝트를 클론 할 수 있다. 하지만 이와 같은 방법이 귀찮다면 한 번에 하는 방법도 있다. 프로젝트를 클론 할 때 --recurse-submodules 옵션을 붙이면 모든 서브모듈을 자동으로 초기화하고 업데이트한다.

➜  sources git clone --recurse-submodules https://github.com/tkdgusl94/Main.git
Cloning into 'Main'...
remote: Enumerating objects: 3, done.
remote: Counting objects: 100% (3/3), done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 3 (delta 0), reused 3 (delta 0), pack-reused 0
Unpacking objects: 100% (3/3), done.
Submodule 'lv-library' (https://github.com/tkdgusl94/lv-library.git) registered for path 'lv-library'
Cloning into '/Users/sanghyun/work/sources/Main/lv-library'...
remote: Enumerating objects: 220, done.
remote: Counting objects: 100% (220/220), done.
remote: Compressing objects: 100% (92/92), done.
remote: Total 220 (delta 93), reused 216 (delta 89), pack-reused 0
Receiving objects: 100% (220/220), 99.43 KiB | 275.00 KiB/s, done.
Resolving deltas: 100% (93/93), done.
Submodule path 'lv-library': checked out '6b2291ef48a52f46480aad3c2c7121d55fbddea0'

 

 

3. 서브모듈 업데이트하기

 서브모듈이 만약 업데이트가 됐다면, 이를 사용하는 다른 프로젝트에서도 업데이트가 필요할 것이다. 서브모듈을 최신 상태로 업데이트하는 명령은 git submodule update --remote이다. 

➜  Main (master) ✔ git submodule update --remote
remote: Enumerating objects: 5, done.
remote: Counting objects: 100% (5/5), done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 3 (delta 2), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (3/3), done.
From https://github.com/tkdgusl94/lv-library
   6b2291e..46c5886  main       -> origin/main
fatal: Needed a single revision
Unable to find current origin/master revision in submodule path 'lv-library'

 뭔가 이상하다. 변경된 데이터는 정상적으로 받아온 것 같은데 최근 커밋으로 checkout은 되지 않았다. 이는 github의 default 브랜치가 최근에 master에서 main으로 변경되었기 때문이다. (github.com/github/renaming)

 예제로 사용하는 서브모듈을 만들 때 default 브랜치를 main으로 만들었고, 따라서 origin/master 브랜치가 없다는 오류가 생긴 것이다. 이와 같은 오류가 생긴 이유는 위의 명령어가 기본적으로 서브모듈 저장소의 master 브랜치를 checkout 하고 업데이트를 하기 때문이다. 

 따라서 정상적으로 서브모듈을 업데이트하기 위해선 업데이트할 대상 브랜치를 변경해줘야 한다.

➜  Main (master) ✔ git config -f .gitmodules submodule.lv-library.branch main
➜  Main (master) ✗ git submodule update --remote
Submodule path 'lv-library': checked out '46c5886add99950a0d15b749acee38b8a4ca3e03'

 대상 브랜치를 main으로 변경한 뒤, 다시 업데이트를 해주면 정상적으로 checkout 된 것을 확인할 수 있다.

 위의 명령어는 기본적으로 모든 서브모듈을 업데이트한다. 서브모듈이 엄청 많을 땐 특정 서브모듈만 업데이트하고자 할 수도 있는데 이럴 때는 서브모듈의 이름을 지정해서 명령을 실행한다.

➜  Main (master) ✗ git submodule update --remote lv-library
Submodule path 'lv-library': checked out '46c5886add99950a0d15b749acee38b8a4ca3e03'

 

 여기서 만약 그냥 git submodule update 명령어를 수행하면 어떻게 될까?

➜  Main (master) ✗ git submodule update
Submodule path 'lv-library': checked out '6b2291ef48a52f46480aad3c2c7121d55fbddea0'

 원격에서 가져온 46c588이 아니라 이전 버전인 6b2291로 다시 checkout 하게 된다. 이는 메인 프로젝트에서 마지막으로 커밋했을 때의 버전으로 checkout 하기 때문이다. --remote 옵션을 붙이면 원격에서 가져온 최신 버전으로 checkout 하고, 옵션을 붙이지 않는다면 마지막으로 커밋했을 때의 버전으로 checkout 한다는 점을 주의해야 한다.

 

 

 

 서브모듈을 사용하기 위한 기본적인 방법에 대해 알아보았다. 이밖에 몇 가지 유용한 명령어를 소개해보고자 한다.

git submodule update --remote --recursive

 서브모듈을 최신 버전으로 업데이트한다고 가정해보자. 위에서 설명한 대로 git submodule update --remote 명령어로 최신 버전으로 업데이트할 수 있다. 하지만 서브모듈 안에 또 다른 서브모듈이 포함되어 있다면 그 서브모듈은 업데이트가 되지 않는다. 이럴 때 --recursive 옵션을 붙여주면 서브모듈 안에 포함되어 있는 서브모듈까지 업데이트를 해준다.

 

git submodule foreach [명령어]

 모든 서브모듈에 대해서 동일한 명령어를 수행하고 싶을 때가 생긴다. 이럴 때는 위의 명령어를 사용하면 된다. 예를 들어, 모든 서브모듈을 git pull을 하고 싶다면, git submodule foreach git pull 이런 식으로 사용하면 된다.

 

 

참고

Git - 서브모듈

생활코딩 / 저장소 안에 저장소 - git submodule

 

728x90