Git Hooks والأتمتة
Git Hooks والأتمتة
في كل مرة تُشغّل فيها أمر git commit أو git push، يتحقق Git من مجلد .git/hooks/ بحثاً عن سكريبتات قابلة للتنفيذ بأسماء معروفة مسبقاً. إذا وُجد سكريبت مطابق، شغّله Git عند تلك النقطة من دورة الحياة. هذا نظام الـ hooks المدمج مباشرةً في عميل Git والخادم، ويُستخدم في كبرى المنظمات الهندسية لتضمين المعايير في سير عمل المطور مباشرةً: وقف الـ commits الخاطئة قبل وصولها إلى CI، وفرض معايير رسائل الـ commit على مستوى الشركة، وتشغيل الإشعارات على الفروع المحمية.
Hooks جانب العميل مقابل جانب الخادم
تنقسم hooks في Git إلى عائلتين بنموذجَي ثقة مختلفَين جوهرياً:
- Hooks جانب العميل تعمل على جهاز المطور وتُطلَق بعمليات محلية: الـ committing والـ merging والـ rebasing. تقع في
.git/hooks/(لا تُودَع في الـ repo قط)، لذا يجب على كل مطور تثبيتها يدوياً — أو تتولى إطار عمل إدارتها. والأهم: يمكن تخطي أي hook للعميل باستخدام--no-verify. - Hooks جانب الخادم تعمل على الـ remote (GitHub أو GitLab أو repo مستضاف ذاتياً). تفرض السياسة على الفريق بأكمله بصرف النظر عن الإعداد المحلي. لا يمكن تجاوز hook من نوع
pre-receiveعلى الخادم باستخدام--no-verify.
أبرز Hooks جانب العميل
pre-commit— يعمل قبل فتح محرر رسالة الـ commit. يُستخدم للـ linting والـ formatting وفحص الأسرار. الخروج بكود غير صفري يُلغي العملية.prepare-commit-msg— يعمل بعد إعداد القالب وقبل فتح المحرر. يُستخدم لحقن معرّفات التذاكر أو أسماء الفروع تلقائياً.commit-msg— يستقبل مسار ملف رسالة الـ commit عبر$1. يُستخدم للتحقق من صيغة الرسالة (Conventional Commits وحدود الأحرف).post-commit— يعمل بعد اكتمال الـ commit. كود الخروج لا يُحسب. يُستخدم للإشعارات المحلية أو إبطال الـ cache.pre-push— يعمل قبل إرسال البيانات إلى الـ remote. يُستخدم لتشغيل مجموعة الاختبارات الكاملة أو منع الـ push إلى الفروع المحمية.
أبرز Hooks جانب الخادم
pre-receive— يعمل مرة واحدة عند وصول الـ push، قبل تحديث أي مرجع. تصل جميع الـ refs المُرسَلة عبر stdin بصيغة<old-sha> <new-sha> <refname>. الرفض هنا يُلغي الـ push بالكامل برسالة للمطور.update— يعمل مرة لكل ref يُحدَّث. يُستخدم لسياسات لكل فرع (منع force-push لـmain، واشتراط commits موقعة على فروع الإصدار).post-receive— يعمل بعد تحديث جميع الـ refs. يُستخدم لتشغيل CI وإرسال إشعارات Slack وتحديث متتبعي المشاكل. كود الخروج لا يُحسب.
إطار عمل pre-commit
كتابة hooks بـ shell خام لكل repo أمر هش وصعب المشاركة. يحل إطار عمل pre-commit (pre-commit.com) هذه المشكلة: تُعلَن الـ hooks في ملف YAML مُودَع في الـ repo، وتُجلب من repositories المصدر، وتُخزَّن مؤقتاً محلياً. هذا هو النهج المعياري في الشركات التي تُشغّل مئات الـ repositories.
ملف .pre-commit-config.yaml لمستوى الإنتاج لـ repo يجمع Python وNode:
rev محدد (tag أو SHA كامل)، وليس اسم فرع. الفرع العائم يعني أن تحديثاً خارجياً قد يغيّر قواعد الـ linting صامتاً ويُوقف كل المطورين يوم الاثنين. نفّذ pre-commit autoupdate في PR منفصل حتى تُراجع التغييرات.
فرض معايير الـ Commit عبر commit-msg
Conventional Commits هو المعيار الفعلي في المنظمات الناضجة في DevOps: كل commit يجب أن يتبع <type>(<scope>): <description> — مثلاً feat(auth): add OAuth2 PKCE flow. هذا البنية تُتيح توليد سجل التغييرات آلياً والإصدار الدلالي. الـ hook الخاص بـ commit-msg يفرضه محلياً:
توزيع الـ Hooks على الفريق
لأن .git/hooks/ لا يُودَع قط، فإن توزيع الـ hooks يتطلب استراتيجية واضحة. ثلاثة خيارات بترتيب الأفضلية:
- إطار عمل pre-commit — الـ hooks مُعلَنة في
.pre-commit-config.yaml(مُودَع)، وتُثبَّت بتشغيلpre-commit install. الأنسب للفرق متعددة اللغات وإعادة استخدام الـ hooks. - core.hooksPath — منذ Git 2.9، ضع
git config core.hooksPath .githooksلتوجيه Git إلى مجلد مُودَع. أبسط للـ hooks النقية بـ shell؛ ضعها في سكريبت bootstrap للمشروع. - Makefile bootstrap — هدف
make setupيربط السكريبتات من مجلد مُودَع بـ.git/hooks/. شائع في الـ repos القديمة لكنه هش عند الترقيات.
core.hooksPath في الـ global git config — سيُطبَّق على كل repo في الجهاز ويُعطّل مشاريع أخرى لا تحتوي على ذلك المجلد. ضعه لكل مشروع على حدة في سكريبت bootstrap أو هدف Makefile يعمل مرة واحدة بعد git clone.
مسار الـ Hooks: الصورة الكاملة
الأداء وانضباط التجاوز
الـ hooks البطيئة تُدمر تجربة المطور. يجب أن تنتهي مرحلة pre-commit في أقل من 3 ثوانٍ. الاستراتيجيات: تشغيل الـ linters بالتوازي، واستخدام الفحص التزايدي (فحص الملفات المُخرَّجة فقط لا الشجرة كلها)، ووضع الفحوصات الثقيلة في pre-push لا pre-commit. لفحص الأسرار، أدوات مثل gitleaks وtrufflehog سريعة بما يكفي لمرحلة pre-commit في معظم الـ repos.
بشأن التجاوز: git commit --no-verify يتخطى pre-commit وcommit-msg معاً. هذا مقصود — حالات الطوارئ الحقيقية موجودة. السياسة الصحيحة: اسمح به، سجّله. يمكن لـ hook من نوع post-commit أو خطوة CI اكتشاف الـ commits التي تجاوزت الـ hooks والإشارة إليها في مراجعة الـ pull request. حظر التجاوز كلياً يُنتج حلولاً بديلة أصعب مراجعةً.