
# Hooky

Háčky Git jsou spustitelné skripty v `.git/hooks/`, které se spouštějí v určitých bodech operací git. Pitmaster háčky detekuje a spouští pomocí řízení procesů v PHP (`proc_open`).

## Háčky se spouštějí automaticky

Operace repozitáře automaticky spouštějí háčky uvedené v části [Háčky, které spouští Pitmaster](#hooks-pitmaster-runs). Chcete-li je pro určitou instanci repozitáře vypnout, předejte při jejím otevření možnosti:

```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()` přijímají stejné možnosti. Spuštění háčku vyžaduje `proc_open()`; na hostitelích, kde je zakázáno, otevírejte repozitáře pomocí `['hooks' => false]`. CLI vždy otevírá repozitář se zapnutými háčky.

Pitmaster hledá háčky pouze v `<gitDir>/hooks`. `core.hooksPath` ignoruje a v propojeném pracovním stromu je hledá v `.git/worktrees/<name>/hooks`, takže se tam sdílené skripty `.git/hooks` nespouštějí ([#12](https://github.com/inline0/pitmaster/issues/12)).

Níže popsaný `HookRunner` tyto možnosti ignoruje: při přímém volání háček vždy spustí.

## HookRunner

```php
use Pitmaster\Hooks\HookRunner;

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

## Ověření existence háčku

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

Háček existuje, pokud se soubor nachází v `.git/hooks/<name>` a je spustitelný.

## Spuštění háčku

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

### S argumenty

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

### Se stdin

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

Háček `pre-push` dostává informace o referencích přes stdin.

### Ověření úspěchu

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

`runAndCheck()` je pomocná metoda, která spustí háček a vrátí `true`, pokud je návratový kód 0. Pokud háček neexistuje, vrátí `true` (absence háčku neznamená námitku).

## Seznam nainstalovaných háčků

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

Vypíše všechny spustitelné soubory v `.git/hooks/` (vynechá soubory `.sample`).

`run()` při chybějícím nebo nespustitelném háčku vrátí návratový kód 0 a prázdný výstup. Pokud proces nelze spustit, vrátí návratový kód 1 a na stderr vypíše `Failed to start hook`.

## Instalace háčku

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

Zapíše obsah skriptu do `.git/hooks/<name>` a nastaví oprávnění ke spuštění (`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);
```

## Háčky, které spouští Pitmaster

`HookRunner` může spustit libovolný háček podle názvu. `Repository` automaticky spouští tyto háčky:

| Háček | Spouští jej | Argumenty | Může přerušit operaci? |
|------|--------|-----------|-----------|
| `pre-commit` | `commit()` | žádné | Ano (nenulový návratový kód) |
| `prepare-commit-msg` | `commit()` | soubor se zprávou, poté `message` (nebo `merge` při dokončení sloučení) | Ano |
| `commit-msg` | `commit()` | soubor se zprávou (`.git/COMMIT_EDITMSG`); použijí se jeho úpravy | Ano (nenulový návratový kód) |
| `post-commit` | `commit()` | žádné | Ne (informativní) |
| `pre-rebase` | `rebase()` | revize upstreamu | Ano |
| `post-checkout` | `checkout()` | starý HEAD, nový HEAD, `1` | Ne (informativní) |
| `post-merge` | `merge()`, `mergeOctopus()`, `mergeContinue()` | `0` | Ne (informativní) |
| `pre-push` | `push()` a jeho varianty | název vzdáleného repozitáře, URL; řádky referencí přes stdin | Ano (nenulový návratový kód) |

Neúspěšný háček způsobí, že operace vyvolá `RuntimeException('<name> hook failed')`; jeho výstup se nepřipojí. Háčky commitu se spouštějí pouze z `commit()`: commity sloučení vytvořené pomocí `merge()` je nespouštějí. `post-checkout` se nespouští pro `addWorktree()`, `reset()` ani `restore()`.

Serverové háčky (`pre-receive`, `update`, `post-receive`, `post-update`), `pre-auto-gc` a `post-rewrite` se nikdy nespouštějí automaticky.

## Prostředí háčku

Při spuštění háčku Pitmaster předá toto prostředí:

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

Háček obdrží pouze tyto proměnné: `PATH`, `HOME` a žádné další proměnné procesu PHP se nepředávají ([#13](https://github.com/inline0/pitmaster/issues/13)). Ve skriptech háčků používejte absolutní cesty nebo nastavte `PATH` přímo ve skriptu; shebang `#!/usr/bin/env php` najde interprety pouze ve výchozí systémové cestě.

Háček se spouští v `dirname($gitDir)`, což je pracovní strom běžného adresáře `.git`.

## Formát skriptu háčku

Háčky lze psát v libovolném jazyce. První řádek musí obsahovat shebang:

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

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

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

## Příklad: pre-commit s Pitmaster

`commit()` už spouští `pre-commit`, `prepare-commit-msg`, `commit-msg` a `post-commit`, takže aplikační kód musí ošetřit pouze neúspěch:

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

`HookRunner` volejte sami pouze na instanci otevřené pomocí `['hooks' => false]` nebo u háčků, které Pitmaster nespouští.

## Ukázkové háčky

Git inicializuje repozitáře se soubory háčků `.sample`; `Pitmaster::init()` vytvoří prázdný adresář `hooks/`. Pitmaster ve svém `listHooks()` vylučuje soubory `.sample`:

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