انتقل إلى المحتوى

نقاط التوسعة

خطافات Git نصوص قابلة للتنفيذ في .git/hooks/ تعمل في نقاط محددة من عمليات git. يكتشف Pitmaster الخطافات ويشغّلها باستخدام التحكم بالعمليات في PHP (proc_open).

تعمل الخطافات افتراضيًا

تشغّل عمليات المستودع الخطافات المدرجة ضمن الخطافات التي يشغّلها Pitmaster تلقائيًا. لتعطيلها لمقبض مستودع، مرّر الخيارات عند فتحه:

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).

يتجاهل 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). استخدم مسارات مطلقة في نصوص الخطافات، أو اضبط PATH داخل النص؛ فالسطر الأول #!/usr/bin/env php لا يعثر على المفسرات إلا في مسار النظام الافتراضي.

يعمل الخطاف في dirname($gitDir)، وهي شجرة العمل لمجلد .git عادي.

تنسيق نص الخطاف

يمكن كتابة الخطافات بأي لغة. يجب أن يكون السطر الأول سطر shebang:

سطر الأوامر
#!/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)