
# Hooky

Obslužné skripty Git sú spustiteľné skripty v `.git/hooks/`, ktoré sa spúšťajú v konkrétnych fázach operácií git. Pitmaster ich zisťuje a spúšťa pomocou riadenia procesov v PHP (`proc_open`).

## Obslužné skripty sa spúšťajú predvolene

Operácie s repozitárom automaticky spúšťajú hooky uvedené v časti [Háčiky Pitmaster sa spúšťajú](#hooks-pitmaster-runs). Ak ich chcete pre konkrétny handle repozitára vypnúť, pri jeho otvorení odovzdajte 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
```

Pre `Pitmaster::init()` aj `Pitmaster::clone()` platia rovnaké možnosti. Spúšťanie obslužného skriptu vyžaduje `proc_open()`; na hostiteľských systémoch, ktoré ho zakazujú, otvárajte repozitáre pomocou `['hooks' => false]`. Trieda CLI vždy otvára repozitár so zapnutými obslužnými skriptmi.

Pitmaster hľadá obslužné skripty iba v `<gitDir>/hooks`. `core.hooksPath` ignoruje a v prepojenom pracovnom strome ich hľadá v `.git/worktrees/<name>/hooks`, preto sa tam zdieľané skripty `.git/hooks` nespustia ([#12](https://github.com/inline0/pitmaster/issues/12)).

Funkcia `HookRunner`, opísaná nižšie, tieto možnosti ignoruje: pri priamom zavolaní vždy spustí obslužný skript.

## HookRunner

```php
use Pitmaster\Hooks\HookRunner;

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

## Kontrola existencie obslužného skriptu

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

Obslužný skript existuje, ak sa súbor nachádza v `.git/hooks/<name>` a má nastavené právo na spustenie.

## Spustenie obslužného skriptu

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

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

### So štandardným vstupom

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

Obslužný skript `pre-push` prijíma informácie o referencii zo štandardného vstupu.

### Kontrola úspešného vykonania

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

`runAndCheck()` je pomocná funkcia, ktorá spustí obslužný skript a vráti `true`, ak je návratový kód 0. Ak obslužný skript neexistuje, vráti `true` (ak skript chýba, ničomu nebráni).

## Zoznam nainštalovaných obslužných skriptov

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

Vypíše všetky spustiteľné súbory v `.git/hooks/` (súbory `.sample` vynechá).

Ak obslužný skript `run()` chýba alebo nie je spustiteľný, vráti návratový kód 0 a prázdny výstup. Ak proces nemožno spustiť, vráti návratový kód 1 a na štandardný chybový výstup vypíše `Failed to start hook`.

## Inštalácia obslužného skriptu

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

Zapíše obsah skriptu do `.git/hooks/<name>` a nastaví mu právo na spustenie (`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);
```

## Obslužné skripty spúšťané Pitmaster

`HookRunner` dokáže spustiť ľubovoľný obslužný skript podľa názvu. `Repository` ich spúšťa automaticky:

| Obslužný skript | Spúšťa ho | Argumenty | Môže prerušiť operáciu? |
|------|--------|-----------|-----------|
| `pre-commit` | `commit()` | žiadne | Áno (nenulový návratový kód) |
| `prepare-commit-msg` | `commit()` | súbor správy, potom `message` (alebo `merge` pri dokončovaní zlúčenia) | Áno |
| `commit-msg` | `commit()` | súbor správy (`.git/COMMIT_EDITMSG`); zmeny v súbore sa použijú | Áno (nenulový návratový kód) |
| `post-commit` | `commit()` | žiadne | Nie (iba informatívny) |
| `pre-rebase` | `rebase()` | revízia vzdialeného repozitára, z ktorého sa sťahujú zmeny | Áno |
| `post-checkout` | `checkout()` | starý HEAD, nový HEAD, `1` | Nie (iba informatívny) |
| `post-merge` | `merge()`, `mergeOctopus()`, `mergeContinue()` | `0` | Nie (iba informatívny) |
| `pre-push` | `push()` a jeho varianty | názov vzdialeného repozitára, URL; riadky s referenciami zo štandardného vstupu | Áno (nenulový návratový kód) |

Ak obslužný skript zlyhá, operácia vyvolá `RuntimeException('<name> hook failed')`; výstup skriptu sa nezahrnie. Obslužné skripty pre odovzdanie zmien sa spúšťajú iba pri operácii `commit()`: odovzdania zlúčenia vytvorené pomocou `merge()` ich nespúšťajú. `post-checkout` sa nespúšťa pri operáciách `addWorktree()`, `reset()` ani `restore()`.

Serverové obslužné skripty (`pre-receive`, `update`, `post-receive`, `post-update`), `pre-auto-gc` a `post-rewrite` sa nikdy nespúšťajú automaticky.

## Prostredie obslužných skriptov

Pri spúšťaní obslužného skriptu Pitmaster mu odovzdá tieto premenné prostredia:

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

Obslužný skript dostane iba tieto premenné: `PATH`, `HOME` a žiadne ďalšie premenné procesu PHP sa neodovzdávajú ([#13](https://github.com/inline0/pitmaster/issues/13)). V skriptoch používajte absolútne cesty alebo v nich nastavte `PATH`; shebang `#!/usr/bin/env php` nájde interprety iba v predvolenej systémovej ceste.

Obslužný skript sa spúšťa v `dirname($gitDir)`, čo je pri bežnom adresári `.git` pracovný strom.

## Formát skriptu obslužného skriptu

Obslužné skripty možno napísať v ľubovoľnom jazyku. Prvý riadok musí obsahovať shebang:

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

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

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

## Príklad: pre-commit pomocou Pitmaster

`commit()` už spúšťa `pre-commit`, `prepare-commit-msg`, `commit-msg` a `post-commit`, takže v kóde aplikácie stačí ošetriť zlyhanie:

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

`HookRunner` volajte samostatne iba pri objekte repozitára otvorenom pomocou `['hooks' => false]` alebo pri obslužných skriptoch, ktoré Pitmaster nespúšťa.

## Ukážkové obslužné skripty

Git inicializuje repozitáre so súbormi obslužných skriptov `.sample`; `Pitmaster::init()` vytvorí prázdny adresár `hooks/`. Konfigurácia Pitmaster `listHooks()` vynecháva súbory `.sample`:

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