18 окт. 2025 г.·6 мин. чтения

Pre-commit хуки для унаследованных репозиториев: простые защитные правила

Настройте pre-commit хуки для унаследованных репозиториев: форматирование, линтинг, сканирование секретов и быстрые тесты, чтобы блокировать плохие коммиты до запуска CI.

Pre-commit хуки для унаследованных репозиториев: простые защитные правила

Почему унаследованные репозитории продолжают ломаться

Унаследованный репозиторий — это код, который вы не формировали. Правила неочевидны, стиль смешан, и фраза «у меня работает» появляется потому, что никто толком не знает, чего ожидает репозиторий.

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

Надежда только на CI обычно слишком поздняя. CI запускается после того, как коммит уже поделён, ревью идут, и люди уже успели скачать сломанное состояние. Когда CI падает, тратится время на повторный запуск пайплайнов, ребейзы и угадывание, какое изменение всё сломало. Со временем ревьюеры перестают доверять сигналам, потому что падения кажутся «обычным делом».

Настоящий вред в том, как маленькие плохие коммиты накапливаются:

  • Крошечное форматирование порождает шумные diff’ы, и реальная ошибка проскальзывает.
  • Линт-проблему откладывают «на потом», и она становится системой.
  • Тесты не запускают локально, и сломанный билд блокирует всех.
  • Секрет коммитят и начинается паника по ротации ключей.

Pre-commit хуки помогают тем, что перемещают наиболее частые проверки в самое раннее место: до того, как изменение станет общей проблемой. Они также создают единообразную базу без долгих споров о «правильном способе».

Когда это работает, всё выглядит скучно (в хорошем смысле): меньше сломанных билдов, мелкие diff’ы и быстрее мерджи, потому что ревью фокусируются на логике, а не на стиле.

Какие вещи должны ловить защитные хуки

Унаследованные репозитории ломаются предсказуемо. Цель — не блокировать каждый коммит, а остановить несколько ошибочных изменений с высоким приоритетом, которые создают шум, ломают билды или сливают данные.

Начните с дрейфа форматирования. Неформатированные файлы дают огромные diff’ы, которые скрывают реальные изменения. Однострочный фикс не должен сопровождаться 400 строками изменения пробелов. Автоформатирование при коммите делает ревью читабельными и упрощает слияния.

Дальше — линт, который ловит потенциальные баги, а не вкусовые правила: несуществующие переменные, неиспользуемые импорты, подозрительные сравнения, пропущенные await или опасные паттерны вроде построения SQL через строки. Если линтер в основном ругается на запятые, люди учатся его игнорировать.

Секреты — главный случай. Ключи попадают в репозиторий, когда кто‑то копирует .env, вставляет токен в конфиг или добавляет лог с учётными данными. Сканер секретов должен блокировать коммит до того, как он попадёт в удалённый репозиторий.

Наконец, запускайте быстрые тесты, которые занимают секунды, а не минуты. Лучший хук — тот, который люди не отключают. Небольшой smoke-тест, быстрый набор юнитов или базовая проверка типов ловит явные регрессии рано.

Практичный набор того, что стоит блокировать:

  • Автоформатирование изменений (чтобы diff’ы были малы)
  • Высокосигнальный линт (вероятные баги)
  • Обнаруженные секреты (токены, API-ключи, приватные ключи)
  • Быстрые тесты или проверки типов (быстрая обратная связь)
  • Санити-проверки (валидный JSON/YAML, консистентность lockfile)

Выберите маленький набор инструментов под репозиторий

Когда вы наследуете репозиторий, цель не «максимальное покрытие», а меньше сломанных коммитов. Начните с трёх–четырёх надёжных проверок и добавляйте остальные только после того, как команда перестанет бороться с инструментом.

Хороший стартовый набор:

  • Форматтер (автопочинка, без споров)
  • Линтер (ловит реальные ошибки, а не стиль)
  • Сканер секретов (останавливает утечки ключей и токенов)
  • Один быстрый тест или проверка сборки (секунды, не минуты)

Подберите инструменты под реальные языки в проекте. Если языков несколько, ограничьте проверки по папкам, чтобы одна часть не замедляла другую.

Скорость важнее совершенства. Если хук занимает 30–60 секунд, люди будут его обходить. Предпочитайте предсказуемые инструменты: одинаковый вывод на всех машинах, низкий процент ложных срабатываний и понятные способы исправления.

