Git و GitHub

خطافات Git

13 دقيقة الدرس 29 من 35

خطافات Git

خطافات Git هي برامج نصية تعمل تلقائياً قبل أو بعد أحداث Git محددة مثل الالتزامات والدفع والدمج. تسمح لك بأتمتة فحوصات الجودة، وفرض معايير الترميز، ومنع الأخطاء الشائعة. في هذا الدرس، ستتعلم كيفية الاستفادة من الخطافات لتحسين سير عمل التطوير الخاص بك.

ما هي خطافات Git؟

خطافات Git هي برامج نصية مخصصة مخزنة في دليل .git/hooks في مستودعك. تُشغَّل في نقاط محددة في سير عمل Git، مما يسمح لك بأتمتة المهام وفرض السياسات.

مفهوم أساسي: تعمل الخطافات محلياً على جهازك أو على الخادم، مما يتيح الأتمتة على جانب العميل والخادم.

أنواع خطافات Git

خطافات جانب العميل (تعمل على جهاز المطور): pre-commit - يعمل قبل إنشاء الالتزام - استخدم لـ: التدقيق، التنسيق، تنفيذ الاختبار - رمز الخروج 0 = متابعة، غير صفري = إلغاء الالتزام prepare-commit-msg - يعمل قبل فتح محرر رسالة الالتزام - استخدم لـ: توليد قوالب رسائل الالتزام تلقائياً commit-msg - يعمل بعد إدخال رسالة الالتزام - استخدم لـ: التحقق من صحة تنسيق رسالة الالتزام post-commit - يعمل بعد اكتمال الالتزام - استخدم لـ: الإخطارات، تشغيل بناءات CI pre-push - يعمل قبل الدفع إلى البعيد - استخدم لـ: تشغيل مجموعة الاختبار الكاملة، منع الدفع السيء post-merge - يعمل بعد الدمج الناجح - استخدم لـ: تحديثات التبعية، ترحيلات قاعدة البيانات pre-rebase - يعمل قبل إعادة الأساس - استخدم لـ: منع إعادة الأساس على الفروع المحمية
خطافات جانب الخادم (تعمل على خادم Git): pre-receive - يعمل عندما يتلقى الخادم الدفع - استخدم لـ: فرض سياسات المشروع update - يعمل لكل فرع يتم تحديثه - استخدم لـ: سياسات خاصة بالفرع post-receive - يعمل بعد قبول الدفع - استخدم لـ: النشر، الإخطارات، مشغلات CI/CD

إنشاء خطافك الأول

لننشئ خطاف pre-commit بسيط يمنع الالتزامات مع عبارات console.log:

# انتقل إلى دليل الخطافات cd .git/hooks # أنشئ خطاف pre-commit touch pre-commit chmod +x pre-commit # حرر ملف pre-commit #!/bin/bash # تحقق من console.log في ملفات JavaScript المرحلة if git diff --cached --name-only | grep '\.js$' | xargs grep -n 'console\.log'; then echo "Error: Found console.log in staged files" echo "Please remove console.log statements before committing" exit 1 fi echo "Pre-commit checks passed" exit 0
نصيحة احترافية: اجعل الخطافات قابلة للتنفيذ مع chmod +x hook-name. بدون إذن التنفيذ، لن تعمل الخطافات!

خطاف Pre-Commit: فحوصات جودة الكود

خطاف pre-commit شامل لمشاريع PHP:

#!/bin/bash # .git/hooks/pre-commit echo "Running pre-commit checks..." # احصل على قائمة ملفات PHP المرحلة FILES=$(git diff --cached --name-only --diff-filter=ACM | grep '\.php$') if [ -z "$FILES" ]; then echo "No PHP files to check" exit 0 fi # تحقق من أخطاء بناء PHP echo "Checking PHP syntax..." for FILE in $FILES; do php -l "$FILE" if [ $? -ne 0 ]; then echo "❌ Syntax error in $FILE" exit 1 fi done # شغّل PHP CodeSniffer if [ -f ./vendor/bin/phpcs ]; then echo "Running PHP CodeSniffer..." ./vendor/bin/phpcs --standard=PSR12 $FILES if [ $? -ne 0 ]; then echo "❌ Code style violations found" echo "Run: ./vendor/bin/phpcbf to auto-fix" exit 1 fi fi # شغّل PHPStan if [ -f ./vendor/bin/phpstan ]; then echo "Running PHPStan..." ./vendor/bin/phpstan analyse $FILES --level=5 if [ $? -ne 0 ]; then echo "❌ Static analysis errors found" exit 1 fi fi echo "✅ All pre-commit checks passed" exit 0

