Aller au contenu

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 :

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() 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

PHP
use Pitmaster\Hooks\HookRunner;

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

Vérifier si un hook existe

PHP
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

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

Avec des arguments

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

Avec stdin

PHP
$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

PHP
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

PHP
$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

PHP
$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).

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);

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 :

PHP
$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 :

Shell
#!/bin/bash
# Bash hook
PHP
#!/usr/bin/env php
<?php
// PHP hook
Python
#!/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 :

PHP
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 :

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