
# Куки

Кукичките Git са изпълними скриптове в `.git/hooks/`, които се стартират в конкретни моменти от git операциите. Pitmaster открива и извиква кукичките чрез управлението на процеси на PHP (`proc_open`).

## Кукичките се изпълняват по подразбиране

При операциите с хранилище кукичките, изброени в [Кукички, които 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()` приемат едни и същи опции. За изпълнение на кукичка е нужен `proc_open()`; на хостове, които го забраняват, отваряйте хранилищата с `['hooks' => false]`. CLI винаги отваря хранилището с включени кукички.

Pitmaster търси кукички само в `<gitDir>/hooks`. Пренебрегва `core.hooksPath`, а в свързано работно дърво търси в `.git/worktrees/<name>/hooks`, затова споделените скриптове `.git/hooks` не се изпълняват там ([#12](https://github.com/inline0/pitmaster/issues/12)).

`HookRunner`, описан по-долу, пренебрегва тези опции: директното му извикване винаги изпълнява кукичката.

## HookRunner

```php
use Pitmaster\Hooks\HookRunner;

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

## Проверка дали съществува кукичка

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

Кукичка съществува, ако файлът е наличен в `.git/hooks/<name>` и е изпълним.

## Изпълнение на кукичка

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

Кукичката `pre-push` получава информация за ref чрез stdin.

### Проверка за успех

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

`runAndCheck()` е удобен метод, който изпълнява кукичката и връща `true`, ако кодът за изход е 0. Ако кукичката не съществува, връща `true` (липсата на кукичка означава липса на възражение).

## Списък на инсталираните кукички

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

Извежда списък на всички изпълними файлове в `.git/hooks/` (с изключение на файловете `.sample`).

`run()` при липсваща или неизпълнима кукичка връща код за изход 0 и празен изход. Ако процесът не може да се стартира, връща код за изход 1 и `Failed to start hook` на stderr.

## Инсталиране на кукичка

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

## Кукички, които Pitmaster изпълнява

`HookRunner` може да изпълнява всяка кукичка по име. `Repository` изпълнява автоматично следните кукички:

| Кукичка | Изпълнява се от | Аргументи | Може ли да прекъсне? |
|------|--------|-----------|-----------|
| `pre-commit` | `commit()` | няма | Да (ненулев код за изход) |
| `prepare-commit-msg` | `commit()` | файл със съобщение, след това `message` (или `merge` при завършване на сливане) | Да |
| `commit-msg` | `commit()` | файл със съобщение (`.git/COMMIT_EDITMSG`); промените във файла се използват | Да (ненулев код за изход) |
| `post-commit` | `commit()` | няма | Не (само информативна) |
| `pre-rebase` | `rebase()` | ревизията нагоре по веригата | Да |
| `post-checkout` | `checkout()` | старият HEAD, новият HEAD, `1` | Не (само информативна) |
| `post-merge` | `merge()`, `mergeOctopus()`, `mergeContinue()` | `0` | Не (само информативна) |
| `pre-push` | `push()` и вариантите му | име на отдалеченото хранилище, URL; редовете за ref се подават чрез stdin | Да (ненулев код за изход) |

Неуспешна кукичка кара операцията да хвърли `RuntimeException('<name> hook failed')`; изходът на кукичката не се включва. Кукичките за commit се изпълняват само от `commit()`: комитите за сливане, създадени от `merge()`, не ги изпълняват. `post-checkout` не се изпълнява за `addWorktree()`, `reset()` или `restore()`.

Сървърните кукички (`pre-receive`, `update`, `post-receive`, `post-update`), `pre-auto-gc` и `post-rewrite` никога не се изпълняват автоматично.

## Среда на кукичката

Когато Pitmaster изпълнява кукичка, ѝ подава следната среда:

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

Това са единствените променливи, достъпни за кукичката: `PATH`, `HOME` и всички останали променливи на процеса PHP не се предават ([#13](https://github.com/inline0/pitmaster/issues/13)). Използвайте абсолютни пътища в скриптовете на кукичките или задайте `PATH` в самия скрипт; shebang `#!/usr/bin/env php` намира интерпретатори само в системния път по подразбиране.

Кукичката се изпълнява в `dirname($gitDir)`, което е работното дърво на обикновена директория `.git`.

## Формат на скрипта на кукичката

Кукичките могат да бъдат написани на всеки език. Първият ред трябва да е 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]`, или за кукички, които Pitmaster не изпълнява.

## Примерни кукички

Git инициализира хранилищата с файлове за кукички `.sample`; `Pitmaster::init()` създава празна директория `hooks/`. `listHooks()` на Pitmaster изключва файловете `.sample`:

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