
# Įskiepiai

Git kabliai yra vykdomi scenarijai `.git/hooks/` aplinkoje, paleidžiami tam tikrais git operacijų momentais. Pitmaster aptinka ir iškviečia kablius naudodama PHP procesų valdymą (`proc_open`).

## Kabliai vykdomi pagal numatytuosius nustatymus

Saugyklos operacijos automatiškai vykdo [Kabliai Pitmaster nurodytus kablius](#hooks-pitmaster-runs). Norėdami juos išjungti saugyklos deskriptoriui, perduokite parinktis jį atidarydami:

```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()` ir `Pitmaster::clone()` priima tas pačias parinktis. Kabliui vykdyti reikia `proc_open()`; aplinkose, kuriose jis išjungtas, atidarykite saugyklas su `['hooks' => false]`. CLI visada atidaro saugyklą su įjungtais kabliais.

Pitmaster kablių ieško tik `<gitDir>/hooks`. Jis nepaiso `core.hooksPath`, o susietame darbiniame medyje ieško `.git/worktrees/<name>/hooks`, todėl bendrinami `.git/hooks` scenarijai ten nevykdomi ([#12](https://github.com/inline0/pitmaster/issues/12)).

`HookRunner`, aprašytas toliau, šių parinkčių nepaiso: iškvietus jį tiesiogiai, kablys visada vykdomas.

## HookRunner

```php
use Pitmaster\Hooks\HookRunner;

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

## Tikrinimas, ar kablys yra

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

Kablys laikomas esančiu, jei failas yra `.git/hooks/<name>` ir yra vykdomas.

## Kabliuko vykdymas

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

### Su argumentais

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

### Su stdin

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

`pre-push` kabliui informacija apie nuorodas perduodama per stdin.

### Sėkmės tikrinimas

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

`runAndCheck()` yra patogumo funkcija: ji vykdo kablį ir grąžina `true`, jei išėjimo kodas yra 0. Jei kablio nėra, grąžinama `true` (kablio nebuvimas reiškia, kad nėra prieštaravimų).

## Įdiegtų kablių sąrašas

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

Išvardija visus vykdomus failus aplanke `.git/hooks/` (neįtraukia `.sample` failų).

`run()`, jei kablio nėra arba jis nevykdomas, grąžina išėjimo kodą 0 ir tuščią išvestį. Jei proceso nepavyksta paleisti, grąžinamas išėjimo kodas 1, o `Failed to start hook` pateikiamas stderr.

## Kabliuko diegimas

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

Įrašo scenarijaus turinį į `.git/hooks/<name>` ir nustato vykdymo leidimą (`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);
```

## Kabliai, kuriuos vykdo Pitmaster

`HookRunner` gali vykdyti bet kurį kablį pagal pavadinimą. `Repository` automatiškai vykdo šiuos:

| Kablys | Vykdo | Argumentai | Gali nutraukti? |
|------|--------|-----------|-----------|
| `pre-commit` | `commit()` | nėra | Taip (ne nulinis išėjimo kodas) |
| `prepare-commit-msg` | `commit()` | pranešimo failas, tada `message` (arba `merge` užbaigiant sujungimą) | Taip |
| `commit-msg` | `commit()` | pranešimo failas (`.git/COMMIT_EDITMSG`); failo pakeitimai naudojami | Taip (ne nulinis išėjimo kodas) |
| `post-commit` | `commit()` | nėra | Ne (informacinis) |
| `pre-rebase` | `rebase()` | pirminės šakos revizija | Taip |
| `post-checkout` | `checkout()` | senasis HEAD, naujasis HEAD, `1` | Ne (informacinis) |
| `post-merge` | `merge()`, `mergeOctopus()`, `mergeContinue()` | `0` | Ne (informacinis) |
| `pre-push` | `push()` ir jo variantai | nuotolinės saugyklos pavadinimas, URL; nuorodų eilutės per stdin | Taip (ne nulinis išėjimo kodas) |

Neveikiančio kablio vykdymas priverčia operaciją išmesti `RuntimeException('<name> hook failed')`; kablio išvestis neįtraukiama. Commit kabliai vykdomi tik iš `commit()`: sujungimo commit, sukurtas naudojant `merge()`, jų nevykdo. `post-checkout` nevykdomas su `addWorktree()`, `reset()` ar `restore()`.

Serverio pusės kabliai (`pre-receive`, `update`, `post-receive`, `post-update`), `pre-auto-gc` ir `post-rewrite` niekada nevykdomi automatiškai.

## Kablių aplinka

Kai Pitmaster vykdo kablį, perduodama ši aplinka:

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

Tai vieninteliai kabliui perduodami kintamieji: `PATH`, `HOME` ir visi kiti PHP proceso kintamieji neperduodami ([#13](https://github.com/inline0/pitmaster/issues/13)). Kablių scenarijuose naudokite absoliučiuosius kelius arba scenarijaus viduje nustatykite `PATH`; `#!/usr/bin/env php` shebang randa interpretatorius tik numatytajame sistemos kelyje.

Kablys vykdomas `dirname($gitDir)` kataloge, kuris įprastos `.git` saugyklos atveju yra darbinis medis.

## Kablių scenarijų formatas

Kablius galima rašyti bet kuria kalba. Pirmoji eilutė turi būti shebang:

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

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

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

## Pavyzdys: pre-commit su Pitmaster

`commit()` jau vykdo `pre-commit`, `prepare-commit-msg`, `commit-msg` ir `post-commit`, todėl programos kodui tereikia apdoroti nesėkmę:

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

`HookRunner` kvieskite patys tik tada, kai deskriptorius atidarytas su `['hooks' => false]`, arba kai vykdomi kabliai, kurių Pitmaster nevykdo.

## Kablių pavyzdžiai

Git inicijuoja saugyklas su `.sample` kablių failais; `Pitmaster::init()` sukuria tuščią `hooks/` katalogą. Pitmaster `listHooks()` neįtraukia `.sample` failų:

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