
# Hooks

Git-krokar är körbara skript i `.git/hooks/` som körs vid specifika tidpunkter under git-åtgärder. Pitmaster identifierar och anropar krokar med hjälp av PHPs processkontroll (`proc_open`).

## Krokar körs som standard

Repositoryåtgärder kör automatiskt de hooks som anges under [Hooks Pitmaster körs](#hooks-pitmaster-runs). Om du vill inaktivera dem för ett repository-handtag skickar du med alternativ när du öppnar det:

```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()` och `Pitmaster::clone()` accepterar samma alternativ. För att köra en krok krävs `proc_open()`; på värdar där det är inaktiverat öppnar du arkiv med `['hooks' => false]`. CLI öppnar alltid arkivet med krokar aktiverade.

Pitmaster letar endast efter krokar i `<gitDir>/hooks`. Den ignorerar `core.hooksPath`, och i ett länkat arbetsträd letar den i `.git/worktrees/<name>/hooks`, så de delade skripten i `.git/hooks` körs inte där ([#12](https://github.com/inline0/pitmaster/issues/12)).

`HookRunner`, som beskrivs nedan, ignorerar dessa alternativ: om den anropas direkt körs kroken alltid.

## HookRunner

```php
use Pitmaster\Hooks\HookRunner;

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

## Kontrollera om en krok finns

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

En krok finns om filen finns på `.git/hooks/<name>` och är körbar.

## Kör en krok

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

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

Kroken `pre-push` tar emot ref-information via stdin.

### Kontrollera om körningen lyckades

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

`runAndCheck()` är en praktisk metod som kör kroken och returnerar `true` om avslutningskoden är 0. Om kroken inte finns returnerar den `true` (ingen krok innebär ingen invändning).

## Lista installerade krokar

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

Listar alla körbara filer i `.git/hooks/` (exkluderar filer av typen `.sample`).

`run()` returnerar avslutningskod 0 med tom utdata om kroken saknas eller inte är körbar. Om processen inte kan startas returnerar den avslutningskod 1 och skriver `Failed to start hook` till stderr.

## Installera en krok

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

Skriver skriptinnehållet till `.git/hooks/<name>` och anger körbehörighet (`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);
```

## Krokar som Pitmaster kör

`HookRunner` kan köra valfri krok med namn. `Repository` kör automatiskt följande:

| Krok | Körs av | Argument | Kan avbryta? |
|------|--------|-----------|-----------|
| `pre-commit` | `commit()` | inga | Ja (avslutningskod som inte är noll) |
| `prepare-commit-msg` | `commit()` | meddelandefil, sedan `message` (eller `merge` när en sammanslagning slutförs) | Ja |
| `commit-msg` | `commit()` | meddelandefil (`.git/COMMIT_EDITMSG`); ändringar i filen används | Ja (avslutningskod som inte är noll) |
| `post-commit` | `commit()` | inga | Nej (endast information) |
| `pre-rebase` | `rebase()` | uppströmsrevisionen | Ja |
| `post-checkout` | `checkout()` | gammalt HEAD, nytt HEAD, `1` | Nej (endast information) |
| `post-merge` | `merge()`, `mergeOctopus()`, `mergeContinue()` | `0` | Nej (endast information) |
| `pre-push` | `push()` och dess varianter | fjärrnamn, URL; ref-rader via stdin | Ja (avslutningskod som inte är noll) |

Om en krok misslyckas kastar operationen `RuntimeException('<name> hook failed')`; krokens utdata inkluderas inte. Commit-krokar körs endast från `commit()`: merge-commits som skapas av `merge()` kör dem inte. `post-checkout` körs inte för `addWorktree()`, `reset()` eller `restore()`.

Krokar på serversidan (`pre-receive`, `update`, `post-receive`, `post-update`), `pre-auto-gc` och `post-rewrite` körs aldrig automatiskt.

## Krokens miljö

När Pitmaster kör en krok skickar den med följande miljö:

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

Detta är de enda variabler som kroken får: `PATH`, `HOME` och alla andra variabler i processen PHP skickas inte vidare ([#13](https://github.com/inline0/pitmaster/issues/13)). Använd absoluta sökvägar i krokskript eller ange `PATH` i skriptet; en shebang med `#!/usr/bin/env php` hittar bara tolkar i systemets standardsökväg.

Kroken körs i `dirname($gitDir)`, vilket är arbetskatalogen för en vanlig `.git`-katalog.

## Format för krokskript

Krokar kan skrivas i valfritt språk. Den första raden måste vara en shebang:

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

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

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

## Exempel: pre-commit med Pitmaster

`commit()` kör redan `pre-commit`, `prepare-commit-msg`, `commit-msg` och `post-commit`, så programkoden behöver bara hantera felet:

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

Anropa `HookRunner` själv endast med ett handtag som öppnats med `['hooks' => false]` eller för krokar som Pitmaster inte kör.

## Exempel på krokar

Git initierar arkiv med `.sample`-krokfiler; `Pitmaster::init()` skapar en tom `hooks/`-katalog. Pitmasters `listHooks()` utesluter `.sample`-filer:

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