Ga naar de inhoud

Hooks

Git-hooks zijn uitvoerbare scripts in .git/hooks/ die op specifieke momenten tijdens git-bewerkingen worden uitgevoerd. Pitmaster detecteert en start hooks met de procesbesturing van PHP (proc_open).

Hooks worden standaard uitgevoerd

Bij repositorybewerkingen worden de hooks in Hooks Pitmaster voert uit automatisch uitgevoerd. U schakelt ze uit voor een repositoryhandle door opties mee te geven wanneer u deze opent:

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() en Pitmaster::clone() accepteren dezelfde opties. Voor het uitvoeren van een hook is proc_open() nodig; open repositories met ['hooks' => false] op hosts waarop dit is uitgeschakeld. De CLI opent de repository altijd met hooks ingeschakeld.

Pitmaster zoekt hooks alleen in <gitDir>/hooks. Het negeert core.hooksPath en zoekt in een gekoppelde worktree in .git/worktrees/<name>/hooks, zodat gedeelde scripts in .git/hooks daar niet worden uitgevoerd (#12).

HookRunner, hieronder beschreven, negeert deze opties: als u deze rechtstreeks aanroept, wordt de hook altijd uitgevoerd.

HookRunner

PHP
use Pitmaster\Hooks\HookRunner;

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

Controleren of een hook bestaat

PHP
if ($hooks->exists('pre-commit')) {
    echo "pre-commit hook is installed\n";
}

Een hook bestaat als het bestand aanwezig is op .git/hooks/<name> en uitvoerbaar is.

Een hook uitvoeren

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

Met argumenten

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

Met stdin

PHP
$result = $hooks->run('pre-push', ['origin', 'https://github.com/user/repo.git'], $stdinData);

De pre-push-hook ontvangt ref-informatie via stdin.

Succes controleren

PHP
if ($hooks->runAndCheck('pre-commit')) {
    // Hook passed (exit code 0), proceed with commit
} else {
    // Hook rejected the operation
}

runAndCheck() is een hulpfunctie die de hook uitvoert en true retourneert als de afsluitcode 0 is. Als de hook niet bestaat, retourneert deze true (geen hook betekent geen bezwaar).

Geïnstalleerde hooks weergeven

PHP
$installed = $hooks->listHooks();
// ['pre-commit', 'commit-msg', 'post-checkout']

Geeft alle uitvoerbare bestanden in .git/hooks/ weer (.sample-bestanden worden uitgesloten).

run() retourneert bij een ontbrekende of niet-uitvoerbare hook afsluitcode 0 en geen uitvoer. Als het proces niet kan worden gestart, retourneert deze afsluitcode 1 en Failed to start hook op stderr.

Een hook installeren

PHP
$hooks->install('pre-commit', "#!/bin/bash\nphp vendor/bin/phpcs --standard=PSR12 .\n");

Schrijft de scriptinhoud naar .git/hooks/<name> en stelt de uitvoerbare machtiging in (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 die Pitmaster uitvoert

HookRunner kan elke hook op naam uitvoeren. Repository voert de volgende hooks automatisch uit:

Hook Uitgevoerd door Argumenten Kan afbreken?
pre-commit commit() geen Ja (niet-nul-afsluitcode)
prepare-commit-msg commit() berichtbestand, daarna message (of merge bij het voltooien van een merge) Ja
commit-msg commit() berichtbestand (.git/COMMIT_EDITMSG); wijzigingen in het bestand worden gebruikt Ja (niet-nul-afsluitcode)
post-commit commit() geen Nee (informatief)
pre-rebase rebase() de upstream-revisie Ja
post-checkout checkout() oude HEAD, nieuwe HEAD, 1 Nee (informatief)
post-merge merge(), mergeOctopus(), mergeContinue() 0 Nee (informatief)
pre-push push() en de varianten daarvan remote-naam, URL; ref-regels via stdin Ja (niet-nul-afsluitcode)

Een mislukte hook zorgt ervoor dat de bewerking RuntimeException('<name> hook failed') genereert; de uitvoer van de hook wordt niet opgenomen. De commit-hooks worden alleen uitgevoerd vanuit commit(): mergecommits die door merge() zijn aangemaakt, voeren ze niet uit. post-checkout wordt niet uitgevoerd voor addWorktree(), reset() of restore().

Serverhooks (pre-receive, update, post-receive, post-update), pre-auto-gc en post-rewrite worden nooit automatisch uitgevoerd.

Hookomgeving

Wanneer Pitmaster een hook uitvoert, geeft het deze omgevingsvariabelen mee:

PHP
$env = [
    'GIT_DIR' => $this->gitDir,
    'GIT_WORK_TREE' => dirname($this->gitDir),
];

Dit zijn de enige variabelen die de hook meekrijgt: PATH, HOME en alle andere variabelen van het PHP-proces worden niet doorgegeven (#13). Gebruik absolute paden in hookscripts of stel PATH in het script in; een #!/usr/bin/env php-shebang vindt alleen interpreters in het standaard systeempad.

De hook wordt uitgevoerd in dirname($gitDir), de working tree voor een normale .git-directory.

Hookscriptindeling

Hooks kunnen in elke taal worden geschreven. De eerste regel moet een shebang zijn:

Shell
#!/bin/bash
# Bash hook
PHP
#!/usr/bin/env php
<?php
// PHP hook
Python
#!/usr/bin/env python3
# Python hook

Voorbeeld: pre-commit met Pitmaster

commit() voert pre-commit, prepare-commit-msg, commit-msg en post-commit al uit, dus hoeft applicatiecode alleen de fout af te handelen:

PHP
try {
    $commitId = $repo->commit('Add feature');
} catch (\RuntimeException $e) {
    echo $e->getMessage(); // "pre-commit hook failed"
}

Roep HookRunner zelf alleen aan bij een handle die is geopend met ['hooks' => false], of voor hooks die Pitmaster niet uitvoert.

Voorbeeldhooks

Git initialiseert repositories met hookbestanden in .sample; Pitmaster::init() maakt een lege hooks/-directory aan. Pitmaster's listHooks() sluit .sample-bestanden uit:

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