Решите, что блокирует коммит, а что только предупреждает. Блокировать стоит то, что почти всегда неверно (форматирование, очевидные ошибки линта, секреты). Предупреждения лучше для «возможно» правил (пределы сложности, строгие стиль‑правила) до тех пор, пока репозиторий не стабилизируется.

В монорепозитории простое правило помогает: запускать только то, что изменилось. Если вы редактируете папку API, запускайте только API‑линт и быстрый API‑тест, а не весь репозиторий.

Пошагово: добавить pre-commit в унаследованный репозиторий

Создайте baseline‑ветку (или тег) от текущей main. Затем запустите проверки по всему репозиторию, чтобы увидеть зону поражения. Первый прогон — не про идеальность. Он показывает, что уже сломано, что просто шум стиля и что будет блокировать всех с первого дня.

Дальше выберите, как запускать хуки.

  • Фреймворк для pre-commit проще распространять между командой и он обычно одинаково ведёт себя на большинстве машин.
  • Нативные git‑хуки просты, но локальны по умолчанию — люди забывают их или не ставят.

Для большинства команд лучше общий конфиг в репозитории, потому что его можно ревьювить как любой другой код.

Держите конфигурацию скромной: один файл, понятные имена и только те проверки, которые вы действительно хотите принудить. Установите хуки локально и подтвердите их работу простым изменением и коммитом.

План внедрения, который избегает драмы:

  • Создать baseline‑ветку и запустить все хуки один раз, чтобы зафиксировать, что падает.
  • Добавить файл конфигурации в репозиторий и сохранить первоначальный набор хуков небольшим.
  • Установить хуки и проверить их на реальном коммите.
  • Сначала запускать только по изменённым файлам (или в режиме предупреждений), затем переключиться на блокировку.
  • Добавлять по одному новому хуку, чтобы ошибки было легко понять.

Постепенное ужесточение важно. Если первый коммит блокируется 700 форматными изменениями, люди будут обходить хуки. Сначала исправляйте высокорисковые вещи (секреты, очевидные ошибки линта), затем возвращайтесь к форматированию.

Добавьте форматирование, которое прекращает споры о стиле

Быстро устранить проблемы безопасности
Удаляем открытые секреты и закрываем типичные дыры, например SQL-инъекции, до следующего деплоя.

Форматирование — самый простой защитный механизм, и оно быстро окупается. Выберите один форматтер на язык и считайте его единственным источником правды. Когда стиль консистентен, ревью фокусируются на логике, а не на табах или кавычках.

Для скорости запускайте форматирование только по staged-файлам. Это держит коммиты быстрыми и избегает ситуации «я тронул одну строку, а поменялось 2000».

Хорошая база форматирования также предотвращает шумные diff’ы:

  • Нормализовать окончания строк (чтобы Windows и macOS не ссорились)
  • Удалять завершающие пробелы
  • Убедиться, что файлы заканчиваются новой строкой
  • Поддерживать единый кодировочный формат (обычно UTF-8)

Будьте аккуратны с сгенерированными файлами и vendor‑папками. Обычно не хочется, чтобы форматтер переписывал сборочные артефакты, lockfile’ы, которые вы не поддерживаете, или сторонний код. Явно исключайте их, чтобы форматирование оставалось предсказуемым:

# Example idea (not a full config)
exclude: "^(dist|build|vendor|\.next|coverage)/"

Наконец, договоритесь, когда происходит форматирование. Если в редакторе у кого‑то включено форматирование по сохранению, форматирование при коммите должно с ним совпадать, а не конфликтовать. Простое правило работает: форматируйте при сохранении для комфорта, а на коммите принуждайте формат, чтобы репозиторий оставался чистым даже при разных редакторах.

Добавьте линтинг, который ловит реальные ошибки

Линтинг полезен в унаследованных репозиториях, когда он предотвращает баги, а не порождает споры о запятых. С pre-commit хуками цель проста: остановить очевидные ошибки до того, как они попадут в ветку и потратят время в CI.

Выберите один линтер под основной язык и настройте его так, чтобы он отдавал приоритет сигналам багов, а не личным вкусам. Правила, которые быстро окупаются:

  • Неиспользуемые переменные и импорты
  • Пропущенная обработка ошибок (непойманные промисы, игнорируемые значения)
  • Опасное построение строк (частая причина инъекций)
  • Подозрительные сравнения и недостижимый код
  • Ошибки при копировании/вставке и дублирующаяся логика

