# git filter-repo: переписывание истории _Безопасность · GitLab Knowledge Base_ **TL;DR:** Современная замена устаревшему `git filter-branch`. Переписывает историю на месте: удаляет файлы, меняет email авторов, заменяет строки. Используется для удаления секретов или огромных бинарей, которые попали в репо. `git filter-repo` - отдельный инструмент (не часть Git core, ставится отдельно). Полностью переписывает историю: каждый коммит пересоздаётся с другим деревом/метаданными. Хирургическая операция, использовать с осторожностью. Замена `git filter-branch`, который официально объявлен deprecated с Git 2.24: filter-branch медленный и легко ошибиться. `filter-repo` - быстрый (написан на Python, использует `git fast-export`) и безопасный (по умолчанию работает с чистым клоном). ## Установка ```bash # macOS brew install git-filter-repo # Debian/Ubuntu apt install git-filter-repo # Pip pip install git-filter-repo ``` ## Главные применения ### Удалить файл из всей истории Часто после случайно закоммиченного секрета. См. [secret-scanning](/courses/git/kb/secret-scanning.md) - **сначала ротируй ключ**, потом чистка. ```bash git filter-repo --path secrets.env --invert-paths ``` `--invert-paths` означает «удалить совпадения». Команда удалит `secrets.env` из всех коммитов истории. ### Заменить строку Если нужно убрать конкретное значение (a-la API-ключ), не файл: ```bash # Создать файл с правилами замены cat > replace.txt <[REMOVED] literal:my-secret-password==>[REMOVED] regex:ghp_[a-zA-Z0-9]{36}==>[REMOVED] EOF git filter-repo --replace-text replace.txt ``` Это пройдёт по содержимому каждого коммита и заменит подходящие строки на placeholder. ### Удалить большой файл Кто-то закоммитил 500MB-датасет, репо распух. Удалить из истории: ```bash git filter-repo --strip-blobs-bigger-than 100M ``` Или конкретно по пути: ```bash git filter-repo --path dataset.csv --invert-paths ``` После - `.git/` обычно физически распухший. Чтобы освободить место: ```bash git reflog expire --expire=now --all git gc --aggressive --prune=now ``` ### Сменить email во всей истории Закоммитил с личного email, хочешь - с рабочего: ```bash cat > mailmap.txt < EOF git filter-repo --mailmap mailmap.txt ``` Все коммиты с `personal@gmail.com` будут показаны как `work@company.com`. **SHA коммитов изменится.** ## После filter-repo Команда переписывает историю: SHA всех коммитов меняются. Последствия: - **Все клоны теперь устаревшие.** Никто не может просто `git pull`: история разошлась. Каждому нужно перейти на новый клон. - **PR и issue, ссылающиеся на старые SHA**, ломаются - старые коммиты не существуют. - **Force push в remote.** Очень внимательно: branch protection обычно блокирует, его нужно временно снять. - **Резервная копия - обязательна.** До запуска: `git clone backup-clone`. Если что-то пошло не так - есть откуда восстановить. Из-за этих последствий filter-repo - операция «один раз в год», не повседневная. Делается обычно командой, согласованно, с предупреждением всех. ## Альтернативы - **BFG Repo-Cleaner** - Java-инструмент, ещё быстрее на больших репах. Менее гибкий: только удаление файлов/блобов, без замены текста. - **Просто оставить и ротировать.** Если речь о секрете в публичном репо - он уже скомпрометирован, чистка не отменяет утечки. Иногда правильно: ротировать ключ и зафиксировать урок, без переписывания истории. ## Подводные камни - **filter-repo по умолчанию работает только на свежем clone.** Если запустишь в репо, где много remote - он откажется (через `--force` можно, но это сигнал «остановись, подумай»). - **Submodules при filter-repo сохраняются**, но если изменяются указатели - может потребоваться отдельная работа в каждом submodule. См. [detached-head](/courses/git/kb/detached-head.md). - **Очень большие репо** (десятки GB) могут филтроваться часами. Запускать с `nohup` или в screen. - **Tags локально переписываются**, но в чужих клонах нет. На своей машине filter-repo обновит refs/tags: тег, указывавший на переписанный коммит, начнёт указывать на новый SHA; тег на удалённый коммит исчезнет. Но у коллег, которые ранее склонили репо, старые теги останутся локально - `git fetch` сам по себе их не удаляет. Нужно или `git fetch --prune --prune-tags`, или клон с нуля. На сервере не забудь снести устаревшие теги: `git push origin :refs/tags/`. ## Команды ```bash git filter-repo --path file.env --invert-paths ``` Удалить файл из всей истории ```bash git filter-repo --replace-text replace.txt ``` Заменить строки во всех коммитах ```bash git filter-repo --strip-blobs-bigger-than 100M ``` Удалить все большие файлы из истории ```bash git reflog expire --expire=now --all && git gc --aggressive --prune=now ``` Освободить место после filter-repo ## См. также - [Поиск секретов в репозитории](/courses/git/kb/secret-scanning.md) - [.gitignore](/courses/git/kb/gitignore.md) - [git rebase](/courses/git/kb/rebase.md)