클라우드 시대에 맞지 않게 사실 난 내 컴퓨터에 자료를 보관하는 것을 선호한다. 완벽한 보안은 없기 때문이라고 하지만, 생각해보면 그렇게까지 중요한 것도 없다. 파일 자체로 보관할 수 있고, 어떤 운영체제에서도 비슷한 모양으로 열어 볼 수 있으며, 사용법까지 간단한 마크다운을 이용하여 글을 쓰는 것을 좋아한다. 그래서 블로그를 만들때, contentful같은 headless CMS를 붙여서 사용할까 고민하다가도 (사실 적용해봤는데 생각보다 불편했다.) 파일로 쌓이는게 보고 싶어서 그냥 마크다운 파일로 포스트를 작성하기로 했다.

그런데 조금 신경쓰이는 부분이 있었다.

  • 블로그 코드는 공개하고 싶은데, 블로그 포스트 마크다운 파일을 쌩(?)으로 공개하기는 좀 부끄럽다.
  • 사소하게 깨작깨작 수정하는데, repository에 수정된 내용이 모두 공개되면서 쌓이는것도 부담스럽다.
  • 포스트에 사용되는 이미지의 원본 파일을 공개하고 싶지 않다.

사실 블로그 repository를 공개하지 않으면 간단히 해결될 일이지만, 그래도 코드를 공개하고 싶은 것이 개발자의 솔찍헌 심정(?)이니까... 그냥 몇개만 숨기고 싶은뒈! 하다가 알게 된 것이 git의 서브모듈이다.

submodule(서브모듈)이란?

원래 서브모듈은 프로젝트 안에 다른 프로젝트를 함께 사용할 경우 사용되는 도구이다. 서로 별개로 다루면서도 다른 프로젝트 안으로 연결 시키는 것이다. 참고로 각 repository의 커밋은 독립적으로 관리한다.

블로그에선 다른 프로젝트는 아니지만, contents 폴더를 비공개 repository로 따로 분리하고, 블로그 프로젝트에서 서브 모듈로 관리하려 한다.
이렇게 되면 공개된 리포지토리는 코드를 누구나 볼 수 있고, contents폴더는 접근이 불가능하다.(클론시에도 해당 폴더는 디렉토리만 있을 뿐 빈폴더이다.)

서브모듈 적용하기

submodule add

submodule 추가하기

