Hook
Gli hook Git sono script eseguibili in .git/hooks/ che vengono eseguiti in momenti specifici delle operazioni git. Pitmaster rileva e invoca gli hook usando il controllo dei processi di PHP (proc_open).
Gli hook vengono eseguiti per impostazione predefinita
Le operazioni sul repository eseguono automaticamente gli hook elencati in Hook eseguiti da Pitmaster. Per disattivarli per un handle del repository, passate le opzioni all’apertura:
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() e Pitmaster::clone() accettano le stesse opzioni. Per eseguire un hook occorre proc_open(); sugli host in cui è disabilitato, aprite i repository con ['hooks' => false]. CLI apre sempre il repository con gli hook abilitati.
Pitmaster cerca gli hook solo in <gitDir>/hooks. Ignora core.hooksPath e, in un worktree collegato, cerca in .git/worktrees/<name>/hooks, quindi gli script .git/hooks condivisi non vengono eseguiti (#12).
HookRunner, descritto sotto, ignora queste opzioni: se chiamato direttamente, esegue sempre l’hook.
HookRunner
use Pitmaster\Hooks\HookRunner;
$hooks = new HookRunner($repo->gitDir());
Verificare se esiste un hook
if ($hooks->exists('pre-commit')) {
echo "pre-commit hook is installed\n";
}
Un hook esiste se il file è presente in .git/hooks/<name> ed è eseguibile.
Eseguire un 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
Con argomenti
$result = $hooks->run('commit-msg', ['/path/to/COMMIT_EDITMSG']);
Con stdin
$result = $hooks->run('pre-push', ['origin', 'https://github.com/user/repo.git'], $stdinData);
L’hook pre-push riceve informazioni sui ref tramite stdin.
Verificare l’esito
if ($hooks->runAndCheck('pre-commit')) {
// Hook passed (exit code 0), proceed with commit
} else {
// Hook rejected the operation
}
runAndCheck() è una funzione di utilità che esegue l’hook e restituisce true se il codice di uscita è 0. Se l’hook non esiste, restituisce true (l’assenza di un hook equivale a nessuna obiezione).
Elencare gli hook installati
$installed = $hooks->listHooks();
// ['pre-commit', 'commit-msg', 'post-checkout']
Elenca tutti i file eseguibili in .git/hooks/ (escludendo i file .sample).
run() restituisce il codice di uscita 0 e un output vuoto se l’hook manca o non è eseguibile. Se non è possibile avviare il processo, restituisce il codice di uscita 1 e invia Failed to start hook su stderr.
Installare un hook
$hooks->install('pre-commit', "#!/bin/bash\nphp vendor/bin/phpcs --standard=PSR12 .\n");
Scrive il contenuto dello script in .git/hooks/<name> e imposta il permesso di esecuzione (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 eseguiti da Pitmaster
HookRunner può eseguire qualsiasi hook per nome. Repository esegue automaticamente questi hook:
| Hook | Eseguito da | Argomenti | Può interrompere? |
|---|---|---|---|
pre-commit |
commit() |
nessuno | Sì (codice di uscita diverso da zero) |
prepare-commit-msg |
commit() |
file del messaggio, poi message (oppure merge al completamento di un merge) |
Sì |
commit-msg |
commit() |
file del messaggio (.git/COMMIT_EDITMSG); vengono usate le modifiche al file |
Sì (codice di uscita diverso da zero) |
post-commit |
commit() |
nessuno | No (informativo) |
pre-rebase |
rebase() |
revisione upstream | Sì |
post-checkout |
checkout() |
HEAD precedente, nuovo HEAD, 1 |
No (informativo) |
post-merge |
merge(), mergeOctopus(), mergeContinue() |
0 |
No (informativo) |
pre-push |
push() e le relative varianti |
nome del remote, URL; righe ref su stdin | Sì (codice di uscita diverso da zero) |
Un hook non riuscito fa generare all’operazione un’eccezione RuntimeException('<name> hook failed'); l’output dell’hook non viene incluso. Gli hook di commit vengono eseguiti solo da commit(): i merge commit creati da merge() non li eseguono. post-checkout non viene eseguito per addWorktree(), reset() o restore().
Gli hook lato server (pre-receive, update, post-receive, post-update), pre-auto-gc e post-rewrite non vengono mai eseguiti automaticamente.
Ambiente degli hook
Quando Pitmaster esegue un hook, gli passa questo ambiente:
$env = [
'GIT_DIR' => $this->gitDir,
'GIT_WORK_TREE' => dirname($this->gitDir),
];
Queste sono le sole variabili disponibili per l’hook: PATH, HOME e tutte le altre variabili del processo PHP non vengono passate (#13). Usate percorsi assoluti negli script degli hook oppure impostate PATH nello script; uno shebang #!/usr/bin/env php trova gli interpreti solo nel percorso predefinito di sistema.
L’hook viene eseguito in dirname($gitDir), che corrisponde all’albero di lavoro di una normale directory .git.
Formato degli script hook
Gli hook possono essere scritti in qualsiasi linguaggio. La prima riga deve contenere uno shebang:
#!/bin/bash
# Bash hook
#!/usr/bin/env php
<?php
// PHP hook
#!/usr/bin/env python3
# Python hook
Esempio: pre-commit con Pitmaster
commit() esegue già pre-commit, prepare-commit-msg, commit-msg e post-commit, quindi il codice dell’applicazione deve solo gestire l’errore:
try {
$commitId = $repo->commit('Add feature');
} catch (\RuntimeException $e) {
echo $e->getMessage(); // "pre-commit hook failed"
}
Chiamate HookRunner direttamente solo su un handle aperto con ['hooks' => false], oppure per gli hook che Pitmaster non esegue.
Esempi di hook
Git inizializza i repository con file hook .sample; Pitmaster::init() crea una directory hooks/ vuota. Il file listHooks() di Pitmaster esclude i file .sample:
.git/hooks/
pre-commit.sample (excluded from listHooks)
commit-msg.sample (excluded from listHooks)
pre-commit (included - installed by user)