~ / posts / developer
💻 개발자

깃 저장소 관리 판단표, 클론 방식·용량·브랜치 수·gc 설정 고르는 법

클론이 몇 분씩 걸리거나 .git이 소스보다 몇 배 크거나, 지운 브랜치의 커밋이 다시 필요해지면 어디부터 손대야 할지 막막해요. 결론부터 말하면 CI처럼 한 번 쓰고 버리는 곳은 --depth=1, 사람이 쓰는 개발 PC는 --filter=blob:none, 자주 바뀌는 큰 바이너리는 처음부터 Git LFS가 기본값이에요. gc와 reflog 설정은 대부분 기본값이 맞고, 지운 커밋은 기본 설정에서 대략 30일 안에 git reflog로 찾으면 돼요.

이 글은 객체 구조보다 운영 판단을 다뤄요. 판단표, 깃 2.52.0으로 직접 잰 숫자, 짧은 해시 충돌 계산, 증상별 확인 순서, SHA-256·reftable 도입 전 점검을 차례로 정리했어요.

광고

깃 저장소 설정 판단표 — 무엇을 언제 바꾸나

깃 설정은 수백 개지만, 저장소 크기와 속도에 영향을 주는 결정은 몇 개로 좁혀져요. 아래 표는 git-clone 문서, git-gc 문서, git-init 문서에 적힌 기본값과 동작을 상황별로 묶은 거예요.

결정기본값바꿀 때그대로 둘 때
클론 범위전체 기록CI는 --depth=1, 큰 저장소 개발 PC는 --filter=blob:none저장소가 수백 MB 이하, 오프라인 작업이 잦음
큰 바이너리그냥 커밋자주 바뀌는 디자인 원본·모델 파일·빌드 산출물 → Git LFS 또는 릴리스 첨부거의 안 바뀌는 작은 아이콘·폰트
자동 정리gc.auto=6700, gc.autoPackLimit=50대형 저장소는 git maintenance start로 백그라운드 정리일반 저장소
reflog 보관닿는 항목 90일, 닿지 않는 항목 30일실수 복구 기간을 늘리고 싶으면 gc.reflogExpireUnreachable 연장대부분
해시 형식sha1새 저장소이고 호스팅·도구가 전부 SHA-256 지원기존 저장소, SHA-256을 못 읽는 도구가 하나라도 있음
ref 저장 형식files(낱개 파일 + packed-refs)브랜치·태그가 수만 개, 대소문자만 다른 브랜치 충돌이 잦음다른 깃 구현(JGit·libgit2 기반 도구)을 쓰는 곳

기본값을 바꾸는 설정은 대부분 규모가 커졌을 때 쓰는 것이라 작은 저장소에서는 얻는 게 거의 없어요. 반대로 바이너리 정책과 해시 형식은 나중에 바꾸기가 가장 비싸서 저장소를 만들 때 정해요.

클론 방식 고르기 — 전체·blobless·얕은 클론 크기 실측

커밋 50개짜리 예제 저장소에서 재 봤어요. 커밋마다 1 MB 무작위 바이너리(asset.bin)를 새로 쓰고 텍스트에 20 KB씩 덧붙였고, uploadpack.allowFilter=true를 켠 뒤 file://로 클론했어요.

클론 방식별 .git 폴더 크기 (커밋 50개 예제 저장소)
전체 클론전체 클론 · 48.5MiB48.5MiBblobless (blob:none)blobless (blob:none) · 1.5MiB1.5MiBtreeless (tree:0)treeless (tree:0) · 1.5MiB1.5MiB얕은 클론 (depth=1)얕은 클론 (depth=1) · 1.5MiB1.5MiB

저장소 무게 대부분이 옛 바이너리 버전이라 그것만 빼도 30분의 1로 줄었어요.

