<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Commit-Msg on DevOps Way - Практические гайды</title>
    <link>https://devopsway.ru/tags/commit-msg/</link>
    <description>Recent content in Commit-Msg on DevOps Way - Практические гайды</description>
    <image>
      <title>DevOps Way - Практические гайды</title>
      <url>https://devopsway.ru/images/devopsway-og.png</url>
      <link>https://devopsway.ru/images/devopsway-og.png</link>
    </image>
    <generator>Hugo -- 0.160.1</generator>
    <language>ru</language>
    <lastBuildDate>Thu, 16 Apr 2026 12:16:22 -0400</lastBuildDate>
    <atom:link href="https://devopsway.ru/tags/commit-msg/feed.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>День 5: git hooks — как отловить мусор до коммита</title>
      <link>https://devopsway.ru/posts/day-05-hooks/</link>
      <pubDate>Thu, 16 Apr 2026 18:00:00 +0300</pubDate>
      <guid>https://devopsway.ru/posts/day-05-hooks/</guid>
      <description>Git hooks без Husky и Node — чистый bash. Pre-commit блокирует секреты и trailing whitespace. Commit-msg проверяет Conventional Commits regex&amp;#39;ом. Распространение hooks на команду через core.hooksPath.</description>
      <content:encoded><![CDATA[<h2 id="цель-урока">Цель урока</h2>
<p>После урока вы <strong>умеете</strong> написать и установить <code>pre-commit</code> и <code>commit-msg</code> hook на чистом bash (без Husky / Node / Python), понимаете почему hooks живут локально и как распространить их на команду через <code>core.hooksPath</code> + папку в репозитории. Можете добавить проверку секретов, trailing whitespace, формата Conventional Commits.</p>
<table>
  <thead>
      <tr>
          <th>Параметр</th>
          <th>Значение</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Bloom</td>
          <td>Применение</td>
      </tr>
      <tr>
          <td>SFIA</td>
          <td>Уровень 3</td>
      </tr>
      <tr>
          <td>Время</td>
          <td>40 минут</td>
      </tr>
      <tr>
          <td>Артефакт</td>
          <td><code>repo/.githooks/pre-commit</code> + <code>commit-msg</code> + <code>core.hooksPath = .githooks</code></td>
      </tr>
      <tr>
          <td>Проверка</td>
          <td>Коммит с секретом блокируется; коммит с кривым сообщением блокируется; <code>git commit --no-verify</code> обходит (и это ожидаемо)</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="теория-что-такое-git-hook">Теория: что такое git hook</h2>
<p><strong>Hook</strong> — это исполняемый файл в папке <code>.git/hooks/</code>, который Git вызывает на определённое событие. Если файл есть и имеет бит <code>+x</code> — Git запустит его и <strong>смотрит на exit code</strong>: <code>0</code> — продолжить операцию, не <code>0</code> — отменить.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">.git/hooks/
</span></span><span class="line"><span class="cl">├── pre-commit         ← вызывается до создания коммита
</span></span><span class="line"><span class="cl">├── commit-msg         ← вызывается с путём к файлу сообщения
</span></span><span class="line"><span class="cl">├── pre-push           ← вызывается до git push
</span></span><span class="line"><span class="cl">├── post-commit        ← после коммита (нотификация)
</span></span><span class="line"><span class="cl">├── pre-rebase         ← до rebase
</span></span><span class="line"><span class="cl">└── pre-commit.sample  ← примеры от Git (не активные)
</span></span></code></pre></div><p>По умолчанию Git кладёт туда <code>.sample</code> файлы — это примеры, они не активны (без бита <code>+x</code>).</p>
<p><strong>Ключевое ограничение:</strong> папка <code>.git/hooks/</code> <strong>не попадает</strong> в git push. Hooks — это <strong>локальная</strong> настройка каждого разработчика. Отсюда задача: как заставить всю команду использовать одни и те же проверки → решение через <code>core.hooksPath</code> (Практика 3).</p>
<p><strong>«Щёлкнуло» дня:</strong> hooks — это <strong>дружеская страховка на твоём компе</strong>, не жёсткая защита сервера. Кто очень хочет — обойдёт через <code>git commit --no-verify</code>. Серьёзная защита делается в CI и серверных hooks (pre-receive на GitLab/GitHub). Hooks на клиенте ловят то, что <strong>забыли</strong>, а не то, что <strong>пытались скрыть</strong>.</p>
<hr>
<h2 id="практика-1-pre-commit--блокируем-секреты-и-пробелы">Практика 1: pre-commit — блокируем секреты и пробелы</h2>
<h3 id="шаг-1-готовим-репозиторий">Шаг 1. Готовим репозиторий</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">mkdir -p /tmp/demo-hooks <span class="o">&amp;&amp;</span> <span class="nb">cd</span> /tmp/demo-hooks
</span></span><span class="line"><span class="cl">git init -q -b main
</span></span><span class="line"><span class="cl">git config user.name Student
</span></span><span class="line"><span class="cl">git config user.email student@example.com
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;hello&#34;</span> &gt; app.txt
</span></span><span class="line"><span class="cl">git add . <span class="o">&amp;&amp;</span> git commit -q -m <span class="s2">&#34;feat: initial&#34;</span>
</span></span></code></pre></div><h3 id="шаг-2-пишем-pre-commit">Шаг 2. Пишем pre-commit</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">cat &gt; .git/hooks/pre-commit <span class="s">&lt;&lt; &#39;EOF&#39;
</span></span></span><span class="line"><span class="cl"><span class="s">#!/bin/bash
</span></span></span><span class="line"><span class="cl"><span class="s"># pre-commit: запретить секреты и trailing whitespace в staged файлах
</span></span></span><span class="line"><span class="cl"><span class="s">set -e
</span></span></span><span class="line"><span class="cl"><span class="s">
</span></span></span><span class="line"><span class="cl"><span class="s"># список файлов в индексе (то, что попадёт в коммит)
</span></span></span><span class="line"><span class="cl"><span class="s">FILES=$(git diff --cached --name-only --diff-filter=ACMR)
</span></span></span><span class="line"><span class="cl"><span class="s">[ -z &#34;$FILES&#34; ] &amp;&amp; exit 0
</span></span></span><span class="line"><span class="cl"><span class="s">
</span></span></span><span class="line"><span class="cl"><span class="s"># 1. Проверка на секреты: имя секретного ключа + &#34;=&#34; + значение длиной ≥16 символов
</span></span></span><span class="line"><span class="cl"><span class="s"># (не ловит безопасные формы типа API_KEY=os.getenv(&#34;API_KEY&#34;) — там нет длинного значения)
</span></span></span><span class="line"><span class="cl"><span class="s">SECRET_PATTERN=&#39;((password|secret|api_key|apikey|token)[[:space:]]*[=:][[:space:]]*[&#34;\x27]?[A-Za-z0-9+/_-]{16,}|BEGIN RSA PRIVATE KEY|AKIA[0-9A-Z]{16})&#39;
</span></span></span><span class="line"><span class="cl"><span class="s">if echo &#34;$FILES&#34; | xargs -r grep -EnHi &#34;$SECRET_PATTERN&#34; 2&gt;/dev/null; then
</span></span></span><span class="line"><span class="cl"><span class="s">  echo &#34;&#34;
</span></span></span><span class="line"><span class="cl"><span class="s">  echo &#34;Похоже на секрет в staged файлах. Коммит отменён.&#34;
</span></span></span><span class="line"><span class="cl"><span class="s">  echo &#34;Если ложное срабатывание — git commit --no-verify&#34;
</span></span></span><span class="line"><span class="cl"><span class="s">  exit 1
</span></span></span><span class="line"><span class="cl"><span class="s">fi
</span></span></span><span class="line"><span class="cl"><span class="s">
</span></span></span><span class="line"><span class="cl"><span class="s"># 2. Trailing whitespace
</span></span></span><span class="line"><span class="cl"><span class="s">if echo &#34;$FILES&#34; | xargs -r grep -EnH &#39; +$&#39; 2&gt;/dev/null; then
</span></span></span><span class="line"><span class="cl"><span class="s">  echo &#34;&#34;
</span></span></span><span class="line"><span class="cl"><span class="s">  echo &#34;Найдены trailing whitespace. Убрать: sed -i &#39;s/ *$//&#39; &lt;файл&gt;&#34;
</span></span></span><span class="line"><span class="cl"><span class="s">  exit 1
</span></span></span><span class="line"><span class="cl"><span class="s">fi
</span></span></span><span class="line"><span class="cl"><span class="s">
</span></span></span><span class="line"><span class="cl"><span class="s">exit 0
</span></span></span><span class="line"><span class="cl"><span class="s">EOF</span>
</span></span><span class="line"><span class="cl">chmod +x .git/hooks/pre-commit
</span></span></code></pre></div><h3 id="шаг-3-проверяем-секрет-блокируется">Шаг 3. Проверяем: секрет блокируется</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s1">&#39;API_KEY=sk-1234567890abcdef&#39;</span> &gt; config.py
</span></span><span class="line"><span class="cl">git add config.py
</span></span><span class="line"><span class="cl">git commit -m <span class="s2">&#34;feat: add config&#34;</span>
</span></span></code></pre></div><p>Вывод:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">config.py:1:API_KEY=sk-1234567890abcdef
</span></span><span class="line"><span class="cl">Похоже на секрет в staged файлах. Коммит отменён.
</span></span></code></pre></div><p>Exit code не 0 — коммит не создан. Исправляем:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">rm config.py
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s1">&#39;API_KEY=os.getenv(&#34;API_KEY&#34;)&#39;</span> &gt; config.py
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;.env&#34;</span> &gt; .gitignore
</span></span><span class="line"><span class="cl">git add config.py .gitignore
</span></span><span class="line"><span class="cl">git commit -m <span class="s2">&#34;feat: read API_KEY from env&#34;</span>
</span></span></code></pre></div><p>Проходит.</p>
<h3 id="шаг-4-проверяем-trailing-whitespace-блокируется">Шаг 4. Проверяем: trailing whitespace блокируется</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nb">printf</span> <span class="s1">&#39;def foo():   \n    pass\n&#39;</span> &gt; dirty.py
</span></span><span class="line"><span class="cl">git add dirty.py
</span></span><span class="line"><span class="cl">git commit -m <span class="s2">&#34;feat: add foo&#34;</span>
</span></span></code></pre></div><p>Вывод:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">dirty.py:1:def foo():
</span></span><span class="line"><span class="cl">Найдены trailing whitespace. Убрать: sed -i &#39;s/ *$//&#39; &lt;файл&gt;
</span></span></code></pre></div><p>Исправляем и коммитим.</p>
<p><strong>Побочный канал:</strong> <code>git commit --no-verify</code> обходит все client-side hooks. Это ожидаемое поведение — hook не тюрьма, а напоминание. Если коллега систематически юзает <code>--no-verify</code> — это не вопрос hook&rsquo;а, а вопрос договора в команде.</p>
<hr>
<h2 id="практика-2-commit-msg--валидация-conventional-commits">Практика 2: commit-msg — валидация Conventional Commits</h2>
<p>pre-commit не видит сообщение коммита (сообщение запрашивается <strong>после</strong> pre-commit). Для валидации сообщения нужен <strong>commit-msg hook</strong> — ему в <code>$1</code> передаётся путь к временному файлу с сообщением.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">cat &gt; .git/hooks/commit-msg <span class="s">&lt;&lt; &#39;EOF&#39;
</span></span></span><span class="line"><span class="cl"><span class="s">#!/bin/bash
</span></span></span><span class="line"><span class="cl"><span class="s"># commit-msg: минимальная проверка Conventional Commits
</span></span></span><span class="line"><span class="cl"><span class="s">MSG_FILE=&#34;$1&#34;
</span></span></span><span class="line"><span class="cl"><span class="s">MSG=$(head -1 &#34;$MSG_FILE&#34;)
</span></span></span><span class="line"><span class="cl"><span class="s">
</span></span></span><span class="line"><span class="cl"><span class="s"># разрешённые типы
</span></span></span><span class="line"><span class="cl"><span class="s">PATTERN=&#39;^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\([a-z0-9-]+\))?!?: .{1,72}$&#39;
</span></span></span><span class="line"><span class="cl"><span class="s">
</span></span></span><span class="line"><span class="cl"><span class="s">if ! echo &#34;$MSG&#34; | grep -qE &#34;$PATTERN&#34;; then
</span></span></span><span class="line"><span class="cl"><span class="s">  echo &#34;&#34;
</span></span></span><span class="line"><span class="cl"><span class="s">  echo &#34;Сообщение коммита не соответствует Conventional Commits:&#34;
</span></span></span><span class="line"><span class="cl"><span class="s">  echo &#34;  $MSG&#34;
</span></span></span><span class="line"><span class="cl"><span class="s">  echo &#34;&#34;
</span></span></span><span class="line"><span class="cl"><span class="s">  echo &#34;Ожидается: &lt;type&gt;(scope)?: &lt;описание&gt;&#34;
</span></span></span><span class="line"><span class="cl"><span class="s">  echo &#34;Типы: feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert&#34;
</span></span></span><span class="line"><span class="cl"><span class="s">  echo &#34;Примеры:&#34;
</span></span></span><span class="line"><span class="cl"><span class="s">  echo &#34;  feat: add user login&#34;
</span></span></span><span class="line"><span class="cl"><span class="s">  echo &#34;  fix(api): handle 500 on /users&#34;
</span></span></span><span class="line"><span class="cl"><span class="s">  echo &#34;  chore!: drop Node 16 support&#34;
</span></span></span><span class="line"><span class="cl"><span class="s">  exit 1
</span></span></span><span class="line"><span class="cl"><span class="s">fi
</span></span></span><span class="line"><span class="cl"><span class="s">EOF</span>
</span></span><span class="line"><span class="cl">chmod +x .git/hooks/commit-msg
</span></span></code></pre></div><h3 id="тестируем">Тестируем</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;content&#34;</span> &gt; x.txt
</span></span><span class="line"><span class="cl">git add x.txt
</span></span><span class="line"><span class="cl">git commit -m <span class="s2">&#34;исправил баг&#34;</span>
</span></span></code></pre></div><p>Вывод:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">Сообщение коммита не соответствует Conventional Commits:
</span></span><span class="line"><span class="cl">  исправил баг
</span></span><span class="line"><span class="cl">Ожидается: &lt;type&gt;(scope)?: &lt;описание&gt;
</span></span><span class="line"><span class="cl">Типы: feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert
</span></span></code></pre></div><p>Повторяем правильно:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">git commit -m <span class="s2">&#34;fix: исправил кейс с пустым юзером&#34;</span>
</span></span></code></pre></div><p>Проходит. Можно и с scope:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;more&#34;</span> &gt;&gt; x.txt
</span></span><span class="line"><span class="cl">git add x.txt
</span></span><span class="line"><span class="cl">git commit -m <span class="s2">&#34;fix(auth): перехват пустого юзера в login&#34;</span>
</span></span></code></pre></div><p><strong>Важно про regex</strong>: он намеренно простой (стартовая строка, тип, опц. scope, <code>!</code> для breaking change, двоеточие, пробел, до 72 символов). Если хотите строгости — есть <code>commitlint</code> (npm) или <code>cocogitto</code> (Rust). Но 80% команд спокойно живут на 10 строк bash.</p>
<hr>
<h2 id="практика-3-распространение-hooks-на-команду">Практика 3: распространение hooks на команду</h2>
<p>Проблема: <code>.git/hooks/</code> <strong>не синхронизируется</strong> через <code>git push</code>. Коллеге нужно руками копировать ваши hooks. Это не работает.</p>
<p><strong>Решение:</strong> положить hooks <strong>в сам репозиторий</strong> (папка <code>.githooks/</code>), и каждый разработчик делает один раз:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">git config core.hooksPath .githooks
</span></span></code></pre></div><p>Эта настройка говорит Git&rsquo;у: «смотри в <code>.githooks/</code> вместо <code>.git/hooks/</code>». А <code>.githooks/</code> — обычная папка в репо, под версионным контролем.</p>
<h3 id="шаг-1-переносим-hooks-в-репозиторий">Шаг 1. Переносим hooks в репозиторий</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nb">cd</span> /tmp/demo-hooks
</span></span><span class="line"><span class="cl">mkdir -p .githooks
</span></span><span class="line"><span class="cl">mv .git/hooks/pre-commit .githooks/
</span></span><span class="line"><span class="cl">mv .git/hooks/commit-msg .githooks/
</span></span><span class="line"><span class="cl">chmod +x .githooks/*
</span></span></code></pre></div><h3 id="шаг-2-переключаем-git-на-эту-папку">Шаг 2. Переключаем Git на эту папку</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">git config core.hooksPath .githooks
</span></span></code></pre></div><h3 id="шаг-3-добавляем-в-репо--readme">Шаг 3. Добавляем в репо + README</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">cat &gt; .githooks/install.sh <span class="s">&lt;&lt; &#39;EOF&#39;
</span></span></span><span class="line"><span class="cl"><span class="s">#!/bin/bash
</span></span></span><span class="line"><span class="cl"><span class="s"># Установка hooks для этого репозитория
</span></span></span><span class="line"><span class="cl"><span class="s">git config core.hooksPath .githooks
</span></span></span><span class="line"><span class="cl"><span class="s">echo &#34;✓ core.hooksPath -&gt; .githooks&#34;
</span></span></span><span class="line"><span class="cl"><span class="s">echo &#34;  проверки pre-commit и commit-msg активны&#34;
</span></span></span><span class="line"><span class="cl"><span class="s">EOF</span>
</span></span><span class="line"><span class="cl">chmod +x .githooks/install.sh
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">git add .githooks
</span></span><span class="line"><span class="cl">git commit -m <span class="s2">&#34;chore(hooks): add pre-commit and commit-msg via core.hooksPath&#34;</span>
</span></span></code></pre></div><h3 id="как-подключается-коллега">Как подключается коллега</h3>
<p>Один раз после <code>git clone</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">./.githooks/install.sh
</span></span></code></pre></div><p>И всё — у коллеги те же самые проверки. Обновления hooks приходят автоматически через <code>git pull</code> (они теперь тоже часть истории).</p>
<p><strong>Альтернатива</strong> — pre-commit framework (pre-commit.com, Python). Он делает ровно это, но ещё умеет ставить плагины и кэшировать окружения. Для JavaScript-стека есть Husky — тоже обёртка над тем же <code>core.hooksPath</code>. Базовый принцип один: <strong>hooks в репо + переключение папки через config</strong>.</p>
<hr>
<h2 id="практика-4-pre-push--запуск-тестов">Практика 4: pre-push — запуск тестов</h2>
<p><strong>pre-push</strong> вызывается перед <code>git push</code>. Exit != 0 отменяет push. Классический кейс: запустить тесты перед отправкой в remote.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">cat &gt; .githooks/pre-push <span class="s">&lt;&lt; &#39;EOF&#39;
</span></span></span><span class="line"><span class="cl"><span class="s">#!/bin/bash
</span></span></span><span class="line"><span class="cl"><span class="s"># pre-push: запустить тесты перед push
</span></span></span><span class="line"><span class="cl"><span class="s"># если тесты не запускаются в этом репо — просто выходим с 0
</span></span></span><span class="line"><span class="cl"><span class="s">
</span></span></span><span class="line"><span class="cl"><span class="s">if [ -f &#34;go.mod&#34; ]; then
</span></span></span><span class="line"><span class="cl"><span class="s">  go test ./... || { echo &#34;тесты упали, push отменён&#34;; exit 1; }
</span></span></span><span class="line"><span class="cl"><span class="s">elif [ -f &#34;package.json&#34; ] &amp;&amp; grep -q &#39;&#34;test&#34;&#39; package.json; then
</span></span></span><span class="line"><span class="cl"><span class="s">  npm test || { echo &#34;тесты упали, push отменён&#34;; exit 1; }
</span></span></span><span class="line"><span class="cl"><span class="s">elif [ -f &#34;pytest.ini&#34; ] || [ -f &#34;pyproject.toml&#34; ]; then
</span></span></span><span class="line"><span class="cl"><span class="s">  pytest || { echo &#34;тесты упали, push отменён&#34;; exit 1; }
</span></span></span><span class="line"><span class="cl"><span class="s">fi
</span></span></span><span class="line"><span class="cl"><span class="s">
</span></span></span><span class="line"><span class="cl"><span class="s">exit 0
</span></span></span><span class="line"><span class="cl"><span class="s">EOF</span>
</span></span><span class="line"><span class="cl">chmod +x .githooks/pre-push
</span></span></code></pre></div><p>Это не заменяет CI, но экономит минуты: падающие тесты ловятся на своей машине, а не после push и жёлтого прогресс-бара в GitLab Actions.</p>
<hr>
<h2 id="артефакт-githooks-в-вашем-репо">Артефакт: <code>.githooks/</code> в вашем репо</h2>
<p>Готовый набор для старта любого проекта:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">repo/
</span></span><span class="line"><span class="cl">├── .githooks/
</span></span><span class="line"><span class="cl">│   ├── install.sh        # один раз после clone
</span></span><span class="line"><span class="cl">│   ├── pre-commit        # секреты + whitespace
</span></span><span class="line"><span class="cl">│   ├── commit-msg        # Conventional Commits
</span></span><span class="line"><span class="cl">│   └── pre-push          # тесты
</span></span><span class="line"><span class="cl">└── README.md             # инструкция: &#34;после clone → ./.githooks/install.sh&#34;
</span></span></code></pre></div><p>README-снипет команды:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl"><span class="gu">## Contributing
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">After cloning, run:
</span></span><span class="line"><span class="cl">\`\`\`bash
</span></span><span class="line"><span class="cl">./.githooks/install.sh
</span></span><span class="line"><span class="cl">\`\`\`
</span></span><span class="line"><span class="cl">This activates pre-commit and commit-msg hooks.
</span></span></code></pre></div><hr>
<h2 id="частые-ошибки">Частые ошибки</h2>
<table>
  <thead>
      <tr>
          <th>Ошибка</th>
          <th>Почему больно</th>
          <th>Как не делать</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Положить hooks в <code>.git/hooks/</code> и забыть — коллега их не увидит</td>
          <td>Коллега коммитит секреты, ваши hooks его не спасают</td>
          <td>Через <code>core.hooksPath</code> + папка в репо</td>
      </tr>
      <tr>
          <td>Забыть <code>chmod +x</code> у hook&rsquo;а</td>
          <td>Git молча игнорирует не-исполняемый файл</td>
          <td><code>chmod +x .githooks/*</code> в install.sh</td>
      </tr>
      <tr>
          <td>Тяжёлые проверки в pre-commit (линт, тесты, сборка) на 10 секунд</td>
          <td>Команда начинает юзать <code>--no-verify</code> и весь pre-commit теряет смысл</td>
          <td>pre-commit &lt; 1 секунды. Линт и тесты — в pre-push или CI</td>
      </tr>
      <tr>
          <td>Ловить секреты только regex&rsquo;ом <code>password=</code></td>
          <td>Ложноотрицательные: переменные, имена функций типа <code>getPassword()</code> тоже проходят</td>
          <td>Использовать специализированный сканер (gitleaks, detect-secrets) в CI; hook — только первая линия</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="-документирование">📝 Документирование</h2>
<p>Создайте <code>~/notes/day-05.md</code> и ответьте:</p>
<ol>
<li><strong>Почему hooks не синхронизируются через push</strong>: объясните одной фразой.</li>
<li><strong><code>core.hooksPath</code> — что и зачем</strong>: опишите механизм в 2-3 предложениях.</li>
<li><strong>Проверка, которую ставите первой</strong>: какой pre-commit сценарий вам сейчас нужнее всего в текущем проекте?</li>
<li><strong><code>--no-verify</code></strong>: это баг или фича? Обоснуйте.</li>
</ol>
<hr>
<h2 id="мини-тест">Мини-тест</h2>
<ol>
<li>Вы положили pre-commit в <code>.git/hooks/pre-commit</code>, но он не срабатывает. Что проверить первым?</li>
<li>Чем отличаются hooks <code>pre-commit</code> и <code>commit-msg</code> по моменту вызова и входным данным?</li>
<li>Вы хотите, чтобы у всей команды работал один pre-commit. Какие два шага для этого нужны?</li>
<li>Коллега делает <code>git commit --no-verify</code> и коммитит секрет. Что вам <strong>не</strong> поможет? А что поможет?</li>
</ol>
<p>Ответы — в конце поста.</p>
<hr>
<h2 id="что-дальше">Что дальше</h2>
<ul>
<li><strong>День 6</strong> → <code>git bisect</code>: двоичный поиск коммита, сломавшего код</li>
<li><strong>Challenge</strong> → <code>docker run devitway/git-challenge</code>: одна из задач — починить репо, где установлен вредный commit-msg hook, блокирующий все коммиты</li>
<li><strong>Системно с нуля</strong> → «Курс молодого бойца» DevIT Academy, продвинутый Git в Неделе 3</li>
</ul>
<hr>
<h2 id="ответы-на-мини-тест">Ответы на мини-тест</h2>
<ol>
<li>
<p><strong>Бит <code>+x</code></strong>. Git запускает hook как обычный исполняемый файл — без права на исполнение он молча игнорируется. Проверьте: <code>ls -l .git/hooks/pre-commit</code>, должно быть <code>-rwxr-xr-x</code>. Если нет — <code>chmod +x</code>.</p>
</li>
<li>
<p><strong>pre-commit</strong> вызывается <strong>до</strong> того, как Git спросит сообщение коммита; на вход ничего не получает (сам читает <code>git diff --cached</code>). <strong>commit-msg</strong> вызывается <strong>после</strong> ввода сообщения, но до его записи в историю; получает в <code>$1</code> путь к временному файлу с сообщением, может его читать и модифицировать.</p>
</li>
<li>
<p>(1) Положить hooks в папку в репозитории (<code>.githooks/</code>). (2) Каждый разработчик после <code>git clone</code> делает <code>git config core.hooksPath .githooks</code> — обычно это скрипт <code>./.githooks/install.sh</code>. После этого hooks общие и синхронизируются через <code>git pull</code>.</p>
</li>
<li>
<p><strong>Не поможет</strong> никакой клиентский hook — <code>--no-verify</code> отключает все локальные hooks. <strong>Поможет</strong> серверный hook (<code>pre-receive</code> на стороне GitHub/GitLab) или secret-scanning в CI. Клиентские hooks — первая линия защиты от <strong>забывчивости</strong>, не от <strong>злого умысла</strong>.</p>
</li>
</ol>
]]></content:encoded>
    </item>
  </channel>
</rss>
