خطافات 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
المتطلبات:
- تحقق من أخطاء بناء PHP في الملفات المرحلة
- امنع الالتزام بملفات تحتوي على
dd() أو dump() (وظائف تصحيح Laravel)
- تأكد من أن جميع ملفات 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!