
# Hookok

A Git hookok a `.git/hooks/` könyvtárban lévő futtatható parancsfájlok, amelyek a git-műveletek meghatározott pontjain futnak le. A Pitmaster felismeri és meghívja a hookokat a PHP folyamatvezérlésével (`proc_open`).

## A hookok alapértelmezés szerint lefutnak

A repository-műveletek automatikusan futtatják a [Hooks Pitmaster által](#hooks-pitmaster-runs)] felsorolt hookokat. Egy repository-kezelőnél a kikapcsolásukhoz átadáskor adja meg az opciókat:

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

A `Pitmaster::init()` és a `Pitmaster::clone()` ugyanazokat az opciókat fogadja. Hook futtatásához `proc_open()` szükséges; az ezt letiltó gazdagépeken a repository-kat `['hooks' => false]` beállítással nyissa meg. A CLI mindig engedélyezett hookokkal nyitja meg a repository-t.

A Pitmaster kizárólag a `<gitDir>/hooks` helyen keresi a hookokat. A `core.hooksPath` helyet figyelmen kívül hagyja, kapcsolt munkafában pedig a `.git/worktrees/<name>/hooks` helyen keres, ezért a megosztott `.git/hooks` parancsfájlok ott nem futnak le ([#12](https://github.com/inline0/pitmaster/issues/12)).

Az alább ismertetett `HookRunner` figyelmen kívül hagyja ezeket az opciókat: közvetlen meghívásakor mindig lefuttatja a hookot.

## HookRunner

```php
use Pitmaster\Hooks\HookRunner;

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

## Hook meglétének ellenőrzése

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

Akkor létezik hook, ha a fájl megtalálható a `.git/hooks/<name>` helyen, és futtatható.

## Hook futtatása

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

### Argumentumokkal

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

### stdin bemenettel

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

A `pre-push` hook stdin-en kapja meg a refadatokat.

### Siker ellenőrzése

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

A `runAndCheck()` kényelmi függvény lefuttatja a hookot, és `true` értéket ad vissza, ha a kilépési kód 0. Ha a hook nem létezik, `true` értéket ad vissza (hook hiányában nincs kifogás).

## Telepített hookok listázása

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

Felsorolja a `.git/hooks/` összes futtatható fájlját (a `.sample` fájlok kivételével).

Ha a `run()` hiányzó vagy nem futtatható hookot kap, 0 kilépési kóddal és üres kimenettel tér vissza. Ha a folyamat nem indítható el, 1-es kilépési kódot ad, a `Failed to start hook` pedig stderr-re kerül.

## Hook telepítése

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

A parancsfájl tartalmát a `.git/hooks/<name>` helyre írja, és futtatási jogosultságot állít be (`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);
```

## A Pitmaster által futtatott hookok

A `HookRunner` név alapján bármely hookot futtathat. A `Repository` automatikusan ezeket futtatja:

| Hook | Futtatja | Argumentumok | Megszakíthatja? |
|------|--------|-----------|-----------|
| `pre-commit` | `commit()` | nincs | Igen (nem nulla kilépés) |
| `prepare-commit-msg` | `commit()` | üzenetfájl, majd `message` (vagy `merge` egy merge befejezésekor) | Igen |
| `commit-msg` | `commit()` | üzenetfájl (`.git/COMMIT_EDITMSG`); a fájl módosításai érvényesülnek | Igen (nem nulla kilépés) |
| `post-commit` | `commit()` | nincs | Nem (tájékoztató jellegű) |
| `pre-rebase` | `rebase()` | upstream revízió | Igen |
| `post-checkout` | `checkout()` | régi HEAD, új HEAD, `1` | Nem (tájékoztató jellegű) |
| `post-merge` | `merge()`, `mergeOctopus()`, `mergeContinue()` | `0` | Nem (tájékoztató jellegű) |
| `pre-push` | `push()` és változatai | távoli név, URL; ref-sorok stdin-en | Igen (nem nulla kilépés) |

A sikertelen hook miatt a művelet `RuntimeException('<name> hook failed')` hibával megszakad; a hook kimenete nem szerepel benne. A commit-hookok csak a `commit()` műveletből futnak le: a `merge()` által létrehozott merge commitok nem futtatják őket. A `post-checkout` nem fut a `addWorktree()`, `reset()` vagy `restore()` esetén.

A szerveroldali hookok (`pre-receive`, `update`, `post-receive`, `post-update`), valamint a `pre-auto-gc` és a `post-rewrite` soha nem futnak le automatikusan.

## Hook-környezet

Amikor a Pitmaster hookot futtat, ezt a környezetet adja át:

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

A hook csak ezeket a változókat kapja meg: a `PATH`, a `HOME` és a PHP folyamat összes többi változója kimarad ([#13](https://github.com/inline0/pitmaster/issues/13)). A hook-parancsfájlokban használjon abszolút útvonalakat, vagy állítsa be a `PATH` értékét magában a parancsfájlban; a `#!/usr/bin/env php` shebang csak a rendszer alapértelmezett elérési útján találja meg az értelmezőket.

A hook a `dirname($gitDir)` könyvtárban fut, ami normál `.git` könyvtár esetén a munkakönyvtár.

## Hook-parancsfájl formátuma

A hookok bármilyen nyelven megírhatók. Az első sornak shebanget kell tartalmaznia:

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

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

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

## Példa: pre-commit Pitmaster-szel

A `commit()` már futtatja a `pre-commit`, `prepare-commit-msg`, `commit-msg` és `post-commit` elemeket, ezért az alkalmazáskódnak csak a hibát kell kezelnie:

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

Csak `['hooks' => false]` beállítással megnyitott kezelőnél hívja meg saját maga a `HookRunner`-t, vagy olyan hookoknál, amelyeket a Pitmaster nem futtat.

## Hookminták

A Git repository-kat inicializál a `.sample` hookfájlokkal; a `Pitmaster::init()` üres `hooks/` könyvtárat hoz létre. A Pitmaster `listHooks()` fájljai kizárják a `.sample` fájlokat:

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