نقاط التوسعة
خطافات Git نصوص قابلة للتنفيذ في .git/hooks/ تعمل في نقاط محددة من عمليات git. يكتشف Pitmaster الخطافات ويشغّلها باستخدام التحكم بالعمليات في PHP (proc_open).
تعمل الخطافات افتراضيًا
تشغّل عمليات المستودع الخطافات المدرجة ضمن الخطافات التي يشغّلها Pitmaster تلقائيًا. لتعطيلها لمقبض مستودع، مرّر الخيارات عند فتحه:
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).
يتجاهل HookRunner، الموضح أدناه، هذه الخيارات: استدعاؤه مباشرة يشغّل الخطاف دائمًا.
HookRunner
use Pitmaster\Hooks\HookRunner;
$hooks = new HookRunner($repo->gitDir());
التحقق من وجود خطاف
if ($hooks->exists('pre-commit')) {
echo "pre-commit hook is installed\n";
}
يوجد الخطاف إذا كان الملف حاضرًا في .git/hooks/<name> وقابلًا للتنفيذ.
تشغيل خطاف
$result = $hooks->run('pre-commit');
echo $result['exitCode']; // 0 = success
echo $result['stdout']; // Hook's standard output
echo $result['stderr']; // Hook's standard error
مع وسائط
$result = $hooks->run('commit-msg', ['/path/to/COMMIT_EDITMSG']);
عبر stdin
$result = $hooks->run('pre-push', ['origin', 'https://github.com/user/repo.git'], $stdinData);
يتلقى خطاف pre-push معلومات المرجع عبر stdin.
التحقق من النجاح
if ($hooks->runAndCheck('pre-commit')) {
// Hook passed (exit code 0), proceed with commit
} else {
// Hook rejected the operation
}
runAndCheck() دالة مساعدة تشغّل الخطاف وتعيد true إذا كان رمز الخروج 0. وإذا لم يوجد الخطاف، تعيد true (غياب الخطاف يعني عدم وجود اعتراض).
سرد الخطافات المثبتة
$installed = $hooks->listHooks();
// ['pre-commit', 'commit-msg', 'post-checkout']
يسرد جميع الملفات القابلة للتنفيذ في .git/hooks/ (باستثناء ملفات .sample).
يعيد run()، عند غياب الخطاف أو عدم قابليته للتنفيذ، رمز خروج 0 ومخرجات فارغة. وإذا تعذر بدء العملية، يعيد رمز خروج 1 ويكتب Failed to start hook إلى stderr.
تثبيت خطاف
$hooks->install('pre-commit', "#!/bin/bash\nphp vendor/bin/phpcs --standard=PSR12 .\n");
يكتب محتوى النص البرمجي إلى .git/hooks/<name> ويضبط صلاحية التنفيذ (chmod 0755).
// 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 خطافًا، يمرّر إليه البيئة التالية:
$env = [
'GIT_DIR' => $this->gitDir,
'GIT_WORK_TREE' => dirname($this->gitDir),
];
هذه هي المتغيرات الوحيدة التي يحصل عليها الخطاف: لا يُمرّر PATH وHOME ولا أي متغير آخر من عملية PHP (#13). استخدم مسارات مطلقة في نصوص الخطافات، أو اضبط PATH داخل النص؛ فالسطر الأول #!/usr/bin/env php لا يعثر على المفسرات إلا في مسار النظام الافتراضي.
يعمل الخطاف في dirname($gitDir)، وهي شجرة العمل لمجلد .git عادي.
تنسيق نص الخطاف
يمكن كتابة الخطافات بأي لغة. يجب أن يكون السطر الأول سطر shebang:
#!/bin/bash
# Bash hook
#!/usr/bin/env php
<?php
// PHP hook
#!/usr/bin/env python3
# Python hook
مثال: pre-commit مع Pitmaster
يشغّل commit() مسبقًا pre-commit وprepare-commit-msg وcommit-msg وpost-commit، لذا لا يحتاج كود التطبيق إلا إلى معالجة الإخفاق:
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)