Git وتدفقات العمل التعاونية

Git Hooks والأتمتة

18 دقيقة الدرس 8 من 28

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 جانب العميل كشبكة أمان وحيدة. دورها إعطاء تغذية راجعة سريعة محلياً، لكن السياسة يجب أن تُفرض من جانب الخادم أو CI. النموذج الصحيح: hooks العميل تكشف المشاكل في أقل من ثانية، أما CI وhooks الخادم فهي البوابة الحاكمة.

أبرز 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.

# تثبيت الإطار مرة واحدة على كل جهاز pip install pre-commit # أو عبر brew على macOS: brew install pre-commit # بعد clone أي repo يحتوي على .pre-commit-config.yaml: pre-commit install # تشغيل جميع الـ hooks يدوياً على كل الملفات: pre-commit run --all-files # تحديث جميع إصدارات الـ hooks وتثبيتها: pre-commit autoupdate

ملف .pre-commit-config.yaml لمستوى الإنتاج لـ repo يجمع Python وNode:

# .pre-commit-config.yaml — أودِع هذا الملف في الـ repository repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.6.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer - id: check-yaml - id: check-json - id: check-merge-conflict - id: detect-private-key - repo: https://github.com/psf/black rev: 24.4.2 hooks: - id: black language_version: python3.12 - repo: https://github.com/pre-commit/mirrors-eslint rev: v9.3.0 hooks: - id: eslint files: \.(js|ts|jsx|tsx)$ additional_dependencies: - eslint@9.3.0 - repo: https://github.com/zricethezav/gitleaks rev: v8.18.4 hooks: - id: gitleaks name: Detect secrets and credentials
ممارسة احترافية: ثبّت دائماً الـ hooks على 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 يفرضه محلياً:

#!/usr/bin/env bash # .githooks/commit-msg (مُودَع في الـ repo ومُفعَّل عبر core.hooksPath) COMMIT_MSG_FILE="$1" COMMIT_MSG=$(cat "$COMMIT_MSG_FILE") # السماح بـ merge commits وfixup/squash commits بالمرور if echo "$COMMIT_MSG" | grep -qE "^(Merge|Revert|fixup!|squash!)"; then exit 0 fi PATTERN="^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\(.+\))?: .{1,100}$" if ! echo "$COMMIT_MSG" | grep -qE "$PATTERN"; then echo "" echo "ERROR: رسالة الـ commit لا تتبع صيغة Conventional Commits." echo "المطلوب: feat(scope): وصف قصير" echo "الأنواع: feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert" echo "" exit 1 fi SUBJECT=$(echo "$COMMIT_MSG" | head -1) if [ "${#SUBJECT}" -gt 72 ]; then echo "ERROR: سطر الموضوع يتجاوز 72 حرفاً (${#SUBJECT} حرف)." exit 1 fi

توزيع الـ Hooks على الفريق

لأن .git/hooks/ لا يُودَع قط، فإن توزيع الـ hooks يتطلب استراتيجية واضحة. ثلاثة خيارات بترتيب الأفضلية:

  1. إطار عمل pre-commit — الـ hooks مُعلَنة في .pre-commit-config.yaml (مُودَع)، وتُثبَّت بتشغيل pre-commit install. الأنسب للفرق متعددة اللغات وإعادة استخدام الـ hooks.
  2. core.hooksPath — منذ Git 2.9، ضع git config core.hooksPath .githooks لتوجيه Git إلى مجلد مُودَع. أبسط للـ hooks النقية بـ shell؛ ضعها في سكريبت bootstrap للمشروع.
  3. Makefile bootstrap — هدف make setup يربط السكريبتات من مجلد مُودَع بـ .git/hooks/. شائع في الـ repos القديمة لكنه هش عند الترقيات.
فخ الإنتاج: لا تضع core.hooksPath في الـ global git config — سيُطبَّق على كل repo في الجهاز ويُعطّل مشاريع أخرى لا تحتوي على ذلك المجلد. ضعه لكل مشروع على حدة في سكريبت bootstrap أو هدف Makefile يعمل مرة واحدة بعد git clone.

مسار الـ Hooks: الصورة الكاملة

Git hook execution sequence from local commit to remote enforcement Developer Machine git commit stage files pre-commit lint · format · secrets commit-msg Conventional Commits pre-push run test suite ✗ abort commit ✗ abort commit ✗ abort push git push Remote (GitHub / GitLab / Bare) pre-receive policy enforcement update per-branch rules post-receive trigger CI · notify Slack ✗ reject push ✗ reject ref
تسلسل تنفيذ hooks في Git: hooks جانب العميل تكشف المشاكل في ميلي ثانية؛ 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. حظر التجاوز كلياً يُنتج حلولاً بديلة أصعب مراجعةً.