دليل عملي لاستخدام Git submodules وsubtrees

دليل عملي لاستخدام Git submodules و subtrees: متى تختار كل منهما وكيف تديرهما باحتراف

Git ليس مجرد أوامر commit و push و pull. في المشاريع المتوسطة والكبيرة ستحتاج غالبًا لإعادة استخدام أكواد من مستودعات أخرى، مثل مكتبات مشتركة، أو موديولات داخلية، أو قوالب جاهزة. هنا يظهر دور Git submodules subtrees كأدوات أساسية لربط عدة مستودعات معًا بشكل منظم.

في هذا الدليل سنشرح:

  • مفهوم Git submodules و Git subtrees والفرق بينهما.
  • متى تختار submodule ومتى تختار subtree.
  • كيفية إعداد كل منهما من الصفر.
  • كيفية إدارة التحديثات والتزامن مع المستودعات الخارجية.
  • أشهر الأخطاء والمشاكل العملية وحلولها.

ما هو Git Submodule؟

Git submodule هو طريقة لإضافة مستودع Git داخل مستودع آخر كمرجع (Reference) إلى commit محدد. أي أن المشروع الرئيسي يعرف أن هناك مشروعًا فرعيًا، لكنه لا ينسخ تاريخه بالكامل؛ فقط يحفظ إشارة إلى رقم commit في المستودع الفرعي.

بصيغة أخرى:

  • المجلد الفرعي (submodule) هو مستودع Git مستقل.
  • المشروع الرئيسي يختزن فقط commit hash يشير إلى نسخة معينة من هذا المستودع.
  • تحديث submodule يتطلب تنفيذ أوامر إضافية من المطور.

هذا مناسب عندما تريد أن يبقى المشروع الفرعي مستقل تمامًا (مثل مكتبة خارجية أو مشروع آخر تستخدمه كما هو).

ما هو Git Subtree؟

Git subtree يقوم بدمج مستودع خارجي داخل مشروعك كجزء من شجرة الملفات (tree) الخاصة بك مع إمكانية الاحتفاظ بتاريخ الكوميتات إن رغبت. من وجهة نظر المشروع الرئيسي، ملفات المشروع الفرعي تصبح جزءًا من المستودع، بدون وجود مجلد Git مستقل داخلها.

  • لا توجد بنية مستقلة للمستودع الفرعي داخل المشروع (مجلد .git واحد فقط للمشروع كله).
  • يمكنك سحب (pull) ودمج (merge) تحديثات المستودع الخارجي عند الحاجة.
  • يمكنك دفع (push) التغييرات التي قمت بها في المجلد الفرعي إلى المستودع الأصلي.

Git subtree يمنحك شعور أن الكود جزء طبيعي من مشروعك، مع إمكانية التزامن ثنائي الاتجاه مع المستودع الأصلي.

Git submodules vs subtrees: متى تختار أيهما؟

اختر Git Submodule عندما:

  • المشروع الفرعي عبارة عن مكتبة مستقلة أو طرف ثالث لا تريد التعديل عليه غالبًا.
  • تريد أن يبقى للمشروع الفرعي مستودع خاص به تمامًا، وستتعامل معه كما هو.
  • حجم المشروع الفرعي كبير، ولا تريد نسخه بالكامل داخل مشروعك (تريد فقط الإشارة إليه).
  • تحتاج إلى التبديل بسهولة بين إصدارات محددة من المشروع الفرعي (commits أو tags).

أمثلة:

  • إضافة مكتبة واجهات UI خاصة بشركتك تستخدمها في عدة مشاريع.
  • إعادة استخدام محرك ألعاب أو محرك Template جاهز مع الحفاظ على فصل تام للمستودعات.

اختر Git Subtree عندما:

  • تريد أن يبدو الكود جزءًا طبيعيًا من مشروعك.
  • لا تريد إرهاق المطورين باستخدام أوامر إضافية مثل git submodule update في كل مرة.
  • تريد القدرة على إرسال تغييراتك إلى المستودع الخارجي بسهولة.
  • المشروع الفرعي مرتبط بشكل وثيق بمشروعك، وقد تقوم بتعديله كثيرًا.

أمثلة:

  • مشاركة موديول مشترك بين عدة Microservices وتريد أن تتمكن من تحديثه من أي خدمة.
  • تضمين مشروع داخلي مع تفرعات متعددة، وتحتاج أن يدير الفريق المشروع ككتلة واحدة.

ملخص سريع للمقارنة

العنصر Submodule Subtree
الاستقلالية مستودع مستقل داخل المشروع الكود مدمج في شجرة المشروع
سهولة الاستخدام أوامر إضافية وتحتاج انتباه أسهل للفريق، تعامل مثل ملفات عادية
حجم التاريخ لا يتم نسخ تاريخ المستودع بالكامل يمكن دمج التاريخ أو جزء منه
مشاركة التغييرات أصعب قليلاً في التزامن ثنائي الاتجاه مدعوم عبر subtree pull/push