خطاف Commit-Msg: فرض تنسيق رسالة الالتزام

فرض تنسيق Conventional Commits:

#!/bin/bash # .git/hooks/commit-msg COMMIT_MSG_FILE=$1 COMMIT_MSG=$(cat "$COMMIT_MSG_FILE") # نمط Conventional Commits: type(scope): description # مثال: feat(auth): add login validation PATTERN="^(feat|fix|docs|style|refactor|test|chore)(\(.+\))?: .{10,}$" if ! echo "$COMMIT_MSG" | grep -qE "$PATTERN"; then echo "❌ Invalid commit message format" echo "" echo "Commit message must follow Conventional Commits:" echo " type(scope): description" echo "" echo "Types: feat, fix, docs, style, refactor, test, chore" echo "Example: feat(auth): add two-factor authentication" echo "" echo "Your message: $COMMIT_MSG" exit 1 fi # تحقق من طول الرسالة (على الأقل 10 أحرف في الوصف) if [ ${#COMMIT_MSG} -lt 15 ]; then echo "❌ Commit message too short" echo "Description must be at least 10 characters" exit 1 fi echo "✅ Commit message format valid" exit 0
أنواع Conventional Commits:
  • feat: ميزة جديدة
  • fix: إصلاح خطأ
  • docs: تغييرات التوثيق
  • style: التنسيق، فواصل منقوطة مفقودة، إلخ.
  • refactor: إعادة هيكلة الكود
  • test: إضافة أو تحديث الاختبارات
  • chore: مهام الصيانة

خطاف Pre-Push: تشغيل مجموعة الاختبار الكاملة

تأكد من نجاح جميع الاختبارات قبل الدفع:

#!/bin/bash # .git/hooks/pre-push echo "Running pre-push checks..." # شغّل مجموعة الاختبار الكاملة echo "Running tests..." php artisan test if [ $? -ne 0 ]; then echo "❌ Tests failed! Push aborted" echo "Fix failing tests before pushing" exit 1 fi # تحقق من TODO أو FIXME في الملفات المرحلة echo "Checking for TODO/FIXME comments..." if git diff origin/main...HEAD --name-only | xargs grep -n "TODO\|FIXME"; then echo "⚠️ Warning: Found TODO or FIXME comments" read -p "Continue push anyway? (y/n) " -n 1 -r echo if [[ ! $REPLY =~ ^[Yy]$ ]]; then echo "Push aborted" exit 1 fi fi echo "✅ All pre-push checks passed" exit 0

خطاف Post-Merge: إدارة التبعية

حدّث التبعيات تلقائياً بعد الدمج:

#!/bin/bash # .git/hooks/post-merge echo "Post-merge hook running..." # تحقق مما إذا تغير composer.lock if git diff-tree -r --name-only --no-commit-id ORIG_HEAD HEAD | grep --quiet "composer.lock"; then echo "composer.lock changed - running composer install" composer install fi # تحقق مما إذا تغير package-lock.json if git diff-tree -r --name-only --no-commit-id ORIG_HEAD HEAD | grep --quiet "package-lock.json"; then echo "package-lock.json changed - running npm install" npm install fi # تحقق من الترحيلات الجديدة if git diff-tree -r --name-only --no-commit-id ORIG_HEAD HEAD | grep --quiet "database/migrations"; then echo "New migrations detected" echo "Run: php artisan migrate" fi exit 0
مهم: الخطافات في .git/hooks لا يتتبعها Git. تحتاج إلى استراتيجية لمشاركة الخطافات مع فريقك (انظر أدناه).

مشاركة الخطافات مع فريقك

نظراً لأن .git/hooks غير متتبع، استخدم هذه الاستراتيجيات:

