Skip to main content

Що таке Git hooks?

Git hooks - це скрипти, які Git запускає автоматично в конкретних точках свого процесу: перед комітом, перед пушем, після злиття гілок.

Теорія

TL;DR

  • Hooks - як паспортний контроль в аеропорту: Git зупиняється в конкретному місці, запускає твій скрипт, і якщо він повертає ненульовий код виходу, операція скасовується.
  • Hooks зберігаються в .git/hooks/ і працюють тільки локально. Колеги їх не отримують при клонуванні.
  • Для локальних перевірок до 10 секунд (lint, тести) - hooks. Для обов'язкового enforcement в команді - CI/CD.
  • git commit --no-verify обходить pre-commit hooks, тому вони не є рівнем безпеки.
  • Husky дозволяє версіонувати hooks разом з кодом і автоматично ділитися ними в команді.

Швидкий приклад

bash
# .git/hooks/pre-commit #!/bin/sh # Запускається перед кожним комітом на цій машині if ! npm run lint; then echo "Lint провалився - виправ перед комітом" exit 1 # Ненульовий код скасовує коміт fi

Спочатку зроби файл виконуваним: chmod +x .git/hooks/pre-commit. Без цього Git ігнорує файл без будь-якого попередження, і коміт проходить так, ніби hook взагалі не існує.

Як Git запускає hooks

Git шукає у .git/hooks/ виконуваний файл з назвою, що відповідає події - наприклад, pre-commit. При виконанні git commit Git запускає підпроцес shell. Код виходу 0 означає «продовжити», будь-що інше скасовує операцію. Hooks успадковують змінні середовища Git, зокрема $GIT_DIR.

Чотири основні фази:

  • pre: запускається до дії (pre-commit, pre-push) і може заблокувати її
  • during: змінює поточну операцію (prepare-commit-msg, commit-msg)
  • post: запускається після завершення дії (post-commit, post-merge) і нічого не може скасувати
  • server-side: на віддаленому сервері (pre-receive, post-receive), потрібен доступ до сервера

Клієнтські vs серверні hooks

Клієнтські hooks лежать у .git/hooks/ на локальній машині. Серверні hooks знаходяться на самому remote-сервері, наприклад у self-hosted GitLab або Gitea. GitHub-репозиторії не дають прямого доступу до серверних hooks.

Важливий наслідок: будь-хто в команді може обійти клієнтський hook, видаливши файл або запустивши git commit --no-verify. Для справжнього enforcement використовуй GitHub Actions або CI-пайплайн.

Коли використовувати

  • Pre-commit лінтинг: ловити проблеми зі стилем до того як вони потраплять в репо
  • Pre-push тести: запускати тест-сьют перед пушем
  • Валідація commit message: перевіряти формат conventional commits через commit-msg
  • Post-merge оновлення залежностей: автоматично запускати npm install після пулу, якщо package.json змінився

Hooks не підходять для перевірок, що тривають більше 10 секунд, або для правил, які команда не повинна мати можливості обійти.

Ділимось hooks через Husky

Директорія .git/ не комітиться в репо. Тому hooks не передаються при клонуванні. Husky вирішує це, вказуючи Git на версіоновану директорію .husky/.

bash
# Встановлення і ініціалізація Husky npm install husky --save-dev npx husky install # Створює директорію .husky/ # Додаємо pre-commit hook npx husky add .husky/pre-commit "npx lint-staged"
json
// .lintstagedrc.json { "*.{js,ts,jsx,tsx}": ["eslint --fix", "git add"] }

Husky v9 спрощує це ще більше: використовує git config core.hooksPath .husky/ напряму, замість npm lifecycle скриптів з v4.

Типові помилки

Забутий chmod +x:

bash
# Файл існує, але не є виконуваним .git/hooks/pre-commit

Git ігнорує файл без жодного повідомлення про помилку. Коміт проходить, нічого не запускається, і ти витрачаєш час здогадуючись чому hook не працює. Виправлення: chmod +x .git/hooks/pre-commit. Це трапляється майже з кожним розробником вперше.

