دمج الأكواد (Git Merge vs Rebase) وحل التعارضات (Merge Conflicts)
مقدمة
عند العمل ضمن فرق تطوير تعتمد على Git، فإن السؤال المتكرر ليس فقط كيف نحفظ التغييرات، بل كيف ندمجها دون إفساد تاريخ المشروع أو تعطيل خط النشر. وهنا يظهر الفرق الجوهري بين Merge وRebase، وهما من أكثر العمليات تأثيراً على جودة التعاون داخل المستودع.
فهم هذا الفرق ليس قضية نظرية فقط، بل له أثر مباشر على مراجعات الكود، سرعة حل التعارضات، ودقة عمل أنظمة CI/CD. وإذا كنت قد قرأت مقال ما هو DevOps؟ ولماذا تدفع الشركات ثروات لمهندسي الأتمتة السحابية؟ فستعرف أن انضباط تاريخ الشيفرة جزء أساسي من ثقافة الأتمتة وليس مجرد تفضيل شخصي.
ما الفرق المعماري بين Git Merge وGit Rebase؟
كيف يعمل Merge
عملية Merge تقوم بضم تاريخ فرعين كما هو، مع إنشاء merge commit عند الحاجة. هذا الأسلوب يحافظ على السجل الحقيقي لتفرع العمل وتقاطعه، ولذلك يكون مناسباً للمشاريع التي تريد توثيق مسار التطوير كما حدث فعلاً.
النتيجة أن تاريخ المستودع قد يصبح أكثر تشعباً، لكنه واضح من ناحية معرفة متى تم دمج فرع ميزة، ومن قام بذلك، وما هي النقطة التي دخل فيها التغيير إلى الفرع الرئيسي.
كيف يعمل Rebase
أما Rebase فيعيد كتابة التاريخ عبر نقل التزامات فرعك فوق آخر نقطة من الفرع الهدف. عملياً، يبدو السجل وكأن العمل تم بناؤه فوق أحدث إصدار منذ البداية، ما ينتج تاريخاً خطياً ونظيفاً وسهل القراءة.
هذا مفيد جداً عند تجهيز feature branch قبل فتح pull request، لكنه يتطلب حذراً لأنك تغيّر معرفات الالتزامات نفسها.
متى نستخدم كل أسلوب في بيئات الفرق وعمليات النشر؟
في المشاريع الصغيرة أو الفرق التي تعطي أولوية للبساطة والتتبع، يكون Merge خياراً ممتازاً. أما في الفرق التي تعتمد مراجعات صارمة وتريد سجلاً خطياً يسهل تحليله في أنظمة Pipelines وتقارير الإصدار، فغالباً تفضّل Rebase.
القرار هنا يجب أن يكون سياسة فريق لا اجتهاداً فردياً. وهذا يرتبط مباشرة بإدارة الفروع، وهي فكرة توسعنا فيها في مقال ما وراء الالتزام (Commit): كيف تدير فروع المشروع (Branches) باحترافية؟.
- استخدم
Mergeإذا أردت الاحتفاظ بسجل التعاون الحقيقي بين الفروع. - استخدم
Rebaseإذا أردت تاريخاً خطياً ونظيفاً قبل الدمج النهائي. - لا تستخدم
Rebaseعلى فرع مشترك يعمل عليه أكثر من مطور دون اتفاق واضح.
أوامر الدمج وإعادة البناء عملياً
تنفيذ Merge
git checkout main
git pull origin main
git merge feature/payment-api
git push origin main
هذا السيناريو يدمج فرع feature/payment-api داخل main. إذا لم توجد تعارضات فسيتم الدمج مباشرة، وقد يُنشأ merge commit تلقائياً.
تنفيذ Rebase
git checkout feature/payment-api
git fetch origin
git rebase origin/main
git checkout main
git merge feature/payment-api
git push origin main
في هذا الأسلوب، نقوم أولاً بتحديث فرع الميزة فوق آخر نسخة من main. وبعدها يصبح الدمج النهائي بسيطاً وغالباً خطياً. كثير من الفرق تستخدم هذا النمط قبل تشغيل اختبارات البناء الخاصة بتطبيقات Docker أو نشر خدمات Kubernetes.
ما هي Merge Conflicts ولماذا تحدث؟
تعارضات الدمج تظهر عندما يكتشف Git أن فرعين قاما بتعديل نفس السطور أو نفس البنية بطريقة لا يمكن حسمها تلقائياً. هذا يحدث كثيراً في ملفات الإعداد مثل deployment.yaml أو Dockerfile أو ملفات سير العمل الخاصة بـ GitHub Actions.
في بيئات البنية التحتية ككود، التعارض أخطر من مجرد مشكلة شكلية؛ لأن اختيار نسخة خاطئة من متغير أو صورة حاوية قد يؤدي إلى فشل النشر أو إلى Downtime فعلي.
لا تتعامل مع تعارضات ملفات البنية التحتية أو إعدادات النشر كأنها مجرد أسطر متنافسة. أي حسم غير مدروس في ملفات
YAMLأو أسرار البيئة قد يفتح ثغرة أمنية أو يوقف الخدمة على الإنتاج.
خطوات حل التعارضات بشكل احترافي
- نفّذ عملية الدمج أو
rebaseحتى تظهر الملفات المتعارضة. - استخدم الأمر التالي لمعرفة الملفات المتأثرة.
git status
بعدها افتح الملف المتعارض وستجد علامات يضعها Git لتوضيح النسختين.
<<<<<<< HEAD
image: myapp:v2
=======
image: myapp:v3
>>>>>>> feature/update-image
الخطوة الصحيحة ليست حذف العلامات عشوائياً، بل فهم أي نسخة تمثل الحقيقة التشغيلية الحالية. إن كان الفريق قد حدّث إصدار الصورة في فرع لتصحيح ثغرة أمنية، بينما غيّر فرع آخر قيمة قديمة، فيجب الإبقاء على النسخة الأحدث لا النسخة الأسهل.
- عدّل الملف يدوياً واختر الصيغة النهائية المتفق عليها.
- أضف الملف بعد الحسم.
git add deployment.yaml
- أكمل العملية حسب نوعها.
git merge --continue
git rebase --continue
إذا اكتشفت أن التعارضات كثيرة أو أن نقطة الانطلاق كانت خاطئة، يمكنك التراجع بأمان.
git merge --abort
git rebase --abort
مثال مرتبط بملفات النشر والحاويات
لنفترض أن أحد المطورين غيّر نسخة صورة التطبيق، بينما عدّل آخر عدد النسخ التشغيلية داخل ملف نشر خاص بـ Kubernetes. هنا يجب حل التعارض مع الحفاظ على التعديلين معاً، لا اختيار أحدهما فقط.
apiVersion: apps/v1
kind: Deployment
metadata:
name: payment-api
spec:
replicas: 3
template:
spec:
containers:
- name: payment-api
image: registry.example.com/payment-api:v3
هذه النوعية من الملفات شائعة جداً في فرق تعتمد الحاويات. وإذا كنت تبني بيئات معزولة وتبحث في طريقة توحيد التشغيل بين المطورين والخوادم، فراجع مقال مشكلة “الكود يعمل على جهازي فقط” وكيف يحلها Docker نهائياً؟ لأنه يوضح لماذا يصبح تاريخ الدمج النظيف مهماً عند التعامل مع صور وبيئات متعددة.
أفضل الممارسات لتقليل التعارضات من الأساس
- حدّث فرعك باستمرار من
mainبدلاً من الانتظار حتى نهاية الأسبوع. - قسّم العمل إلى تغييرات صغيرة وقابلة للمراجعة، فالفروع الطويلة تزيد احتمالات التصادم.
- افصل بين تعديلات المنطق البرمجي وتعديلات ملفات البنية التحتية متى أمكن.
- وحّد تنسيق الملفات باستخدام أدوات مثل
lintersوformatters. - اجعل اختبارات
CIتعمل بعد الدمج للتأكد من أن الحسم لم يكسر البناء.
إذا كنت تستخدم
Rebaseعلى فرع تم دفعه مسبقاً إلى المستودع البعيد، فغالباً ستحتاج إلىgit push --force-with-leaseوليس--forceالأعمى، لتجنب محو عمل زملائك بالخطأ.
الخلاصة
الاختيار بين Git Merge وGit Rebase ليس معركة من هو الأفضل مطلقاً، بل قرار معماري يعتمد على طريقة عمل الفريق ومستوى الانضباط في الفروع والمراجعات. Merge يحافظ على الحقيقة التاريخية، بينما Rebase يمنحك سجلاً أنظف وأسهل في التتبع.
أما تعارضات الدمج، فهي ليست علامة فشل، بل إشارة إلى وجود تغييرات متقاطعة تحتاج فهماً واعياً للسياق البرمجي والتشغيلي. وكلما كانت فروعك قصيرة، وسياساتك واضحة، وربطك مع اختبارات النشر مضبوطاً، أصبح حل التعارضات أسرع وأكثر أماناً على الكود والخوادم معاً.
5 comments