تخطَّ إلى المحتوى

🔀 شرح Git

خطافات Git وأتمتة سير العمل (Git Hooks)

الدرس 20 من 28· ⏱ 5 دقائق قراءة· 🗓 آخر تحديث: ١٨ أكتوبر ٢٠٢٦

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

الخطاف (Hook) سكربت يشغّله Git تلقائيًّا عند حدث معيّن: قبل الـ commit، بعد كتابة رسالته، قبل الرفع، بعد الدمج. الفكرة: الأشياء التي "يجب أن تتذكّر فعلها" (تشغيل المدقّق، تنسيق الرسالة، عدم الرفع لـ main) تحدث وحدها، فلا تعتمد على ذاكرة أحد.

الخطافات ملفات في .git/hooks/ تحمل اسم الحدث بالضبط، بلا امتداد:

الخطافمتىفشله (exit غير 0)
pre-commitقبل إنشاء الـ commitيلغي الـ commit
commit-msgبعد كتابة الرسالة، ويستلم مسار ملفهايلغي الـ commit
post-commitبعد الـ commitلا يؤثّر: للإشعارات فقط
pre-pushقبل الرفعيلغي الرفع
post-mergeبعد دمج ناجح (ومنه git pull)لا يؤثّر
pre-receiveعلى الخادم قبل قبول رفعيرفض الرفع

القاعدة الأهم: الخطاف الذي ينتهي بـ exit 1 يوقف العملية. هذا ما يجعل pre-* أداة منع لا تذكير فقط.

حين تُنشئ مستودعًا، تجد في .git/hooks/ أمثلة بامتداد .sample لا تعمل. لتفعيل أحدها، أزل الامتداد.

pre-commit: افحص قبل الحفظ

#!/bin/sh
# .git/hooks/pre-commit

branch=$(git rev-parse --abbrev-ref HEAD)
if [ "$branch" = "main" ]; then
  echo "⛔ لا commit مباشر على main. أنشئ فرعًا: git switch -c feature/..."
  exit 1
fi

# ملفات أكبر من 5MB بين المجهَّز
for f in $(git diff --cached --name-only --diff-filter=AM); do
  size=$(git cat-file -s ":$f")
  if [ "$size" -gt 5000000 ]; then
    echo "⛔ $f حجمه $size بايت (الحد 5MB)"
    exit 1
  fi
done
  • git diff --cached --name-only تعطي الملفات المجهّزة فقط، أي ما سيدخل الـ commit فعلًا، لا كل ما في المجلّد. و--diff-filter=AM تستبعد المحذوف.
  • git cat-file -s ":$f" حجم النسخة المجهّزة (النقطتان تعنيان منطقة التجهيز)، لا النسخة على القرص التي قد تكون عدّلتها بعد add.

الملف يحتاج صلاحية تنفيذ، وإلا تجاهله Git مع تنبيه:

chmod +x .git/hooks/pre-commit
hint: The '.git/hooks/pre-commit' hook was ignored because it's not set as executable.

commit-msg: صيغة موحّدة للرسائل

#!/bin/sh
# .git/hooks/commit-msg

pattern='^((feat|fix|docs|refactor|test|ci|chore)(\(.+\))?: .+|Merge .+|Revert .+)'
if ! head -n1 "$1" | grep -qE "$pattern"; then
  echo "⛔ رسالة الـ commit لا تتبع الصيغة: type(scope): وصف"
  echo "   مثال: feat(auth): add Google sign-in"
  exit 1
fi

يستلم الخطاف مسار ملف الرسالة في $1، ويفحص سطرها الأول. هذه صيغة Conventional Commits. لاحظ استثناء رسائل Merge وRevert التي يكتبها Git نفسه؛ بدونه يفشل كل git merge وgit revert.

post-merge: تذكير بعد السحب

#!/bin/sh
# .git/hooks/post-merge

if git diff --name-only ORIG_HEAD HEAD | grep -q "package-lock.json"; then
  echo "📦 package-lock.json تغيّر: شغّل npm install"
fi