Если проект типизирован, добавьте лёгкую проверку типов там, где это помогает. Не начинайте с полной проверки всего проекта. Сфокусируйтесь на зонах, которые чаще всего ломаются в продакшене (аутентификация, платежи, доступ к данным), затем постепенно расширяйтесь.

Наследный код — причина, по которой линтер валится в унаследованных проектах. Не пытайтесь «поправить мир», чтобы ввести хук. Используйте целевые игноры или лимитируйте линт по изменённым файлам. Ещё вариант — временная база, при которой хук блокирует только новые нарушения, а старые исправляются постепенно.

Сделайте вывод дружелюбным: короткое объяснение, предложенное исправление и авто-фикс там, где это безопасно.

Сохраняйте предсказуемость времени выполнения. Хук, который иногда занимает 5 секунд, а иногда — 2 минуты, будет отключен. Стремитесь к стабильным быстрым проверкам по staged‑файлам, а тяжёлый анализ оставляйте для CI.

Добавьте сканирование секретов до отправки на remote

В унаследованных репозиториях секреты часто прячутся на виду. Быстрый сканер секретов в pre-commit поймает очевидные утечки (API-ключи, access токены, приватные ключи, клиентские секреты OAuth) до того, как они покинут ноутбук.

Используйте сканер, который умеет находить паттерны и блокировать новые утечки. Многие команды настраивают такой инструмент так, чтобы он фейлил коммит при нахождении нового секрета, но при этом позволял иметь ревьюемую базу известных находок.

Сделайте правило простым: блокировать коммиты, которые добавляют новые секреты. Если хук сработал, разработчик удаляет секрет или помечает как ревьюемое ложное срабатывание через управляемый процесс (не отключая хук).

Для исключений предпочитайте явный allowlist, который легко ревьювить. Храните baseline в версии и относитесь к изменениям в нём как к значимым.

Чаще всего секреты попадают в:

  • .env файлы и локальные конфиги
  • Примерные файлы настроек
  • Отладочные логи и дампы ошибок
  • Тестовые фикстуры и записанные HTTP-ответы
  • Фрагменты, скопированные из панелей управления

Если вы обнаружили, что секрет уже был закоммичен, считайте его скомпрометированным, даже если репозиторий приватный:

  • Ротируйте или отзывайте ключ/токен
  • Удалите секрет из кода и замените переменной окружения
  • Проверьте историю git и при необходимости очистите её
  • Поиск по использованию (логи, деплойменты, CI)
  • Добавьте регрессионную проверку, чтобы этого не повторилось

Добавьте быстрые тесты, которые разработчики действительно будут запускать

Начните с бесплатного аудита кода
Отправьте унаследованный репозиторий — мы покажем узкие места, риски и быстрые улучшения.

Единственные тесты, которые помогают при коммитах, — это те, которые люди не пропускают. Стремитесь к небольшому надёжному набору, который выполняется менее чем за 60 секунд на обычном ноутбуке. Если это дольше, разработчики будут обходить хуки, и защитный механизм превратится в шум.

Начните с smoke-тестов, которые доказывают, что репозиторий собирается и ключевые пути работают. Хорошие кандидаты — тесты, которые быстро ловят очевидные поломки, а не полное покрытие.

Что запускать в pre-commit

Держите набор коротким и привязанным к реальным падениям:

  • Проверка сборки (компиляция, тайпчек или минимальный бандл)
  • Санити миграций базы (валидировать схему или применить к временной БД)
  • Один-два API теста (health endpoint, базовый create-read flow)
  • Smoke для аутентификации (логин работает, защищённый роут остаётся защищённым)
  • Минимальный набор юнит-тестов, помеченных как «smoke» или «fast»

Сделайте команду удобной на любой машине с минимальной настройкой. Предпочитайте единый вход (make‑таск или скрипт в package.json), который в случае ошибки выдаёт понятный вывод. Избегайте зависимостей от сервисов, которые есть только на ноутбуке одного разработчика.

Куда перенести тяжёлые тесты

Если набор тестов занимает минуты, вынесите его из pre-commit:

  • Pre-commit: быстрые smoke-тесты только
  • На push или PR: полные юнит и интеграционные тесты
  • Ночью: медленные end-to-end тесты, нагрузочные проверки, аудит зависимостей