Хардкод шляхів:

bash
#!/bin/sh node_modules/.bin/eslint . # Зламається на свіжому клоні

Краще: npx eslint . або npm run lint. Хардкодований шлях до node_modules падає одразу після git clone, коли залежності ще не встановлено.

Спроба скасувати дію в post-hook:

bash
# post-commit npm run notify-slack || exit 1 # exit 1 тут не має ефекту

Post-hooks запускаються після того як операція вже виконана. Ненульовий код виходу там ні на що не впливає. Щоб заблокувати дію, використовуй відповідний pre-hook.

Очікування що hooks запустяться в CI: GitHub Actions клонує репо з нуля і не отримує локальних hooks. Hooks - це інструмент локальної розробки. Якщо хочеш ті ж самі перевірки в CI, налаштуй їх окремо у workflow-файлі.

Де зустрічається

  • Husky + lint-staged (60k+ зірок на GitHub): запускає ESLint тільки на staged-файлах, стандарт у React і Next.js репо
  • Lefthook: YAML-конфігурація, поширений у Ruby/Rails проектах на GitLab
  • pre-commit.com framework: керування hooks для кількох мов, використовується в Airbnb і проекті dask
  • commit-msg hook: перевірка формату conventional commits перед збереженням повідомлення

Follow-up питання

Q: Як обійти hook в екстреній ситуації?


A: git commit --no-verify. Це пропускає pre-commit і commit-msg hooks. Корисно для WIP-комітів або коли зламаний hook блокує всю команду.

Q: Яка різниця між pre-commit і commit-msg hooks?


A: pre-commit запускається до того як Git відкриває редактор для повідомлення. commit-msg запускається після того як ти написав повідомлення, але до збереження коміту. Для перевірки коду - pre-commit, для перевірки формату повідомлення - commit-msg.

Q: Чому hooks не запускаються в CI?


A: CI-runner клонує репо з нуля. .git/hooks/ існує тільки локально і не є частиною версійного контролю. Hooks є лише на тих машинах, де їх явно налаштували.

Q: Чим Husky v9 відрізняється від v4?


A: Husky v4 зберігав конфіг у package.json і встановлював hooks через npm lifecycle скрипти. v9 використовує git config core.hooksPath .husky/ напряму і вимагає явного виклику husky install. Менше магії, простіше налагодження.

Q: Якщо є і pre-commit, і prepare-commit-msg, який запускається першим?


A: Git запускає їх у фіксованому порядку: спочатку pre-commit, потім prepare-commit-msg, потім commit-msg, потім post-commit. Цей порядок не можна змінити. Якщо обидва hooks змінюють один і той же стан, потрібно враховувати послідовність вручну або використовувати Lefthook для координації.

Приклади

Базовий: лінтинг тільки staged файлів

bash
#!/bin/sh # .git/hooks/pre-commit (потрібен chmod +x) # Перевіряє тільки staged JS файли, не весь проект changed_files=$(git diff --cached --name-only --diff-filter=ACM | grep '\.js$') for file in $changed_files; do npx eslint "$file" || exit 1 done

Це швидше ніж eslint . на великих репо, бо перевіряє лише файли у поточному коміті. Якщо будь-який файл провалюється, цикл виходить з кодом 1 і Git скасовує коміт. На чистому проекті без staged JS файлів цикл виконується нуль разів і завершується з кодом 0.

Середній рівень: Husky + lint-staged в Next.js проекті

bash
# .husky/pre-commit #!/bin/sh . "$(dirname "$0")/_/husky.sh" npx lint-staged
json
// .lintstagedrc.json { "*.{js,ts,jsx,tsx}": ["eslint --fix", "git add"], "*.{css,scss}": ["stylelint --fix", "git add"] }

lint-staged автоматично виправляє проблеми і повторно додає виправлені файли в стейдж. Якщо файл не вдається виправити автоматично, коміт скасовується. Файл .husky/pre-commit відстежується Git, тому кожен розробник після npm install отримує ті ж самі перевірки завдяки скрипту prepare, який запускає husky install.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.

Дочитали статтю?