ORIG_HEAD هو موقعك قبل الدمج. سحبت تحديثات فيها مكتبة جديدة؟ الخطاف ينبّهك قبل أن تقضي عشر دقائق في خطأ "module not found".

المشكلة: .git/hooks لا يُرفع

كل ما في .git/ محلي. الخطافات التي كتبتها لا تصل لزملائك مع git push، ولا تأتي مع git clone. حلّان:

1. مجلّد متتبَّع + core.hooksPath

mkdir .githooks
# ضع الخطافات فيه، وأعطها صلاحية التنفيذ
chmod +x .githooks/*
git add .githooks && git commit -m "chore: add shared hooks"

# كل عضو في الفريق، مرة واحدة بعد clone:
git config core.hooksPath .githooks

بلا أي اعتماديات، ويعمل في أي مشروع بأي لغة (منذ Git 2.9). عيبه الخطوة اليدوية الأخيرة لكل شخص.

2. Husky في مشاريع JavaScript

npm install --save-dev husky
npx husky init

husky init يفعل ثلاثة أشياء: ينشئ مجلّد .husky/، ويضع فيه pre-commit يشغّل npm test، ويضيف لـ package.json السكربت "prepare": "husky". سكربت prepare يُشغَّل تلقائيًّا مع npm install، فيضبط Husky الخطافات لكل من يثبّت المشروع، بلا خطوة يدوية.

لتعديل خطاف أو إضافة آخر، اكتب ملفه مباشرة (الأمر القديم husky add لم يعد مدعومًا في الإصدار 9):

echo "npm run lint" > .husky/pre-commit
echo "npm test" > .husky/pre-push
git add .husky && git commit -m "chore: lint before commit, test before push"

ومع lint-staged، يفحص pre-commit الملفات المجهّزة فقط بدل المشروع كله، فيبقى سريعًا في المستودعات الكبيرة.

الخطافات ليست حماية

git commit --no-verify -m "..."

--no-verify (أو -n) تتجاوز pre-commit وcommit-msg، وتعمل مع git push أيضًا. وأي شخص يستطيع حذف الخطاف من جهازه. الخطافات المحلية راحة تكتشف الأخطاء مبكرًا وتوفّر رحلة إلى CI، لا قاعدة لا يمكن كسرها.

ما يجب أن يكون مضمونًا يُفرض على الخادم: فحوصات CI على كل Pull Request، وحماية الفرع التي تمنع الدمج في main بلا موافقة واختبارات ناجحة (درس طلبات السحب). الخطاف المحلي يقول لك "ستفشل في CI" قبل أن ترفع، وCI هو الحكم.

خطاف جيد

  • سريع: pre-commit يعمل عشرات المرات يوميًّا. خطاف يأخذ دقيقتين سيتجاوزه الجميع بـ --no-verify. ضع الاختبارات الطويلة في pre-push أو CI.
  • رسالة واضحة: ماذا فشل، ولماذا، وكيف يُصلح.
  • يفحص المجهَّز لا مجلّد العمل.

أخطاء شائعة

  • خطاف بلا صلاحية تنفيذ: يُتجاهل بتنبيه. chmod +x.
  • امتداد في الاسم (pre-commit.sh): Git يبحث عن الاسم بالضبط.
  • فحص كل الملفات بدل المجهّزة: بطيء، ويرفض commits بسبب ملفات لن تدخلها.
  • commit-msg يرفض رسائل Git التلقائية (Merge ...): استثنِها.
  • الاعتماد على الخطافات للأمان: تُتجاوز بـ --no-verify. افرض القواعد في CI وحماية الفروع.
  • npx husky add من شرح قديم: لم يعد مدعومًا في Husky 9. اكتب الملف مباشرة.

🎯 التالي: آلية Git الداخلية (Objects, Refs, Packfiles).

شرح خطافات Git وأتمتة سير العمل (Git Hooks) — Git بالعربي
خطافات Git وأتمتة سير العمل (Git Hooks) — Git بالعربي · The Code Fix

📚 لمزيد من التعمّق في Git، راجِع التوثيق الرسمي لـ Git.

هل كان هذا الدرس مفيدًا؟