Для змін, які зачіпають усю історію (а не кілька останніх комітів), interactive rebase не підходить. Інструмент для цього - git-filter-repo, який рекомендує сам проєкт Git замість застарілого й повільного git filter-branch.
brew install git-filter-repo # чи pip install git-filter-repo
Типові задачі:
# видалити файл з усієї історії
git filter-repo --invert-paths --path storage/dump.sql
# видалити всі файли, більші за 10 МБ
git filter-repo --strip-blobs-bigger-than 10M
# лишити лише підкаталог (виділити пакет в окремий репозиторій)
git filter-repo --subdirectory-filter packages/billing
# замінити текст у всіх файлах історії (наприклад, ключ)
git filter-repo --replace-text replacements.txt
Змінити автора - через файл .mailmap:
Olena Petrenko <olena@company.com> <olena@old-laptop.local>
git filter-repo --mailmap .mailmap
Що відбувається: кожен коміт, починаючи з першого зміненого, отримує новий хеш - бо змінюється вміст або батько. Фактично це новий репозиторій зі схожою історією.
Наслідки, які треба спланувати:
- усі клони застаріли: колеги мають зробити свіжий клон, а не
pull- інакше старі коміти повернуться при наступному злитті; - force push усіх гілок і тегів, тимчасове зняття захисту гілок;
- відкриті pull request посилаються на старі коміти - їх доведеться перестворити;
- посилання на коміти в задачах, документації, changelog стають недійсними;
- копії на сервері: GitHub ще деякий час зберігає старі об'єкти в кеші й у форках - для повного видалення чутливих даних потрібне звернення в підтримку.
Захисні механізми filter-repo: за замовчуванням він відмовляється працювати не на свіжому клоні (щоб не зіпсувати робочий репозиторій) і видаляє origin після переписування, щоб випадковий push не перезаписав сервер.
Альтернатива для великих файлів - BFG Repo-Cleaner: простіший, але менш гнучкий.
Перед тим як переписувати - перевірити, чи не простіше залишити історію як є: великий файл у минулому лише збільшує розмір клону, а витік секрету лікується ротацією ключа, а не переписуванням історії.