Treci la conținut

Cârlige

Hook-urile Git sunt scripturi executabile în .git/hooks/, care rulează în anumite puncte ale operațiunilor git. Pitmaster detectează și apelează hook-urile folosind controlul proceselor din PHP (proc_open).

Hook-urile rulează implicit

Operațiunile asupra depozitului rulează automat hook-urile listate la Hook-urile Pitmaster. Pentru a le dezactiva pentru un obiect handle de depozit, transmiteți opțiuni la deschiderea acestuia:

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() acceptă aceleași opțiuni. Pentru a rula un hook este necesar proc_open(); pe sistemele gazdă unde acesta este dezactivat, deschideți depozitele cu ['hooks' => false]. CLI deschide întotdeauna depozitul cu hook-urile activate.

Pitmaster caută hook-uri numai în <gitDir>/hooks. Ignoră core.hooksPath, iar într-un worktree asociat caută în .git/worktrees/<name>/hooks, astfel că scripturile .git/hooks partajate nu rulează acolo (#12).

HookRunner, descris mai jos, ignoră aceste opțiuni: apelarea lui directă rulează întotdeauna hook-ul.

HookRunner

PHP
use Pitmaster\Hooks\HookRunner;

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

Verificarea existenței unui hook

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

Un hook există dacă fișierul este prezent în .git/hooks/<name> și este executabil.

Rularea unui 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

Cu argumente

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

Cu stdin

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

Hook-ul pre-push primește informații despre referință prin stdin.

Verificarea reușitei

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

runAndCheck() este o metodă convenabilă care rulează hook-ul și returnează true dacă codul de ieșire este 0. Dacă hook-ul nu există, returnează true (absența unui hook înseamnă că nu există obiecții).

Listarea hook-urilor instalate

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

Listează toate fișierele executabile din .git/hooks/ (cu excepția fișierelor .sample).

run() returnează codul de ieșire 0 cu ieșire goală pentru un hook inexistent sau neexecutabil. Dacă procesul nu poate fi pornit, returnează codul de ieșire 1 și Failed to start hook pe stderr.

Instalarea unui hook

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

Scrie conținutul scriptului în .git/hooks/<name> și setează permisiunea de execuție (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);

Hook-urile rulate de Pitmaster

HookRunner poate rula orice hook după nume. Repository le rulează automat pe acestea:

Hook Rulat de Argumente Poate anula?
pre-commit commit() niciunul Da (cod de ieșire diferit de zero)
prepare-commit-msg commit() fișierul mesajului, apoi message (sau merge la finalizarea unei îmbinări) Da
commit-msg commit() fișierul mesajului (.git/COMMIT_EDITMSG); modificările fișierului sunt folosite Da (cod de ieșire diferit de zero)
post-commit commit() niciunul Nu (informativ)
pre-rebase rebase() revizia din amonte Da
post-checkout checkout() HEAD vechi, HEAD nou, 1 Nu (informativ)
post-merge merge(), mergeOctopus(), mergeContinue() 0 Nu (informativ)
pre-push push() și variantele sale numele remote-ului, URL; liniile referințelor prin stdin Da (cod de ieșire diferit de zero)

Un hook care eșuează face operațiunea să arunce RuntimeException('<name> hook failed'); ieșirea hook-ului nu este inclusă. Hook-urile de commit rulează numai din commit(): commit-urile de îmbinare create de merge() nu le rulează. post-checkout nu rulează pentru addWorktree(), reset() sau restore().

Hook-urile de pe server (pre-receive, update, post-receive, post-update), pre-auto-gc și post-rewrite nu rulează niciodată automat.

Mediul hook-urilor

Când Pitmaster rulează un hook, îi transmite acest mediu:

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

Acestea sunt singurele variabile disponibile pentru hook: PATH, HOME și toate celelalte variabile ale procesului PHP nu sunt transmise (#13). Folosiți căi absolute în scripturile hook-urilor sau setați PATH în script; un shebang #!/usr/bin/env php găsește interpreți doar în calea implicită a sistemului.

Hook-ul rulează în dirname($gitDir), care este arborele de lucru pentru un director .git obișnuit.

Formatul scriptului hook-ului

Hook-urile pot fi scrise în orice limbaj. Prima linie trebuie să fie un shebang:

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

Exemplu: pre-commit cu Pitmaster

commit() rulează deja pre-commit, prepare-commit-msg, commit-msg și post-commit, așa că în codul aplicației trebuie tratat doar eșecul:

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

Apelați HookRunner doar pentru un handle deschis cu ['hooks' => false] sau pentru hook-uri pe care Pitmaster nu le rulează.

Hook-uri exemplu

Git inițializează depozite cu fișiere hook .sample; Pitmaster::init() creează un director hooks/ gol. Pitmaster din listHooks() exclude fișierele .sample:

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