
# نقاط التوسعة

خطافات 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` معلومات المرجع عبر 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؛ أسطر المراجع عبر stdin | نعم (خروج غير صفري) |

يجعل الخطاف الفاشل العملية تطرح `RuntimeException('<name> hook failed')`؛ ولا يُضمّن خرج الخطاف. تعمل خطافات الإيداع فقط من `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` داخل النص؛ فالسطر الأول `#!/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/` فارغًا. ويستثني Pitmaster في `listHooks()` ملفات `.sample`:

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