각 숫자는 아래 단계와 같다.
  1. 메인 프로젝트(Main))와 서브 프로젝트(Sub)를 각각 repository를 설정한다.

    • 이 블로그에서 메인 프로젝트는 블로그로 공개 repository이며, 서브 프로젝트는 컨텐츠 폴더로 비공개 repository이다.
    • Main은 (A), Sub는 (가) 첫번째 commit이고, 모두 push한다.
  2. Local-Main 디렉토리 속 원하는 위치에서 서브모듈을 추가한다.

    $ git submodule add [repository url] [submodule-name]
    • 메인 프로젝트에 파일 .gitmodules[name]으로 서브모듈 디렉토리가 생성된다.
    • `.gitmodules' 에는 다음과 같은 내용이 만들어진다.
    .gitmodules
    [submodule "name"]
     	path = name
     	url = [repository url]
    • Local-Main 디렉토리에서 git status로 확인해보면, 이미 add되어 있는 상태이다.
    • commit(B)한다.

submodule-vscode

submodule을 추가하면, vs코드 파일 목록 끝에 작게 s가 보인다

  1. 필요하다면 Remote-Main에 push까지.

    • 폴더명 @ 커밋id 형태로 서브모듈인 폴더가 어디를 가리키고 있는지 표시된다.
    • 위 이미지에선 서브모듈은 (가)를 가리키고 있으므로 name @ (가)로 표시될 것이며, 메인프로젝트에선 (B)커밋을 가리킨다.

submodule-github

메인 프로젝트 repository에서 보이는 모습

화살표가 있는 폴더 아이콘/디렉토리/@Remote-Sub의 commit id/Remote-Main commit 메세지

서브모듈 수정하기

이제 포스트를 작성(수정)할 것이다. 방법은 두가지이다.

메인 프로젝트 속 서브 모듈 디렉토리에서 수정

submodule update 1

submodule 업데이트

  1. Local-Main 속 submodule 위치에서 새로운 파일을 추가하거나, 수정한다.

    • ~...Main/submodule 위치, 즉 서브모듈 위치에서 add, commit, push한다.
    • 서브모듈에서 push했기 때문에 Remote-Sub의 해당 브랜치가 업데이트 된다.
    • 여기까지 Main은 (B)를, Local-submodule은 (나)를 가리키고 있고, 그리고 Remote-submodule은 아직 (가)를 가리킨다.
  2. Local-Main 상단에서 서브 모듈이 변화했다는 것을 commit(C)한다.

    • ~...Main 위치에서 git status로 확인 해보면, modified: submodule-name (new commits)으로 서브 모듈 디렉토리가 수정된 사항이 보인다.
  3. Remote-Main에 push.

    • 이 시점에서 Remote-submodule은 (나)를 가리킨다.
  4. 이 방법은 Local-Sub에 변화내용이 반영되지 않는다.

    • 업데이트를 하길 원한다면, git pull

서브 프로젝트 디렉토리에서 추가한 후 메인 프로젝트에서 수정

submodule update 2

git pull

  1. Local-Sub에서 새로운 파일을 추가하거나, 수정한다.

    • ~...Sub 위치, 서브 프로젝트 위치에서 add, commit, push한다.
    • Remote-Sub의 해당 브랜치가 업데이트 된다.
  2. Local-Main 속 submodule로 이동하여 pull한다.

    • git pull or git fetch + git merge
    • pull은 fetch와 merge를 동시에 하는 작업이니, 주의해야한다.
    • 해당 브랜치의 commit으로 업데이트 된다.
  3. Local-Main 상단에서 서브 모듈의 변화했다는 것을 commit(C)한다.

    • 이 시점에서 Remote-Main은 (B), Remote-submodule은 (나)를 가리킨다.
  4. Remote-Main에 Push

서브모듈이 있는 프로젝트 clone하기

submodule clone update

submodule을 가진 프로젝트 clone

  1. 서브모듈을 포함하는 프로젝트를 클론한다.

    • 디렉토리는 존재하지만, 비어있다.
  2. 서브모듈을 초기화 한다.

    $ git submodule init
    • 서브모듈 저장소(Remote-sub)와 클론한 프로젝트 속 서브모듈(Local-submodule)을 연결해준다.
  3. Local-Main 디렉토리에서 서브 모듈의 변화한 것을 commit(C)한다.

    $ git submodule update
    • Remote-Sub의 브랜치를 Checkout하고 업데이트한다.
    • 프로젝트 속 서브모듈(Local-submodule) 속은 대상 커밋과 동일한 상태가 된다.
    • 브랜치를 확인해 보면, * (detached from 커밋커밋) 상태이다. 로컬에서 (추적하는) 브랜치 없이, 바로 커밋을 체크아웃하기 때문이다.
    • 만약 이 상태에서 다른 수정을 한다고 하면, 커밋 체크아웃 상태를 (특정)브랜치에 merge 한 후, 해당 브랜치를 푸시하고, 관리한다. 자세한 사항은 detached HEAD 참고!
  4. 1~3단계를 한번에 끝내는 방법이 있다.

    $ git clone --recurse-submodule 주소
    • 클론할때 --recurse-submodule 옵션을 추가하여 클론시 서브모듈을 자동으로 초기화하고 업데이트까지 한다.

메인 프로젝트에서 (수정없이) 서브모듈 업데이트

다른 수정 없이 그냥 서브모듈이 최신 커밋으로만 업데이트 돼도 된다면, 계속 detached HEAD상태여도 상관은 없다.

submodule update --remote

git submodule update --remote

  1. Local-Sub에서 새로운 파일을 추가하거나, 수정이 됐다.

    • Remote-Sub에 push까지 마치고 수정이 되어있다. 커밋id는 (나).
  2. Local-Main 디렉토리에서 submodule을 업데이트한다.

    • Remote-Sub의 최신 커밋을 Checkout하고 업데이트한다.
    • 서브모듈이름을 뒤에 넣으면 해당 서브모듈만 업데이트 된다. 넣지 않으면 서브모듈 전체가 업데이트.

      $ git submodule update --remote [submodule-name]
    • 만약 서브모듈이 그 전에도 detached HEAD상태(가)였다면, 새로운 커밋(나)으로 detached HEAD상태가 된다.
    • 만약 detached HEAD가 아닌 master 브랜치로 체크아웃하여 관리 하여왔고, 지금하는 업데이트도 브랜치로 관리하고싶다면, --merge 옵션을 추가해야한다.

      $ git submodule update --remote --merge [submodule-name]
  3. Local-Main 디렉토리에서 서브 모듈의 변화한 것을 commit(C)한다.

    • Local-Main 디렉토리에서 git status를 실행하면, submodule은 커밋이 완료된 상태(new commits)로 나온다.

      On branch master
      Your branch is up to date with 'origin/master'.
      Changes not staged for commit:
      (use "git add <file>..." to update what will be committed)
      (use "git checkout -- <file>..." to discard changes in working directory)
      
         modified:   submodule (new commits)
  4. Remote-Main에 Push

    • 이제 Remote-Main은 (C), Remote-submodule은 (나)를 가리킨다. 저장소에도 업데이트 완료!
    • 단계 2에서 --merge 옵션이 없으면, Local-submodule은 (나)커밋을 직접 가리키며(detached HEAD), --merge 옵션이 있으면 master 브랜치를 거쳐 (나)커밋을 가리킨다.

마무리

git submodule의 다양한 옵션과 자세한 설명은 Pro Git 7.11을 참고.
어느 작업이나 마찬가지지만, 특히 다수와 함께 작업을 하는 경우에는, submodule의 브랜치와 진행 상황을 파악하고 작업해야한다. (참고로 git diff를 사용하면 달라진 부분을 알 수 있다.)
물론 이 블로그를 관리하는 것처럼 혼자서 submodule을 사용할 때 조차도, 여러 방향으로 작업을 하다 보면 작업이 꼬일 위험이 있다. 특히 git submodule update, pull, merge 등의 작업때는 주의해야한다. 공들여 작업한 것이 사라지지 않도록 자신의 방법을 하나로 정하는 것이 좋을 것 같다.
또한 지금 내가 작업하고 있는 브랜치가 무엇인지, detached HEAD는 아닌지 잘 살펴보아야 한다. detached HEAD라는 것이 익숙치 않아서, 새로운 포스트 detached HEAD에 더 자세히 작성해보았다.

Reference & Learn More

Pro Git 7.11 Git 도구 - 서브모듈
Pro Git Reference git-submodule

Comments