단위: MiB · 자료: Git 2.52.0 · macOS · file:// 로컬 클론 · du 로 직접 측정
표로 보기
구분.git 크기
전체 클론48.5
blobless (blob:none)1.5
treeless (tree:0)1.5
얕은 클론 (depth=1)1.5

세 방식이 비슷한 건 이 예제의 무게가 옛 파일 내용에 몰려 있어서예요. 커밋이 수십만 개면 tree까지 받는 blobless와 안 받는 treeless의 차이가 벌어져요.

가볍다고 공짜는 아니에요. blobless 클론에서 git log -p -- asset.bin을 돌리자 4.5초가 걸렸고 팩이 48.2 MiB로 늘었어요. 옛 버전 50개를 그 자리에서 받아 온 거예요. partial-clone 설계 문서도 없는 객체를 그때그때 받아 오는 동작은 객체를 하나씩 가져와서 느린 편이라고 적어요. 실제 네트워크라면 더 느려져요.

클론 방식 판단 순서도: 한 번 쓰고 버리면 얕은 클론, 옛 내용을 자주 안 보면 blobless, 아니면 전체 클론과 각 .git 크기
기록을 계산할 일과 옛 내용을 볼 일, 두 질문으로 골라요

얕은 클론은 기록이 잘려 있다는 점을 기억해야 해요. git-clone 문서 기준으로 --depth는 --single-branch를 함께 뜻하므로 다른 브랜치가 안 보여요. 그래서 CI에서 "main과 비교해 바뀐 파일만 검사" 같은 작업을 하면 merge-base를 못 찾아 실패해요. 이때는 --depth를 늘리기보다 그 작업만 blobless 클론으로 바꾸는 편이 깔끔해요.

바이너리가 저장소를 키우는 속도 — 직접 계산

깃은 바뀐 파일을 통째로 새 객체로 저장하고, git gc가 비슷한 객체끼리 차이만 남겨 묶어요. 이 묶음이 잘 되는지 두 저장소로 비교했어요.

11개 버전을 커밋한 뒤 .git/objects 크기
gc 전 (느슨한 객체)gc 후 (팩파일)
0102030405060텍스트 6MB 파일 · gc 전 (느슨한 객체) 12.2MiB텍스트 6MB 파일 · gc 후 (팩파일) 0.8MiB텍스트 6MB 파일바이너리 5MB 파일 · gc 전 (느슨한 객체) 52.6MiB바이너리 5MB 파일 · gc 후 (팩파일) 53.3MiB바이너리 5MB 파일

텍스트는 gc 뒤 0.8 MiB로 줄었지만, 바이너리는 11개 × 5 MB가 거의 그대로 남았어요.

단위: MiB · 자료: Git 2.52.0 · gc.auto=0 으로 자동 정리를 끄고 du 로 직접 측정
표로 보기
구분gc 전 (느슨한 객체)gc 후 (팩파일)
텍스트 6MB 파일12.20.8
바이너리 5MB 파일52.653.3

이 결과를 공식으로 쓰면 바이너리 저장소의 증가량은 대략 파일 크기 × 바뀐 횟수예요. 20 MB짜리 디자인 원본을 하루 3번씩 1년(약 250 근무일) 고치면 20 × 3 × 250 = 15,000 MB, 약 15 GB가 기록에 쌓여요. GitHub의 대용량 파일 안내는 저장소를 1 GB 미만, 적어도 5 GB 미만으로 두라고 권하고, 50 MiB가 넘는 파일은 경고, 100 MiB가 넘는 파일은 푸시를 막아요. MiB와 MB가 섞여 헷갈리면 데이터 용량 변환기로 1024 기준 값을 맞춰 보세요.

git-config 문서 기준으로 core.bigFileThreshold(기본 512 MiB)보다 큰 파일은 차이 저장(delta)을 아예 시도하지 않아요. 위 실험처럼 그보다 작아도 압축된 형식이면 차이 저장이 거의 안 돼요.

