التحكم بالإصدارات
درس 3 من 3
3التحكم بالإصدارات: Git و GitHub Flow
أول أداة احترافية حقيقية في رحلتك: طريقة تتبّع كل تغيير على كودك، والتعاون مع آخرين دون أن يمحو أحدكم عمل الآخر.
ماذا تشاهد في الرسم: التعديل يبدأ في مجلدك، ثم git add يضعه في التحضير، وgit commit يحفظه لقطة، وgit push يرفعه إلى GitHub.
خارطة هذا الجزء
المفاهيم بالترتيب، اضغط أيًّا منها للقفز إليه:
طريقان إلى نفس الفكرة
Git مثل زر «حفظ» له ذاكرة: كل حفظة صورة كاملة لمشروعك تستطيع أن ترجع إليها. وGitHub مثل مكتبة مشتركة على الإنترنت يضع فيها كل فرد نسخته. هكذا يعمل عشرة أشخاص على نفس المشروع دون أن يمحو أحدهم عمل الآخر.
Git نظام تحكم موزّع: مجلد العمل ثم منطقة التحضير git add ثم المستودع git commit. GitHub يستضيف المستودع البعيد ويضيف الاشتقاق fork وطلبات الدمج والمراجعة. التسمية الجيدة للفروع والترقيم الدلالي semver مثل 1.4.2 يجعلان التعاون والإصدارات واضحة.
قبل أن تبدأ
💻أساسيات الطرفية (Terminal)
تحتاج تتحرك بين المجلدات وتكتب أوامر نصية، Git بحد ذاته أداة سطر أوامر.
الفكرة الجوهرية: Git مقابل GitHub
1Git: أداة التحكم بالإصدارات
برنامج يعمل محليًا على جهازك، يحتفظ بـ"لقطات" (Snapshots) كاملة لتاريخ مشروعك عبر الزمن، تسمى Commits. يمكنك الرجوع لأي لقطة سابقة في أي وقت.
2GitHub: منصة الاستضافة والتعاون
خدمة سحابية تستضيف مستودعات Git (Repositories) وتضيف طبقة تعاون فوقها: مراجعة الكود (Pull Requests)، تتبّع المشاكل (Issues)، وأتمتة الاختبارات.
3منطقة التحضير (Staging Area)
خطوة وسيطة بين تعديل ملف وحفظه في التاريخ الدائم، git add يضع الملف في هذه المنطقة، و git commit يحوّلها للقطة دائمة.
كيف يعمل، خطوة بخطوة: دورة حياة الملف في Git
شكل 1، كل ملف يمر بهذه المراحل الأربع؛ git status يخبرك في أي مرحلة يقف كل ملف حاليًا.
لفهم GitHub Flow عمليًا، يجب أولًا فهم ما هو "الفرع" (Branch) فعليًا داخل Git: الفرع ليس نسخة منفصلة من المشروع بالكامل كما يبدو ظاهريًا، هو مجرد مؤشر خفيف يشير إلى commit واحد محدد، وينتقل تلقائيًا للأمام كل مرة تضيف commit جديدًا عليه. هذا بالضبط سبب سرعة إنشاء فروع Git الهائلة مقارنة بنسخ مجلد كامل، git checkout -b لا ينسخ شيئًا، فقط يخلق مؤشرًا جديدًا يشير لنفس النقطة التي كان فيها main لحظة إنشائه، ثم يتفرّق الاثنان كل commit جديد تضيفه على الفرع الجديد فقط.
GitHub Flow يبني فوق هذا بتنظيم العمل الجماعي حول فرع واحد رئيسي (main) دائم الاستقرار:
main لكل ميزة أو إصلاح جديد.main يبقى سليمًا طوال الوقت.main.main الآن يحتوي الميزة الجديدة.الخطوة الرابعة (الدمج) تحديدًا تخفي تفصيلة مهمة: عند الدمج، يفعل Git أحد شيئين حسب تاريخ الفرعين. إن لم يحدث أي commit جديد على main منذ إنشاء الفرع، فـGit يقوم بما يُسمى "دمج تقديمي" (Fast-Forward)، ببساطة يحرّك مؤشر main ليشير لآخر commit في الفرع، دون إنشاء أي commit جديد؛ كأن العمل حدث على main نفسه من البداية. لكن إن تحرّك main بعض الخطوات أثناء عملك على الفرع (زميل آخر دمج شيئًا قبلك)، لا يمكن لـGit مجرد تحريك المؤشر، فالتاريخان افترقا فعليًا. في هذه الحالة يُنشئ Git "commit دمج" (Merge Commit) جديدًا له والدان (Two Parents) بدل واحد: أحدهما آخر commit على main، والآخر آخر commit على فرعك، وهذا الـcommit الجديد هو ما يجمع تاريخي الفرعين معًا في نقطة واحدة. هذا بالضبط لماذا تحتاج أحيانًا حل "تعارضات الدمج" (Merge Conflicts): لو عدّل الفرعان نفس السطر بطريقتين مختلفتين، لا يستطيع Git تلقائيًا تقرير أي نسخة "صحيحة" فيطلب منك أنت الحسم.
شكل 2، GitHub Flow يحافظ على استقرار main بعزل كل ميزة في فرعها الخاص حتى تُراجَع وتُدمج، فلا يصل إليه كود لم يُفحص بعد.
مثال محلول: أوامر يوم عمل نموذجي
# إنشاء فرع جديد من main git checkout -b feature/login-page # بعد تعديل ملفات git status # عرض الملفات المعدَّلة git add login.py # نقلها لمنطقة التحضير git commit -m "Add login form validation" # لقطة دائمة بوصف واضح # رفع الفرع لـGitHub وفتح Pull Request من هناك git push origin feature/login-page # بعد موافقة الفريق ودمج الـPR على GitHub، حدّث نسختك المحلية git checkout main git pull origin main
git diff أصلاً يُظهر "ماذا" تغيّر بالتفصيل؛ رسالتك يجب أن تضيف السياق الذي لا يظهره الفرق النصي وحده.
تعمّق: Git من الداخل وأدوات التعاون
تعلمت أوامر Git الأساسية، ونرى هنا ما يحدث تحتها: كيف يخزّن Git التاريخ، وما الفرق بين ثلاث مناطق عمل، ولماذا يوجد أمران مختلفان للتراجع، وكيف تتكوّن النزاعات وتُحل، وكيف تُرقَّم الإصدارات.
أولًا: التاريخ سلسلة من اللقطات لا من الفروق
كل commit لقطة كاملة لحالة الملفات في لحظة معينة، ويحمل مؤشرًا إلى الـ commit الذي قبله (أبيه) مع اسم صاحبه ورسالته. الفرع (branch) ليس نسخة من الملفات بل اسم متحرك يشير إلى commit، وكل commit جديد يحرّك الاسم خطوة. وبسبب ذلك يكون إنشاء فرع جديد فوريًا ورخيصًا. أما HEAD فهو مؤشر خاص يدل على الفرع (أو الـ commit) الذي تقف عليه الآن.
شكل 3، تفرّع ثم دمج: الفرعان يتقاسمان التاريخ حتى النقطة B ثم يتباعدان، ويعيدهما commit الدمج M إلى خط واحد.
ثانيًا: ثلاث مناطق لكل تعديل
أي ملف تعدّله يمر بثلاث محطات: مجلد العمل (الملفات التي تراها وتحررها)، ثم منطقة التحضير (ما اخترت أن يدخل اللقطة القادمة)، ثم المستودع (التاريخ المحفوظ). الأمر git add ينقل من الأولى إلى الثانية، و git commit من الثانية إلى الثالثة. ومنطقة التحضير هي ما يتيح لك تقسيم عملك إلى commits صغيرة ذات معنى حتى لو عدّلت عشرة ملفات.
شكل 4، رحلة التعديل: من مجلد العمل إلى GitHub والعودة.
ثالثًا: reset مقابل revert
كلاهما يتراجع، لكن بفلسفتين مختلفتين. git reset يعيد تحريك اسم الفرع إلى commit أقدم كأن ما بعده لم يحدث، وهو مناسب لعملك المحلي قبل مشاركته. أما git revert فيُنشئ commit جديدًا يعكس أثر commit سابق دون لمس التاريخ، فيناسب ما نُشر ويراه الآخرون. القاعدة: لا تعد كتابة تاريخ شاركه غيرك.
| الجانب | git reset | git revert |
|---|---|---|
| التاريخ | يُعاد كتابته | يبقى كما هو ويضاف إليه |
| الأمان بعد المشاركة | خطِر | آمن |
| الاستخدام المعتاد | تنظيف عمل محلي | إلغاء تغيير منشور |
رابعًا: كيف تتكوّن نزاعات الدمج؟
يدمج Git تلقائيًا ما عدّله كل فرع في أماكن مختلفة. النزاع يحدث فقط حين يعدّل الفرعان الأسطر نفسها بطريقتين مختلفتين، فلا يستطيع Git أن يقرر أيهما الصحيح. عندها يضع في الملف علامات تفصل النسختين، وعملك هو أن تقرر المحتوى النهائي، وتحذف العلامات، ثم تضيف الملف وتنشئ commit الدمج. النزاع ليس خطأ بل سؤال يطرحه Git عليك.
<<<<<<< HEAD the line as it is on your branch ======= the line as it is on the branch being merged >>>>>>> feature
خامسًا: أدوات التعاون في GitHub Flow
🍴الاشتقاق (Fork)
نسخة من مستودع غيرك تحت حسابك، ترتبط معه بمرجعين بعيدين: origin نسختك، و upstream الأصل الذي تتابع تحديثاته.
🌿أسماء الفروع
اسم يصف الغرض مثل feature/add-footer أو fix/typo-readme، لأن الفريق كله يقرأه.
🔍مراجعة الأقران
تعلّق على أسطر محددة، وتطلب تعديلات، ثم توافق. المراجعة تلتقط الأخطاء وتنشر المعرفة بين الفريق.
🏷️الإصدارات والترقيم الدلالي
الصيغة MAJOR.MINOR.PATCH: ترفع الأول عند تغيير يكسر التوافق، والثاني عند ميزة جديدة متوافقة، والثالث عند إصلاح خطأ فقط.
شكل 5، دورة GitHub Flow كاملة من الاشتقاق إلى الإصدار.
اكتب رسالة commit بصيغة تصف ما يفعله التغيير ("Add footer link") لا ما فعلته أنت ("I changed stuff"). بعد شهر ستقرأ تاريخك كما يقرؤه الغرباء.
جرّب بنفسك
ما الفرق بين Git وGitHub؟
Git يعمل على جهازك ويحتفظ بلقطات (commits). GitHub يستضيف المستودعات ويضيف المراجعة (Pull Requests) والمشكلات (Issues).
ماذا يفعل git add؟
git add ينقل التعديل إلى منطقة التحضير، وgit commit هو ما يحوّلها إلى لقطة دائمة.
رفعت إصلاحًا لخطأ فقط دون ميزة جديدة، فإلى أي رقم في MAJOR.MINOR.PATCH تضيف؟
PATCH للإصلاح فقط، وMINOR لميزة جديدة متوافقة، وMAJOR لتغيير يكسر التوافق.
بطاقات مراجعة سريعة
تحقق من فهمك
لو عدّلت ملفًا لكن لم تُنفّذ git add بعد، وشغّلت git commit، ماذا يحدث؟
لا شيء يُحفظ لهذا التعديل، commit يحفظ فقط ما في منطقة التحضير، والملف يبقى "معدَّل" غير مُتتبَّع حتى تضيفه أولًا.
لماذا يُفضَّل فتح Pull Request بدل الدفع المباشر لـmain؟
لأنه يتيح مراجعة الكود قبل دخوله للفرع الرئيسي، فيقلّل فرصة إدخال أخطاء أو كسر شيء يعتمد عليه بقية الفريق.
أنشأت commit خاطئًا وأرسلته إلى GitHub ورآه زملاؤك، أيهما تستعمل ولماذا؟
revert، لأنه يضيف commit يعكس التغيير دون إعادة كتابة تاريخ شاركه الآخرون.
هل يظهر نزاع إن عدّل فرعان ملفًا واحدًا في موضعين بعيدين؟
لا، يدمجهما Git تلقائيًا؛ النزاع فقط عند تعديل الأسطر نفسها.
مصادر هذا الدرس
مرتّبة بالنوع. ابدأ بالتوثيق الرسمي إذا تبي المرجع الدقيق، وبالمقال أو الفيديو إذا تبي الشرح أولًا.
مقالات2
خلّصت الدرس؟
سجّل الدخول ليُحفظ تقدّمك.