Cârlige
Hook-urile Git sunt scripturi executabile în .git/hooks/, care rulează în anumite puncte ale operațiunilor git. Pitmaster detectează și apelează hook-urile folosind controlul proceselor din PHP (proc_open).
Hook-urile rulează implicit
Operațiunile asupra depozitului rulează automat hook-urile listate la Hook-urile Pitmaster. Pentru a le dezactiva pentru un obiect handle de depozit, transmiteți opțiuni la deschiderea acestuia:
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() acceptă aceleași opțiuni. Pentru a rula un hook este necesar proc_open(); pe sistemele gazdă unde acesta este dezactivat, deschideți depozitele cu ['hooks' => false]. CLI deschide întotdeauna depozitul cu hook-urile activate.
Pitmaster caută hook-uri numai în <gitDir>/hooks. Ignoră core.hooksPath, iar într-un worktree asociat caută în .git/worktrees/<name>/hooks, astfel că scripturile .git/hooks partajate nu rulează acolo (#12).
HookRunner, descris mai jos, ignoră aceste opțiuni: apelarea lui directă rulează întotdeauna hook-ul.
HookRunner
use Pitmaster\Hooks\HookRunner;
$hooks = new HookRunner($repo->gitDir());
Verificarea existenței unui hook
if ($hooks->exists('pre-commit')) {
echo "pre-commit hook is installed\n";
}
Un hook există dacă fișierul este prezent în .git/hooks/<name> și este executabil.
Rularea unui hook
$result = $hooks->run('pre-commit');
echo $result['exitCode']; // 0 = success
echo $result['stdout']; // Hook's standard output
echo $result['stderr']; // Hook's standard error
Cu argumente
$result = $hooks->run('commit-msg', ['/path/to/COMMIT_EDITMSG']);
Cu stdin
$result = $hooks->run('pre-push', ['origin', 'https://github.com/user/repo.git'], $stdinData);
Hook-ul pre-push primește informații despre referință prin stdin.
Verificarea reușitei
if ($hooks->runAndCheck('pre-commit')) {
// Hook passed (exit code 0), proceed with commit
} else {
// Hook rejected the operation
}
runAndCheck() este o metodă convenabilă care rulează hook-ul și returnează true dacă codul de ieșire este 0. Dacă hook-ul nu există, returnează true (absența unui hook înseamnă că nu există obiecții).
Listarea hook-urilor instalate
$installed = $hooks->listHooks();
// ['pre-commit', 'commit-msg', 'post-checkout']
Listează toate fișierele executabile din .git/hooks/ (cu excepția fișierelor .sample).
run() returnează codul de ieșire 0 cu ieșire goală pentru un hook inexistent sau neexecutabil. Dacă procesul nu poate fi pornit, returnează codul de ieșire 1 și Failed to start hook pe stderr.
Instalarea unui hook
$hooks->install('pre-commit', "#!/bin/bash\nphp vendor/bin/phpcs --standard=PSR12 .\n");
Scrie conținutul scriptului în .git/hooks/<name> și setează permisiunea de execuție (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);
Hook-urile rulate de Pitmaster
HookRunner poate rula orice hook după nume. Repository le rulează automat pe acestea:
| Hook | Rulat de | Argumente | Poate anula? |
|---|---|---|---|
pre-commit |
commit() |
niciunul | Da (cod de ieșire diferit de zero) |
prepare-commit-msg |
commit() |
fișierul mesajului, apoi message (sau merge la finalizarea unei îmbinări) |
Da |
commit-msg |
commit() |
fișierul mesajului (.git/COMMIT_EDITMSG); modificările fișierului sunt folosite |
Da (cod de ieșire diferit de zero) |
post-commit |
commit() |
niciunul | Nu (informativ) |
pre-rebase |
rebase() |
revizia din amonte | Da |
post-checkout |
checkout() |
HEAD vechi, HEAD nou, 1 |
Nu (informativ) |
post-merge |
merge(), mergeOctopus(), mergeContinue() |
0 |
Nu (informativ) |
pre-push |
push() și variantele sale |
numele remote-ului, URL; liniile referințelor prin stdin | Da (cod de ieșire diferit de zero) |
Un hook care eșuează face operațiunea să arunce RuntimeException('<name> hook failed'); ieșirea hook-ului nu este inclusă. Hook-urile de commit rulează numai din commit(): commit-urile de îmbinare create de merge() nu le rulează. post-checkout nu rulează pentru addWorktree(), reset() sau restore().
Hook-urile de pe server (pre-receive, update, post-receive, post-update), pre-auto-gc și post-rewrite nu rulează niciodată automat.
Mediul hook-urilor
Când Pitmaster rulează un hook, îi transmite acest mediu:
$env = [
'GIT_DIR' => $this->gitDir,
'GIT_WORK_TREE' => dirname($this->gitDir),
];
Acestea sunt singurele variabile disponibile pentru hook: PATH, HOME și toate celelalte variabile ale procesului PHP nu sunt transmise (#13). Folosiți căi absolute în scripturile hook-urilor sau setați PATH în script; un shebang #!/usr/bin/env php găsește interpreți doar în calea implicită a sistemului.
Hook-ul rulează în dirname($gitDir), care este arborele de lucru pentru un director .git obișnuit.
Formatul scriptului hook-ului
Hook-urile pot fi scrise în orice limbaj. Prima linie trebuie să fie un shebang:
#!/bin/bash
# Bash hook
#!/usr/bin/env php
<?php
// PHP hook
#!/usr/bin/env python3
# Python hook
Exemplu: pre-commit cu Pitmaster
commit() rulează deja pre-commit, prepare-commit-msg, commit-msg și post-commit, așa că în codul aplicației trebuie tratat doar eșecul:
try {
$commitId = $repo->commit('Add feature');
} catch (\RuntimeException $e) {
echo $e->getMessage(); // "pre-commit hook failed"
}
Apelați HookRunner doar pentru un handle deschis cu ['hooks' => false] sau pentru hook-uri pe care Pitmaster nu le rulează.
Hook-uri exemplu
Git inițializează depozite cu fișiere hook .sample; Pitmaster::init() creează un director hooks/ gol. Pitmaster din listHooks() exclude fișierele .sample:
.git/hooks/
pre-commit.sample (excluded from listHooks)
commit-msg.sample (excluded from listHooks)
pre-commit (included - installed by user)