Перейти до вмісту

Хуки

Хуки Git — це виконувані сценарії в .git/hooks/, які запускаються в певні моменти операцій git. Pitmaster виявляє та запускає хуки за допомогою керування процесами в PHP (proc_open).

Хуки запускаються за замовчуванням

Під час операцій із репозиторієм автоматично запускаються хуки зі списку Хуки Pitmaster виконує. Щоб вимкнути їх для дескриптора репозиторію, передайте параметри під час його відкриття:

PHP
use Pitmaster\Pitmaster;

// Skip .git/hooks scripts for this handle
$repo = Pitmaster::open('/path/to/project', ['hooks' => false]);

// Start no processes at all: hooks, the fsmonitor hook, clone/fetch/push, and SSH are disabled
$repo = Pitmaster::open('/path/to/project', Pitmaster::processFreeOptions());

$repo->hooksEnabled();      // false
$repo->processesEnabled();  // false for the second handle

Pitmaster::init() і Pitmaster::clone() приймають ті самі параметри. Для запуску хуків потрібен proc_open(); на системах, де його вимкнено, відкривайте репозиторії з ['hooks' => false]. CLI завжди відкриває репозиторій із увімкненими хуками.

Pitmaster шукає хуки лише в <gitDir>/hooks. Він ігнорує core.hooksPath, а у зв’язаному робочому дереві шукає в .git/worktrees/<name>/hooks, тож спільні сценарії .git/hooks там не запускаються (#12).

HookRunner, описаний нижче, ігнорує ці параметри: якщо викликати його безпосередньо, хук запускається завжди.

HookRunner

PHP
use Pitmaster\Hooks\HookRunner;

$hooks = new HookRunner($repo->gitDir());

Перевірка наявності хука

PHP
if ($hooks->exists('pre-commit')) {
    echo "pre-commit hook is installed\n";
}

Хук вважається наявним, якщо файл є за адресою .git/hooks/<name> і має право на виконання.

Запуск хука

PHP
$result = $hooks->run('pre-commit');

echo $result['exitCode'];  // 0 = success
echo $result['stdout'];    // Hook's standard output
echo $result['stderr'];    // Hook's standard error

З аргументами

PHP
$result = $hooks->run('commit-msg', ['/path/to/COMMIT_EDITMSG']);

Через stdin

PHP
$result = $hooks->run('pre-push', ['origin', 'https://github.com/user/repo.git'], $stdinData);

Хук pre-push отримує інформацію про посилання через stdin.

Перевірка успішного виконання

PHP
if ($hooks->runAndCheck('pre-commit')) {
    // Hook passed (exit code 0), proceed with commit
} else {
    // Hook rejected the operation
}

runAndCheck() — допоміжна функція, яка запускає хук і повертає true, якщо код завершення дорівнює 0. Якщо хука немає, вона повертає true (відсутність хука означає відсутність заперечень).

Список установлених хуків

PHP
$installed = $hooks->listHooks();
// ['pre-commit', 'commit-msg', 'post-checkout']

Виводить список усіх виконуваних файлів у .git/hooks/ (файли .sample не враховуються).

Якщо хук run() відсутній або не має права на виконання, повертається код завершення 0 і порожній вивід. Якщо не вдається запустити процес, повертається код завершення 1, а в stderr виводиться Failed to start hook.

Встановлення хука

PHP
$hooks->install('pre-commit', "#!/bin/bash\nphp vendor/bin/phpcs --standard=PSR12 .\n");

Записує вміст сценарію в .git/hooks/<name> і надає право на виконання (chmod 0755).

PHP
// Install a PHP hook
$hooks->install('pre-commit', <<<'HOOK'
#!/usr/bin/env php
<?php
// Run static analysis before commit
$output = [];
$returnCode = 0;
exec('php vendor/bin/phpstan analyse --no-progress 2>&1', $output, $returnCode);

if ($returnCode !== 0) {
    echo "PHPStan found errors:\n";
    echo implode("\n", $output) . "\n";
    exit(1);
}
HOOK);

Хуки, які виконує Pitmaster

HookRunner може запускати будь-який хук за назвою. Repository запускає автоматично такі хуки:

Хук Запускає Аргументи Може перервати операцію?
pre-commit commit() немає Так (ненульовий код завершення)
prepare-commit-msg commit() файл повідомлення, потім message (або merge під час завершення злиття) Так
commit-msg commit() файл повідомлення (.git/COMMIT_EDITMSG); зміни у файлі використовуються Так (ненульовий код завершення)
post-commit commit() немає Ні (інформаційний)
pre-rebase rebase() ревізія upstream Так
post-checkout checkout() старий HEAD, новий HEAD, 1 Ні (інформаційний)
post-merge merge(), mergeOctopus(), mergeContinue() 0 Ні (інформаційний)
pre-push push() та його варіанти назва віддаленого репозиторію, URL; рядки ref через stdin Так (ненульовий код завершення)

Якщо хук завершується з помилкою, операція викидає RuntimeException('<name> hook failed'); вивід хука не додається. Хуки коміту запускаються лише з commit(): злиття, створені командою merge(), їх не запускають. post-checkout не запускається для addWorktree(), reset() або restore().

Серверні хуки (pre-receive, update, post-receive, post-update), pre-auto-gc і post-rewrite ніколи не запускаються автоматично.

Середовище хуків

Під час запуску хука Pitmaster передає таке середовище:

PHP
$env = [
    'GIT_DIR' => $this->gitDir,
    'GIT_WORK_TREE' => dirname($this->gitDir),
];

Хук отримує лише ці змінні: PATH, HOME та всі інші змінні процесу PHP не передаються (#13). У сценаріях хуків використовуйте абсолютні шляхи або задайте PATH у самому сценарії; shebang #!/usr/bin/env php знаходить інтерпретатори лише в системному шляху за замовчуванням.

Хук запускається в dirname($gitDir) — робочому дереві звичайного каталогу .git.

Формат сценарію хука

Хуки можна писати будь-якою мовою. Перший рядок має бути shebang:

Командна оболонка
#!/bin/bash
# Bash hook
PHP
#!/usr/bin/env php
<?php
// PHP hook
Python
#!/usr/bin/env python3
# Python hook

Приклад: pre-commit із Pitmaster

commit() уже запускає pre-commit, prepare-commit-msg, commit-msg і post-commit, тож у коді застосунку потрібно лише обробити помилку:

PHP
try {
    $commitId = $repo->commit('Add feature');
} catch (\RuntimeException $e) {
    echo $e->getMessage(); // "pre-commit hook failed"
}

Викликайте HookRunner самостійно лише для дескриптора, відкритого з ['hooks' => false], або для хуків, які Pitmaster не запускає.

Приклади хуків

Git ініціалізує репозиторії з файлами хуків .sample; Pitmaster::init() створює порожній каталог hooks/. Pitmaster у listHooks() виключає файли .sample:

Код
.git/hooks/
  pre-commit.sample       (excluded from listHooks)
  commit-msg.sample       (excluded from listHooks)
  pre-commit              (included - installed by user)