
# Hooki

Hooki Git to wykonywalne skrypty w `.git/hooks/` uruchamiane w określonych momentach operacji git. Pitmaster wykrywa i wywołuje hooki, korzystając z kontroli procesów PHP (`proc_open`).

## Hooki uruchamiane domyślnie

Operacje na repozytorium automatycznie uruchamiają hooki wymienione w [Hooki Pitmaster uruchamia](#hooks-pitmaster-runs). Aby je wyłączyć dla uchwytu repozytorium, przekaż opcje podczas jego otwierania:

```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()` i `Pitmaster::clone()` przyjmują te same opcje. Uruchomienie hooka wymaga `proc_open()`; na hostach, które je wyłączają, otwieraj repozytoria za pomocą `['hooks' => false]`. CLI zawsze otwiera repozytorium z włączonymi hookami.

Pitmaster szuka hooków tylko w `<gitDir>/hooks`. Ignoruje `core.hooksPath`, a w połączonym worktree szuka w `.git/worktrees/<name>/hooks`, więc współdzielone skrypty `.git/hooks` nie są tam uruchamiane ([#12](https://github.com/inline0/pitmaster/issues/12)).

`HookRunner`, opisany niżej, ignoruje te opcje: bezpośrednie wywołanie zawsze uruchamia hook.

## HookRunner

```php
use Pitmaster\Hooks\HookRunner;

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

## Sprawdzanie, czy hook istnieje

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

Hook istnieje, jeśli plik znajduje się w `.git/hooks/<name>` i jest wykonywalny.

## Uruchamianie hooka

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

### Z argumentami

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

### Ze stdin

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

Hook `pre-push` otrzymuje informacje o ref na stdin.

### Sprawdzanie powodzenia

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

`runAndCheck()` to funkcja pomocnicza, która uruchamia hook i zwraca `true`, jeśli kod wyjścia wynosi 0. Jeśli hook nie istnieje, zwraca `true` (brak hooka oznacza brak sprzeciwu).

## Wyświetlanie zainstalowanych hooków

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

Wyświetla wszystkie wykonywalne pliki w `.git/hooks/` (z wyłączeniem plików `.sample`).

`run()` dla brakującego lub niewykonywalnego hooka zwraca kod wyjścia 0 i puste wyjście. Jeśli nie można uruchomić procesu, zwraca kod wyjścia 1, a na stderr `Failed to start hook`.

## Instalowanie hooka

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

Zapisuje treść skryptu w `.git/hooks/<name>` i nadaje mu uprawnienie do wykonywania (`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);
```

## Hooki uruchamiane przez Pitmaster

`HookRunner` może uruchomić dowolny hook według nazwy. `Repository` uruchamia automatycznie następujące hooki:

| Hook | Uruchamiany przez | Argumenty | Może przerwać? |
|------|--------|-----------|-----------|
| `pre-commit` | `commit()` | brak | Tak (niezerowy kod wyjścia) |
| `prepare-commit-msg` | `commit()` | plik wiadomości, następnie `message` (lub `merge` podczas kończenia scalania) | Tak |
| `commit-msg` | `commit()` | plik wiadomości (`.git/COMMIT_EDITMSG`); zmiany w pliku są uwzględniane | Tak (niezerowy kod wyjścia) |
| `post-commit` | `commit()` | brak | Nie (informacyjny) |
| `pre-rebase` | `rebase()` | rewizja upstream | Tak |
| `post-checkout` | `checkout()` | stary HEAD, nowy HEAD, `1` | Nie (informacyjny) |
| `post-merge` | `merge()`, `mergeOctopus()`, `mergeContinue()` | `0` | Nie (informacyjny) |
| `pre-push` | `push()` i jego warianty | nazwa remote, URL; wiersze ref na stdin | Tak (niezerowy kod wyjścia) |

Niepowodzenie hooka powoduje, że operacja zgłasza `RuntimeException('<name> hook failed')`; wynik hooka nie jest dołączany. Hooki commit są uruchamiane tylko z `commit()`: scalenia commitów utworzone przez `merge()` ich nie uruchamiają. `post-checkout` nie jest uruchamiany dla `addWorktree()`, `reset()` ani `restore()`.

Hooki po stronie serwera (`pre-receive`, `update`, `post-receive`, `post-update`), `pre-auto-gc` i `post-rewrite` nigdy nie są uruchamiane automatycznie.

## Środowisko hooka

Gdy Pitmaster uruchamia hook, przekazuje mu następujące zmienne środowiskowe:

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

Hook otrzymuje tylko te zmienne: `PATH`, `HOME` oraz pozostałe zmienne procesu PHP nie są przekazywane ([#13](https://github.com/inline0/pitmaster/issues/13)). W skryptach hooków używaj ścieżek bezwzględnych albo ustaw `PATH` wewnątrz skryptu; shebang `#!/usr/bin/env php` znajduje interpretery tylko w domyślnej ścieżce systemowej.

Hook jest uruchamiany w `dirname($gitDir)`, czyli katalogu roboczym zwykłego repozytorium `.git`.

## Format skryptu hooka

Hooki można napisać w dowolnym języku. Pierwszy wiersz musi zawierać shebang:

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

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

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

## Przykład: pre-commit z Pitmaster

`commit()` już uruchamia `pre-commit`, `prepare-commit-msg`, `commit-msg` i `post-commit`, dlatego kod aplikacji musi jedynie obsłużyć niepowodzenie:

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

Wywołuj `HookRunner` samodzielnie tylko na uchwycie otwartym z `['hooks' => false]` albo dla hooków, których Pitmaster nie uruchamia.

## Przykładowe hooki

Git inicjuje repozytoria z plikami hooków `.sample`; `Pitmaster::init()` tworzy pusty katalog `hooks/`. Plik `listHooks()` w Pitmaster wyklucza pliki `.sample`:

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