
# Cârlige

Hook-urile Git sunt scripturi executabile în `.git/hooks/`, care rulează în anumite puncte ale operațiunilor git. Pitmaster detectează și apelează hook-urile folosind controlul proceselor din PHP (`proc_open`).

## Hook-urile rulează implicit

Operațiunile asupra depozitului rulează automat hook-urile listate la [Hook-urile Pitmaster](#hooks-pitmaster-runs). Pentru a le dezactiva pentru un obiect handle de depozit, transmiteți opțiuni la deschiderea acestuia:

```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()` și `Pitmaster::clone()` acceptă aceleași opțiuni. Pentru a rula un hook este necesar `proc_open()`; pe sistemele gazdă unde acesta este dezactivat, deschideți depozitele cu `['hooks' => false]`. CLI deschide întotdeauna depozitul cu hook-urile activate.

Pitmaster caută hook-uri numai în `<gitDir>/hooks`. Ignoră `core.hooksPath`, iar într-un worktree asociat caută în `.git/worktrees/<name>/hooks`, astfel că scripturile `.git/hooks` partajate nu rulează acolo ([#12](https://github.com/inline0/pitmaster/issues/12)).

`HookRunner`, descris mai jos, ignoră aceste opțiuni: apelarea lui directă rulează întotdeauna hook-ul.

## HookRunner

```php
use Pitmaster\Hooks\HookRunner;

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

## Verificarea existenței unui hook

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

Un hook există dacă fișierul este prezent în `.git/hooks/<name>` și este executabil.

## Rularea unui hook

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

### Cu argumente

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

### Cu stdin

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

Hook-ul `pre-push` primește informații despre referință prin stdin.

### Verificarea reușitei

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

`runAndCheck()` este o metodă convenabilă care rulează hook-ul și returnează `true` dacă codul de ieșire este 0. Dacă hook-ul nu există, returnează `true` (absența unui hook înseamnă că nu există obiecții).

## Listarea hook-urilor instalate

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

Listează toate fișierele executabile din `.git/hooks/` (cu excepția fișierelor `.sample`).

`run()` returnează codul de ieșire 0 cu ieșire goală pentru un hook inexistent sau neexecutabil. Dacă procesul nu poate fi pornit, returnează codul de ieșire 1 și `Failed to start hook` pe stderr.

## Instalarea unui hook

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

Scrie conținutul scriptului în `.git/hooks/<name>` și setează permisiunea de execuție (`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);
```

## Hook-urile rulate de Pitmaster

`HookRunner` poate rula orice hook după nume. `Repository` le rulează automat pe acestea:

| Hook | Rulat de | Argumente | Poate anula? |
|------|---------|-----------|--------------|
| `pre-commit` | `commit()` | niciunul | Da (cod de ieșire diferit de zero) |
| `prepare-commit-msg` | `commit()` | fișierul mesajului, apoi `message` (sau `merge` la finalizarea unei îmbinări) | Da |
| `commit-msg` | `commit()` | fișierul mesajului (`.git/COMMIT_EDITMSG`); modificările fișierului sunt folosite | Da (cod de ieșire diferit de zero) |
| `post-commit` | `commit()` | niciunul | Nu (informativ) |
| `pre-rebase` | `rebase()` | revizia din amonte | Da |
| `post-checkout` | `checkout()` | HEAD vechi, HEAD nou, `1` | Nu (informativ) |
| `post-merge` | `merge()`, `mergeOctopus()`, `mergeContinue()` | `0` | Nu (informativ) |
| `pre-push` | `push()` și variantele sale | numele remote-ului, URL; liniile referințelor prin stdin | Da (cod de ieșire diferit de zero) |

Un hook care eșuează face operațiunea să arunce `RuntimeException('<name> hook failed')`; ieșirea hook-ului nu este inclusă. Hook-urile de commit rulează numai din `commit()`: commit-urile de îmbinare create de `merge()` nu le rulează. `post-checkout` nu rulează pentru `addWorktree()`, `reset()` sau `restore()`.

Hook-urile de pe server (`pre-receive`, `update`, `post-receive`, `post-update`), `pre-auto-gc` și `post-rewrite` nu rulează niciodată automat.

## Mediul hook-urilor

Când Pitmaster rulează un hook, îi transmite acest mediu:

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

Acestea sunt singurele variabile disponibile pentru hook: `PATH`, `HOME` și toate celelalte variabile ale procesului PHP nu sunt transmise ([#13](https://github.com/inline0/pitmaster/issues/13)). Folosiți căi absolute în scripturile hook-urilor sau setați `PATH` în script; un shebang `#!/usr/bin/env php` găsește interpreți doar în calea implicită a sistemului.

Hook-ul rulează în `dirname($gitDir)`, care este arborele de lucru pentru un director `.git` obișnuit.

## Formatul scriptului hook-ului

Hook-urile pot fi scrise în orice limbaj. Prima linie trebuie să fie un shebang:

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

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

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

## Exemplu: pre-commit cu Pitmaster

`commit()` rulează deja `pre-commit`, `prepare-commit-msg`, `commit-msg` și `post-commit`, așa că în codul aplicației trebuie tratat doar eșecul:

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

Apelați `HookRunner` doar pentru un handle deschis cu `['hooks' => false]` sau pentru hook-uri pe care Pitmaster nu le rulează.

## Hook-uri exemplu

Git inițializează depozite cu fișiere hook `.sample`; `Pitmaster::init()` creează un director `hooks/` gol. Pitmaster din `listHooks()` exclude fișierele `.sample`:

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