Git 히스토리의 대용량 파일 제거 — filter-repo

기초·개념3분조회 –

S3 에 쌓인 로그를 내려받아 분석하는 작업이었고, 작업 디렉토리가 그대로 Git 저장소라 스크립트와 정리한 결과를 커밋해 두고 있었습니다. 내려받은 원본 JSON 도 같은 디렉토리에 있었으니 그게 커밋에 딸려 들어갔습니다.

Git 은 커밋할 때 파일 내용을 통째로 개체 하나에 담습니다. 텍스트든 바이너리든 상관없이 그 시점의 전체 내용이 저장소 안에 들어가고 한 번 들어간 개체는 나중에 워킹 디렉토리에서 파일을 지워도 이전 커밋 안에 그대로 남습니다.

push 하자 GitHub 이 막았습니다.

remote: error: File AWS/s3/2024_back_log.json is 2343.67 MB;
  this exceeds GitHub's file size limit of 100.00 MB
remote: error: GH001: Large files detected.

파일 하나가 2.3GB 니 100MB 제한에 걸린 건데, 워킹 디렉토리에서 지우고 다시 push 하면 될 것 같지만 그걸로는 안 풀립니다.

.gitignore로는 못 지우는 이유

먼저 .gitignore 에 그 파일을 넣어봤지만 소용없었습니다. .gitignore 는 아직 추적 안 하는 파일을 앞으로 안 넣겠다는 것이지, 이미 커밋 히스토리에 들어간 파일을 지우지 않습니다.

GitHub 은 push 되는 커밋의 히스토리 전체를 보니, 지금 워킹 디렉토리에서 파일을 지우고 새 커밋을 만들어도 이전 커밋에는 그 2.3GB 가 그대로 들어 있습니다. 그 이전 커밋이 push 대상에 포함되니 계속 막히고, 그래서 히스토리 자체에서 파일을 빼내야 합니다.

히스토리에 남은 대용량 파일 찾기

무엇이 히스토리에 들어 있는지 확인합니다.

git rev-list --objects --all \
  | git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)' \
  | awk '/^blob/ && $3 > 52428800 {print $3, $4}' \
  | sort -rn

50MB(52428800 바이트) 넘는 blob 을 큰 순서로 보여줍니다. 워킹 디렉토리에서 이미 지운 파일도 히스토리에 있으면 여기 나오고, 실제로 돌려 보니 GitHub 이 지목한 백업 JSON 말고도 예전에 지운 백업 파일이 둘 더 걸려 나왔습니다.

git-filter-repo로 히스토리에서 제거

pip install git-filter-repo

히스토리 자체를 다시 쓰는 작업이고 git filter-branch 라는 오래된 방법도 있지만 느리고 위험해서 지금은 git-filter-repo 가 공식 권장 도구입니다.

git filter-repo \
  --path AWS/s3/2023_back_log.json \
  --path AWS/s3/2024_back_log.json \
  --path some_bucket_backup.zip \
  --invert-paths \
  --force
  • --invert-paths : 지정한 경로를 제외한 나머지를 유지합니다. 즉 이 파일들만 히스토리에서 빼냅니다.
  • --force : 새로 클론한 저장소가 아니어도 실행합니다. 원래는 안전장치로 막혀 있는 자리라, 되돌릴 수 없다는 뜻이기도 합니다.

원본 파일이 필요하면 먼저 저장소 밖으로 옮겨 둬야 합니다. filter-repo 는 히스토리를 다시 쓴 뒤 git reset --hard 로 워킹 트리를 새 HEAD 에 맞추니, 히스토리에서 뺀 파일은 워킹 디렉토리에서도 같이 사라집니다.

다시 push 하기

filter-repo 는 작업이 끝나면 origin 을 지웁니다. 원래 저장소로 실수로 되돌려 push 하는 걸 막으려는 것이라, 다시 붙여 줘야 합니다.

git remote add origin <원래 저장소 URL>
git push --force --all
git push --force --tags

커밋 해시가 전부 바뀌었으니 --force 가 필요합니다.

예방 — 애초에 git 에 안 넣기

대용량 파일은 처음부터 git 에 안 넣는 게 맞아서, 백업이나 데이터 덤프는 S3 같은 오브젝트 스토리지에 두고 git 에는 그 위치만 적습니다. 정말 큰 파일을 버전 관리해야 하면 Git LFS 를 씁니다.

히스토리를 다시 쓰면 커밋 해시가 전부 바뀝니다. 혼자 쓰는 리포면 상관없지만 남과 공유하는 리포라면 다른 사람들이 다시 클론하거나 히스토리를 맞춰야 하니, 이 작업 자체가 이미 사고가 난 뒤의 수습이고 그런 상황을 안 만드는 게 제일 낫습니다.

참고

  1. 불러오는 중