2026년 08월 21일

Git 대용량 파일 실수로 커밋했을 때 git-filter-repo로 완전히 제거한 과정

대용량 파일을 실수로 커밋하고 나서 git push까지 해버렸다. 100MB가 넘는 바이너리 파일이 원격 저장소에 올라간 것을 뒤늦게 발견했을 때 꽤 당황스러웠다. 단순히 git rm으로 파일을 삭제해도 Git 히스토리에는 해당 커밋이 그대로 남아 있기 때문에 저장소 크기는 줄어들지 않는다. 이 글은 그 상황을 git-filter-repo로 해결한 과정을 직접 겪은 그대로 정리한 것이다.

왜 git rm만으로는 부족한가

Git은 기본적으로 모든 커밋 히스토리를 보존한다. 파일을 삭제하는 커밋을 새로 만들어도 이전 커밋 객체 안에는 그 파일의 내용이 그대로 남아 있다. 즉, git log나 git checkout으로 과거 커밋을 참조하면 여전히 그 대용량 파일에 접근할 수 있다.

GitHub의 경우 100MB를 초과하는 파일은 아예 푸시를 거부하지만, 그보다 작더라도 수십 MB짜리 파일이 히스토리 전체에 걸쳐 남아 있으면 git clone 시간이 눈에 띄게 늘어나고, 저장소 관리가 불편해진다. 실제로 50MB 정도 되는 로그 파일을 실수로 커밋했을 때 저장소 크기가 급격히 커진 것을 git count-objects -vH 명령으로 확인할 수 있었다.

이런 경우 히스토리 자체를 재작성해서 해당 파일을 없었던 것처럼 만들어야 한다. 과거에는 git filter-branch가 주로 쓰였지만, 속도가 느리고 안전성 문제가 있어 현재는 git-filter-repo가 공식적으로 권장된다.

git-filter-repo란

git-filter-repo는 Git 히스토리를 빠르고 안전하게 재작성하기 위한 Python 기반 도구다. Git 공식 문서에서도 git filter-branch 대신 사용하도록 안내하고 있다. 단일 Python 스크립트로 동작하기 때문에 설치가 간단하고, 대형 저장소에서도 속도가 빠른 편이다.

설치 방법은 운영체제마다 조금씩 다르다.

# macOS (Homebrew)
brew install git-filter-repo

# Ubuntu / Debian
pip3 install git-filter-repo

# 또는 직접 다운로드 후 PATH에 추가

설치 후 git filter-repo --version으로 정상 동작을 확인한다.

작업 전에 반드시 백업부터

히스토리 재작성은 되돌리기 어렵다. 작업 전에 저장소 전체를 복사해두는 것이 좋다.

cp -r my-repo my-repo-backup

또는 원격 저장소를 별도 브랜치로 백업해두거나, 로컬에서 git bundle로 전체 히스토리를 파일로 내보내 두는 방법도 있다.

git bundle create ../my-repo-backup.bundle --all

나중에 문제가 생겼을 때 이 번들 파일로 저장소를 복구할 수 있다.

대용량 파일 찾기

어떤 파일이 저장소를 크게 만들고 있는지 먼저 파악해야 한다. git-filter-repo 자체에도 분석 기능이 있다.

git filter-repo --analyze

이 명령을 실행하면 .git/filter-repo/analysis/ 디렉터리 안에 여러 분석 파일이 생성된다. 그중 blob-shas-and-paths.txt와 path-all-sizes.txt를 보면 히스토리 전체에서 용량을 많이 차지하는 파일 목록을 확인할 수 있다.

All sizes:
  size  pack size  path
 52428800   51200000  logs/debug.log
  8388608    7000000  assets/video.mp4

이런 식으로 어떤 경로의 파일이 문제인지 바로 알 수 있다.

git-filter-repo로 파일 제거하기

제거할 파일 경로를 확인했다면 다음 명령으로 히스토리에서 완전히 삭제한다.

git filter-repo --path logs/debug.log --invert-paths

--path는 대상 파일 경로를 지정하고, --invert-paths는 해당 경로를 제외한 나머지를 유지하라는 의미다. 즉, 지정한 파일만 히스토리에서 제거된다.

여러 파일을 동시에 제거하려면 --path를 여러 번 쓰면 된다.

git filter-repo --path logs/debug.log --path assets/video.mp4 --invert-paths

특정 확장자를 가진 파일을 모두 제거하려면 --path-glob을 활용한다.

git filter-repo --path-glob '*.log' --invert-paths