.git이 커졌을 때 무엇이 범인인지는 이 한 줄로 찾아요.

git rev-list --objects --all \
  | git cat-file --batch-check='%(objecttype) %(objectsize) %(objectsize:disk) %(rest)' \
  | awk '$1=="blob"' | sort -k3 -n -r | head -10

세 번째 칸(objectsize:disk)이 실제 디스크 크기예요. 예제에서 1,000,000바이트짜리 asset.bin은 디스크 1,000,320바이트로 안 줄었고, 같은 크기의 docs/notes.txt는 451,672바이트였어요. 안 줄어드는 큰 파일이 LFS 후보예요.

브랜치 1만 개의 비용 — 낱개 ref·packed-refs·reftable 실측

브랜치를 만드는 봇이나 빌드마다 붙는 태그가 쌓이면 ref가 수만 개가 돼요. 브랜치 10,000개를 만들어 세 저장 방식을 비교했어요(다섯 번 돌린 가운데 값).

저장 방식디스크 사용for-each-refbranch --listrev-parse b5000
낱개 파일 (10,001개)약 39 MiB (40,004 KB)1,287 ms379 ms9.7 ms
git pack-refs --all 뒤 packed-refs578,997 B660 ms111 ms15.8 ms
git refs migrate --ref-format=reftable 뒤335,338 B644 ms109 ms15.5 ms

낱개 파일이 39 MiB인 이유는 내용(41바이트)이 아니라 파일 시스템의 최소 할당 단위예요. 파일 하나가 4 KiB를 차지해 10,001 × 4 KiB ≈ 39 MiB가 됐어요. 느슨한 객체도 같아서, 자동 gc 기준인 6,700개는 최소 26 MiB예요.

목록 조회는 묶은 쪽이 2~3배 빨랐고, 이름 하나를 찾는 rev-parse는 낱개 파일이 몇 밀리초 빨랐어요. git gc가 packed-refs 묶기를 같이 하므로 자동 정리를 켜 두면 따로 할 일은 없어요.

reftable이 확실히 나은 지점은 크기보다 이름 충돌이에요.

짧은 해시는 언제 겹치나 — 충돌 확률 계산

git log --oneline이 보여 주는 7자리 해시는 16진수 7자, 즉 28비트예요. 객체가 n개일 때 앞 28비트가 같은 쌍이 하나라도 있을 확률은 생일 문제 공식 1 − e^(−n(n−1)/2^29)로 어림해요. 직접 계산하면 이래요.

객체 수에 따른 짧은 해시 겹침 확률 (한 쌍이라도 같을 확률)
7자리 (28비트)8자리 (32비트)
02550751001천5천1만3만10만1천 · 7자리 (28비트) 0.19%5천 · 7자리 (28비트) 4.5%1만 · 7자리 (28비트) 17%3만 · 7자리 (28비트) 81.3%10만 · 7자리 (28비트) 100%1001천 · 8자리 (32비트) 0.01%5천 · 8자리 (32비트) 0.29%1만 · 8자리 (32비트) 1.2%3만 · 8자리 (32비트) 9.9%10만 · 8자리 (32비트) 68.8%68.8

객체 3만 개면 7자리가 겹치는 쌍이 있을 확률이 80%를 넘어요. 커밋·tree·blob을 모두 센 수예요.

단위: % · 자료: 생일 문제 근사식 1 − exp(−n(n−1)/2^(b+1)) 로 직접 계산
표로 보기
구분7자리 (28비트)8자리 (32비트)
1천0.190.01
5천4.50.29
1만171.2
3만81.39.9
10만10068.8

git-config 문서의 core.abbrev 설명대로, 깃은 따로 정하지 않으면 팩에 든 객체 수에 맞춰 짧은 해시 길이를 자동으로 늘려요. 객체 약 22만 개(git count-objects -v의 in-pack 222,692)인 저장소에서 git log의 짧은 해시는 9자리였어요. 문제는 지금 고유했던 짧은 해시가 나중에도 고유하다는 보장이 없다는 점이에요. 저장소가 커지면 short object ID ... is ambiguous 오류가 나요.

