البرمجة متوسط 8 دقيقة

كيفية إنشاء Git hook قبل الـ commit للتحقق من الكود

يشغّل الـ pre-commit hook سكريبتًا في اللحظة التي تكتب فيها git commit، قبل إنشاء أي commit. إذا خرج السكريبت بحالة غير صفرية، يُحظر الـ commit. عند تطبيقه بشكل صحيح، يُمسك بأخطاء الـ linting وانتهاكات التنسيق قبل أن تصل إلى المستودع — لا فشل في CI، ولا تعليق مراجعة يقول "نسيت تشغيل prettier".

الخطوات

  1. 1

    افهم النهج الخام للـ hook

    كل مستودع Git يحتوي مجلد .git/hooks/ مع سكريبتات نموذجية. أنشئ ملفًا قابلًا للتنفيذ باسم pre-commit هناك وسيشغّله Git قبل كل commit. القيد الجوهري: .git/ لا يُضاف إلى الـ commit أبدًا، لذا يعيش الـ hook على جهازك فقط ولا يُشارك مع الفريق.

    bash
    ls .git/hooks/
    # pre-commit.sample  commit-msg.sample  post-merge.sample ...
    
    # أنشئ hook خامًا (ليس النهج الموصى به على المدى الطويل)
    cp .git/hooks/pre-commit.sample .git/hooks/pre-commit
    chmod +x .git/hooks/pre-commit
  2. 2

    اكتب hook bash خامًا يحجب عند الأخطاء

    الـ pre-commit hook سكريبت shell عادي. اخرج بـ 0 للسماح بالـ commit؛ اخرج بأي قيمة أخرى لإلغائه. يشغّل هذا المثال ESLint على كل ملف .js في الـ staging ويحجب إذا وُجدت أخطاء.

    bash
    #!/bin/bash
    # .git/hooks/pre-commit
    
    STAGED_JS=$(git diff --cached --name-only --diff-filter=ACMR | grep '\.js$')
    
    if [ -n "$STAGED_JS" ]; then
      echo "Running ESLint on staged files..."
      npx eslint $STAGED_JS
      if [ $? -ne 0 ]; then
        echo "ESLint failed. Commit aborted."
        exit 1
      fi
    fi
    
    exit 0
  3. 3

    ثبّت Husky لإضافة الـ hooks إلى المستودع

    يخزن Husky الـ hooks في مجلد .husky/ الذي يُضاف إلى الـ commit، لذا يحصل عليها كل مطور تلقائيًا عند الـ clone أو الـ pull. إنه المعيار الصناعي لمشاريع Node.js.

    bash
    npm install --save-dev husky
    
    # هيّئ Husky (ينشئ .husky/ ويضيف prepare script إلى package.json)
    npx husky-init
    
    # بعد التهيئة، ثبّت الاعتماديات لتشغيل prepare script:
    npm install
  4. 4

    اضبط Husky pre-commit hook

    بعد husky-init، يُنشأ ملف .husky/pre-commit بمحتوى نموذجي. استبدل النموذج بأمرك الفعلي. هذا الملف يُضاف إلى الـ commit ويُشارك مع الفريق كله.

    bash
    # .husky/pre-commit
    #!/usr/bin/env sh
    . "$(dirname -- "$0")/_/husky.sh"
    
    npx lint-staged
  5. 5

    أضف lint-staged للتحقق من الملفات المُغيَّرة فقط

    تشغيل الـ linter على كامل قاعدة الكود عند كل commit بطيء ويُنتج ضجيجًا من ملفات لم تلمسها. يشغّل lint-staged الـ linters فقط على الملفات الموجودة فعلًا في الـ staging — مما يُبقي الـ hook سريعًا والتغذية الراجعة ذات صلة.

    bash
    npm install --save-dev lint-staged
  6. 6

    اضبط lint-staged في package.json

    أضف مفتاح lint-staged إلى package.json. كل مفتاح نمط glob؛ والقيمة مصفوفة أوامر تُشغَّل على الملفات المطابقة في الـ staging. يعمل Prettier أولًا (تنسيق)، ثم ESLint (فحص الأخطاء).

    bash
    // package.json
    {
      "lint-staged": {
        "*.{js,ts,tsx,jsx}": [
          "prettier --write",
          "eslint --fix"
        ],
        "*.{css,scss}": [
          "prettier --write"
        ],
        "*.php": [
          "./vendor/bin/phpcs --standard=PSR12"
        ]
      }
    }
  7. 7

    اختبر الـ hook وتجاوزه عند الضرورة

    ضع في الـ staging ملفًا به خطأ lint وشغّل git commit — يجب أن يحجبه الـ hook. إذا احتجت تجاوز الـ hook بشكل مشروع (مثلًا commit لملف تصحيح مؤقت أثناء حادثة)، استخدم --no-verify. استخدمه باعتدال؛ جعله عادةً يُفرغ الغرض منه.

    bash
    # commit عادي — يعمل الـ hook تلقائيًا
    git add src/broken.js
    git commit -m "test: trigger the hook"
    # ✘ ESLint: 3 errors found. Commit aborted.
    
    # تجاوز الـ hook — فقط حين تقصد ذلك فعلًا
    git commit --no-verify -m "chore: temp debug commit"
  8. 8

    أبقِ الـ hook أقل من ثلاث ثوانٍ

    سيتجاوز المطورون الـ hook الذي يستغرق أكثر من بضع ثوانٍ. يساعد lint-staged بالفعل بتحديد النطاق على الملفات في الـ staging. نصائح إضافية: شغّل الـ linters بالتوازي حيث أمكن، وتجنب فحص الأنواع (type-checking) في الـ pre-commit hook (شغّل tsc --noEmit في CI بدلًا من ذلك)، وتجنب تثبيت الحزم داخل سكريبت الـ hook.

    bash
    # قِس سرعة الـ hook لتأكد أنه سريع
    time git commit --allow-empty -m "timing test"
    
    # إذا كان لا يزال بطيئًا، شغّل فحص أنواع tsc في CI فقط لا في الـ hook:
    # في CI (.github/workflows/ci.yml):
    #   - run: npx tsc --noEmit