Чтобы тесты были детерминированными, уменьшайте флакерность сетевых зависимостей. Мокайте внешние API, фиксируйте время при необходимости и используйте локальные фикстуры. Если всё же нужно обращаться к сервису, делайте это опционально и отключайте по умолчанию в pre-commit.

Пример: стабилизация AI‑сгенерированного прототипа

Представьте репозиторий, который начался как быстрый прототип от AI-инструмента. Демонстрирует хорошо, но в проде постоянно ломается. Аутентификация иногда зацикливается, папки в беспорядке, и любое изменение рискует открыть новую дыру.

Здесь pre-commit хуки особенно полезны: добавьте небольшие защитные механизмы, которые остановят худшие ошибки рано, не начиная глобальный рефактор.

Простой rollout из трёх коммитов поддержит доверие:

  • Первый коммит: добавить форматтер и базовый линтер с безопасными настройками.
  • Второй коммит: добавить сканирование секретов, аккуратно удалить закоммиченные .env, положить примерный файл и ротировать утёкшие ключи.
  • Третий коммит: добавить один smoke-тест, отражающий последний инцидент (например, аутхи-флоу, который должен выдавать валидную сессию).

Объясняя эти изменения нетехническому основателю или клиенту, фокусируйтесь на результатах:

  • «Это блокирует случайные утечки паролей и API-ключей до того, как код покинет ноутбук.»
  • «Это ловит очевидные ошибки до того, как они потратят ваше время в CI или на деплое.»
  • «Один быстрый тест предотвращает конкретный сбой, за который вы уже платили.»
  • «Это не меняет фичи. Это делает будущие изменения безопаснее.»

Распространённые ловушки и как их избежать

Сделайте pre-commit надёжным
Если хуки постоянно падают, мы починим код, чтобы защитные механизмы перестали мешать.

Унаследованные репозитории и так создают трение. Самый быстрый путь провалить pre-commit — сделать его наказанием. Хорошие защитные механизмы тихи большую часть времени и громки только тогда, когда ловят то, что в будущем стоило бы вам времени.

Держите хуки быстрыми и предсказуемыми

Главная ловушка — сделать хуки настолько строгими, что никто не может закоммитить. Если выполнение превышает минуту, люди будут обходить их.

Практичные правила:

  • Запускайте локально только быстрые проверки (формат, линт, секреты и небольшой smoke).
  • Не запускайте весь тест‑сьют на каждый коммит.
  • Работайте только со staged‑файлами. Автоисправление незастейдженных файлов порождает неожиданные diff’ы.
  • Сначала делайте предупреждения для шумных правил, затем ужесточайте.
  • Делайте вывод понятным: одно сообщение об ошибке — и сразу инструкция, что делать дальше.

Если форматтер или линтер регулярно правит файлы, которые не входят в коммит, переключитесь на staged‑режим, чтобы хук никогда не менял незаконченные правки.

Сделайте обход хука осознанным выбором

Другой провальный сценарий — позволить людям игнорировать падения без процесса. Иногда обход оправдан (hotfix, сломанный внешний инструмент, срочная демо), но это должно быть сознательное исключение.

Зафиксируйте ожидания в репозитории:

  • Документируйте, когда обход допустим и какие действия требуется выполнить после.
  • Если кто-то обходит хук, CI всё равно должен ловить те же классы проблем.

Следите за паритетом CI. Если локально используют одну версию линтера, а в CI — другую, получаются «работает у меня» коммиты. Фиксируйте версии инструментов и держите локальные хуки в паре с CI, чтобы ошибки были консистентными.

Короткий чеклист и следующие шаги

Если вы добавляете pre-commit в унаследованный репозиторий, держите первый проход небольшим и надёжным. Ваша цель — поймать очевидные проблемы рано, не делая коммиты наказанием.

Базовая настройка, которую можно сделать за день:

  • Автоформат при коммите
  • Линт только по тронутым файлам (быстрые, высокосигнальные правила)
  • Сканирование секретов (ключи, токены, приватные ключи, случайные .env)
  • Один быстрый тест (smoke или узкий набор юнитов)
  • Зафиксировать версии инструментов

