Git은 코드 같은 글자 파일을 다루는 데엔 천재인데, 디자인 원본이나 동영상 같은 덩치 큰 파일 앞에선 갑자기 굼떠져요. 저는 예전에 몇백 메가짜리 시안 파일을 아무 생각 없이 커밋했다가, 저장소를 새로 받는 데만 몇 분씩 걸리는 지옥을 팀 전체에 안긴 적이 있어요. 이번 편은 그때 배운 두 가지, Git LFS서브모듈 이야기예요. 둘 다 처음엔 낯설지만 알고 나면 든든한 도구예요.

84.1 큰 파일을 그냥 커밋하면 왜 안 되나요?

Git은 파일이 바뀔 때마다 모든 버전을 저장소 안에 쌓아 둬요. 글자 파일은 바뀐 줄만 영리하게 저장하지만, 이미지나 동영상 같은 바이너리(글자가 아닌 파일)는 조금만 고쳐도 사실상 통째로 새로 쌓여요. 그래서 100메가 파일을 열 번 수정하면 저장소가 1기가 가까이 부풀죠.


더 무서운 건 한 번 커밋하면 히스토리에 영원히 남는다는 점이에요. 나중에 파일을 지워도 과거 커밋 안에 그대로 있어서, 저장소를 받는 모든 사람이 그 무게를 평생 짊어져요. 새로 온 동료가 저장소 하나 받는 데 커피 한 잔을 다 비우게 되는 거죠. 그래서 큰 파일은 커밋하기 전에 다르게 다뤄야 해요. 이미 넣고 나서 빼내는 건 히스토리를 통째로 손봐야 하는 큰 공사라, 훨씬 아파요.

84.2 Git LFS는 어떻게 도와주나요?

LFS는 Large File Storage, 큰 파일 저장소예요. 원리가 똑똑해요. 큰 파일을 저장소에 직접 넣는 대신, 주소가 적힌 작은 쪽지만 저장소에 커밋하고 진짜 덩치는 따로 마련된 창고에 보관해요. 저장소엔 가벼운 쪽지만 쌓이니 받고 오가는 게 빨라지죠. 먼저 저장소에서 한 번 켜 줘요.


git lfs install


그다음 어떤 종류를 LFS로 다룰지 알려 줘요. 예를 들어 포토샵 원본을 맡기려면 이렇게 해요.


$ git lfs track "*.psd"
Tracking "*.psd"


이러면 .gitattributes라는 파일에 규칙이 한 줄 적혀요.


*.psd filter=lfs diff=lfs merge=lfs -text


이제 .psd 파일을 여느 때처럼 add하고 commit하면, Git이 알아서 쪽지만 커밋하고 원본은 창고로 보내요. 잘 맡겨졌는지는 목록으로 확인해요.


$ git lfs ls-files
25b59a185a * design.psd


이 줄이 보이면 design.psd가 LFS로 관리되고 있다는 뜻이에요. 동료가 저장소를 받으면 처음엔 쪽지만 오고, 실제로 그 파일을 열거나 받을 때 창고에서 원본을 끌어와요. 그래서 안 건드리는 무거운 파일은 굳이 안 받아도 돼 저장소가 날렵하게 유지돼요.

84.3 LFS 쓸 때 뭘 조심해야 해요?

가장 흔한 실수가 순서예요. 파일을 이미 커밋한 다음에 뒤늦게 track을 걸면, 그 파일은 이미 저장소 히스토리에 무겁게 들어간 뒤라 LFS로 안 옮겨져요. 그래서 track을 먼저 걸고, 그다음에 add하는 순서를 꼭 지켜야 해요. 저는 이걸 헷갈려서 저장소를 통째로 청소한 적이 있어요.


또 하나, .gitattributes 파일을 꼭 함께 커밋해야 해요. 이 파일이 규칙표라서, 이게 없으면 동료가 저장소를 받아도 어떤 파일이 LFS인지 몰라요. 그리고 LFS는 저장소를 올려 두는 곳(원격 서버)이 지원해야 창고가 마련돼요. 팀에서 쓰는 곳이 LFS를 받아 주는지, 창고 용량 한도는 어떤지 미리 확인하는 게 좋아요. 무턱대고 몰아 넣다 한도를 넘겨 막히는 일이 종종 있거든요. 마지막으로 글자 파일은 LFS에 넣지 마세요. 코드나 문서는 Git이 바뀐 줄만 영리하게 저장해서 원래도 가벼운데, 굳이 LFS로 빼면 오히려 관리만 번거로워져요. LFS는 글자가 아닌 무거운 파일에만 쓰는 게 원칙이에요.

