Git 저장소 다이어트: .git 용량 영혼까지 끌어모아 줄이는 대청소 명령어 조합
개발자라면 누구나 한 번쯤 경험하는 상황이 있다. 소스 코드는 몇 MB에 불과한데, .git 폴더 용량은 수 GB까지 비대해져 있는 경우다. 특히 이미지나 바이너리 같은 대용량 파일(Git LFS)을 다루는 프로젝트라면 디스크 압박이 더 크게 느껴진다.
2026년 수정 안내 이 글을 올린 뒤 다시 읽어보니 중요한 것을 빠뜨렸다. 아래 명령 조합은 로컬에 쌓인 잔재를 정리하는 것이지, 히스토리에 커밋된 대용량 파일 때문에 커진 저장소를 줄이지는 못한다. 그런데 글 도입부가 설명하는 상황이 바로 후자다. 이 구분을 먼저 짚고,
git pull을 조합에 넣었던 부분도 함께 바로잡는다.
먼저 : 내 저장소가 왜 큰지부터 확인하자
원인이 둘이고 해결책이 완전히 다르다.
원인 A. 로컬에만 쌓인 잔재
git reset, 브랜치 삭제, rebase 등으로 도달할 수 없게 된 객체들이 reflog에 붙들려 남아 있는 경우다. 아래 대청소 명령으로 정리된다.
원인 B. 히스토리에 커밋된 대용량 파일
과거 어느 커밋에 100MB짜리 빌드 산출물을 넣었다면, 그 뒤에 지웠더라도 그 객체는 히스토리에 영원히 남는다. 도달 가능한 객체이므로 git gc는 절대 건드리지 않는다. 대청소를 몇 번 돌려도 용량이 그대로인 이유가 이것이다.
무엇이 원인인지는 이렇게 확인한다.
# 저장소 구성 요약 (size-pack이 실제 팩 용량)
git count-objects -vH
# 히스토리에서 가장 큰 객체 10개와 그 경로
git rev-list --objects --all \
| git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)' \
| awk '$1=="blob"' | sort -k3 -n -r | head -10
여기서 낯선 대용량 파일 이름이 줄줄이 나온다면 원인 B이고, 대청소로는 해결되지 않는다. 이 경우는 글 마지막의 “히스토리에서 큰 파일 제거” 절을 봐야 한다.
원인 A를 위한 대청소 명령어
프로젝트의 Git 루트 폴더에서 아래를 실행한다.
git reflog expire --expire=now --expire-unreachable=now --all && git gc --prune=now --aggressive && git lfs prune
각 명령어가 실제로 어떤 역할을 하는지 순서대로 살펴보자.
명령어 상세 분석
1) 연결 고리 끊기: git reflog expire
git reflog expire --expire=now --all
Git은 git reset이나 브랜치 삭제 이후에도 reflog에 작업 이력을 일정 기간(기본 약 90일) 보관한다. 문제는 이 기록이 디스크 용량을 계속 차지한다는 점이다.
--expire=now --all은 이 보관 이력을 즉시 만료 처리한다. 즉, 아직 객체는 남아 있어도 “더 이상 되살릴 근거가 없는 상태”로 만들어 이후 정리 단계에서 제거 가능하게 만든다.
--expire-unreachable=now를 함께 준 이유는, --expire만으로는 도달 가능한 커밋의 reflog 항목만 만료되고 도달 불가능한 항목은 기본 30일간 남기 때문이다. 둘 다 지정해야 완전히 비워진다.
2) 정리 전에 미푸시 커밋이 없는지 확인
원래 이 자리에 git fetch && git pull을 넣었는데 빼는 것이 맞다.
git pull은 내부적으로fetch를 포함하므로 둘을 나란히 쓰는 것은 중복이다.- 무엇보다
pull은 머지를 일으킨다. 정리 스크립트 한가운데에서 충돌이 나거나, upstream이 설정되지 않은 브랜치에서 그대로 실패한다. “안정성을 높인다”고 썼지만 실제로는 위험을 더하는 단계였다.
정리 전에 확인할 것은 동기화가 아니라 잃어버리면 안 될 커밋이 원격에 올라가 있는가이다.
# 어느 브랜치에도 푸시되지 않은 커밋이 있는지 확인
git log --branches --not --remotes --oneline
# 작업 중인 변경사항이 없는지 확인
git status
여기서 뭔가 나온다면 먼저 git push하거나 백업 브랜치를 만들어 둔다. 이 단계를 끝내고 다음으로 넘어간다.
3) 물리적 삭제 및 압축: git gc
git gc --prune=now
Git의 GC(Garbage Collection)를 강제 실행한다. 앞 단계에서 만료 처리된 객체 중 도달 불가능한 데이터들을 실제 디스크에서 삭제하고, 남은 객체들은 packfile로 재정렬/압축한다.
결과적으로 .git 용량 감소뿐 아니라 저장소 성능(객체 조회, 전송 효율)에도 도움이 된다.
다만 gc가 지우는 것은 “도달 불가능한” 객체뿐이다. 어느 브랜치나 태그에서든 닿을 수 있는 객체는 그대로 남는다. 이것이 원인 B를 해결하지 못하는 이유다.
--aggressive를 붙이면 델타 압축을 처음부터 다시 계산해 더 작아지지만 시간이 오래 걸린다. 가끔 한 번만 쓰면 된다.
4) 대용량 파일 캐시 정리: git lfs prune
git lfs prune
Git LFS를 사용하는 저장소라면 사실상 필수 단계다. 과거 체크아웃에서 내려받은 대용량 LFS 객체 로컬 캐시가 누적되어 용량을 크게 잡아먹기 때문이다.
git lfs prune은 현재 기준으로 더 이상 필요 없는 오래된 LFS 로컬 객체를 정리한다. 원격 LFS 저장소 데이터가 지워지는 것은 아니므로, 일반적인 사용에서는 안전하다.
실행 전 주의사항 (필독)
아래 항목은 반드시 확인하고 실행하자.
-
복구 불가능성
reflog를 만료하고gc --prune=now까지 실행하면, 실수로 지운 커밋/브랜치를 되살릴 여지가 크게 줄어든다. 사실상 로컬 타임머신을 비우는 작업이다. -
미푸시 작업 백업 원격에 올리지 않은 커밋이 있다면 먼저
git push로 보호하자. 중요한 변경은 별도 백업 브랜치/태그를 만들어 두는 것을 권장한다. -
깨끗한 작업 상태 권장 정리 작업은 충돌 요인이 적은 상태에서 하는 것이 좋다. 가능하면 작업 중인 변경이 없는 클린 상태에서 실행하자. 다른 Git 프로세스(IDE의 백그라운드 fetch 포함)가 동시에 돌고 있지 않은지도 확인하자.
원인 B : 히스토리에서 큰 파일 제거하기
git count-objects -vH로 확인했을 때 대청소 후에도 용량이 그대로라면, 대용량 파일이 히스토리에 박혀 있는 것이다. 이때는 히스토리를 다시 쓰는 수밖에 없다.
현재 권장 도구는 git filter-repo다. (git filter-branch는 느리고 함정이 많아 공식적으로 권장되지 않는다.)
pip install git-filter-repo
# 특정 경로를 히스토리 전체에서 제거
git filter-repo --path assets/old_movies --invert-paths
# 또는 크기 기준으로 제거
git filter-repo --strip-blobs-bigger-than 10M
이 작업의 대가는 크다.
- 모든 커밋 해시가 바뀐다. 히스토리를 다시 쓰기 때문이다.
- 원격에
--force로 밀어야 하고, 팀원 전원이 저장소를 새로 클론해야 한다. 기존 클론에서 pull하면 히스토리가 뒤엉킨다. - 진행 중인 PR과 브랜치가 전부 깨진다.
- 기존 커밋 해시를 참조하던 이슈 링크, CI 설정, 배포 태그가 무효가 된다.
그래서 이 작업은 팀과 일정을 맞춰 공지하고 진행해야 한다. 혼자 쓰는 저장소가 아니라면 즉흥적으로 실행할 일이 아니다.
애초에 대용량 파일은 처음부터 Git LFS로 관리하는 것이 답이다.
git lfs track "*.psd" "*.fbx" "*.mp4"
마무리
정리하면 이렇다.
| 증상 | 원인 | 해결 |
|---|---|---|
| reset/브랜치 삭제를 많이 했다 | 도달 불가능한 객체 | 대청소 명령 |
| LFS 캐시가 쌓였다 | 로컬 LFS 객체 | git lfs prune |
| 큰 파일을 커밋한 적이 있다 | 히스토리에 박힌 blob | git filter-repo + 강제 푸시 |
로컬 잔재 정리는 아래 한 줄이면 된다.
git reflog expire --expire=now --expire-unreachable=now --all && git gc --prune=now && git lfs prune
이걸 돌려도 용량이 안 줄어든다면, 문제는 로컬 잔재가 아니라 히스토리다.
댓글 남기기