히스토리에서 파일 제거
git rm은 파일이 새 커밋에 나타나는 것을 멈춰요. 이미 만들어진 커밋에는
아무 일도 하지 않아요. 블롭은 여전히 객체 데이터베이스 안에, 여전히 모든 클론
안에, 여전히 git show 한 번 거리에 있어요.
그게 두 경우에 문제가 돼요. 그 파일이 자격 증명이었을 때, 그리고 400 MB였을 때.
⌘K → 히스토리에서 파일 제거, 또는 파일 우클릭 — 프로젝트 트리에서, 커밋의
파일 목록에서, 또는 커밋 작성기에서. 어떤 파일을 삭제한 커밋이야말로 그것이
아직 히스토리에 남아 있다는 사실을 누군가 깨닫는 자리이므로, 빠져나갈 길도 그
메뉴 안에 있어요.
경로 찾기
들어가는 길은 두 개예요. 서로 다른 질문에 답하기 때문이죠.
직접 입력하기 — 저장소 기준 상대 경로, 앞에 슬래시 없이 — 무엇을 지우러 왔는지 이미 아는 경우예요.
히스토리 훑어보기는 모를 때 써요. 지금까지 커밋된 적이 있는 모든 경로를 무거운 순으로, 각각 몇 개의 버전이 있는지와 아직 추적되고 있는지를 함께 나열해요. 삭제된 경로는 그렇게 표시되며, 보통 그것이 여러분이 찾는 대상이에요. 작업 트리에서는 사라졌지만 모든 클론에는 남아 있는 파일이야말로 평범한 파일 대화 상자가 보여 줄 수 없는 경우거든요. 고를 파일이 거기 없으니까요.
같은 목록은 사람들이 여기 오는 다른 이유 — 왜 이 클론이 2기가바이트지 — 에도 답해 줘요. 각 경로의 블롭이 실제로 차지하는 바이트 순으로 정렬되어 있기 때문이에요. 행을 하나 고르면 곧바로 측정해요.

동의하기 전에 측정하기
측정을 누르세요(또는 행을 하나 고르세요). 아직 아무것도 쓰이지 않아요. 다음을 얻어요.
| 재작성될 커밋 | 그 파일을 담고 있던 첫 커밋부터 그 이후의 모든 커밋 |
| 브랜치 / 태그 | 움직이게 될 ref |
| 블롭이 차지한 용량 | 그 버전들이 실제로 차지하는 바이트 |
| 첫 커밋 | 재작성이 시작되는 지점 — 그 뒤의 모든 것이 새 해시를 받아요 |

개수가 0이라면 경로가 틀린 것이에요. 보통은 없어서가 아니라 철자나 디렉터리 접두사 문제예요.
재작성이 실제로 하는 일
Gitcito는 모든 브랜치와 태그를 refs/gitcito/pre-purge/<timestamp>/…로 복사한
다음, 이렇게 실행해요.
git filter-branch --force \
--index-filter 'git rm --cached --ignore-unmatch -- <path>' \
--prune-empty --tag-name-filter cat -- --branches --tags
--index-filter는 커밋마다 체크아웃하는 대신 인덱스를 직접 재작성해요. 몇
분과 몇 시간의 차이죠. --all이 아니라 --branches --tags인 것은 의도적이에요.
--all은 백업 ref까지 포함하게 되어, 재작성이 자기 안전망을 삼켜 버릴 테니까요.
제거된 파일 말고는 아무것도 담고 있지 않던 커밋은 버려져요(--prune-empty).
태그는 재작성된 커밋을 다시 가리키게 돼요.
백업, 그리고 왜 공간이 아직 돌아오지 않는가
퍼지는 되돌릴 수 있고, 그 대가는 여러분이 말하기 전까지는 디스크 공간이 회수되지 않는다는 것이에요. 백업이 존재하는 동안에는 옛 커밋들이 여전히 도달 가능하므로, git은 그것들을 수집하지 않아요.
| 동작 | 효과 |
|---|---|
| 복원 | 모든 브랜치와 태그가 퍼지 이전 커밋으로 돌아가고, 파일도 그와 함께 돌아와요 |
| 백업 버리기 | 백업 ref를 지우고, reflog를 만료시키고, git gc --prune=now를 실행해요 — 공간이 돌아오고, 퍼지는 이제 영구적이에요 |
한 단계가 아니라 두 단계인 이유는, 첫 번째는 회복 가능한 쪽이고 두 번째는 그렇지 않기 때문이에요.
어차피 비밀 키는 교체하세요
자격 증명이 한 번이라도 푸시되었다면, 히스토리를 재작성해도 유출이 없던 일이 되지는 않아요. 누군가 이미 가져갔을 수 있고, 포지 서버는 참조되지 않는 객체를 한동안 들고 있으며, CI 로그가 그것을 출력했을 수도 있어요. 재작성은 더 퍼지는 것을 막을 뿐, 노출을 되돌리지는 않아요.
키를 교체하세요. 그런 다음 퍼지해서, 다음에 클론하는 사람이 그것을 발견하지 않게 하세요.
하지 않는 일
- 푸시하지 않아요. 재작성은 로컬 작업이에요. 결과를 공개한다는 것은 영향받은 모든 브랜치에 강제 푸시한다는 뜻이고, 나머지 사람들은 모두 다시 클론하거나 하드 리셋해야 해요 — 그 결정이 사는 곳은 강제 푸시 가드이에요.
- 작업 트리가 더럽거나 머지/리베이스 중이면 거부해요. 재작성은 HEAD를 여러 번 움직이는데, 커밋되지 않은 작업을 두고 그러는 것이 바로 그 작업을 잃는 방법이거든요.
- 내용이 아니라 경로 기준으로 재작성해요. 자기 파일에 들어 있는 대신 소스
파일 안에 붙여 넣어진 비밀 키를 제거하려면 내용 필터가 필요해요. 그건
git filter-repo --replace-text의 영역이고, Gitcito는 그것을 감싸지 않아요. filter-branch는 아주 큰 히스토리에서는 느려요. 하지만 어디서나 git과 함께 딸려 오는 도구이고, 그래서 Gitcito가 그것을 써요. 커밋이 수만 개인 저장소라면 터미널에서 쓰는git filter-repo가 더 빠른 도구예요.- 다른 사람의 클론은 여러분의 저장소가 아니에요. 그들은 다시 클론하기 전까지 옛 히스토리를 그대로 갖고 있어요.