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:
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
use Pitmaster\Hooks\HookRunner;
$hooks = new HookRunner($repo->gitDir());
Sprawdzanie, czy hook istnieje
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
$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
$result = $hooks->run('commit-msg', ['/path/to/COMMIT_EDITMSG']);
Ze stdin
$result = $hooks->run('pre-push', ['origin', 'https://github.com/user/repo.git'], $stdinData);
Hook pre-push otrzymuje informacje o ref na stdin.
Sprawdzanie powodzenia
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
$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
$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).
// 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:
$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:
#!/bin/bash
# Bash hook
#!/usr/bin/env php
<?php
// PHP hook
#!/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:
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:
.git/hooks/
pre-commit.sample (excluded from listHooks)
commit-msg.sample (excluded from listHooks)
pre-commit (included - installed by user)