그래서 실무 규칙은 이래요. 사람이 읽는 화면에는 짧은 해시를 써도 되지만, 배포 기록·CI 아티팩트 이름·이슈 트래커 연동처럼 오래 남는 곳에는 40자 전체 해시(git rev-parse HEAD)를 저장해요. 참고로 깃 해시는 파일 내용만의 해시가 아니라 앞에 blob 크기 헤더를 붙인 값의 해시라서, 해시 생성기로 파일 내용을 해시한 값과 다르게 나와요. 해시 함수 자체의 차이는 MD5 SHA-1 SHA-256 차이 글에 정리해 뒀어요.

지운 커밋 되살리기 — reflog·gc 기본값과 기한

브랜치를 지우거나 reset --hard를 해도 객체는 바로 사라지지 않아요. 실험 저장소에서 tmp 브랜치에 커밋하고 git branch -D tmp로 지운 뒤에도 git reflog에 HEAD@{1}: commit: lost 줄이 남아 있었어요. 브랜치의 reflog는 함께 지워져도 HEAD의 reflog는 남기 때문이에요.

지운 커밋 복구 타임라인: 30일까지 HEAD reflog로 찾을 수 있고 그 뒤 gc에서 지워지며, 복구할 수 없는 경우를 정리한 그림
기본값이면 대략 30일 안에 찾으면 돼요

기한은 git-gc 문서의 세 기본값이 정해요.

설정기본값뜻
gc.reflogExpire90일현재 브랜치 끝에서 닿는 reflog 항목 보관 기간
gc.reflogExpireUnreachable30일현재 끝에서 닿지 않는 항목(지운 커밋이 여기) 보관 기간
gc.pruneExpire2주아무도 가리키지 않는 객체를 지우기 전 유예 기간

2주 유예는 닿지 않게 된 날이 아니라 객체 파일의 나이로 따져요. 만든 지 한 달 된 커밋은 reflog 항목이 만료된 뒤 다음 gc에서 바로 지워질 수 있어서, 30일 + 2주가 아니라 약 30일로 잡아요. 반대로 gc가 안 돈 저장소에는 더 오래된 객체가 남아 있기도 해요.

복구 순서는 이래요.

  1. git reflog (또는 git reflog show 브랜치이름)에서 커밋 메시지로 해시를 찾아요.
  2. git branch rescue 해시로 이름을 붙여요. 이름이 붙는 순간 정리 대상에서 빠져요.
  3. reflog에 없으면 git fsck --unreachable --no-reflogs나 git fsck --lost-found로 남은 객체를 훑어요.
  4. 찾기 전에는 git gc --prune=now를 돌리지 않아요. 문서도 동시에 쓰는 프로세스가 있으면 저장소가 망가질 위험이 커진다고 적어요.

reflog는 로컬 기록이라 서버나 동료 클론에는 없어요. 푸시하지 않은 커밋을 잃었다면 그 컴퓨터에서 찾아야 해요.

운영에서 자주 나는 문제와 확인 순서

증상별로 처음 볼 곳이에요.

증상첫 확인다음 확인흔한 해결
클론이 너무 느림git count-objects -vH의 size-pack위 큰 blob 찾기 명령CI는 --depth=1, PC는 --filter=blob:none, 큰 파일은 LFS
cannot lock ref ... exists; cannot creategit for-each-ref refs/heads/이름같은 앞부분 이름의 브랜치이름 규칙 통일, 오래된 원격 추적 ref는 git fetch --prune
a branch named 'X' already exists (대소문자만 다름)macOS·Windows 파일 시스템인지원격에 대소문자만 다른 두 브랜치원격에서 하나로 정리, 새 저장소면 reftable 검토
느슨한 객체 경고·명령마다 gc가 돎git count-objects -v의 countgc.auto 값git maintenance start로 백그라운드로 옮겨요
bad object·missing blobgit fsck --fullblobless 클론인지(remote.origin.promisor)blobless면 네트워크 확인 후 다시 시도, 아니면 새로 클론