명령 실행 후 터미널에 각 브랜치와 태그의 재작성 결과가 출력된다. 이 과정이 끝나면 로컬 저장소의 히스토리에서 해당 파일은 완전히 사라진다.

원격 저장소에 강제 푸시하기

로컬 히스토리가 재작성되었으면 원격 저장소에도 반영해야 한다. 히스토리가 바뀌었기 때문에 일반 git push는 거부된다. 강제 푸시가 필요하다.

git push origin --force --all
git push origin --force --tags

--all은 모든 브랜치를, --tags는 모든 태그를 강제 푸시한다. GitHub을 사용하는 경우 브랜치 보호 규칙이 설정되어 있으면 강제 푸시가 차단될 수 있다. 이 경우 잠시 보호 규칙을 해제하거나, 저장소 설정에서 관리자 권한으로 강제 푸시를 허용해야 한다.

강제 푸시 이후 팀원이 있다면 반드시 공지해야 한다. 기존 로컬 저장소와 원격 저장소의 히스토리가 달라지기 때문에 팀원들은 기존 클론을 버리고 새로 클론하거나, git fetch와 git reset --hard origin/main 방식으로 로컬을 원격에 맞춰야 한다.

저장소 정리와 용량 확인

git-filter-repo 실행 후에도 .git 디렉터리 안에 참조되지 않는 객체들이 남아 있을 수 있다. 이를 정리하려면 Git의 가비지 컬렉션을 실행한다.

git reflog expire --expire=now --all
git gc --prune=now --aggressive

git reflog expire는 reflog에 남아 있는 오래된 참조를 정리하고, git gc --prune=now는 더 이상 참조되지 않는 객체를 즉시 제거한다. --aggressive 옵션을 추가하면 더 철저하게 최적화하지만 시간이 좀 더 걸린다.

정리 후 저장소 크기를 다시 확인한다.

git count-objects -vH

size-pack 값이 줄어들었다면 정상적으로 처리된 것이다. 실제로 작업 후 저장소 크기가 수십 MB에서 수 MB 수준으로 줄어드는 것을 확인할 수 있었다.

GitHub에서 캐시 제거 요청하기

GitHub의 경우 강제 푸시로 히스토리를 재작성해도 서버 측 캐시에 이전 데이터가 일정 기간 남아 있을 수 있다. 완전히 제거하려면 GitHub Support에 캐시 삭제를 요청하거나, 저장소를 삭제하고 새로 만드는 방법을 선택해야 한다.

민감한 정보(API 키, 비밀번호 등)가 포함된 파일이었다면 히스토리 제거와 동시에 해당 정보 자체를 즉시 무효화하는 것이 더 중요하다. Git 히스토리 정리는 보조적인 조치일 뿐이다.

.gitignore로 재발 방지

같은 실수를 반복하지 않으려면 .gitignore에 대용량 파일이나 민감한 파일이 포함되는 경로를 미리 등록해두어야 한다.

# 로그 파일
logs/
*.log

# 대용량 미디어
assets/video/
*.mp4
*.mov

# 환경 변수 파일
.env
*.env.local

이미 추적 중인 파일은 .gitignore에 추가해도 무시되지 않는다. 이런 경우 git rm --cached <파일경로>로 추적을 먼저 해제한 뒤 .gitignore에 등록해야 한다.

Git LFS(Large File Storage)도 고려할 만한 선택이다. 대용량 바이너리 파일을 Git 저장소 본체가 아닌 별도 스토리지에 저장하고, 저장소에는 포인터만 남기는 방식이다. 동영상, 이미지, 데이터셋처럼 버전 관리가 필요하지만 크기가 큰 파일을 다룰 때 유용하다.

작업 요약

전체 과정을 순서대로 정리하면 다음과 같다.

  1. 저장소 백업 (cp 또는 git bundle)
  2. git filter-repo --analyze로 문제 파일 확인
  3. git filter-repo --path <경로> --invert-paths로 히스토리에서 제거
  4. git push origin --force --all && git push origin --force --tags로 원격 반영
  5. git gc --prune=now로 로컬 정리
  6. .gitignore 업데이트로 재발 방지

git-filter-repo는 git filter-branch에 비해 속도도 빠르고 사용법도 직관적이다. 대용량 파일 제거뿐 아니라 특정 디렉터리만 남기거나, 커밋 메시지를 일괄 수정하는 등 다양한 히스토리 재작성 작업에 폭넓게 활용할 수 있다.