open atlas
↑ К треку
Git: от нуля до сеньора GIT · 09 · 03

Переписывание всей истории репозитория

Старый закоммиченный секрет живёт в каждом клоне. git filter-repo переписывает всю историю, убирая путь (--path X --invert-paths) или заменяя текст. Каждый хеш коммита меняется, поэтому нужен force-push и всеобщий re-clone — а утёкший секрет надо ротировать: он уже публичен.

GIT Senior ◷ 19 min
Уровень
ОсновыJuniorMiddleSenior

Кто-то закоммитил config/prod.env с живым ключом AWS восемь месяцев и четыреста коммитов назад. Удаление его новым коммитом не даёт ничего: ключ по-прежнему сидит в старом коммите, в каждом клоне, в каждом форке и в истории на удалённом сервере. git revert и даже git rebase трогают только недавние коммиты — а это закопано глубоко, в каждой версии дерева с тех пор. Чтобы по-настоящему его убрать, надо переписать всю историю, каждый коммит от той точки до HEAD.

К концу этого урока ты сможешь вычистить файл или секрет из всей истории репозитория через git filter-repo и будешь знать две вещи, которые обязаны за этим последовать — согласованный re-clone и ротацию утёкшего ключа, — иначе вся очистка просто театр.

Цель

После этого урока ты сможешь переписать всю историю репозитория, чтобы вычистить утёкший секрет или большой файл с помощью git filter-repo, объяснить, почему меняется каждый хеш коммита, и провести обязательные последствия: force-push, заставить всех сделать re-clone и ротировать раскрытый секрет.

1

Проблема в глубине: секрет в старом коммите есть в каждой версии истории, поэтому переписать надо всё. История Git — это цепочка, где хеш каждого коммита вычисляется из его содержимого и хеша его родителя. Секрет, закоммиченный давно, присутствует в дереве того коммита и наследуется вперёд каждым потомком. Повседневные инструменты отмены до него не дотягиваются — git revert добавляет новый коммит, git rebase -i правит недавние коммиты, но ни один не стирает содержимое из коммитов сотни шагов назад. Нужен инструмент, который проходит весь граф коммитов и пересобирает каждый коммит без вредного содержимого.

2

git filter-repo — современный, рекомендуемый инструмент для переписывания всей истории. Это отдельная установка (pip install git-filter-repo), не встроенная в Git, и по задумке он работает на свежем клоне. Чтобы убрать путь из всей истории:

# Вычистить файл из каждого коммита, где он когда-либо был
git filter-repo --path config/prod.env --invert-paths

--path выбирает файл; --invert-paths инвертирует выбор, превращая его в «сохранить всё, кроме этого». Чтобы вытереть значение секрета везде, где оно встречается (даже в файлах, которые ты сохраняешь), используй файл замен:

# secrets.txt содержит:  AKIA...EXAMPLE==>REMOVED
git filter-repo --replace-text secrets.txt

filter-repo переписывает каждый коммит, удаляя файл или редактируя текст, и он драматически быстрее и безопаснее старых альтернатив.

3

git filter-branch устарел; BFG Repo-Cleaner — другой принятый вариант. Ты ещё встретишь filter-branch в старых ответах — но документация самого Git теперь предупреждает против него: он чудовищно медленный на реальных репо и усеян ловушками (тихо неверно обрабатывает краевые случаи и может оставить наполовину переписанную историю). Проект Git явно рекомендует вместо него filter-repo. Для узкой задачи удаления больших блобов или известных секретов есть BFG Repo-Cleaner — быстрый, простой Java-инструмент (bfg --delete-files prod.env, bfg --replace-text secrets.txt). Бери filter-repo для общих переписываний и BFG, когда подходит его более узкий набор функций; избегай filter-branch.

4

Каждый хеш коммита меняется, поэтому переписывание — лишь половина работы; последствия обязательны. Поскольку каждый хеш зависит от содержимого и родителя, пересборка коммита даёт новый SHA, и каждый коммит после него тоже получает новый SHA. Переписанная история — это другая цепочка, разошедшаяся с тем, что держат удалённый сервер и каждый клон. Должны последовать три вещи:

