Hooks
Les hooks Git sont des scripts exécutables dans .git/hooks/ qui s’exécutent à des moments précis des opérations git. Pitmaster les détecte et les appelle à l’aide du contrôle des processus de PHP (proc_open).
Les hooks s’exécutent par défaut
Les opérations du dépôt exécutent automatiquement les hooks répertoriés sous Hooks exécutés par Pitmaster. Pour les désactiver pour un handle de dépôt, transmettez des options à son ouverture :
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() et Pitmaster::clone() acceptent les mêmes options. L’exécution d’un hook nécessite proc_open() ; sur les hôtes où cette fonctionnalité est désactivée, ouvrez les dépôts avec ['hooks' => false]. Le CLI ouvre toujours le dépôt avec les hooks activés.
Pitmaster recherche les hooks uniquement dans <gitDir>/hooks. Il ignore core.hooksPath et, dans un worktree lié, les recherche dans .git/worktrees/<name>/hooks ; les scripts partagés de .git/hooks ne s’y exécutent donc pas (#12).
HookRunner, décrit ci-dessous, ignore ces options : appelé directement, il exécute toujours le hook.
HookRunner
use Pitmaster\Hooks\HookRunner;
$hooks = new HookRunner($repo->gitDir());
Vérifier si un hook existe
if ($hooks->exists('pre-commit')) {
echo "pre-commit hook is installed\n";
}
Un hook existe si le fichier est présent à .git/hooks/<name> et est exécutable.
Exécuter 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
Avec des arguments
$result = $hooks->run('commit-msg', ['/path/to/COMMIT_EDITMSG']);
Avec stdin
$result = $hooks->run('pre-push', ['origin', 'https://github.com/user/repo.git'], $stdinData);
Le hook pre-push reçoit les informations de référence via stdin.
Vérifier la réussite
if ($hooks->runAndCheck('pre-commit')) {
// Hook passed (exit code 0), proceed with commit
} else {
// Hook rejected the operation
}
runAndCheck() est une méthode pratique qui exécute le hook et renvoie true si le code de sortie est 0. Si le hook n’existe pas, elle renvoie true (l’absence de hook ne vaut pas objection).
Lister les hooks installés
$installed = $hooks->listHooks();
// ['pre-commit', 'commit-msg', 'post-checkout']
Répertorie tous les fichiers exécutables de .git/hooks/ (à l’exclusion des fichiers .sample).
run() renvoie le code de sortie 0 et une sortie vide si le hook est absent ou non exécutable. Si le processus ne peut pas démarrer, il renvoie le code de sortie 1 et écrit Failed to start hook sur stderr.
Installer un hook
$hooks->install('pre-commit', "#!/bin/bash\nphp vendor/bin/phpcs --standard=PSR12 .\n");
Écrit le contenu du script dans .git/hooks/<name> et définit le droit d’exécution (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);
Hooks exécutés par Pitmaster
HookRunner peut exécuter n’importe quel hook par son nom. Repository exécute automatiquement les hooks suivants :
| Hook | Exécuté par | Arguments | Peut interrompre ? |
|---|---|---|---|
pre-commit |
commit() |
aucun | Oui (code de sortie non nul) |
prepare-commit-msg |
commit() |
fichier de message, puis message (ou merge lors de la finalisation d’une fusion) |
Oui |
commit-msg |
commit() |
fichier de message (.git/COMMIT_EDITMSG) ; les modifications du fichier sont utilisées |
Oui (code de sortie non nul) |
post-commit |
commit() |
aucun | Non (informatif) |
pre-rebase |
rebase() |
révision amont | Oui |
post-checkout |
checkout() |
ancien HEAD, nouveau HEAD, 1 |
Non (informatif) |
post-merge |
merge(), mergeOctopus(), mergeContinue() |
0 |
Non (informatif) |
pre-push |
push() et ses variantes |
nom de la télécommande, URL ; lignes de référence via stdin | Oui (code de sortie non nul) |
Un hook qui échoue fait lever RuntimeException('<name> hook failed') par l’opération ; la sortie du hook n’est pas incluse. Les hooks de commit ne s’exécutent que depuis commit() : les commits de fusion créés par merge() ne les exécutent pas. post-checkout ne s’exécute pas pour addWorktree(), reset() ou restore().
Les hooks côté serveur (pre-receive, update, post-receive, post-update), pre-auto-gc et post-rewrite ne sont jamais exécutés automatiquement.
Environnement des hooks
Lorsque Pitmaster exécute un hook, il lui transmet cet environnement :
$env = [
'GIT_DIR' => $this->gitDir,
'GIT_WORK_TREE' => dirname($this->gitDir),
];
Ce sont les seules variables accessibles au hook : PATH, HOME et toutes les autres variables du processus PHP ne sont pas transmises (#13). Utilisez des chemins absolus dans les scripts de hook, ou définissez PATH dans le script ; un shebang #!/usr/bin/env php ne trouve les interpréteurs que dans le chemin système par défaut.
Le hook s’exécute dans dirname($gitDir), qui correspond au répertoire de travail d’un répertoire .git normal.
Format des scripts de hook
Les hooks peuvent être écrits dans n’importe quel langage. La première ligne doit être un shebang :
#!/bin/bash
# Bash hook
#!/usr/bin/env php
<?php
// PHP hook
#!/usr/bin/env python3
# Python hook
Exemple : pre-commit avec Pitmaster
commit() exécute déjà pre-commit, prepare-commit-msg, commit-msg et post-commit ; le code de l’application doit donc uniquement gérer l’échec :
try {
$commitId = $repo->commit('Add feature');
} catch (\RuntimeException $e) {
echo $e->getMessage(); // "pre-commit hook failed"
}
Appelez vous-même HookRunner uniquement sur un handle ouvert avec ['hooks' => false], ou pour les hooks que Pitmaster n’exécute pas.
Exemples de hooks
Git initialise les dépôts avec des fichiers de hook .sample ; Pitmaster::init() crée un répertoire hooks/ vide. Le Pitmaster de listHooks() exclut les fichiers .sample :
.git/hooks/
pre-commit.sample (excluded from listHooks)
commit-msg.sample (excluded from listHooks)
pre-commit (included - installed by user)