コンテンツへスキップ

フック

Gitフックは、.git/hooks/内にある実行可能スクリプトで、git操作の特定のタイミングで実行されます。PitmasterはPHPのプロセス制御(proc_open)を使ってフックを検出し、実行します。

フックはデフォルトで実行される

リポジトリ操作では、Hooks Pitmaster runsに記載されたフックが自動的に実行されます。リポジトリハンドルでフックを無効にするには、開くときにオプションを渡します。

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は無視され、リンクされたworktreeでは.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フックは、ref情報をstdinから受け取ります。

成功を確認する

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と空の出力を返します。プロセスを起動できない場合は、終了コード1を返し、stderrにFailed to start hookを出力します。

フックをインストールする

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() upstreamのリビジョン はい
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のshebangでは、システムのデフォルトパスにあるインタープリターしか見つかりません。

フックはdirname($gitDir)で実行されます。通常の.gitディレクトリでは、ここが作業ツリーになります。

フックスクリプトの形式

フックは任意の言語で記述できます。1行目にはshebangが必要です。

シェル
#!/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)