Внедряйте поэтапно, чтобы команда не сопротивлялась:

  1. Базовый режим: запускать хуки только по новым/изменённым файлам
  2. Предупреждать: локально делать хук фейлящим, но не блокировать CI
  3. Принудительно: блокировать коммиты и фейлить CI при ошибках хуков

Через неделю вы должны увидеть меньше коммитов «исправить форматирование», меньше комментариев в PR про стиль, меньше CI‑падений по простым проблемам и более быстрые ревью, потому что люди фокусируются на логике, а не на шуме.

Знайте, когда остановиться латанием. Если каждый хук обнаруживает более глубокие проблемы (флаки тесты, постоянно ломающееся аутх, утечки конфигов, спутанные модули), защитные механизмы не исправят основу — нужно целенаправленное восстановление: реструктуризация, усиление безопасности и приведение к состоянию, где быстрые тесты действительно что‑то значат.

Если репозиторий начался как AI‑сгенерированный прототип и продолжает ломаться в продакшене, FixMyMess (fixmymess.ai) создан именно для таких случаев: диагностика кодовой базы, исправление логики и проблем безопасности и подготовка к деплою. Быстрый аудит также поможет решить, какие защитные правила включать первыми, чтобы не замедлять команду.

Часто задаваемые вопросы

Зачем нужны pre-commit хуки, если у нас уже есть CI?

CI обнаруживает проблемы после того, как плохой коммит уже распространился. Pre-commit хуки предотвращают самые частые ошибки до того, как они попадут в вашу ветку, к коллегам или в пайплайн, что сокращает число сломанных сборок и время на повторный запуск CI.

Какие первые защитные механизмы стоит добавить в унаследованный репозиторий?

Начните с малого: форматтер, линтер с высокосигнальными правилами, сканер секретов и один быстрый тест или тип-проверка. Такой набор предотвращает шумные diff’ы, очевидные баги, утечки учетных данных и простые регрессии, не превращая коммиты в мучение.

Насколько быстрыми должны быть pre-commit хуки?

Старайтесь уложиться в 60 секунд на обычном ноутбуке и предпочитайте проверки, которые работают только по staged/изменённым файлам. Если хуки медленные или непредсказуемые, люди начнут их обходить и вы потеряете весь эффект.

Как избежать огромного «отформатировать всё» diff’а?

Форматируйте только то, что вы коммитите, а не весь репозиторий. Это держит diff’ы малыми, избегает неожиданных правок в чужих файлах и позволяет внедрить защиту без масштабной войны с форматированием.

Как включить линтинг, не сломав весь репозиторий?

Сделайте начальный прогон, чтобы увидеть существующие ошибки, а затем блокируйте только новые нарушения. Так вы сможете продолжать работать, постепенно вычищая наследие, вместо того чтобы пытаться починить всё в одном больном PR.

Что делать, если хук находит секрет?

Сделайте скан секретов жёстким блоком для новых утечек и считайте любой найденный коммит с секретом скомпрометированным. Удалите секрет из кода, ротируйте или отзывайте ключ, затем удерживайте сканер включённым, чтобы ошибка не повторилась.

Какие тесты должны выполняться в pre-commit, а какие в CI?

В pre-commit запускайте простой smoke-тест, который отражает реальные инциденты (например, аутентификация, минимальная сборка). Тяжёлые интеграционные и end-to-end тесты перенесите в CI, чтобы локальные коммиты оставались быстрыми и надёжными.

Как работают pre-commit хуки в монорепозитории?

Ограничьте проверки по папкам, где вы работали: правки в API запускают только API-линт и быстрые API-тесты, а не весь фронтенд. Правило «запускай только то, что поменялось» помогает избежать слишком медленных хуков в монорепозиториях.

Когда допустимо обходить хуки?

Обходите хуки только при реальной экстренной необходимости и фиксируйте это как сознательное исключение с последующим разбором. Даже если кто-то обходит локально, CI всё равно должен запускать те же проверки, чтобы исключения не стали нормой.

Когда стоит перестать добавлять защитные правила и обратиться за внешней помощью?

Если каждое изменение продолжает ломать аутентификацию, протекать конфиги или открывать структурные проблемы, хуки сами по себе не решат фундаментальные баги — они лишь выставят боль раньше. В таком случае полезно пригласить внешнюю помощь: FixMyMess может выполнить бесплатный аудит, диагностировать проблему, восстановить логику, усилить безопасность, отрефакторить и подготовить репозиторий к деплою.