8. Git Hooks
Git Hooks – это как стражники вашего репозитория, автоматически проверяющие и выполняющие действия перед или после определенных событий в Git. Они помогают автоматизировать задачи и поддерживать качество кода.
## Что такое Git Hooks?
Git Hooks – это скрипты, которые Git выполняет автоматически до или после событий, таких как коммит, пуш или мерж. Они позволяют вам настраивать рабочий процесс Git, добавляя собственные проверки, уведомления и автоматические действия. Представьте, что это автоматические помощники, которые помогают вам следить за порядком в вашем коде.
Git Hooks хранятся в каталоге `.git/hooks` внутри вашего репозитория. По умолчанию, там находятся примеры скриптов с расширением `.sample`. Чтобы активировать hook, нужно убрать расширение `.sample` и сделать файл исполняемым (например, `chmod +x pre-commit`).
## Типы Git Hooks и примеры
Существует множество Git Hooks, которые срабатывают в разных точках рабочего процесса. Рассмотрим некоторые из них:
* **`pre-commit`**: Выполняется перед созданием коммита. Часто используется для проверки стиля кода, запуска тестов или проверки наличия комментариев.
```bash #!/bin/sh # Проверяем, что сообщение коммита не пустое if [ -z "$(git commit -m ' ' --allow-empty | grep 'No commit message')" ]; then echo "Пожалуйста, добавьте сообщение к коммиту." exit 1 fi-
prepare-commit-msg: Выполняется перед открытием редактора для ввода сообщения коммита. Можно использовать для автоматического добавления информации в сообщение коммита.#!/bin/sh# Добавляем номер задачи из Jira в сообщение коммитаecho "#JIRA-123" >> $1 -
commit-msg: Проверяет сообщение коммита. Можно использовать для обеспечения соответствия сообщения коммита определенному формату.#!/bin/sh# Проверяем, что сообщение коммита начинается с префиксаif ! grep -q "^feat\|^fix\|^docs\|^style\|^refactor\|^test\|^chore" "$1"; thenecho "Сообщение коммита должно начинаться с одного из префиксов: feat, fix, docs, style, refactor, test, chore"exit 1fi -
pre-push: Выполняется перед отправкой изменений на удаленный репозиторий. Можно использовать для запуска тестов или проверки соответствия кода стандартам.#!/bin/sh# Запускаем тесты перед отправкойnpm testif [ $? -ne 0 ]; thenecho "Тесты не прошли. Отправка отменена."exit 1fi -
post-receive: Выполняется на удаленном репозитории после получения изменений. Можно использовать для развертывания кода на сервере или отправки уведомлений.
Жизненный пример
Заголовок раздела «Жизненный пример»Представьте себе проект веб-приложения, где важно, чтобы все коммиты соответствовали определенному стилю кодирования и проходили тесты перед отправкой на удаленный репозиторий.
pre-commit: Скрипт запускает линтер (например, ESLint для JavaScript) и форматировщик кода (например, Prettier). Если линтер находит ошибки или код не отформатирован, коммит будет отменен.pre-push: Скрипт запускает все автоматизированные тесты. Если хотя бы один тест не проходит, отправка на удаленный репозиторий будет отменена.commit-msg: Скрипт проверяет, что сообщение коммита соответствует соглашению Conventional Commits (например, “feat: add new feature”).
Такой подход позволяет поддерживать высокое качество кода и предотвращать попадание ошибок в основную ветку разработки. Многие фреймворки и библиотеки, такие как React, Vue.js и Laravel, используют Git Hooks для автоматизации задач и обеспечения качества кода.
Ключевые моменты
Заголовок раздела «Ключевые моменты»- Git Hooks – это скрипты, которые выполняются автоматически до или после определенных событий в Git.
- Они позволяют автоматизировать задачи, такие как проверка стиля кода, запуск тестов и проверка сообщений коммитов.
- Git Hooks хранятся в каталоге
.git/hooksвнутри репозитория. - Использование Git Hooks помогает поддерживать качество кода и предотвращать попадание ошибок в основную ветку разработки.
- Можно использовать различные языки программирования для написания Git Hooks, например, Bash, Python или JavaScript.
## Интерактивный пример
Как работают Git Hooks — триггеры на события:
<Playground client:load template="vite-react" files={{ "/App.jsx": `import { useState, useRef } from "react";
const HOOKS = [ { name: "pre-commit", desc: "Lint & format check" }, { name: "commit-msg", desc: "Validate commit message format" }, { name: "pre-push", desc: "Run tests before push" }, { name: "post-commit", desc: "Notify team (Slack webhook)" },];
export default function App() { const [statuses, setStatuses] = useState(HOOKS.map(() => "idle")); const [log, setLog] = useState([]); const running = useRef(false);
const simulate = () => { if (running.current) return; running.current = true; setStatuses(HOOKS.map(() => "idle")); setLog([]); const results = HOOKS.map(() => Math.random() > 0.15); let i = 0; const step = () => { if (i >= HOOKS.length) { setLog((l) => [...l, "Commit successful!"]); running.current = false; return; } setStatuses((s) => s.map((v, j) => j === i ? "running" : v)); setLog((l) => [...l, "Running " + HOOKS[i].name + "..."]); const idx = i; setTimeout(() => { if (results[idx]) { setStatuses((s) => s.map((v, j) => j === idx ? "pass" : v)); setLog((l) => [...l, HOOKS[idx].name + ": passed"]); i++; step(); } else { setStatuses((s) => s.map((v, j) => j === idx ? "fail" : v)); setLog((l) => [...l, HOOKS[idx].name + ": FAILED! Commit aborted."]); running.current = false; } }, 600); }; step(); };
const colors = { idle: "#334155", running: "#eab308", pass: "#22c55e", fail: "#ef4444" }; const icons = { idle: "--", running: "...", pass: "OK", fail: "XX" };
return ( <div style={{ fontFamily: "monospace", background: "#0f172a", color: "#e2e8f0", padding: 16, minHeight: "100vh" }}> <h3 style={{ color: "#818cf8", marginBottom: 12 }}>Git Hooks Lifecycle</h3> <button onClick={simulate} style={{ padding: "7px 16px", borderRadius: 6, border: "none", cursor: "pointer", fontWeight: 700, fontSize: 12, background: "#6366f1", color: "#fff", marginBottom: 10 }}>git commit -m "fix: bug"</button> <div style={{ display: "flex", flexDirection: "column", gap: 6, marginBottom: 12 }}> {HOOKS.map((h, i) => ( <div key={h.name} style={{ display: "flex", alignItems: "center", gap: 10, background: statuses[i] === "running" ? "#1c1917" : "#1e293b", border: "1px solid " + colors[statuses[i]], borderRadius: 8, padding: "8px 12px", fontSize: 12, transition: "all .3s" }}> <span style={{ color: "#22d3ee", fontWeight: 700, minWidth: 100 }}>{h.name}</span> <span style={{ color: "#94a3b8", flex: 1 }}>{h.desc}</span> <span style={{ color: colors[statuses[i]], fontWeight: 700, fontSize: 11 }}>{icons[statuses[i]]}</span> </div> ))} </div> <div style={{ background: "#1e293b", border: "1px solid #334155", borderRadius: 8, padding: 10, fontSize: 11, color: "#94a3b8", maxHeight: 80, overflowY: "auto" }}> {log.length ? log.map((l, i) => <div key={i}>{l}</div>) : <div>Нажми кнопку, чтобы запустить симуляцию</div>} </div> </div> );}` }}/>