# 1. Принудительно положить новую историю на удалённый сервер (перезаписать старую цепочку)
git push --force-with-lease --all
git push --force-with-lease --tags
  1. Все должны сделать re-clone или жёсткий reset. У любого со старым клоном по-прежнему есть и секрет, и старые хеши; обычный git pull попытается влить старую историю обратно. Коллеги должны выбросить свои копии и склонировать заново.

  2. Ротировать секрет. Это не обсуждается и не зависит от Git. Ключ был раскрыт — он может быть закеширован на GitHub, в форках, в логах CI, в чьём-то скроллбэке терминала или уже собран ботом. Переписывание истории убирает его вперёд, но не может «раз-утечь» то, что было публичным. Отзови и перевыпусти учётные данные.

Частая ошибка

Самая опасная ошибка — считать переписывание истории тем самым исправлением и пропустить ротацию. Боты-сканеры секретов прочёсывают публичные push за секунды; утёкший облачный ключ часто используют для крипто-майнинга раньше, чем человек вообще заметит. К моменту, когда ты запускаешь filter-repo, считай секрет уже скомпрометированным. Правильный порядок на самом деле: сначала ротация (немедленно убить живые учётные данные), затем переписывание истории, чтобы перестать его пере-утекать. Хирургия истории без ротации — это мыть окно, пока дверь нараспашку.

Разбор примера

Ключ AWS утёк в публичный репо — полный ответ.

Срабатывает алерт сканера: config/prod.env с живым ключом AKIA... в публичной main, закоммичен месяцы назад. Лид не начинает с Git.

Минута 0 — ротация. Он открывает консоль AWS и немедленно деактивирует ключ, выпуская новый и обновляя хранилище секретов и CI. Радиус поражения теперь закрыт независимо от того, что Git делает дальше; старый ключ мёртв.

Затем — переписывание. На свежем клоне он запускает pip install git-filter-repo, потом git filter-repo --path config/prod.env --invert-paths. Каждый коммит пересобран без этого файла; все SHA меняются. Он делает git push --force-with-lease --all --tags, чтобы перезаписать удалённый сервер, затем просит поддержку GitHub вычистить кешированные представления и проверяет форки, всё ещё несущие этот блоб.

Наконец — координация. Он пишет команде: удалите свои локальные клоны и склонируйте заново, не делайте git pull. Двое разработчиков с открытыми ветками пересоздают их поверх новой истории. Неделю спустя ключ давно мёртв, файла нет ни в одном достижимом коммите, а в постмортеме записано одно правило: секреты живут в менеджере секретов, никогда в закоммиченном файле. Ротация сделала утечку безвредной за минуты; переписывание сделало её аккуратной.

Почему это работает

Почему force-push обычно запрещён, но здесь обязателен? Обычный push должен быть fast-forward — он лишь добавляет коммиты поверх того, что есть на удалённом, так что ничья история не перезаписывается. Переписывание истории расходится с удалённым: новая цепочка не является потомком старой, поэтому fast-forward невозможен и Git отказывает в обычном push. --force обходит эту защиту, а --force-with-lease — более безопасная форма, которая прерывается, если кто-то другой запушил тем временем, чтобы ты тихо не затёр новую работу коллеги. Force-push опасен именно потому, что может стереть общую историю — а это ровно и только то, что и намерено сделать переписывание истории.

Проверь себя
Викторина

Ты использовал git filter-repo, чтобы убрать утёкший API-ключ из всей истории, и сделал force-push. Что ещё строго обязательно, чтобы утечка была обработана?

Итог

Секрет или большой файл, закоммиченный давно, живёт в каждом коммите с тех пор и в каждом клоне, поэтому убрать его — значит переписать всю историю; revert и rebase так глубоко не дотягиваются. git filter-repo — современный, рекомендуемый инструмент: --path X --invert-paths вычищает файл из всей истории, --replace-text редактирует значение везде. git filter-branch устарел (медленный, ловушки), а BFG Repo-Cleaner — более узкая альтернатива. Поскольку каждый хеш коммита пересобирается, история расходится: нужно сделать git push --force-with-lease, заставить всех сделать re-clone и — самое главное — ротировать утёкший секрет, ведь он уже был публичным, и переписывание не может его «раз-утечь». Сначала ротация, потом переписывание. Дальше ты научишься доказывать, кто на самом деле написал коммит, — через криптографическую подпись.

Практика

Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.

вспомнитьприменитьуглубить0 из 4 завершено

Что-то непонятно?

Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.

хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.