Хуки и подписи коммитов
Как автоматически прогонять проверки перед коммитом и пушем, криптографически подписывать коммиты и теги и раскапывать причины багов через blame и log.
Git hook — исполняемый скрипт, который Git сам запускает в определённый момент (перед коммитом, перед пушем и т.д.); если он завершается ненулевым кодом, операция отменяется. Подпись коммита — криптографическая гарантия (GPG или SSH-ключом), что автором изменения действительно были вы.
Зачем это нужно на практике
Линтер, который вы «забыли запустить», секрет, случайно попавший в коммит, или сломанные тесты, уехавшие в общую ветку, — всё это ловится до того, как мусор окажется в истории. Хуки автоматизируют дисциплину: правила выполняются машиной, а не силой воли. Подписи решают другую задачу — доверие: в открытых и корпоративных репозиториях важно доказать, что коммит от «Alice» сделала именно Alice, а не кто-то, подставивший её имя в user.name.
Хуки: pre-commit и pre-push
Хуки лежат в .git/hooks/. Там есть примеры с суффиксом .sample; чтобы хук заработал, создайте файл без суффикса и сделайте его исполняемым. pre-commit запускается до создания коммита — идеальное место для линтера и быстрых проверок:
#!/usr/bin/env bash
# .git/hooks/pre-commit — блокирует коммит при ошибках линтера
files=$(git diff --cached --name-only --diff-filter=ACM | grep '\.py$')
[ -z "$files" ] && exit 0
if ! ruff check $files; then
echo "✗ Линтер нашёл ошибки — коммит отменён" >&2
exit 1
fi
exit 0
chmod +x .git/hooks/pre-commit # без этого Git хук не запустит
pre-push срабатывает перед отправкой и подходит для более тяжёлых проверок — например, полного прогона тестов, чтобы не пушить красное:
#!/usr/bin/env bash
# .git/hooks/pre-push — не пускать пуш, если тесты падают
if ! pytest -q; then
echo "✗ Тесты не прошли — push заблокирован" >&2
exit 1
fi
Важный нюанс: папка .git/hooks/ не коммитится, поэтому хуки не разъезжаются с репозиторием автоматически. Чтобы делиться ими в команде, хранят скрипты в отдельной папке под версионным контролем (например, .githooks/) и переключают путь:
git config core.hooksPath .githooks # искать хуки в версионируемой папке
На практике для общих хуков часто берут менеджер вроде pre-commit или husky, но под капотом они делают ровно то же — кладут исполняемые скрипты в точки pre-commit/pre-push.
Подпись коммитов и тегов
Git умеет подписывать коммиты двумя видами ключей: классический GPG и современный SSH-ключ (тот же, которым вы пушите). Настройка SSH-подписи проще:
git config --global gpg.format ssh
git config --global user.signingkey ~/.ssh/id_ed25519.pub
git config --global commit.gpgsign true # подписывать ВСЕ коммиты автоматически
Для GPG настройка похожа, только формат другой:
git config --global user.signingkey ABCDEF1234567890 # id GPG-ключа
git config --global commit.gpgsign true
Разово подписать конкретный коммит или тег можно флагом -S:
git commit -S -m "release: v2.0.0" # подписанный коммит
git tag -s v2.0.0 -m "signed release" # подписанный тег
Проверить подписи в истории и у тега:
git log --show-signature -1 # показать статус подписи последнего коммита
git verify-tag v2.0.0 # проверить подпись тега
На GitHub/GitLab подписанные коммиты помечаются плашкой Verified, если публичная часть ключа добавлена в профиль. Это и есть практический смысл подписи: визуальная и проверяемая гарантия авторства.
blame и log-археология для расследований
Когда баг найден, нужно понять, кто, когда и зачем написал подозрительную строку. git blame показывает для каждой строки файла коммит, автора и дату:
git blame -L 40,60 src/auth.py # авторство строк 40-60
git blame -w -M src/auth.py # -w игнорирует пробелы, -M видит перемещения
Флаги важны: -w не даёт «форматировщику» перетянуть на себя авторство (игнорирует изменения пробелов), а -M отслеживает строки, перемещённые внутри файла. Дальше — раскопки логом. Поиск коммитов по тексту изменения (когда появилась или исчезла строка) делает «кирка» -S:
git log -S "calculate_tax" -- src/billing.py # где трогали этот идентификатор
git log -p -- src/auth.py # история файла с дифами
git log --oneline --follow -- src/old_name.py # история сквозь переименования
Связка железная: blame даёт хеш виновной строки, git show <хеш> — полный контекст того коммита (сообщение, все изменённые файлы, diff), а git log -S — когда конкретная логика вообще появилась в проекте. Вместе с git bisect из первого урока это полный набор для расследования регрессий: bisect находит какой коммит сломал, blame/show — почему.
Как это работает под капотом
Хук — это не магия, а обычный файл, который Git ищет по фиксированному имени в core.hooksPath (по умолчанию .git/hooks/) и просто запускает в нужный момент жизненного цикла. Решение «продолжать или нет» принимается по коду возврата процесса: 0 — продолжить, иначе — прервать. Поэтому хуки клиентские (живут на вашей машине) и не являются защитой сервера: их можно обойти флагом --no-verify. Серверную гарантию дают защищённые ветки и серверные хуки на стороне GitHub/GitLab.
Подпись же встраивается в сам объект коммита: при создании Git считает хеш содержимого и шифрует его вашим приватным ключом, добавляя блок подписи в коммит. Любой, у кого есть ваш публичный ключ, пересчитывает хеш и сверяет — совпало значит коммит не подменён и сделан владельцем ключа. Именно поэтому подделать имя в user.name легко, а подделать подпись без приватного ключа — нет.
Частые ошибки
- Хук не исполняемый. Забыли
chmod +x— Git молча его не запускает. Проверьте права файла. - Надежда на клиентские хуки как на защиту. Их обходят
git commit --no-verify. Критичные правила дублируйте серверными проверками/CI. - Хуки не доезжают до команды.
.git/hooks/не версионируется; используйтеcore.hooksPathи папку в репозитории. - Подпись настроена, но не Verified. Публичный ключ не добавлен в профиль GitHub/GitLab, либо email коммита не совпадает с email ключа.
- blame обвиняет форматировщик. Без
-wи-Mавторство уезжает к тому, кто переформатировал или переместил код, а не к автору логики.
Итоги
- Хуки
pre-commit/pre-pushавтоматизируют проверки; решение принимается по коду возврата, файл должен быть исполняемым. .git/hooks/не коммитится — для команды используйтеcore.hooksPathи версионируемую папку.- Клиентские хуки обходятся
--no-verify; настоящая защита — серверные проверки и защищённые ветки. - Подпись (
commit.gpgsign true, ключ GPG или SSH) криптографически доказывает авторство; на хостингах это плашка Verified. git blame -w -M,git log -Sиgit show— связка для археологии истории и поиска причины бага.