Hooks
Los hooks Git son scripts ejecutables en .git/hooks/ que se ejecutan en puntos específicos de las operaciones de git. Pitmaster detecta e invoca hooks mediante el control de procesos de PHP (proc_open).
Los hooks se ejecutan de forma predeterminada
Las operaciones del repositorio ejecutan los hooks enumerados en Hooks que Pitmaster ejecuta automáticamente. Para desactivarlos en un identificador de repositorio, pasa opciones al abrirlo:
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() y Pitmaster::clone() aceptan las mismas opciones. Para ejecutar un hook se necesita proc_open(); en los hosts donde está desactivado, abre los repositorios con ['hooks' => false]. CLI siempre abre el repositorio con los hooks habilitados.
Pitmaster busca hooks solo en <gitDir>/hooks. Ignora core.hooksPath y, en un árbol de trabajo vinculado, busca en .git/worktrees/<name>/hooks, por lo que los scripts compartidos de .git/hooks no se ejecutan allí (#12).
HookRunner, descrito más abajo, ignora estas opciones: si lo llamas directamente, siempre ejecuta el hook.
HookRunner
use Pitmaster\Hooks\HookRunner;
$hooks = new HookRunner($repo->gitDir());
Comprobar si existe un hook
if ($hooks->exists('pre-commit')) {
echo "pre-commit hook is installed\n";
}
Un hook existe si el archivo está presente en .git/hooks/<name> y es ejecutable.
Ejecutar 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
Con argumentos
$result = $hooks->run('commit-msg', ['/path/to/COMMIT_EDITMSG']);
Con stdin
$result = $hooks->run('pre-push', ['origin', 'https://github.com/user/repo.git'], $stdinData);
El hook pre-push recibe información de referencias por stdin.
Comprobar el éxito
if ($hooks->runAndCheck('pre-commit')) {
// Hook passed (exit code 0), proceed with commit
} else {
// Hook rejected the operation
}
runAndCheck() es una función auxiliar que ejecuta el hook y devuelve true si el código de salida es 0. Si el hook no existe, devuelve true (si no hay hook, no hay objeción).
Enumerar los hooks instalados
$installed = $hooks->listHooks();
// ['pre-commit', 'commit-msg', 'post-checkout']
Enumera todos los archivos ejecutables en .git/hooks/ (excluye los archivos .sample).
run() devuelve el código de salida 0 y una salida vacía si el hook no existe o no es ejecutable. Si no se puede iniciar el proceso, devuelve el código de salida 1 y Failed to start hook en stderr.
Instalar un hook
$hooks->install('pre-commit', "#!/bin/bash\nphp vendor/bin/phpcs --standard=PSR12 .\n");
Escribe el contenido del script en .git/hooks/<name> y establece el permiso de ejecución (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 que ejecuta Pitmaster
HookRunner puede ejecutar cualquier hook por nombre. Repository ejecuta estos automáticamente:
| Hook | Lo ejecuta | Argumentos | ¿Puede abortar? |
|---|---|---|---|
pre-commit |
commit() |
ninguno | Sí (código de salida distinto de cero) |
prepare-commit-msg |
commit() |
archivo de mensaje y luego message (o merge al completar una fusión) |
Sí |
commit-msg |
commit() |
archivo de mensaje (.git/COMMIT_EDITMSG); se usan los cambios en el archivo |
Sí (código de salida distinto de cero) |
post-commit |
commit() |
ninguno | No (informativo) |
pre-rebase |
rebase() |
la revisión de origen | Sí |
post-checkout |
checkout() |
HEAD anterior, HEAD nuevo, 1 |
No (informativo) |
post-merge |
merge(), mergeOctopus(), mergeContinue() |
0 |
No (informativo) |
pre-push |
push() y sus variantes |
nombre del remoto, URL; líneas de referencia por stdin | Sí (código de salida distinto de cero) |
Si un hook falla, la operación genera RuntimeException('<name> hook failed'); su salida no se incluye. Los hooks de confirmación solo se ejecutan desde commit(): las fusiones creadas por merge() no los ejecutan. post-checkout no se ejecuta para addWorktree(), reset() ni restore().
Los hooks del lado del servidor (pre-receive, update, post-receive, post-update), pre-auto-gc y post-rewrite nunca se ejecutan automáticamente.
Entorno del hook
Cuando Pitmaster ejecuta un hook, le pasa este entorno:
$env = [
'GIT_DIR' => $this->gitDir,
'GIT_WORK_TREE' => dirname($this->gitDir),
];
Estas son las únicas variables que recibe el hook: PATH, HOME y todas las demás variables del proceso PHP no se pasan (#13). Usa rutas absolutas en los scripts de hooks o establece PATH dentro del script; un shebang #!/usr/bin/env php solo encuentra intérpretes en la ruta predeterminada del sistema.
El hook se ejecuta en dirname($gitDir), que es el árbol de trabajo de un directorio .git normal.
Formato del script del hook
Los hooks pueden escribirse en cualquier lenguaje. La primera línea debe ser un shebang:
#!/bin/bash
# Bash hook
#!/usr/bin/env php
<?php
// PHP hook
#!/usr/bin/env python3
# Python hook
Ejemplo: pre-commit con Pitmaster
commit() ya ejecuta pre-commit, prepare-commit-msg, commit-msg y post-commit, así que el código de la aplicación solo tiene que gestionar el error:
try {
$commitId = $repo->commit('Add feature');
} catch (\RuntimeException $e) {
echo $e->getMessage(); // "pre-commit hook failed"
}
Llama a HookRunner por tu cuenta solo si el identificador se abrió con ['hooks' => false] o para los hooks que Pitmaster no ejecuta.
Hooks de ejemplo
Git inicializa repositorios con archivos de hook .sample; Pitmaster::init() crea un directorio hooks/ vacío. El listHooks() de Pitmaster excluye los archivos .sample:
.git/hooks/
pre-commit.sample (excluded from listHooks)
commit-msg.sample (excluded from listHooks)
pre-commit (included - installed by user)