كيفية إعداد Git Submodule خطوة بخطوة

1. إضافة Submodule إلى مشروع موجود

لنفترض أن لديك مشروعًا رئيسيًا، وتريد إضافة مكتبة من GitHub كمجلد فرعي:

git submodule add https://github.com/user/lib.git libs/lib
  • https://github.com/user/lib.git: رابط المستودع الخارجي.
  • libs/lib: المسار الذي سيُضاف فيه المجلد داخل مشروعك.

بعد تنفيذ الأمر:

  • سيتم إنشاء مجلد libs/lib وفي داخله مستودع Git مستقل.
  • سيتم إنشاء ملف .gitmodules في جذر مشروعك لتخزين إعدادات الـ submodules.

لا تنسَ تنفيذ:

git commit -am "Add lib as submodule"

2. استنساخ مشروع يحتوي على Submodules

عندما يقوم مطور جديد باستنساخ المشروع، عليه استخدام:

git clone <repo-url>
cd project
git submodule update --init --recursive

الأمر --recursive مفيد إذا كان هناك submodules داخل submodules.

3. تحديث Submodule إلى آخر إصدار

من داخل المشروع الرئيسي:

cd libs/lib
git fetch
git checkout main   # أو أي فرع تريده
git pull origin main
cd ../..
git add libs/lib
git commit -m "Update lib submodule to latest main"

يمكنك أيضًا من جذر المشروع:

git submodule update --remote libs/lib
git commit -am "Update lib submodule"

4. تثبيت Submodule على Commit أو Tag محدد

من داخل مجلد الـ submodule:

cd libs/lib
git checkout v1.2.0   # أو commit hash
cd ../..
git add libs/lib
git commit -m "Lock lib submodule to v1.2.0"

مشكلات شائعة مع Submodules وحلولها

1. نسيان تهيئة أو تحديث Submodules بعد الاستنساخ

الأعراض: المجلدات الفرعية فارغة أو تحتوي فقط على هيكل بدون ملفات حقيقية.

الحل:

git submodule update --init --recursive

2. تعارضات في ملف .gitmodules أو مراجع submodule

  • تأكد أن ملف .gitmodules مضاف إلى Git ومُدار مع بقية المشروع.
  • في حالة تغيير المسار أو URL، عدّل الملف ثم نفّذ:
git submodule sync
git submodule update --init --recursive

3. نسيان عمل Commit لتحديث submodule

بعد تحديث submodule داخليًا، يجب عليك العودة للمشروع الرئيسي وعمل git add للمجلد الفرعي (المرجع نفسه يتغير)، ثم commit، وإلا سيبقى الفريق على الإصدار القديم.

كيفية إعداد Git Subtree خطوة بخطوة

بعض إصدارات Git تتطلب استخدام git subtree كإضافة، لكن في معظم الإصدارات الحديثة أصبح مدعومًا كأمر مدمج. تأكد من وجود الأمر:

git help subtree

1. إضافة مستودع خارجي كـ Subtree

لنفترض أنك تريد إضافة مكتبة في المسار libs/lib:

git remote add lib-remote https://github.com/user/lib.git
git fetch lib-remote

git subtree add --prefix=libs/lib lib-remote main --squash
  • --prefix=libs/lib: المسار الذي سيوضع فيه الكود داخل مشروعك.
  • lib-remote: اسم الـ remote الذي يشير للمستودع الخارجي.
  • main: الفرع الذي تريد استيراد الكود منه.
  • --squash: يدمج تاريخ المستودع الفرعي في commit واحد لتقليل الحجم (اختياري).

2. جلب تحديثات من المستودع الخارجي

بعد ظهور تحديثات في المستودع الخارجي، يمكنك جلبها:

git fetch lib-remote
git subtree pull --prefix=libs/lib lib-remote main --squash

سيتم دمج التحديثات في مشروعك كما لو كنت تعمل بـ merge عادي، بدون الحاجة لأوامر خاصة لباقي أعضاء الفريق.

3. دفع تغييراتك من Subtree إلى المستودع الخارجي

إذا أجريت تعديلات داخل libs/lib وتريد إرسالها للمستودع الأصلي:

git subtree push --prefix=libs/lib lib-remote main

هذا الأمر سيأخذ التغييرات التي تمت داخل هذا المجلد ويحولها إلى commits تُدفَع إلى الفرع main في lib-remote.

مشكلات شائعة مع Subtrees وحلولها

1. نسيان إعداد remote أو تغيير URL

عند تغيير مكان المستودع الخارجي:

git remote set-url lib-remote <new-url>
git fetch lib-remote

2. تعارضات Merge أثناء pull

