
# Zanke

Prestrezniki Git so izvedljivi skripti v `.git/hooks/`, ki se zaženejo na določenih točkah operacij git. Pitmaster zazna in sproži prestreznike z nadzorom procesov v PHP (`proc_open`).

## Prestrezniki se izvajajo privzeto

Operacije v repozitoriju samodejno zaženejo navedene kavlje [Hooks Pitmaster izvede](#hooks-pitmaster-runs). Če jih želite izklopiti za ročaj repozitorija, ob njegovem odpiranju podajte 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()` in `Pitmaster::clone()` sprejemata enake možnosti. Za izvajanje prestreznika je potreben `proc_open()`; v gostiteljih, kjer je ta onemogočen, odprite repozitorije z `['hooks' => false]`. CLI vedno odpre repozitorij z omogočenimi prestrezniki.

Pitmaster išče prestreznike samo v `<gitDir>/hooks`. `core.hooksPath` prezre, v povezanem delovnem drevesu pa jih išče v `.git/worktrees/<name>/hooks`, zato se tam skripti v skupni rabi `.git/hooks` ne izvajajo ([#12](https://github.com/inline0/pitmaster/issues/12)).

`HookRunner`, opisan spodaj, te možnosti prezre: če ga pokličete neposredno, se prestreznik vedno izvede.

## HookRunner

```php
use Pitmaster\Hooks\HookRunner;

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

## Preverjanje, ali prestreznik obstaja

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

Prestreznik obstaja, če je datoteka prisotna na poti `.git/hooks/<name>` in je izvedljiva.

## Zagon prestreznika

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

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

### S standardnim vhodom

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

Prestreznik `pre-push` prejme informacije o sklicu na standardnem vhodu.

### Preverjanje uspešnosti

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

`runAndCheck()` je priročna funkcija, ki zažene prestreznik in vrne `true`, če je izhodna koda 0. Če prestreznik ne obstaja, vrne `true` (če prestreznika ni, ni ugovora).

## Seznam nameščenih prestreznikov

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

Navede vse izvedljive datoteke v `.git/hooks/` (datotek `.sample` ne vključuje).

`run()` za manjkajoč ali neizvedljiv prestreznik vrne izhodno kodo 0 in prazen izhod. Če procesa ni mogoče zagnati, vrne izhodno kodo 1, na standardni napaki pa `Failed to start hook`.

## Namestitev prestreznika

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

Vsebino skripta zapiše v `.git/hooks/<name>` in nastavi dovoljenje za izvajanje (`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);
```

## Prestrezniki, ki jih izvaja Pitmaster

`HookRunner` lahko zažene kateri koli prestreznik po imenu. `Repository` jih zažene samodejno:

| Prestreznik | Izvaja ga | Argumenti | Lahko prekine? |
|------|--------|-----------|-----------|
| `pre-commit` | `commit()` | nič | Da (izhodna koda ni nič) |
| `prepare-commit-msg` | `commit()` | datoteka sporočila, nato `message` (ali `merge` pri zaključevanju združitve) | Da |
| `commit-msg` | `commit()` | datoteka sporočila (`.git/COMMIT_EDITMSG`); spremembe datoteke se uporabijo | Da (izhodna koda ni nič) |
| `post-commit` | `commit()` | nič | Ne (informativno) |
| `pre-rebase` | `rebase()` | revizija nadrejenega repozitorija | Da |
| `post-checkout` | `checkout()` | stari HEAD, novi HEAD, `1` | Ne (informativno) |
| `post-merge` | `merge()`, `mergeOctopus()`, `mergeContinue()` | `0` | Ne (informativno) |
| `pre-push` | `push()` in njegove različice | ime oddaljenega repozitorija, URL; vrstice sklicev na standardnem vhodu | Da (izhodna koda ni nič) |

Če prestreznik ne uspe, operacija sproži `RuntimeException('<name> hook failed')`; izhod prestreznika ni vključen. Prestrezniki za potrjevanje se izvajajo samo iz `commit()`: združitve, ki jih ustvari `merge()`, jih ne zaženejo. `post-checkout` se ne zažene za `addWorktree()`, `reset()` ali `restore()`.

Strežniški prestrezniki (`pre-receive`, `update`, `post-receive`, `post-update`), `pre-auto-gc` in `post-rewrite` se nikoli ne izvajajo samodejno.

## Okolje prestreznika

Ko Pitmaster zažene prestreznik, mu posreduje to okolje:

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

Prestreznik prejme samo te spremenljivke: `PATH`, `HOME` in nobena druga spremenljivka procesa PHP se ne posreduje ([#13](https://github.com/inline0/pitmaster/issues/13)). V skriptih prestreznikov uporabljajte absolutne poti ali znotraj skripta nastavite `PATH`; vstavljena vrstica shebang `#!/usr/bin/env php` najde tolmače samo na privzeti sistemski poti.

Prestreznik se izvaja v `dirname($gitDir)`, ki je pri običajnem imeniku `.git` delovno drevo.

## Oblika skripta prestreznika

Prestreznike lahko napišete v katerem koli jeziku. Prva vrstica mora biti vrstica shebang:

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

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

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

## Primer: pre-commit z Pitmaster

`commit()` že zažene `pre-commit`, `prepare-commit-msg`, `commit-msg` in `post-commit`, zato mora koda aplikacije obravnavati samo neuspeh:

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

`HookRunner` pokličite sami samo pri ročici, odprti z `['hooks' => false]`, ali za prestreznike, ki jih Pitmaster ne zažene.

## Vzorčni prestrezniki

Git inicializira repozitorije z datotekami prestreznikov `.sample`; `Pitmaster::init()` ustvari prazen imenik `hooks/`. Pitmaster v svojem `listHooks()` izključi datoteke `.sample`:

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