الطريقة 1: برنامج نصي للتثبيت اليدوي # أنشئ دليل hooks في مستودعك mkdir -p .githooks # خزّن خطافاتك هناك .githooks/pre-commit .githooks/commit-msg # أنشئ برنامج نصي للتثبيت # install-hooks.sh #!/bin/bash cp .githooks/* .git/hooks/ chmod +x .git/hooks/* echo "Hooks installed successfully" # يشغّل أعضاء الفريق: ./install-hooks.sh الطريقة 2: تكوين Git (Git 2.9+) # عيّن دليل الخطافات في تكوين git git config core.hooksPath .githooks # الآن سيستخدم Git .githooks بدلاً من .git/hooks # التزم بـ .githooks في المستودع # جميع أعضاء الفريق يستخدمون الخطافات المشتركة تلقائياً الطريقة 3: استخدام Husky (مشاريع Node.js) # ثبّت Husky npm install --save-dev husky # هيّئ Husky npx husky init # أضف خطافات عبر Husky npx husky add .husky/pre-commit "npm test" # خطافات Husky متتبعة في دليل .husky/

استخدام Husky لإدارة الخطافات

Husky هي أداة إدارة الخطافات الأكثر شعبية لمشاريع Node.js:

التثبيت: # ثبّت Husky npm install --save-dev husky npx husky init أضف خطاف Pre-Commit: npx husky add .husky/pre-commit "npm run lint" npx husky add .husky/pre-commit "npm test" أضف خطاف Commit-Msg: npx husky add .husky/commit-msg "npx commitlint --edit $1" تكوين package.json: { "scripts": { "prepare": "husky install", "lint": "eslint .", "test": "jest" }, "devDependencies": { "husky": "^8.0.0", "@commitlint/cli": "^17.0.0", "@commitlint/config-conventional": "^17.0.0" } } .husky/pre-commit: #!/usr/bin/env sh . "$(dirname -- "$0")/_/husky.sh" npm run lint npm test
أفضل ممارسة: لمشاريع Laravel/PHP، فكر في استخدام Captain Hook أو GrumPHP كبدائل لـ Husky.

إدارة خطافات PHP: GrumPHP

GrumPHP هو مدير خطافات قوي لمشاريع PHP:

التثبيت: composer require --dev phpro/grumphp التكوين (grumphp.yml): grumphp: tasks: phplint: triggered_by: [php] phpcs: standard: PSR12 triggered_by: [php] phpstan: level: 5 triggered_by: [php] phpunit: always_execute: false git_hook_variables: EXEC_GRUMPHP_COMMAND: 'php' الاستخدام: # يثبّت GrumPHP الخطافات تلقائياً # يشغّل المهام المكونة على git commit # شغّل يدوياً vendor/bin/grumphp run # تخطى GrumPHP (حالة طوارئ فقط!) git commit --no-verify -m "Emergency fix"

تجاوز الخطافات (عند الضرورة)

أحياناً تحتاج إلى تجاوز الخطافات (استخدم بحذر!):

# تخطى خطافات pre-commit و commit-msg git commit --no-verify -m "WIP: Emergency hotfix" # أو git commit -n -m "WIP: Emergency hotfix" # تخطى خطاف pre-push git push --no-verify متى تتجاوز: ✓ إصلاح عاجل للإنتاج في حالة طوارئ ✓ التزامات العمل الجاري في فرع الميزة ✓ الخطاف معطل ويحتاج إلى إصلاح متى لا تتجاوز: ✗ كسول جداً لإصلاح أخطاء التدقيق ✗ الاختبارات فاشلة ولكن "يعمل على جهازي" ✗ رسالة الالتزام لا تتبع التنسيق (فقط أصلحها!)
تحذير: يجب أن يكون تجاوز الخطافات نادراً وموثقاً. إذا وجدت نفسك تستخدم --no-verify غالباً، فقد تكون خطافاتك صارمة جداً أو بطيئة.

تحسين أداء الخطافات

