
# Koukkukohdat

Git-koukut ovat suoritettavia komentosarjoja paikassa `.git/hooks/`, ja ne suoritetaan tietyissä git-toimintojen vaiheissa. Pitmaster tunnistaa ja käynnistää koukut hyödyntämällä PHP-prosessinhallintaa (`proc_open`).

## Koukkuja suoritetaan oletusarvoisesti

Varastotoiminnot suorittavat automaattisesti koukut, jotka on lueteltu kohdassa [Koukut, jotka Pitmaster suorittaa](#hooks-pitmaster-runs). Voit poistaa ne käytöstä varastokahvaa varten antamalla asetukset sitä avattaessa:

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

Sekä `Pitmaster::init()` että `Pitmaster::clone()` hyväksyvät samat asetukset. Koukun suorittaminen vaatii toiminnon `proc_open()`; ympäristöissä, joissa se on poistettu käytöstä, avaa varastot asetuksella `['hooks' => false]`. CLI avaa varaston aina koukut käytössä.

Pitmaster etsii koukkuja vain kohteesta `<gitDir>/hooks`. Se ohittaa kohteen `core.hooksPath`, ja linkitetyssä työpuussa se etsii kohteesta `.git/worktrees/<name>/hooks`, joten jaetut `.git/hooks`-komentosarjat eivät suoritu siellä ([#12](https://github.com/inline0/pitmaster/issues/12)).

Alla kuvattu `HookRunner` ohittaa nämä asetukset: sen suora kutsuminen suorittaa koukun aina.

## HookRunner

```php
use Pitmaster\Hooks\HookRunner;

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

## Tarkista, onko koukku olemassa

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

Koukku on olemassa, jos tiedosto on kohteessa `.git/hooks/<name>` ja suoritettavissa.

## Suorita koukku

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

### Argumenteilla

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

### Vakiosyötteellä

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

`pre-push`-koukku saa viitetiedot vakiosyötteestä.

### Tarkista onnistuminen

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

`runAndCheck()` on apumenetelmä, joka suorittaa koukun ja palauttaa arvon `true`, jos poistumiskoodi on 0. Jos koukkua ei ole olemassa, se palauttaa arvon `true` (koukun puuttuminen ei estä toimintoa).

## Luettele asennetut koukut

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

Luettelee kaikki kohteessa `.git/hooks/` olevat suoritettavat tiedostot (ohittaa tiedostot `.sample`).

Jos koukkua ei ole tai se ei ole suoritettavissa, `run()` palauttaa poistumiskoodin 0 ja tyhjän tulosteen. Jos prosessia ei voi käynnistää, se palauttaa poistumiskoodin 1 ja kirjoittaa arvon `Failed to start hook` vakiovirtaan.

## Asenna koukku

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

Kirjoittaa komentosarjan sisällön kohteeseen `.git/hooks/<name>` ja asettaa suoritusoikeuden (`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);
```

## Koukkujen suoritus kohteessa Pitmaster

`HookRunner` voi suorittaa minkä tahansa koukun nimen perusteella. `Repository` suorittaa nämä automaattisesti:

| Koukku | Suorittaja | Argumentit | Voiko keskeyttää? |
|------|--------|-----------|-----------|
| `pre-commit` | `commit()` | ei mitään | Kyllä (muu kuin nolla -poistumiskoodi) |
| `prepare-commit-msg` | `commit()` | viestitiedosto, sitten `message` (tai `merge` yhdistämisen valmistuessa) | Kyllä |
| `commit-msg` | `commit()` | viestitiedosto (`.git/COMMIT_EDITMSG`); tiedostoon tehdyt muutokset otetaan käyttöön | Kyllä (muu kuin nolla -poistumiskoodi) |
| `post-commit` | `commit()` | ei mitään | Ei (vain tiedoksi) |
| `pre-rebase` | `rebase()` | ylävirran revisio | Kyllä |
| `post-checkout` | `checkout()` | vanha HEAD, uusi HEAD, `1` | Ei (vain tiedoksi) |
| `post-merge` | `merge()`, `mergeOctopus()`, `mergeContinue()` | `0` | Ei (vain tiedoksi) |
| `pre-push` | `push()` ja sen muunnelmat | etänimi, URL; viiterivit vakiosyötteessä | Kyllä (muu kuin nolla -poistumiskoodi) |

Epäonnistunut koukku aiheuttaa toiminnon keskeyttävän `RuntimeException('<name> hook failed')`-poikkeuksen; koukun tulostetta ei sisällytetä siihen. Commit-koukut suoritetaan vain kohteesta `commit()`: kohteen `merge()` luomat yhdistämiscommitit eivät suorita niitä. `post-checkout` ei suoritu kohteille `addWorktree()`, `reset()` eikä `restore()`.

Palvelinpuolen koukkuja (`pre-receive`, `update`, `post-receive`, `post-update`), `pre-auto-gc`-koukkua eikä `post-rewrite`-koukkua suoriteta koskaan automaattisesti.

## Koukkuympäristö

Kun Pitmaster suorittaa koukun, se välittää sille tämän ympäristön:

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

Nämä ovat ainoat muuttujat, jotka koukku saa: muuttujia `PATH` ja `HOME` eikä mitään muita PHP-prosessin muuttujia välitetä ([#13](https://github.com/inline0/pitmaster/issues/13)). Käytä koukkukomentosarjoissa absoluuttisia polkuja tai aseta `PATH` komentosarjan sisällä; `#!/usr/bin/env php`-shebang löytää tulkit vain järjestelmän oletuspolusta.

Koukku suoritetaan kohteessa `dirname($gitDir)`, joka on tavallisen `.git`-hakemiston työpuu.

## Koukkukomentosarjan muoto

Koukkuja voi kirjoittaa millä tahansa kielellä. Ensimmäisen rivin on oltava shebang:

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

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

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

## Esimerkki: pre-commit ja Pitmaster

`commit()` suorittaa jo kohteet `pre-commit`, `prepare-commit-msg`, `commit-msg` ja `post-commit`, joten sovelluskoodin tarvitsee vain käsitellä epäonnistuminen:

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

Kutsu `HookRunner` itse vain kahvalla, joka on avattu asetuksella `['hooks' => false]`, tai niiden koukkujen kohdalla, joita Pitmaster ei suorita.

## Esimerkkikoukut

Git alustaa varastot `.sample`-koukkutiedostoilla; `Pitmaster::init()` luo tyhjän `hooks/`-hakemiston. Kohteen Pitmaster `listHooks()` ohittaa tiedostot `.sample`:

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