Ganchos
Os hooks Git são scripts executáveis em .git/hooks/, acionados em pontos específicos das operações de git. Pitmaster detecta e invoca hooks usando o controle de processos de PHP (proc_open).
Hooks executados por padrão
As operações de repositório executam automaticamente os hooks listados em Hooks executados pelo Pitmaster. Para desativá-los em uma instância de repositório, passe as opções ao abri-la:
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() e Pitmaster::clone() aceitam as mesmas opções. A execução de um hook requer proc_open(); em hosts que o desativam, abra os repositórios com ['hooks' => false]. O CLI sempre abre o repositório com hooks ativados.
Pitmaster procura hooks somente em <gitDir>/hooks. Ignora core.hooksPath e, em uma árvore de trabalho vinculada, procura em .git/worktrees/<name>/hooks; por isso, os scripts compartilhados de .git/hooks não são executados ali (#12).
HookRunner, descrito abaixo, ignora essas opções: chamá-lo diretamente sempre executa o hook.
HookRunner
use Pitmaster\Hooks\HookRunner;
$hooks = new HookRunner($repo->gitDir());
Verificar se um hook existe
if ($hooks->exists('pre-commit')) {
echo "pre-commit hook is installed\n";
}
Um hook existe se o arquivo estiver presente em .git/hooks/<name> e for executável.
Executar um 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
Com argumentos
$result = $hooks->run('commit-msg', ['/path/to/COMMIT_EDITMSG']);
Com stdin
$result = $hooks->run('pre-push', ['origin', 'https://github.com/user/repo.git'], $stdinData);
O hook pre-push recebe informações de referência via stdin.
Verificar sucesso
if ($hooks->runAndCheck('pre-commit')) {
// Hook passed (exit code 0), proceed with commit
} else {
// Hook rejected the operation
}
runAndCheck() é um atalho que executa o hook e retorna true se o código de saída for 0. Se o hook não existir, retorna true (sem hook, não há objeção).
Listar hooks instalados
$installed = $hooks->listHooks();
// ['pre-commit', 'commit-msg', 'post-checkout']
Lista todos os arquivos executáveis em .git/hooks/ (exclui arquivos .sample).
run(), quando o hook não existe ou não é executável, retorna código de saída 0 e saída vazia. Se não for possível iniciar o processo, retorna código de saída 1 e Failed to start hook em stderr.
Instalar um hook
$hooks->install('pre-commit', "#!/bin/bash\nphp vendor/bin/phpcs --standard=PSR12 .\n");
Grava o conteúdo do script em .git/hooks/<name> e define a permissão de execução (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 executados por Pitmaster
HookRunner pode executar qualquer hook pelo nome. Repository executa automaticamente estes:
| Hook | Executado por | Argumentos | Pode abortar? |
|---|---|---|---|
pre-commit |
commit() |
nenhum | Sim (código de saída diferente de zero) |
prepare-commit-msg |
commit() |
arquivo de mensagem, depois message (ou merge ao concluir uma mesclagem) |
Sim |
commit-msg |
commit() |
arquivo de mensagem (.git/COMMIT_EDITMSG); alterações no arquivo são usadas |
Sim (código de saída diferente de zero) |
post-commit |
commit() |
nenhum | Não (informativo) |
pre-rebase |
rebase() |
revisão upstream | Sim |
post-checkout |
checkout() |
HEAD antigo, HEAD novo, 1 |
Não (informativo) |
post-merge |
merge(), mergeOctopus(), mergeContinue() |
0 |
Não (informativo) |
pre-push |
push() e suas variantes |
nome remoto, URL; linhas de referência via stdin | Sim (código de saída diferente de zero) |
Um hook com falha faz a operação lançar RuntimeException('<name> hook failed'); a saída do hook não é incluída. Os hooks de commit só são executados por commit(): commits de mesclagem criados por merge() não os executam. post-checkout não é executado para addWorktree(), reset() ou restore().
Hooks do lado do servidor (pre-receive, update, post-receive, post-update), pre-auto-gc e post-rewrite nunca são executados automaticamente.
Ambiente do hook
Quando Pitmaster executa um hook, ele fornece este ambiente:
$env = [
'GIT_DIR' => $this->gitDir,
'GIT_WORK_TREE' => dirname($this->gitDir),
];
Estas são as únicas variáveis disponíveis para o hook: PATH, HOME e todas as demais variáveis do processo PHP não são repassadas (#13). Use caminhos absolutos nos scripts de hook ou defina PATH dentro do script; um shebang #!/usr/bin/env php só encontra interpretadores no caminho padrão do sistema.
O hook é executado em dirname($gitDir), que é a árvore de trabalho de um diretório .git normal.
Formato do script de hook
Os hooks podem ser escritos em qualquer linguagem. A primeira linha deve ser um shebang:
#!/bin/bash
# Bash hook
#!/usr/bin/env php
<?php
// PHP hook
#!/usr/bin/env python3
# Python hook
Exemplo: pre-commit com Pitmaster
commit() já executa pre-commit, prepare-commit-msg, commit-msg e post-commit; portanto, o código da aplicação só precisa tratar a falha:
try {
$commitId = $repo->commit('Add feature');
} catch (\RuntimeException $e) {
echo $e->getMessage(); // "pre-commit hook failed"
}
Chame HookRunner manualmente somente em um identificador aberto com ['hooks' => false] ou para hooks que Pitmaster não executa.
Hooks de exemplo
Git inicializa repositórios com arquivos de hook .sample; Pitmaster::init() cria um diretório hooks/ vazio. O Pitmaster de listHooks() exclui arquivos .sample:
.git/hooks/
pre-commit.sample (excluded from listHooks)
commit-msg.sample (excluded from listHooks)
pre-commit (included - installed by user)