عبدالله
الخرج، السعوديةيقود المشروع من مكتبه في الخرج. بجانبه قهوة عربية وجهازه مفتوح على GitHub وNetlify. عبدالله مسؤول عن إنشاء المستودع، مراجعة التعديلات، ودمج الفروع المهمة داخل main.
في هذا الدرس لن نتعامل مع أوامر Git كقائمة محفوظات. سنعيش قصة فريق حقيقي يعمل من ثلاث دول على مشروع واحد، ونرى كيف تظهر الحاجة إلى المستودع، الفروع، الدمج، التعارضات، الجلب، السحب، التخزين المؤقت، التراجع، ونقل Commit محدد.
Git لا يظهر بقيمته الحقيقية عندما يعمل شخص واحد على ملف واحد. قوته تظهر عندما يعمل فريق كامل على مشروع واحد، وكل شخص يغير ملفات مختلفة، وأحيانًا الملف نفسه، وفي أوقات مختلفة، ومن أماكن بعيدة.
تخيل أن المشروع موقع حجوزات سياحية باسم NTF Travel. المدير في السعودية، مطور الواجهات الخلفية للصفحات في أمريكا، ومصممة الواجهة في إيطاليا. لا يمكن أن يرسل كل شخص ملفًا مضغوطًا للآخرين كل يوم؛ هذه طريقة فوضوية ستنتهي بملفات مثل:
بدل ذلك سيستخدم الفريق Git لتسجيل التاريخ، وGitHub لمشاركة المشروع، والفروع حتى يعمل كل شخص دون أن يكسر عمل الآخرين.
يقود المشروع من مكتبه في الخرج. بجانبه قهوة عربية وجهازه مفتوح على GitHub وNetlify. عبدالله مسؤول عن إنشاء المستودع، مراجعة التعديلات، ودمج الفروع المهمة داخل main.
يعمل من مساحة عمل قريبة من وسط المدينة. مهمته بناء صفحة الرحلات وصفحة الحجز، ويركز على HTML وJavaScript وتنظيم بيانات الرحلات.
تعمل من مكتب صغير في فلورنسا بين المباني القديمة والشوارع الضيقة. مهمتها تحسين شكل الموقع، الألوان، الأزرار، وتجربة المستخدم.
يبدأ عبدالله بإنشاء مجلد المشروع. في هذه اللحظة لا يوجد Git ولا يوجد تاريخ للتغييرات. مجرد مجلد عادي.
بعد ذلك يحوّل المجلد إلى مستودع Git:
الآن أنشأ Git مجلدًا مخفيًا باسم .git. هذا المجلد هو عقل المستودع؛ فيه يحفظ Git معلومات الفروع، تاريخ الـ commits، وموقع HEAD.
ينشئ عبدالله الملفات الأولى:
ثم يسأل Git عن حالة المشروع:
سيخبره Git أن الملفات غير متتبعة. معنى ذلك أنها موجودة في Working Directory لكن لم تدخل بعد في منطقة التجهيز.
الأمر git add . لا ينشئ Commit. هو فقط يقول: “هذه الملفات جاهزة للدخول في اللقطة القادمة”.
الآن أصبح لدى المشروع نقطة حفظ واضحة. يمكن للفريق الرجوع إليها أو مقارنة التعديلات اللاحقة بها.
إلى الآن المشروع موجود فقط على جهاز عبدالله. لو تعطل جهازه أو أراد Ethan وGiulia المشاركة، فلن يستطيعوا الوصول للمشروع. لذلك ينشئ عبدالله مستودعًا بعيدًا على GitHub.
يربط المستودع المحلي بالمستودع البعيد:
يتأكد من الرابط:
ثم يرفع الفرع الرئيسي لأول مرة:
الخيار -u يربط الفرع المحلي main بالفرع البعيد origin/main، وبعدها يستطيع عبدالله استخدام git push مباشرة في المرات القادمة.
الآن يرسل عبدالله رابط المستودع لأعضاء الفريق. كل عضو لا يبدأ من الصفر، بل يستنسخ نسخة كاملة من المشروع.
الأمر git clone ينزل الملفات وتاريخ Git والرابط البعيد معًا. لذلك يصبح لدى كل عضو نسخة محلية كاملة، وليست مجرد نسخة من الملفات الحالية.
طلب عبدالله من Ethan إنشاء صفحة الرحلات. الخطأ الشائع أن يعمل Ethan مباشرة على main. الأفضل أن ينشئ فرعًا مستقلًا للميزة الجديدة.
هذا الأمر ينشئ فرعًا جديدًا باسم flights-page وينتقل إليه مباشرة.
في الوقت نفسه تعمل Giulia على صفحة تسجيل الدخول:
بهذه الطريقة يمكن لاثنين العمل في الوقت نفسه دون أن يتعارض عملهما مباشرة. الفرع ليس نسخة كاملة منفصلة من المشروع، بل مؤشر خفيف يشير إلى مسار من الـ commits.
أضاف Ethan الملفات التالية:
ثم يرفع الفرع إلى GitHub:
يفتح عبدالله GitHub ويراجع التغييرات. بعد التأكد أن صفحة الرحلات لا تكسر المشروع، يدمج العمل داخل الفرع الرئيسي. يمكن الدمج من GitHub، أو محليًا بهذا الشكل:
قبل الدمج نفذ عبدالله git pull حتى يتأكد أن نسخة main عنده محدثة.
إذا كان main لم يتحرك منذ إنشاء الفرع، قد يحدث Fast-forward merge، أي أن Git يقدّم مؤشر main للأمام فقط. أما إذا كان هناك عمل آخر على main، فقد ينشئ Git Merge Commit.
في اليوم التالي عدّل عبدالله زر الحجز في style.css ليكون ذهبيًا، لأن هوية NTF Hub تعتمد على اللون الذهبي.
في الوقت نفسه، كانت Giulia تعمل على فرع login-page وعدّلت نفس السطر تقريبًا ليكون أزرقًا لأنها كانت تختبر نمطًا جديدًا.
المشكلة ليست أن أحدهما أخطأ. التعارض حدث لأن الاثنين عدلا نفس المكان في الملف نفسه، وGit لا يستطيع أن يقرر وحده أي نية هي الصحيحة.
عندما حاولت Giulia تحديث فرعها ظهر التعارض:
فتحوا الملف فوجدوا علامات التعارض:
ناقش عبدالله وGiulia الهوية البصرية، وقرروا استخدام لون ذهبي أهدأ مع ظل مناسب:
بعد إزالة علامات التعارض وحفظ الملف:
الآن صار حل التعارض جزءًا من تاريخ المشروع، ويمكن لأي عضو رؤية متى حدث ولماذا.
أثناء عمل Giulia على تحسين صفحة تسجيل الدخول، وصل تحديث عاجل من عبدالله: هناك خطأ في الصفحة الرئيسية ويجب أن تسحب آخر تحديث قبل المتابعة. المشكلة أن تعديلات Giulia غير مكتملة ولا تريد حفظها في Commit.
الحل هو استخدام Stash:
هذا الأمر يخفي التعديلات مؤقتًا ويعيد Working Directory إلى حالة نظيفة.
بعد جلب التحديثات تعيد Giulia تعديلاتها:
لو حدث تعارض بعد stash pop يتم حله مثل أي تعارض عادي.
قبل الدمج النهائي، أراد عبدالله أن يعرف ما الذي تغيّر على GitHub دون أن يدمج شيئًا مباشرة في جهازه.
هذا الأمر يجلب معلومات التحديثات من GitHub لكنه لا يدمجها في الفرع الحالي.
هنا يقارن عبدالله بين الفرع المحلي والفرع البعيد. إذا كان كل شيء مناسبًا، يمكنه تنفيذ:
git pull عمليًا تعني: Fetch ثم دمج أو تطبيق التحديثات على الفرع الحالي.
رفع Ethan بالخطأ Commit يحتوي ملفًا تجريبيًا باسم test-data.json إلى فرع تمت مشاركته مع الفريق. هنا يجب الانتباه: إذا وصل الـ Commit إلى GitHub وشاهده الآخرون، فالأكثر أمانًا غالبًا هو revert.
هذا لا يمسح التاريخ القديم، بل ينشئ Commit جديدًا يعكس أثر الـ Commit الخاطئ. لذلك هو آمن في العمل الجماعي.
أما reset فيستخدم غالبًا قبل مشاركة التعديل، أو في حالات يعرف الفريق أثرها جيدًا:
هذا يرجع خطوة واحدة مع إبقاء التعديلات في Staging Area.
هذا يرجع خطوة واحدة ويخرج التعديلات من Staging Area لكنها تبقى في الملفات.
هذا أخطر خيار لأنه يرجع التاريخ ويحذف التعديلات من الملفات. لا تستخدمه على عمل مهم دون فهم كامل.
أنشأ Ethan فرعًا تجريبيًا اسمه experimental-search. كان الفرع يحتوي أفكارًا كثيرة غير جاهزة، لكن فيه Commit واحد ممتاز يحسن طريقة البحث عن الرحلات.
عبدالله لا يريد دمج الفرع كاملًا. يريد Commit واحدًا فقط.
يأخذ عبدالله الـ Commit المطلوب فقط:
الآن انتقل أثر ذلك الـ Commit إلى main دون نقل بقية التجارب غير الجاهزة.
قبل الإطلاق، يتأكد عبدالله أن المشروع نظيف:
ثم يعرض التاريخ بشكل مختصر:
يرفع عبدالله آخر نسخة:
بعد ذلك يكتشف نظام النشر أن هناك تحديثًا جديدًا على GitHub، فيبدأ بناء الموقع ونشر النسخة الجديدة.
هذا السيناريو جمع معظم أوامر Git التي يحتاجها الفريق في مشروع حقيقي:
استخدم عبدالله git init ثم add وcommit لحفظ أول نسخة.
استخدم remote وpush -u لنقل المشروع إلى مستودع بعيد.
استخدم Ethan وGiulia أمر clone للحصول على المشروع كاملًا.
استخدم الفريق branch وswitch لعزل كل ميزة عن main.
استخدموا merge وحلوا conflict في style.css.
استخدموا fetch وpull وstash وrevert وreset وcherry-pick حسب الحاجة.
حتى يطوّر الميزة دون التأثير على النسخة الرئيسية المستقرة.
لأن عبدالله وGiulia عدلا السطر نفسه تقريبًا في الملف نفسه.
fetch يجلب التحديثات فقط، أما pull فيجلبها ويطبقها على الفرع الحالي.
عندما يكون الـ Commit منشورًا أو مشتركًا مع الفريق.
لأنه احتاج Commit واحدًا من فرع تجريبي دون دمج كل الفرع.
افتح المحاكي وجرّب إنشاء الفروع، الدمج، التعارض، stash، pull، وpush بنفسك.