Перейти к содержимому

Хуки

Перехватчики Git — это исполняемые скрипты в .git/hooks/, которые запускаются в определённые моменты операций git. Pitmaster обнаруживает и запускает перехватчики с помощью управления процессами в PHP (proc_open).

Перехватчики запускаются автоматически

При операциях с репозиторием автоматически запускаются перехватчики, перечисленные в разделе Hooks 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; строки ссылок через 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/. В файле listHooks() для Pitmaster исключены файлы .sample:

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