اجعل الخطافات سريعة: 1. تحقق فقط من الملفات المرحلة # جيد: تحقق فقط من الملفات التي يتم الالتزام بها git diff --cached --name-only --diff-filter=ACM # سيء: تحقق من قاعدة الكود بأكملها في كل مرة find . -name "*.php" 2. التنفيذ الموازي # شغّل التدقيق والاختبارات بشكل متوازٍ phpcs $FILES & phpstan analyse $FILES & wait 3. نتائج التخزين المؤقت # ذاكرة التخزين المؤقت لنتائج PHPStan phpstan analyse --cache-dir=.phpstan-cache 4. تخطى المهام البطيئة في Pre-Commit # Pre-commit: فحوصات سريعة فقط (تدقيق، بناء) # Pre-push: فحوصات شاملة (مجموعة الاختبار الكاملة) 5. خروج مبكر عند الفشل # توقف عند أول فشل command1 || exit 1 command2 || exit 1

أمثلة خطافات من العالم الحقيقي

منع الالتزام بالأسرار: #!/bin/bash # تحقق من أنماط الأسرار الشائعة if git diff --cached | grep -E '(API_KEY|SECRET|PASSWORD|TOKEN).*=.*[^\'\\"]\w{20,}'; then echo "❌ Potential secret detected in commit" echo "Never commit API keys or passwords" exit 1 fi تنسيق الكود تلقائياً: #!/bin/bash # شغّل تلقائياً PHP CS Fixer على الملفات المرحلة FILES=$(git diff --cached --name-only --diff-filter=ACM | grep '\.php$') if [ ! -z "$FILES" ]; then ./vendor/bin/php-cs-fixer fix $FILES git add $FILES fi التحقق من اسم الفرع: #!/bin/bash # فرض تسمية الفرع: feature/*, bugfix/*, hotfix/* BRANCH=$(git rev-parse --abbrev-ref HEAD) if ! echo "$BRANCH" | grep -qE '^(feature|bugfix|hotfix)\/[a-z0-9-]+$'; then echo "❌ Invalid branch name: $BRANCH" echo "Use: feature/name, bugfix/name, or hotfix/name" exit 1 fi رقم التذكرة في الالتزام: #!/bin/bash # تأكد من أن رسالة الالتزام تتضمن رقم التذكرة if ! grep -qE '#[0-9]+|JIRA-[0-9]+' "$1"; then echo "❌ Commit must reference a ticket (e.g., #123 or JIRA-456)" exit 1 fi

تمرين عملي:

أنشئ خطاف Pre-Commit

المتطلبات:

  1. تحقق من أخطاء بناء PHP في الملفات المرحلة
  2. امنع الالتزام بملفات تحتوي على dd() أو dump() (وظائف تصحيح Laravel)
  3. تأكد من أن جميع ملفات PHP لديها علامات فتح مناسبة

الحل:

#!/bin/bash # .git/hooks/pre-commit echo "🔍 Running pre-commit checks..." FILES=$(git diff --cached --name-only --diff-filter=ACM | grep '\.php$') if [ -z "$FILES" ]; then exit 0 fi # تحقق من بناء PHP for FILE in $FILES; do php -l "$FILE" > /dev/null 2>&1 if [ $? -ne 0 ]; then echo "❌ Syntax error in $FILE" php -l "$FILE" exit 1 fi done # تحقق من وظائف التصحيح if echo "$FILES" | xargs grep -n "\(dd\|dump\)("; then echo "❌ Found dd() or dump() in staged files" echo "Remove debug statements before committing" exit 1 fi # تحقق من علامات فتح PHP المناسبة for FILE in $FILES; do if ! head -n 1 "$FILE" | grep -q "<?php"; then echo "❌ Missing PHP opening tag in $FILE" exit 1 fi done echo "✅ All checks passed" exit 0

الملخص

في هذا الدرس، تعلمت:

  • خطافات Git هي برامج نصية تؤتمت المهام في أحداث Git محددة
  • خطافات جانب العميل (pre-commit, commit-msg, pre-push) تعمل محلياً
  • خطافات جانب الخادم (pre-receive, post-receive) تعمل على خادم Git
  • الخطافات تفرض جودة الكود، تنسيق رسالة الالتزام، وتنفيذ الاختبار
  • شارك الخطافات عبر دليل .githooks/ أو أدوات مثل Husky/GrumPHP
  • حسّن أداء الخطاف من خلال التحقق فقط من الملفات المرحلة
  • استخدم --no-verify بحذر لتجاوز الخطافات في حالات الطوارئ
التالي: في الدرس التالي، سنستكشف أوامر Git المتقدمة مثل cherry-pick و reflog و bisect!