نصائح ومحاذير

  • الـ hooks في <code>.git/hooks/</code> لا تُستنسخ ولا تُرفع — استخدم دائمًا Husky (أو أداة مشابهة مثل <code>lefthook</code>) لمشاركة الـ hooks مع فريقك.
  • أضف <code>node_modules/.bin</code> إلى PATH داخل سكريبت الـ hook حتى تستطيع استدعاء <code>eslint</code> بدلًا من <code>./node_modules/.bin/eslint</code>.
  • لمشاريع PHP بدون Node، استخدم <a href="https://github.com/captainhookphp/captainhook" target="_blank" rel="noopener noreferrer">CaptainHook</a> — يفعل الشيء نفسه بملف إعداد JSON.
  • شغّل <code>git stash --include-untracked</code> في بداية الـ hook و<code>git stash pop</code> في نهايته إذا أردت فحص شجرة عمل نظيفة (ليس الملفات في الـ staging فقط).

خاتمة

نهج .git/hooks/pre-commit الخام مناسب للمشاريع الفردية؛ استخدم Husky + lint-staged فور أن يصبح لديك فريق. قاعدة الثلاث ثوانٍ غير قابلة للتفاوض: الـ hook الذي يتجاوزه المطورون لبطئه أسوأ من عدم وجود hook على الإطلاق. أبقِ الـ hook مركّزًا على الفحوصات المحلية السريعة (التنسيق + lint) وادفع الفحوصات البطيئة (فحص الأنواع، مجموعة الاختبارات الكاملة) إلى CI حيث تنتمي.

#Git #Hooks #Linting
العودة إلى جميع الأدلة

هل تحتاج مساعدة في مشروعك؟

احجز استشارة مجانية لمدة 30 دقيقة لمناقشة تحدياتك التقنية واستكشاف الحلول معًا.