
# Hooks

Git-hooks er eksekverbare scripts i `.git/hooks/`, der køres på bestemte tidspunkter under git-operationer. Pitmaster registrerer og kalder hooks ved hjælp af PHPs processtyring (`proc_open`).

## Hooks køres som standard

Ved repository-operationer køres de hooks, der er angivet under [Hooks, som Pitmaster kører](#hooks-pitmaster-runs) automatisk. Slå dem fra for et repository-håndtag ved at angive indstillinger, når det åbnes:

```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()` og `Pitmaster::clone()` accepterer de samme indstillinger. Det kræver `proc_open()` at køre en hook; på værter, hvor det er deaktiveret, skal repositories åbnes med `['hooks' => false]`. CLI åbner altid repositoriet med hooks aktiveret.

Pitmaster leder kun efter hooks i `<gitDir>/hooks`. Den ignorerer `core.hooksPath`, og i et tilknyttet worktree leder den i `.git/worktrees/<name>/hooks`, så de delte `.git/hooks`-scripts ikke køres dér ([#12](https://github.com/inline0/pitmaster/issues/12)).

`HookRunner`, som beskrives nedenfor, ignorerer disse indstillinger: et direkte kald kører altid hooken.

## HookRunner

```php
use Pitmaster\Hooks\HookRunner;

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

## Kontrollér, om en hook findes

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

En hook findes, hvis filen ligger på `.git/hooks/<name>` og er eksekverbar.

## Kør en 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
```

### Med argumenter

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

### Med stdin

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

Hooken `pre-push` modtager ref-oplysninger via stdin.

### Kontrollér, om den lykkedes

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

`runAndCheck()` er en genvej, der kører hooken og returnerer `true`, hvis exitkoden er 0. Hvis hooken ikke findes, returnerer den `true` (ingen hook betyder ingen indsigelse).

## Vis installerede hooks

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

Viser alle eksekverbare filer i `.git/hooks/` (udelader `.sample`-filer).

`run()` returnerer exitkode 0 uden output, hvis hooken mangler eller ikke er eksekverbar. Hvis processen ikke kan startes, returneres exitkode 1 med `Failed to start hook` på stderr.

## Installér en hook

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

Skriver scriptindholdet til `.git/hooks/<name>` og angiver eksekverbar tilladelse (`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);
```

## Hooks, som Pitmaster kører

`HookRunner` kan køre enhver hook efter navn. `Repository` kører automatisk disse:

| Hook | Køres af | Argumenter | Kan afbryde? |
|------|----------|-----------|-------------|
| `pre-commit` | `commit()` | ingen | Ja (exitkode forskellig fra nul) |
| `prepare-commit-msg` | `commit()` | meddelelsesfil, derefter `message` (eller `merge` ved afslutning af en merge) | Ja |
| `commit-msg` | `commit()` | meddelelsesfil (`.git/COMMIT_EDITMSG`); ændringer i filen anvendes | Ja (exitkode forskellig fra nul) |
| `post-commit` | `commit()` | ingen | Nej (kun til orientering) |
| `pre-rebase` | `rebase()` | upstream-revisionen | Ja |
| `post-checkout` | `checkout()` | gammel HEAD, ny HEAD, `1` | Nej (kun til orientering) |
| `post-merge` | `merge()`, `mergeOctopus()`, `mergeContinue()` | `0` | Nej (kun til orientering) |
| `pre-push` | `push()` og dens varianter | fjernnavn, URL; ref-linjer via stdin | Ja (exitkode forskellig fra nul) |

En fejlet hook får operationen til at kaste `RuntimeException('<name> hook failed')`; hookens output medtages ikke. Commit-hooks køres kun fra `commit()`: merge-commits oprettet af `merge()` kører dem ikke. `post-checkout` køres ikke for `addWorktree()`, `reset()` eller `restore()`.

Server-side-hooks (`pre-receive`, `update`, `post-receive`, `post-update`), `pre-auto-gc` og `post-rewrite` køres aldrig automatisk.

## Hook-miljø

Når Pitmaster kører en hook, videregiver den dette miljø:

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

Dette er de eneste variabler, hooken får: `PATH`, `HOME` og alle andre variabler fra PHP-processen videregives ikke ([#13](https://github.com/inline0/pitmaster/issues/13)). Brug absolutte stier i hook-scripts, eller angiv `PATH` i scriptet; en `#!/usr/bin/env php`-shebang finder kun fortolkere i systemets standardsti.

Hooken kører i `dirname($gitDir)`, som er working tree for en normal `.git`-mappe.

## Hook-scriptformat

Hooks kan skrives på ethvert sprog. Første linje skal være en shebang:

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

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

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

## Eksempel: pre-commit med Pitmaster

`commit()` kører allerede `pre-commit`, `prepare-commit-msg`, `commit-msg` og `post-commit`, så applikationskoden behøver kun at håndtere fejlen:

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

Kald kun selv `HookRunner` på et håndtag, der er åbnet med `['hooks' => false]`, eller for hooks, som Pitmaster ikke kører.

## Eksempel-hooks

Git initialiserer repositories med `.sample`-hookfiler; `Pitmaster::init()` opretter en tom `hooks/`-mappe. Pitmasters `listHooks()` udelader `.sample`-filer:

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