본문으로 건너뛰기

훅

Git 훅은 .git/hooks/의 실행 가능한 스크립트로, git 작업의 특정 시점에 실행됩니다. Pitmaster는 PHP의 프로세스 제어(proc_open)를 사용해 훅을 감지하고 호출합니다.

훅은 기본적으로 실행됩니다

저장소 작업을 수행하면 훅Pitmaster에 나열된 훅이 자동으로 실행합니다. 저장소 핸들에서 훅을 끄려면 핸들을 열 때 옵션을 전달하세요:

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()와 Pitmaster::clone()은 같은 옵션을 받습니다. 훅을 실행하려면 proc_open()가 필요합니다. 이를 비활성화한 호스트에서는 ['hooks' => false]을 사용해 저장소를 여세요. CLI는 항상 훅을 활성화한 상태로 저장소를 엽니다.

Pitmaster는 <gitDir>/hooks에서만 훅을 찾습니다. core.hooksPath는 무시하며, 연결된 워크트리에서는 .git/worktrees/<name>/hooks에서 찾으므로 공유 .git/hooks 스크립트는 그곳에서 실행되지 않습니다(#12).

아래에서 설명하는 HookRunner는 이러한 옵션을 무시합니다. 직접 호출하면 항상 훅을 실행합니다.

HookRunner

PHP
use Pitmaster\Hooks\HookRunner;

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

훅 존재 여부 확인

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

.git/hooks/<name>에 파일이 있고 실행 권한이 있으면 훅이 존재합니다.

훅 실행

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

인수 사용

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

stdin 사용

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

pre-push 훅은 stdin으로 ref 정보를 받습니다.

성공 여부 확인

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

runAndCheck()는 훅을 실행하고 종료 코드가 0이면 true을 반환하는 편의 기능입니다. 훅이 없으면 true를 반환합니다(훅이 없으면 거부할 이유도 없습니다).

설치된 훅 나열

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

.git/hooks/에 있는 파일 중 실행 가능한 파일을 모두 나열합니다(.sample 파일은 제외).

훅이 없거나 실행할 수 없는 경우 run()은 빈 출력과 종료 코드 0을 반환합니다. 프로세스를 시작할 수 없으면 stderr에 Failed to start hook을 출력하고 종료 코드 1을 반환합니다.

훅 설치

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

스크립트 내용을 .git/hooks/<name>에 쓰고 실행 권한(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);

Pitmaster이 실행하는 훅

HookRunner는 이름으로 어떤 훅이든 실행할 수 있습니다. Repository은 다음 훅을 자동으로 실행합니다:

훅 실행 주체 인수 작업 중단 가능 여부
pre-commit commit() 없음 예(0이 아닌 종료 코드)
prepare-commit-msg commit() 메시지 파일, 이어서 message (병합 완료 시에는 merge) 예
commit-msg commit() 메시지 파일(.git/COMMIT_EDITMSG); 파일의 수정 내용이 사용됨 예(0이 아닌 종료 코드)
post-commit commit() 없음 아니요(정보 제공용)
pre-rebase rebase() 업스트림 리비전 예
post-checkout checkout() 이전 HEAD, 새 HEAD, 1 아니요(정보 제공용)
post-merge merge(), mergeOctopus(), mergeContinue() 0 아니요(정보 제공용)
pre-push push() 및 그 변형 원격 이름, URL; ref 줄은 stdin으로 전달 예(0이 아닌 종료 코드)

훅이 실패하면 작업에서 RuntimeException('<name> hook failed')을 발생시키며 훅의 출력은 포함되지 않습니다. 커밋 훅은 commit()에서만 실행됩니다. merge()가 만든 병합 커밋에서는 실행되지 않습니다. post-checkout은 addWorktree(), reset() 또는 restore()에 대해서는 실행되지 않습니다.

서버 측 훅(pre-receive, update, post-receive, post-update), pre-auto-gc 및 post-rewrite는 자동으로 실행되지 않습니다.

훅 환경

Pitmaster가 훅을 실행할 때 다음 환경을 전달합니다:

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

훅이 받는 변수는 이것뿐입니다. PATH, HOME 및 PHP 프로세스의 다른 모든 변수는 전달되지 않습니다(#13). 훅 스크립트에서는 절대 경로를 사용하거나 스크립트 안에서 PATH을 설정하세요. #!/usr/bin/env php 셰뱅은 시스템 기본 경로에서만 인터프리터를 찾습니다.

훅은 dirname($gitDir)에서 실행됩니다. 일반 .git 디렉터리의 경우 이곳은 작업 트리입니다.

훅 스크립트 형식

훅은 어떤 언어로든 작성할 수 있습니다. 첫 줄은 셰뱅이어야 합니다:

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

예시: Pitmaster을 사용하는 pre-commit

commit()는 이미 pre-commit, prepare-commit-msg, commit-msg 및 post-commit를 실행하므로 애플리케이션 코드는 실패를 처리하기만 하면 됩니다:

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

HookRunner를 직접 호출하는 경우, ['hooks' => false]로 연 핸들에서 호출하거나 Pitmaster가 실행하지 않는 훅에 대해서만 호출하세요.

샘플 훅

Git는 .sample 훅 파일로 저장소를 초기화하고, Pitmaster::init()는 빈 hooks/ 디렉터리를 만듭니다. Pitmaster의 listHooks()은 .sample 파일을 제외합니다:

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