느슨한 객체 경고는 숫자로 감을 잡을 수 있어요. 파일 5개를 고치는 커밋은 blob 5개, 바뀐 폴더마다 tree 하나(최상위 포함 보통 2~4개), commit 1개를 만들어 10개 안팎이에요. 6,700 ÷ 10 ≈ 670커밋마다 자동 gc가 한 번 도는 셈이에요. fetch로 받은 객체는 팩으로 와서 이 숫자에 거의 안 들어가요.

git-maintenance 문서 기준으로 git maintenance start는 매시간 스케줄을 걸고, incremental 전략으로 무거운 gc는 끄고 commit-graph·prefetch는 매시간, 느슨한 객체 정리·증분 재압축은 매일 돌려요. 명령 중간의 자동 정리(maintenance.auto)도 꺼져서 커밋 뒤 멈칫하는 일이 사라져요.

SHA-256·reftable 도입 전에 따질 것

BreakingChanges 문서에 따르면 깃 3.0에서 새 저장소의 기본 해시는 sha256, 기본 ref 형식은 reftable로 바뀔 예정이에요. 문서는 출시 날짜가 아직 없다고 적고, 두 변경 모두 생태계(라이브러리·애플리케이션·호스팅)가 준비되는 것을 전제 조건으로 걸어요. 지금 직접 켜기 전에 확인할 것은 이래요.

SHA-256

reftable

자주 묻는 질문

blobless 클론과 얕은 클론 중 개발 PC에는 무엇이 좋나요?

blobless 클론(--filter=blob:none)이 좋아요. 커밋과 tree를 모두 받아 log·merge-base·브랜치 비교가 정상으로 되고, 옛 파일 내용만 필요할 때 받아 와요. 얕은 클론은 기록이 잘리고 다른 브랜치도 없어 자주 막혀요.

git gc를 주기적으로 직접 돌려야 하나요?

대부분 필요 없어요. 느슨한 객체 6,700개나 팩 50개를 넘으면 커밋 같은 명령이 git gc --auto를 스스로 돌려요. 그 순간 멈칫하는 게 거슬리면 git maintenance start로 백그라운드에 맡겨요.

실수로 지운 브랜치는 언제까지 살릴 수 있나요?

기본 설정이면 약 30일 안에 git reflog로 해시를 찾아 git branch 새이름 해시로 되살리면 돼요. reflog는 로컬에만 있어서 그 커밋을 만든 컴퓨터에서 찾아야 하고, git gc --prune=now를 돌린 뒤에는 되살릴 수 없어요.

새 저장소를 지금 SHA-256으로 만들어도 되나요?

쓰는 호스팅과 모든 도구가 SHA-256 저장소를 지원할 때만 권해요. git-init 문서 기준으로 SHA-1 저장소와 상호운용이 없어서, 나중에 지원하지 않는 서비스와 연동해야 하면 되돌리기 어려워요. 깃 3.0에서 기본값이 될 예정이지만 날짜는 정해지지 않았어요.

정리

참고한 자료

#깃 저장소 용량#git partial clone#blobless 클론#git gc 설정#git reflog 복구#packed-refs#reftable#깃 SHA-256
← 이전 글DDD 애그리거트 경계 정하는 법, 판단표와 낙관적 잠금 충돌률 계산다음 글 →Content-Encoding 압축 해제 보안, 압축 폭탄과 크기 제한 설정

더 둘러보기

💻 개발자 다른 도구

🔥 많이 찾는 도구

🔮 재미로 한 번 더

📝 읽어 볼 만한 글

글 전체 보기 →