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