Дванадцять гігабайтів. Саме стільки важив цей репозиторій, коли я клонував його вперше. Перша думка — щось зламалося в мене: кривий mount, роздутий кеш, побитий pack. Нічого подібного. Репозиторій справді був таким. За тиждень я дізнався значно більше, ніж хотів, про те, як зменшити Git-репозиторій і вичистити паролі з історії, не втративши жодного рядка справжньої роботи.
Далі — як минув цей порятунок і які guardrails я лишив, щоб усе не наросло назад.
Археологія
Перш ніж щось чистити, треба зрозуміти, що саме ти чистиш. Репозиторій не доростає до дванадцяти гігабайтів випадково — за кожним мегабайтом стоїть історія, зазвичай негарна.
Я пройшовся базою обʼєктів через git rev-list --objects --all і git cat-file, відсортувавши блоби за розміром. Карта скарбів намалювалася сама. Просто в історію було закомічено:
- JAR-и та ZIP-и — збіркові артефакти, яким поруч із Git робити нічого
- Docker-образи, закомічені тарболами, бо комусь вони знадобилися «лише раз»
- Тисячі мертвих гілок, більшість — покинуті роками
- І те, від чого мене пересмикнуло: справжні паролі та API-ключі, що лежали в старих комітах і тихо чекали на того, хто виявиться досить цікавим, щоб запустити
git log -p
Останній пункт міняє всю картину. Це був не роздутий репозиторій. Це був інцидент безпеки, який просто ще не помітили.
Спершу ротація, потім чистка
Усі першими хапаються за BFG Repo Cleaner або git filter-repo, щоб вирізати секрети з історії. Інструмент правильний — але порядок дій важливіший за інструмент.
Переписування історії не «скасовує» витік. Щойно пароль потрапив у remote, який тягнули люди, CI-раннери та форки, — вважай, що він скомпрометований. Тому послідовність не обговорюється:
- Спершу зротувати всі засвічені креди — нові ключі, нові паролі, старі відкликати
- Потім вичистити старі значення з кожного коміту
- І лише потім force-push та re-clone у всіх
Зробиш навпаки — витратиш вихідні на переписування історії заради ключа, який усе ще робочий.
Чистка
Секрети зротовані — почалася операція. BFG Repo Cleaner вирізав з усієї історії кожен бінарник і кожен відомий секретний рядок. Великі файли, у яких усе ж була причина існувати, переїхали в нормальний artifact registry — туди, де бінарникам і місце. Мертві гілки — в архів і під ніж пачкою. Далі git gc --aggressive і repack, щоб реально повернути місце на диску.
Дванадцять гігабайтів перетворилися на охайний репозиторій, який клонується за секунди, а не за перекур.
Раз уже заліз — пайплайн
У настільки занедбаного репозиторію і пайплайн ніколи не буває здоровим. Раз усе й так розкрито, я заразом перекроїв CI:
- Послідовні стадії рознесені в паралель там, де ніхто не змушував їх іти по черзі
- Додано Docker layer caching — перестали пересобирати світ на кожен коміт
- Вибіркове тестування: правка одного модуля ганяє тести цього модуля, а не всього проєкту
Час клону та збірки впав із «піду зварю каву» до «вже готово».
Guardrails
Ось частина, яку пропускають у більшості чисток, — і єдина, що реально важлива вдовгу. Чистка без guardrails — тимчасова латка: ті самі звички, що роздули репозиторій, за рік наповнять його знову. Тому я поставив паркан:
- pre-commit hooks, які відбивають будь-який файл більший за 5 МБ ще до потрапляння в історію
- Політика життєвого циклу гілок: авто-видалення через 7 днів після мержу
- Secret scanning просто в CI-пайплайні — засвічений ключ роняє білд, а не осідає в історії
- Моніторинг розміру репозиторію з алертами, щоб розростання ловилося на 200 МБ, а не на 12 ГБ
Висновок
Ваш Git-репозиторій — фундамент усього delivery-пайплайна. Усе — CI, деплої, онбординг розробників, безпека — стоїть поверх нього. Коли він чистий, усе нижче за течією тече вільно. Коли це дванадцятигігабайтна капсула часу з поганими рішеннями — усе нижче за течією грузне.
Якщо клон триває довше, ніж чашка кави, репозиторій намагається вам щось сказати. Прислухайтеся — а потім сьогодні ж проженіть по ньому secret scan. Результат може не сподобатися. Саме тому й варто подивитися.