قد تظهر تعارضات عندما يتغير نفس الملف في المستودع الخارجي ومشروعك في نفس الوقت. الحل:

  • حل التعارضات يدويًا كما تفعل في أي merge.
  • تنفيذ git add للملفات المتعارضة بعد التعديل.
  • ثم git commit لإكمال عملية الـ subtree pull.

أفضل الممارسات لاستخدام Git submodules subtrees في المشاريع الحقيقية

1. وثّق اختيارك في ملف README

سواء استخدمت submodule أو subtree، اشرح في README أو وثائق المشروع:

  • لماذا تم اختيار هذا الأسلوب؟
  • كيف يقوم المطوّر الجديد باستنساخ المشروع والتعامل مع التحديثات؟
  • الأوامر الأساسية المطلوبة للتعامل مع المكتبات المضافة.

2. استخدم فروع/Tags مستقرة للمشاريع الفرعية

بدلاً من تتبع main مباشرة، اربط مشروعك بـ:

  • Tag ثابت مثل v1.0.0 في submodule أو subtree.
  • أو فرع خاص مستقر للاستخدام في المشاريع الإنتاجية.

3. اتفق مع الفريق على سياسة إدارة التحديثات

  • من المسؤول عن تحديث submodules أو subtrees؟
  • هل يتم التحديث بشكل دوري، أم فقط عند الحاجة؟
  • هل هناك بيئة اختبار (Staging) يجب تجربة التحديثات فيها قبل الإنتاج؟

إذا كنت مهتمًا بتنظيم البنية التحتية والتحديثات بشكل مؤتمت، يمكنك الاطلاع على مقال: GitOps باختصار: كيف تجعل Git مدير البنية التحتية حيث ستجد مبادئ مفيدة لتنسيق عمليات النشر مع Git.

4. تجنب التعقيد الزائد

لا تستخدم submodules داخل submodules داخل subtrees إلا إذا كان هناك سبب قوي جدًا. كل طبقة إضافية تزيد من تعقيد إدارة المستودعات، خاصة مع فرق كبيرة أو متوزعة.

أمثلة عملية على اتخاذ القرار بين Submodule و Subtree

سيناريو 1: مكتبة طرف ثالث (Open Source)

  • تستخدم مكتبة من GitHub غالبًا كما هي.
  • نادرًا ما تعدل عليها بنفسك.

الأنسب: Git Submodule

لأنك تريد أن تبقى المكتبة مستقلة، وتستطيع التحديث للإصدار الجديد مباشرة من المستودع الرسمي، وربما تستخدمها في عدة مشاريع.

سيناريو 2: موديول داخلي مشترك بين عدة مشاريع داخل الشركة

  • تجري تعديلات متكررة على هذا الموديول.
  • قد تحتاج إرسال التغييرات من أي مشروع إلى المستودع المركزي.

الأنسب: Git Subtree

يمكنك التعامل مع الكود وكأنه جزء من المشروع، ثم دفع التغييرات إلى المستودع المركزي عند الحاجة، بدون تعقيد submodules على المطورين.

سيناريو 3: مشروع ضخم متعدد المكونات (Microservices)

  • كل خدمة لها مستودع مستقل.
  • لديك مستودع "تجميعي" يضم جميع الخدمات لتسهيل النشر أو التطوير.

المزج بين الاثنين ممكن:

  • خدمات مستقلة جدًا يمكن إضافتها كـ submodules.
  • مكتبات مشتركة حيوية بين الخدمات يمكن إضافتها كـ subtrees.

خلاصة: كيف تبدأ مع Git submodules subtrees بطريقة عملية؟

  1. اسأل نفسك: هل أريد أن يكون المشروع الفرعي مستقلًا تمامًا؟ أم جزءًا من المشروع؟
  2. اختر:
    • Submodule للاستقلالية والاعتمادية على إصدارات محددة.
    • Subtree للاندماج السلس والتعامل البسيط داخل الفريق.
  3. ابدأ بمشروع تجريبي صغير لتجربة الأوامر وفهم سلوك Git قبل تطبيقه على مشروع إنتاجي.
  4. وثّق الأوامر القياسية في ملف داخلي أو Wiki للفريق.
  5. تابع بشكل دوري تحديثات Git نفسها، حيث تتحسن بعض الأدوات والأوامر بمرور الوقت.

إتقان Git submodules subtrees يوفر عليك الكثير من العشوائية عند إعادة استخدام الكود بين المشاريع، ويجعل بنية مشاريعك أكثر تنظيمًا وقابلية للصيانة، خاصة مع نمو عدد الخدمات والمكتبات الداخلية في الفريق أو الشركة.

حول المحتوى:

متى تختار submodule أو subtree، كيفية إعداد كل منهما، إدارة التحديثات والتزامن بين المستودعات، وحلول لمشكلات الشائعة أثناء الاستخدام الفعلي.

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

أضف تعليقك