
# Άγκιστρα

Τα hooks Git είναι εκτελέσιμα σκριπτ στο `.git/hooks/` που εκτελούνται σε συγκεκριμένα σημεία των λειτουργιών του git. Το Pitmaster εντοπίζει και καλεί hooks μέσω του ελέγχου διεργασιών του PHP (`proc_open`).

## Τα hooks εκτελούνται από προεπιλογή

Οι λειτουργίες αποθετηρίου εκτελούν αυτόματα τα hooks που παρατίθενται στην ενότητα [Hooks που εκτελεί το Pitmaster](#hooks-pitmaster-runs). Για να τα απενεργοποιήσετε για έναν χειριστή αποθετηρίου, περάστε επιλογές κατά το άνοιγμά του:

```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()` και `Pitmaster::clone()` δέχονται τις ίδιες επιλογές. Για να εκτελεστεί ένα hook απαιτείται το `proc_open()`· σε συστήματα όπου είναι απενεργοποιημένο, ανοίξτε τα αποθετήρια με `['hooks' => false]`. Το CLI ανοίγει πάντα το αποθετήριο με ενεργοποιημένα τα hooks.

Το Pitmaster αναζητά hooks μόνο στο `<gitDir>/hooks`. Αγνοεί το `core.hooksPath` και, σε συνδεδεμένο worktree, αναζητά στο `.git/worktrees/<name>/hooks`, επομένως τα κοινόχρηστα σκριπτ `.git/hooks` δεν εκτελούνται εκεί ([#12](https://github.com/inline0/pitmaster/issues/12)).

Το `HookRunner`, που περιγράφεται παρακάτω, αγνοεί αυτές τις επιλογές: η απευθείας κλήση του εκτελεί πάντα το hook.

## HookRunner

```php
use Pitmaster\Hooks\HookRunner;

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

## Έλεγχος ύπαρξης hook

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

Ένα hook υπάρχει αν το αρχείο βρίσκεται στο `.git/hooks/<name>` και είναι εκτελέσιμο.

## Εκτέλεση 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
```

### Με ορίσματα

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

### Με stdin

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

Το hook `pre-push` λαμβάνει πληροφορίες αναφοράς μέσω stdin.

### Έλεγχος επιτυχίας

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

Το `runAndCheck()` είναι μια βοηθητική συνάρτηση που εκτελεί το hook και επιστρέφει `true` αν ο κωδικός εξόδου είναι 0. Αν το hook δεν υπάρχει, επιστρέφει `true` (η απουσία hook σημαίνει ότι δεν υπάρχει αντίρρηση).

## Λίστα εγκατεστημένων hooks

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

Παραθέτει όλα τα εκτελέσιμα αρχεία στο `.git/hooks/` (εξαιρούνται τα αρχεία `.sample`).

Το `run()` επιστρέφει κωδικό εξόδου 0 με κενή έξοδο αν το hook λείπει ή δεν είναι εκτελέσιμο. Αν δεν είναι δυνατή η εκκίνηση της διεργασίας, επιστρέφει κωδικό εξόδου 1 και γράφει το `Failed to start hook` στο stderr.

## Εγκατάσταση hook

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

Γράφει το περιεχόμενο του σκριπτ στο `.git/hooks/<name>` και ορίζει δικαίωμα εκτέλεσης (`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 που εκτελεί το Pitmaster

Το `HookRunner` μπορεί να εκτελέσει οποιοδήποτε hook βάσει ονόματος. Το `Repository` εκτελεί αυτόματα τα εξής:

| Hook | Εκτελείται από | Ορίσματα | Μπορεί να ακυρώσει; |
|------|--------|-----------|-----------|
| `pre-commit` | `commit()` | κανένα | Ναι (μη μηδενικός κωδικός εξόδου) |
| `prepare-commit-msg` | `commit()` | αρχείο μηνύματος και μετά `message` (ή `merge` κατά την ολοκλήρωση συγχώνευσης) | Ναι |
| `commit-msg` | `commit()` | αρχείο μηνύματος (`.git/COMMIT_EDITMSG`)· χρησιμοποιούνται οι αλλαγές στο αρχείο | Ναι (μη μηδενικός κωδικός εξόδου) |
| `post-commit` | `commit()` | κανένα | Όχι (ενημερωτικό) |
| `pre-rebase` | `rebase()` | η αναθεώρηση upstream | Ναι |
| `post-checkout` | `checkout()` | παλιό HEAD, νέο HEAD, `1` | Όχι (ενημερωτικό) |
| `post-merge` | `merge()`, `mergeOctopus()`, `mergeContinue()` | `0` | Όχι (ενημερωτικό) |
| `pre-push` | `push()` και οι παραλλαγές του | όνομα remote, URL· γραμμές ref στο stdin | Ναι (μη μηδενικός κωδικός εξόδου) |

Ένα hook που αποτυγχάνει κάνει τη λειτουργία να προκαλέσει `RuntimeException('<name> hook failed')`· η έξοδος του hook δεν περιλαμβάνεται. Τα hooks commit εκτελούνται μόνο από το `commit()`: οι συγχωνεύσεις commit που δημιουργούνται από το `merge()` δεν τα εκτελούν. Το `post-checkout` δεν εκτελείται για `addWorktree()`, `reset()` ή `restore()`.

Τα hooks στην πλευρά του διακομιστή (`pre-receive`, `update`, `post-receive`, `post-update`), τα `pre-auto-gc` και `post-rewrite` δεν εκτελούνται ποτέ αυτόματα.

## Περιβάλλον hook

Όταν το Pitmaster εκτελεί ένα hook, του περνά το εξής περιβάλλον:

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

Αυτές είναι οι μόνες μεταβλητές που λαμβάνει το hook: τα `PATH`, `HOME` και κάθε άλλη μεταβλητή της διεργασίας PHP δεν μεταβιβάζονται ([#13](https://github.com/inline0/pitmaster/issues/13)). Χρησιμοποιήστε απόλυτες διαδρομές στα σκριπτ hook ή ορίστε το `PATH` μέσα στο σκριπτ· ένα shebang `#!/usr/bin/env php` εντοπίζει διερμηνευτές μόνο στην προεπιλεγμένη διαδρομή του συστήματος.

Το hook εκτελείται στο `dirname($gitDir)`, τον κατάλογο εργασίας για έναν κανονικό κατάλογο `.git`.

## Μορφή σκριπτ hook

Τα hooks μπορούν να γραφτούν σε οποιαδήποτε γλώσσα. Η πρώτη γραμμή πρέπει να είναι shebang:

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

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

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

## Παράδειγμα: pre-commit με Pitmaster

Το `commit()` εκτελεί ήδη τα `pre-commit`, `prepare-commit-msg`, `commit-msg` και `post-commit`, επομένως ο κώδικας της εφαρμογής χρειάζεται μόνο να χειριστεί την αποτυχία:

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

Καλέστε μόνοι σας το `HookRunner` σε χειριστή που άνοιξε με `['hooks' => false]` ή για hooks που δεν εκτελεί το Pitmaster.

## Ενδεικτικά hooks

Το Git αρχικοποιεί αποθετήρια με αρχεία hook `.sample`· το `Pitmaster::init()` δημιουργεί έναν κενό κατάλογο `hooks/`. Το Pitmaster του `listHooks()` εξαιρεί αρχεία `.sample`:

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