Přejít k obsahu

Hooky

Háčky Git jsou spustitelné skripty v .git/hooks/, které se spouštějí v určitých bodech operací git. Pitmaster háčky detekuje a spouští pomocí řízení procesů v PHP (proc_open).

Háčky se spouštějí automaticky

Operace repozitáře automaticky spouštějí háčky uvedené v části Háčky, které spouští Pitmaster. Chcete-li je pro určitou instanci repozitáře vypnout, předejte při jejím otevření možnosti:

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() i Pitmaster::clone() přijímají stejné možnosti. Spuštění háčku vyžaduje proc_open(); na hostitelích, kde je zakázáno, otevírejte repozitáře pomocí ['hooks' => false]. CLI vždy otevírá repozitář se zapnutými háčky.

Pitmaster hledá háčky pouze v <gitDir>/hooks. core.hooksPath ignoruje a v propojeném pracovním stromu je hledá v .git/worktrees/<name>/hooks, takže se tam sdílené skripty .git/hooks nespouštějí (#12).

Níže popsaný HookRunner tyto možnosti ignoruje: při přímém volání háček vždy spustí.

HookRunner

PHP
use Pitmaster\Hooks\HookRunner;

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

Ověření existence háčku

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

Háček existuje, pokud se soubor nachází v .git/hooks/<name> a je spustitelný.

Spuštění háčku

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

S argumenty

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

Se stdin

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

Háček pre-push dostává informace o referencích přes stdin.

Ověření úspěchu

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

runAndCheck() je pomocná metoda, která spustí háček a vrátí true, pokud je návratový kód 0. Pokud háček neexistuje, vrátí true (absence háčku neznamená námitku).

Seznam nainstalovaných háčků

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

Vypíše všechny spustitelné soubory v .git/hooks/ (vynechá soubory .sample).

run() při chybějícím nebo nespustitelném háčku vrátí návratový kód 0 a prázdný výstup. Pokud proces nelze spustit, vrátí návratový kód 1 a na stderr vypíše Failed to start hook.

Instalace háčku

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

Zapíše obsah skriptu do .git/hooks/<name> a nastaví oprávnění ke spuštění (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);

Háčky, které spouští Pitmaster

HookRunner může spustit libovolný háček podle názvu. Repository automaticky spouští tyto háčky:

Háček Spouští jej Argumenty Může přerušit operaci?
pre-commit commit() žádné Ano (nenulový návratový kód)
prepare-commit-msg commit() soubor se zprávou, poté message (nebo merge při dokončení sloučení) Ano
commit-msg commit() soubor se zprávou (.git/COMMIT_EDITMSG); použijí se jeho úpravy Ano (nenulový návratový kód)
post-commit commit() žádné Ne (informativní)
pre-rebase rebase() revize upstreamu Ano
post-checkout checkout() starý HEAD, nový HEAD, 1 Ne (informativní)
post-merge merge(), mergeOctopus(), mergeContinue() 0 Ne (informativní)
pre-push push() a jeho varianty název vzdáleného repozitáře, URL; řádky referencí přes stdin Ano (nenulový návratový kód)

Neúspěšný háček způsobí, že operace vyvolá RuntimeException('<name> hook failed'); jeho výstup se nepřipojí. Háčky commitu se spouštějí pouze z commit(): commity sloučení vytvořené pomocí merge() je nespouštějí. post-checkout se nespouští pro addWorktree(), reset() ani restore().

Serverové háčky (pre-receive, update, post-receive, post-update), pre-auto-gc a post-rewrite se nikdy nespouštějí automaticky.

Prostředí háčku

Při spuštění háčku Pitmaster předá toto prostředí:

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

Háček obdrží pouze tyto proměnné: PATH, HOME a žádné další proměnné procesu PHP se nepředávají (#13). Ve skriptech háčků používejte absolutní cesty nebo nastavte PATH přímo ve skriptu; shebang #!/usr/bin/env php najde interprety pouze ve výchozí systémové cestě.

Háček se spouští v dirname($gitDir), což je pracovní strom běžného adresáře .git.

Formát skriptu háčku

Háčky lze psát v libovolném jazyce. První řádek musí obsahovat shebang:

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

Příklad: pre-commit s Pitmaster

commit() už spouští pre-commit, prepare-commit-msg, commit-msg a post-commit, takže aplikační kód musí ošetřit pouze neúspěch:

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

HookRunner volejte sami pouze na instanci otevřené pomocí ['hooks' => false] nebo u háčků, které Pitmaster nespouští.

Ukázkové háčky

Git inicializuje repozitáře se soubory háčků .sample; Pitmaster::init() vytvoří prázdný adresář hooks/. Pitmaster ve svém listHooks() vylučuje soubory .sample:

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