84.4 서브모듈은 언제 쓰나요?

서브모듈은 완전히 다른 문제를 풀어요. 여러 프로젝트가 같은 공용 코드를 나눠 쓰고 싶을 때가 있잖아요. 회사 여러 서비스가 함께 쓰는 디자인 부품 모음 같은 거요. 이걸 복사해서 여기저기 붙여 넣으면, 하나 고칠 때마다 모든 곳을 손봐야 해서 금세 엇갈려요.


서브모듈은 다른 저장소를 내 저장소 안에 통째로 얹어 쓰는 방법이에요. 핵심은 내가 그 공용 저장소의 특정 시점(커밋)을 콕 집어 고정해 둔다는 거예요. 그래서 공용 코드가 저쪽에서 막 바뀌어도, 내 프로젝트는 내가 고정한 버전 그대로 안정적으로 돌아가요. 내가 올릴 준비가 됐을 때만 고정점을 새 버전으로 옮기면 되고요. 남의 사정에 내 빌드가 갑자기 흔들리지 않는다는 게 서브모듈의 가장 큰 장점이에요.

84.5 서브모듈은 어떻게 다뤄요?

공용 저장소를 libs/shared라는 자리에 얹으려면 이렇게 해요.


$ git submodule add [저장소주소] libs/shared
Cloning into '.../libs/shared'...
done.


그러면 .gitmodules 파일에 얹은 위치와 주소가 기록돼요.


[submodule "libs/shared"]
path = libs/shared
url = [저장소주소]


여기서 초보가 가장 많이 데는 함정이 있어요. 서브모듈이 든 저장소를 동료가 평범하게 받으면, libs/shared 폴더가 텅 비어 있어요. 얹었다는 기록만 왔지 실제 코드는 안 따라온 거죠. 그래서 받을 때 이렇게 딸린 것까지 함께 받아야 해요.


git clone --recurse-submodules [저장소주소]


이미 받아 놓고 빈 걸 뒤늦게 알았다면 다음으로 채워요.


git submodule update --init --recursive

84.6 서브모듈이 왜 불편하다고들 해요?

편리한 만큼 손이 좀 가요. 앞의 빈 폴더 함정처럼, 팀원이 절차 하나만 빠뜨려도 "왜 코드가 없냐"며 헤매요. 공용 코드를 저쪽 저장소에서 고친 뒤엔, 내 프로젝트에서 고정점을 새 커밋으로 옮겨 다시 커밋해 줘야 반영돼요. 이 두 단계를 자꾸 잊어서, 나는 최신인 줄 아는데 동료는 옛 버전을 보는 엇갈림이 생기죠.


그래서 요즘은 서브모듈 대신 공용 코드를 독립된 꾸러미(패키지)로 만들어 버전을 붙여 배포하는 방식을 더 많이 써요. 다만 사내에서만 쓰는 비공개 코드를 가볍게 엮을 땐 서브모듈이 여전히 편해요. 완벽한 도구는 없으니 상황에 맞게 고르면 돼요. 처음 서브모듈을 만나면 낯설고 성가시게 느껴지지만, 고정점 개념만 손에 익으면 공용 코드를 안정적으로 나눠 쓰는 든든한 방법이 돼요.

84.7 정리하면 둘을 어떻게 나눠 쓰나요?

헷갈리기 쉬운데 목적이 완전히 달라요. LFS는 큰 파일 하나를 가볍게 다루려는 거고, 서브모듈은 남의 저장소를 통째로 끌어와 버전을 고정해 쓰려는 거예요. 디자인 원본이 무거워 고민이면 LFS, 공용 코드 묶음을 여러 프로젝트가 나눠 쓰고 싶으면 서브모듈이죠.


둘 다 편하지만 팀원이 규칙을 모르면 사고가 나요. LFS는 track 순서와 서버 지원을, 서브모듈은 딸린 것까지 받는 절차를 꼭 공유해 두세요. 저는 이 두 가지를 저장소 안내문에 짧게 적어 두고 새로 온 사람에게 먼저 읽게 해요. 그 한 장이 빈 폴더와 부푼 저장소로 반나절 헤매는 일을 막아 주거든요.