LabHub
배우기 러닝패스 코스

Git実戦

自分の環境では動く — サブモジュールが古いコミットに刺さっていた

LabHub 에서 이어서 보기

한국어 원문으로 표시합니다.

목표

라이브러리 저장소를 부모 저장소에 서브모듈로 꽂고, 갱신을 부모에 커밋하지 않았을 때 동료가 옛 코드를 받는 사고를 직접 만들어 봅니다.

왜 중요한가

서브모듈에서 나는 사고는 거의 전부 한 가지 오해에서 나옵니다 — 부모가 브랜치를 가리킨다는 오해입니다. 부모의 트리에 들어가는 것은 모드 160000 인 항목 하나이고 값은 커밋 해시입니다. 브랜치가 아니라 커밋이므로, 서브모듈은 항상 detached HEAD 로 체크아웃되고, 라이브러리에 새 커밋이 쌓여도 부모는 저절로 따라가지 않습니다. 이 실습은 그 사실을 설명으로 듣는 대신 이력에서 직접 꺼내 보게 합니다. 그리고 git clone 이 기본으로 서브모듈을 받지 않는다는 것까지 확인하면, "CI 에서만 빌드가 깨진다" 는 흔한 증상이 왜 생기는지가 한 번에 정리됩니다.

단계

  1. /root/gitx5/lib 에 커밋 2개짜리 라이브러리 저장소를 만듭니다(version.txt1.0.0 에서 1.1.0 으로).
  2. /root/gitx5/app 을 만들고 libvendor/lib 자리에 서브모듈로 꽂습니다. 첫 시도가 막히는 것을 notes/protocol.txt 에 남깁니다.
  3. 부모가 기억하는 것이 무엇인지 notes/pointer.txt 에 남깁니다.
  4. lib1.2.0 커밋을 쌓고 서브모듈을 그 커밋으로 옮긴 뒤, detached HEAD 와 submodule status 의 앞 글자를 notes/detached.txt 에 남깁니다.
  5. 그 갱신을 부모에 커밋합니다.
  6. lib1.3.0 을 쌓고 서브모듈만 옮긴 채 부모는 커밋하지 않은 상태에서 /root/gitx5/clone 을 떠, 받은 쪽이 몇 번을 보는지 notes/accident.txt 에 적습니다.
  7. --recurse-submodules 없이 /root/gitx5/clone2 를 떠서 앞 글자 - 를 만들고, 네 가지 앞 글자를 notes/status.txt 에 정리합니다.
  8. notes/report.md 에 서브모듈을 쓸 때의 규칙을 정리합니다.

참고

공통 라이브러리 저장소 만들기

/root/gitx5/lib 에 커밋 2개짜리 라이브러리 저장소를 만듭니다(version.txt1.0.0 에서 1.1.0 으로).

git init -b main 으로 만들고 저장소마다 user.email·user.name 을 정합니다. version.txt 한 줄만 바꿔 커밋 두 개를 쌓으면 됩니다 — 뒤 단계에서 이 값이 어느 커밋에 꽂혔는지를 눈으로 보는 표시가 됩니다.

로컬 경로 서브모듈이 기본으로 막힌다

/root/gitx5/app 을 만들고 libvendor/lib 자리에 서브모듈로 꽂습니다. 첫 시도가 막히는 것을 notes/protocol.txt 에 남깁니다.

git submodule add /root/gitx5/lib vendor/lib 를 그냥 하면 transport 'file' not allowed 로 막힙니다. CVE-2022-39253 이후의 기본값입니다. git -c protocol.file.allow=always submodule add ... 로 다시 하고, 꽂은 뒤에는 .gitmodulesvendor/lib커밋까지 해야 합니다.

부모의 트리에 들어간 한 줄

부모가 기억하는 것이 무엇인지 notes/pointer.txt 에 남깁니다.

git ls-tree HEAD vendor/lib 한 줄이 서브모듈의 전부입니다. 모드가 무엇인지, 그 값이 lib 의 어느 커밋인지, 그리고 .gitmodules 에는 무엇이 적혀 있는지 세 가지를 함께 적으세요. 브랜치 이름이 어디에도 없다는 것이 요점입니다.

서브모듈은 언제나 detached HEAD 다

lib1.2.0 커밋을 쌓고 서브모듈을 그 커밋으로 옮긴 뒤, detached HEAD 와 submodule status 의 앞 글자를 notes/detached.txt 에 남깁니다.

lib 에서 1.2.0 을 커밋한 뒤, app/vendor/lib 안에서 git fetch origin 하고 그 커밋으로 git checkout 합니다. 그 안에서 git status 첫 줄이 무엇인지, 그리고 부모에서 git submodule status앞 글자 한 칸이 무엇으로 바뀌는지 보세요. 아직 부모를 커밋하지는 않습니다.

갱신을 부모에 커밋한다

그 갱신을 부모에 커밋합니다.

부모에서 git add vendor/lib 를 하면 gitlink 값 하나가 스테이징됩니다. 디렉터리 안의 파일들이 아니라 커밋 해시 한 줄이 바뀌는 것이라, git diff --cached 를 보면 Subproject commit 두 줄만 나옵니다.

커밋하지 않은 갱신은 나만의 것이다

lib1.3.0 을 쌓고 서브모듈만 옮긴 채 부모는 커밋하지 않은 상태에서 /root/gitx5/clone 을 떠, 받은 쪽이 몇 번을 보는지 notes/accident.txt 에 적습니다.

lib1.3.0 을 커밋하고 app/vendor/lib 를 그리로 옮깁니다. 부모에서 git status 를 보면 vendor/lib 가 수정된 것으로 뜨는데, 커밋하지 마세요. 그 상태로 git -c protocol.file.allow=always clone --recurse-submodules /root/gitx5/app /root/gitx5/clone 을 하고 양쪽 version.txt 를 견줍니다.

앞 글자 한 칸이 말하는 네 가지

--recurse-submodules 없이 /root/gitx5/clone2 를 떠서 앞 글자 - 를 만들고, 네 가지 앞 글자를 notes/status.txt 에 정리합니다.

git clone /root/gitx5/app /root/gitx5/clone2 는 서브모듈을 받아오지 않습니다. 그 안에서 git submodule status 를 하면 앞 글자가 무엇인지 보세요. 초기화하는 명령이 무엇인지도 함께 적으면 다음에 CI 가 깨질 때 바로 씁니다.

서브모듈을 쓸 때의 규칙

notes/report.md 에 서브모듈을 쓸 때의 규칙을 정리합니다.

이번에 직접 만든 세 가지 — 부모가 기억하는 것, detached HEAD, 갱신 누락 사고 — 를 규칙 형태로 바꿔 쓰세요. clone 할 때 무엇을 해야 하는지와, 리뷰에서 Subproject commit 한 줄짜리 diff 를 어떻게 읽을지도 함께 적습니다.