Przejdź do treści

Hooki

Hooki Git to wykonywalne skrypty w .git/hooks/ uruchamiane w określonych momentach operacji git. Pitmaster wykrywa i wywołuje hooki, korzystając z kontroli procesów PHP (proc_open).

Hooki uruchamiane domyślnie

Operacje na repozytorium automatycznie uruchamiają hooki wymienione w Hooki Pitmaster uruchamia. Aby je wyłączyć dla uchwytu repozytorium, przekaż opcje podczas jego otwierania:

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() i Pitmaster::clone() przyjmują te same opcje. Uruchomienie hooka wymaga proc_open(); na hostach, które je wyłączają, otwieraj repozytoria za pomocą ['hooks' => false]. CLI zawsze otwiera repozytorium z włączonymi hookami.

Pitmaster szuka hooków tylko w <gitDir>/hooks. Ignoruje core.hooksPath, a w połączonym worktree szuka w .git/worktrees/<name>/hooks, więc współdzielone skrypty .git/hooks nie są tam uruchamiane (#12).

HookRunner, opisany niżej, ignoruje te opcje: bezpośrednie wywołanie zawsze uruchamia hook.

HookRunner

PHP
use Pitmaster\Hooks\HookRunner;

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

Sprawdzanie, czy hook istnieje

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

Hook istnieje, jeśli plik znajduje się w .git/hooks/<name> i jest wykonywalny.

Uruchamianie hooka

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

Z argumentami

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

Ze stdin

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

Hook pre-push otrzymuje informacje o ref na stdin.

Sprawdzanie powodzenia

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

runAndCheck() to funkcja pomocnicza, która uruchamia hook i zwraca true, jeśli kod wyjścia wynosi 0. Jeśli hook nie istnieje, zwraca true (brak hooka oznacza brak sprzeciwu).

Wyświetlanie zainstalowanych hooków

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

Wyświetla wszystkie wykonywalne pliki w .git/hooks/ (z wyłączeniem plików .sample).

run() dla brakującego lub niewykonywalnego hooka zwraca kod wyjścia 0 i puste wyjście. Jeśli nie można uruchomić procesu, zwraca kod wyjścia 1, a na stderr Failed to start hook.

Instalowanie hooka

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

Zapisuje treść skryptu w .git/hooks/<name> i nadaje mu uprawnienie do wykonywania (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);

Hooki uruchamiane przez Pitmaster

HookRunner może uruchomić dowolny hook według nazwy. Repository uruchamia automatycznie następujące hooki:

Hook Uruchamiany przez Argumenty Może przerwać?
pre-commit commit() brak Tak (niezerowy kod wyjścia)
prepare-commit-msg commit() plik wiadomości, następnie message (lub merge podczas kończenia scalania) Tak
commit-msg commit() plik wiadomości (.git/COMMIT_EDITMSG); zmiany w pliku są uwzględniane Tak (niezerowy kod wyjścia)
post-commit commit() brak Nie (informacyjny)
pre-rebase rebase() rewizja upstream Tak
post-checkout checkout() stary HEAD, nowy HEAD, 1 Nie (informacyjny)
post-merge merge(), mergeOctopus(), mergeContinue() 0 Nie (informacyjny)
pre-push push() i jego warianty nazwa remote, URL; wiersze ref na stdin Tak (niezerowy kod wyjścia)

Niepowodzenie hooka powoduje, że operacja zgłasza RuntimeException('<name> hook failed'); wynik hooka nie jest dołączany. Hooki commit są uruchamiane tylko z commit(): scalenia commitów utworzone przez merge() ich nie uruchamiają. post-checkout nie jest uruchamiany dla addWorktree(), reset() ani restore().

Hooki po stronie serwera (pre-receive, update, post-receive, post-update), pre-auto-gc i post-rewrite nigdy nie są uruchamiane automatycznie.

Środowisko hooka

Gdy Pitmaster uruchamia hook, przekazuje mu następujące zmienne środowiskowe:

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

Hook otrzymuje tylko te zmienne: PATH, HOME oraz pozostałe zmienne procesu PHP nie są przekazywane (#13). W skryptach hooków używaj ścieżek bezwzględnych albo ustaw PATH wewnątrz skryptu; shebang #!/usr/bin/env php znajduje interpretery tylko w domyślnej ścieżce systemowej.

Hook jest uruchamiany w dirname($gitDir), czyli katalogu roboczym zwykłego repozytorium .git.

Format skryptu hooka

Hooki można napisać w dowolnym języku. Pierwszy wiersz musi zawierać shebang:

Powłoka
#!/bin/bash
# Bash hook
PHP
#!/usr/bin/env php
<?php
// PHP hook
Python
#!/usr/bin/env python3
# Python hook

Przykład: pre-commit z Pitmaster

commit() już uruchamia pre-commit, prepare-commit-msg, commit-msg i post-commit, dlatego kod aplikacji musi jedynie obsłużyć niepowodzenie:

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

Wywołuj HookRunner samodzielnie tylko na uchwycie otwartym z ['hooks' => false] albo dla hooków, których Pitmaster nie uruchamia.

Przykładowe hooki

Git inicjuje repozytoria z plikami hooków .sample; Pitmaster::init() tworzy pusty katalog hooks/. Plik listHooks() w Pitmaster wyklucza pliki .sample:

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