
# Hook

Hook Git adalah skrip yang dapat dijalankan di `.git/hooks/` dan berjalan pada titik tertentu dalam operasi git. Pitmaster mendeteksi dan memanggil hook menggunakan kendali proses milik PHP (`proc_open`).

## Hook dijalankan secara default

Operasi repositori menjalankan hook yang tercantum di [Hook Pitmaster berjalan](#hooks-pitmaster-runs) secara otomatis. Untuk menonaktifkannya pada handle repositori, berikan opsi saat membukanya:

```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()` dan `Pitmaster::clone()` menerima opsi yang sama. Menjalankan hook memerlukan `proc_open()`; pada host yang menonaktifkannya, buka repositori dengan `['hooks' => false]`. CLI selalu membuka repositori dengan hook diaktifkan.

Pitmaster hanya mencari hook di `<gitDir>/hooks`. `core.hooksPath` diabaikan, dan pada worktree tertaut hook dicari di `.git/worktrees/<name>/hooks`, sehingga skrip `.git/hooks` bersama tidak berjalan di sana ([#12](https://github.com/inline0/pitmaster/issues/12)).

`HookRunner`, yang dijelaskan di bawah, mengabaikan opsi ini: pemanggilan langsung selalu menjalankan hook.

## HookRunner

```php
use Pitmaster\Hooks\HookRunner;

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

## Memeriksa apakah hook tersedia

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

Hook tersedia jika filenya ada di `.git/hooks/<name>` dan dapat dieksekusi.

## Menjalankan 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
```

### Dengan argumen

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

### Dengan stdin

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

Hook `pre-push` menerima informasi ref melalui stdin.

### Memeriksa keberhasilan

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

`runAndCheck()` adalah fungsi praktis yang menjalankan hook dan mengembalikan `true` jika kode keluar bernilai 0. Jika hook tidak tersedia, hasilnya `true` (tidak adanya hook berarti tidak ada keberatan).

## Mencantumkan hook yang terpasang

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

Mencantumkan semua file yang dapat dieksekusi di `.git/hooks/` (tidak termasuk file `.sample`).

`run()` mengembalikan kode keluar 0 dengan keluaran kosong jika hook tidak ada atau tidak dapat dieksekusi. Jika proses tidak dapat dimulai, hasilnya kode keluar 1 dengan `Failed to start hook` pada stderr.

## Memasang hook

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

Menulis konten skrip ke `.git/hooks/<name>` dan mengatur izin eksekusi (`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 yang dijalankan Pitmaster

`HookRunner` dapat menjalankan hook apa pun berdasarkan nama. `Repository` menjalankan hook berikut secara otomatis:

| Hook | Dijalankan oleh | Argumen | Dapat membatalkan? |
|------|--------|-----------|-----------|
| `pre-commit` | `commit()` | tidak ada | Ya (kode keluar bukan nol) |
| `prepare-commit-msg` | `commit()` | file pesan, lalu `message` (atau `merge` saat menyelesaikan merge) | Ya |
| `commit-msg` | `commit()` | file pesan (`.git/COMMIT_EDITMSG`); perubahan pada file akan digunakan | Ya (kode keluar bukan nol) |
| `post-commit` | `commit()` | tidak ada | Tidak (hanya informasi) |
| `pre-rebase` | `rebase()` | revisi upstream | Ya |
| `post-checkout` | `checkout()` | HEAD lama, HEAD baru, `1` | Tidak (hanya informasi) |
| `post-merge` | `merge()`, `mergeOctopus()`, `mergeContinue()` | `0` | Tidak (hanya informasi) |
| `pre-push` | `push()` beserta variannya | nama remote, URL; baris ref melalui stdin | Ya (kode keluar bukan nol) |

Hook yang gagal menyebabkan operasi melempar `RuntimeException('<name> hook failed')`; keluarannya tidak disertakan. Hook commit hanya dijalankan dari `commit()`: commit merge yang dibuat oleh `merge()` tidak menjalankannya. `post-checkout` tidak dijalankan untuk `addWorktree()`, `reset()`, atau `restore()`.

Hook sisi server (`pre-receive`, `update`, `post-receive`, `post-update`), `pre-auto-gc`, dan `post-rewrite` tidak pernah dijalankan secara otomatis.

## Lingkungan hook

Saat Pitmaster menjalankan hook, variabel lingkungan berikut diteruskan:

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

Hanya variabel ini yang diterima hook: `PATH`, `HOME`, dan semua variabel lain dari proses PHP tidak diteruskan ([#13](https://github.com/inline0/pitmaster/issues/13)). Gunakan path absolut dalam skrip hook, atau atur `PATH` di dalam skrip; shebang `#!/usr/bin/env php` hanya menemukan interpreter di path default sistem.

Hook berjalan di `dirname($gitDir)`, yaitu working tree untuk direktori `.git` biasa.

## Format skrip hook

Hook dapat ditulis dalam bahasa apa pun. Baris pertama harus berupa shebang:

```bash
#!/bin/bash
# Bash hook
```

```php
#!/usr/bin/env php
<?php
// PHP hook
```

```python
#!/usr/bin/env python3
# Python hook
```

## Contoh: pre-commit dengan Pitmaster

`commit()` sudah menjalankan `pre-commit`, `prepare-commit-msg`, `commit-msg`, dan `post-commit`, sehingga kode aplikasi hanya perlu menangani kegagalan:

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

Panggil `HookRunner` sendiri hanya pada handle yang dibuka dengan `['hooks' => false]`, atau untuk hook yang tidak dijalankan oleh Pitmaster.

## Contoh hook

Git menginisialisasi repositori dengan file hook `.sample`; `Pitmaster::init()` membuat direktori `hooks/` kosong. Pitmaster mengecualikan file `.sample` melalui `listHooks()`:

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