مقارنة

Git merge مقابل rebase: أيهما تختار لفريقك

آخر تحديث: · Skillaria.Top

يدمج كل من merge وrebase التغييرات، لكن كلاً منهما يتعامل مع التاريخ بطريقته: يحفظ merge التاريخ كما هو ويضيف commit دمج، ويعيد rebase كتابة commits عملك فوق قاعدة أحدث ويجعل التاريخ خطياً. قاعدة عملية: rebase لترتيب commits غير المنشورة الخاصة بك، وmerge لإدخال العمل المكتمل إلى الفروع المشتركة.

باختصار: الفرق بين merge وrebase

النقاش «merge أم rebase» ليس في الحقيقة حول أي أمر أفضل، بل حول التاريخ الذي تريد أن ترى المشروع به. كلا الأمرين يصل في النهاية إلى الكود نفسه. الفرق في المسار الذي يُسجَّل في السجل.

  • Merge يصل بين خطّي تاريخ ويسجل بأمانة أن العمل جرى بالتوازي. لا تتغير الـ commits الموجودة، بل يُضاف commit دمج له أبوان.
  • Rebase يعيد كتابة commits عملك كأنك بدأت العمل من أحدث نسخة للفرع الأساسي. يبدو التاريخ خطاً مستقيماً، لكن الـ commits تحصل على hashes جديدة.

آلية كل أمر — خياراته، وحل التعارضات، والوضع التفاعلي — شُرحت في مقالات منفصلة. وهنا نتناول الاختيار فقط.

كيف يبدو التاريخ في الحالتين

الوضع الابتدائي: تفرعت feature، ونفذت commitين، وفي main ظهر في الأثناء commitان من زملائك.

          D---E        feature
         /
A---B---C---F---G      main

بعد git merge feature في main:

          D---E
         /     \
A---B---C---F---G---M  main

بعد git rebase main في feature وتحريك main:

A---B---C---F---G---D'---E'   main

في الحالة الأولى يظهر في التاريخ أن الميزة جرت بالتوازي ومتى دُمجت. وفي الثانية يُقرأ التاريخ كقصة متسلسلة، لكن لم يعد هناك أثر للعمل المتوازي: فـ D' وE' commits جديدة أُنشئت لحظة إعادة التأسيس.

جدول مقارنة merge مقابل rebase

المعيارgit mergegit rebase
الـ commits الموجودةلا تتغيرتُعاد إنشاؤها بـ hashes جديدة
شكل التاريخمخطط متفرع مع commits دمجخطي
الأمان للفروع المشتركةآمن دائماًخطر على الـ commits المنشورة
هل يلزم force pushلانعم، إذا كان الفرع موجوداً على الخادم
التعارضاتتُحل مرة واحدة، في commit الدمجقد تظهر في كل commit يُنقل
معلومة «من أين جاء»محفوظةتضيع
التراجع عن ميزة كاملةأمر واحد git revert -m 1 على commit الدمجrevert لكل commit أو لنطاق
وضوح git bisect وgit logالمخطط أصعب في القراءةأسهل: خط مستقيم واحد
حاجز الدخولمنخفضأعلى، ويلزم فهم إعادة كتابة التاريخ
تنظيف الـ commits (squash وreword)فقط --squash للفرع كلهمرن عبر rebase -i

مزايا merge وعيوبه

المزايا

  • لا يُتلف التاريخ. كل ما نُشر يبقى في مكانه، ولا يحتاج الزملاء إلى إصلاح شيء بعد فعلك.
  • التعارض يُحل مرة واحدة. حتى لو كان في الفرع 30 commit، فأنت تتعامل مع الحالة النهائية لا مع كل خطوة.
  • يُحفظ السياق. من commit الدمج تعرف أي مهمة دخلت الفرع الرئيسي ومتى، ويسهل التراجع عنها كاملة.

العيوب

  • مخطط مزدحم. مع المزامنات المتكررة يمتلئ التاريخ بـ commits باسم «Merge branch 'main' into feature»، ويتحول git log --graph إلى مخطط يشبه خطوط السكك الحديدية.
  • commits المسودات تدخل التاريخ. رسائل «wip» و«fix» و«fix مرة أخرى» تبقى في الفرع الرئيسي إلى الأبد ما لم تُضغط.

مزايا rebase وعيوبه

المزايا

  • تاريخ خطي مقروء. كل commit في مكانه، وgit log يُقرأ كقائمة تغييرات، وgit bisect يجد الـ commit المعطوب بسهولة أكبر.
  • إمكانية ترتيب الـ commits قبل المراجعة: دمجها، وإعادة تسميتها، وإعادة ترتيبها.
  • لا توجد commits دمج تقنية ناتجة عن المزامنة مع الفرع الأساسي.

العيوب

  • يعيد كتابة التاريخ. إعادة تأسيس فرع مشترك منشور تكسر عمل كل من نسخه.
  • يتطلب force push للفروع الموجودة على الخادم، ولذلك يلزم الانضباط واستخدام --force-with-lease.
  • تعارضات متكررة. قد تتعارض المنطقة نفسها في عدة commits متتالية.
  • قد لا تُبنى الـ commits الوسيطة. بعد النقل إلى قاعدة جديدة لم يختبر أحد الـ commits القديمة في البيئة الجديدة.

متى تستخدم كلاً منهما

Rebase — لعملك المحلي

عندما تحدّث فرعك قبل إرساله — git pull --rebase. وعندما تجهز الفرع للمراجعة وتريد التخلص من commits المسودات — git rebase -i. وعندما يتأخر فرعك الشخصي عن main — git rebase origin/main. في كل هذه الحالات تُعاد كتابة commits لم يرها إلا أنت (أو التي تخصك وحدك).

Merge — للفروع المشتركة والتكامل

عندما تُدمج ميزة جاهزة في main أو develop. وعندما يعمل على الفرع أكثر من شخص. وعندما تحتاج إلى إدخال فرع إصدار في الفرع الرئيسي. هنا المهم ألا تمس commits منشورة، وهذا لا يكون إلا بـ merge.

قاعدة اختيار بسيطة

إذا كانت الـ commits لك وحدك، يمكنك استخدام rebase. وإذا نسخها أحد بالفعل، فاستخدم merge فقط.

مخططات مشتركة للفريق

المخططكيف يعمللمن يناسب
merge فقطتُدمج الميزات بـ --no-ff، والمزامنة بـ git merge mainالمبتدئون في Git، والفرق التي تهمها الحكاية الكاملة للتاريخ والتراجعات البسيطة
rebase + mergeيُعاد تأسيس الفرع على main قبل الإدخال، ثم commit دمج بـ --no-ffالفرق التي تريد تاريخاً نظيفاً وحدوداً واضحة للميزات
rebase + fast-forwardيُعاد تأسيس الفرع ويُدمج بالتقدم السريع، فيصبح التاريخ خطياً تماماًالفرق الصغيرة ذات الـ commits الذرية المرتبة
squash mergeكل pull request يتحول إلى commit واحد في mainالفرق ذات المهام القصيرة التي لا تحتاج تاريخاً تفصيلياً للفرع

على GitHub وGitLab تقابل هذه المخططات الأزرار «Create a merge commit» و«Rebase and merge» و«Squash and merge». من الأفضل أن تسمح في إعدادات المستودع بالخيار المختار فقط، حتى لا يصبح التاريخ خليطاً من الأساليب.

إعدادات تثبّت الاختيار

# pull دون commits دمج زائدة
git config --global pull.rebase true

# أو: pull بالتقدم السريع فقط، وإلا يفشل فتقرر بنفسك
git config --global pull.ff only

# دائماً commit دمج عند إدخال الميزات
git config --global merge.ff false

انتبه إلى merge.ff false: فهو يؤثر أيضاً في git pull في وضع merge. ويفضّل كثيرون عدم ضبطه عالمياً، والكتابة صراحة --no-ff عند إدخال الميزة.

مثال: مهمة واحدة بأسلوبين

لنأخذ سيناريو حياً. تعمل ثلاثة أيام على ميزة الدفع في الفرع feature/payment، وفي هذه الأثناء أُدمجت في main خمس مهام لغيرك. قبل طلب الدمج تحتاج إلى سحب أحدث main والتحقق من أن كل شيء يعمل.

بأسلوب merge

git switch feature/payment
git fetch origin
git merge origin/main
# حللت التعارضات مرة واحدة، ثم git add وgit commit
git push

صار للفرع commit بعنوان «Merge remote-tracking branch 'origin/main' into feature/payment». يمر push بشكل عادي، ولا يلاحظ الزملاء الذين يتابعون الفرع أي شيء. أما إذا تزامنت هكذا كل يوم فستحمل الفرع ثلاثة من هذه الـ commits، وستدخل إلى main معها إذا دُمجت دون squash.

بأسلوب rebase

git switch feature/payment
git fetch origin
git rebase origin/main
# تُحل التعارضات أثناء السير، في كل commit: git add ثم git rebase --continue
git rebase -i origin/main      # اختيارياً: دمج المسودات
git push --force-with-lease

يبدو الفرع كأنه بُدئ هذا الصباح من main الحديث، دون commits زائدة، ويقرأ المراجع التغييرات خطوة بخطوة بسهولة. الثمن هو push بالقوة، والحرص على ألا يكتب أحد آخر في هذا الفرع.

ماذا سيرى main في النهاية

عند الإدخال عبر pull request يبقى الفرق واضحاً. يعطي أسلوب merge مخططاً فيه تفرعات وتزامنات، ويعطي أسلوب rebase سلسلة نظيفة من commits الميزة يمكن إدخالها بالتقدم السريع أو بـ commit دمج واحد. والكود سيكون واحداً في الحالتين إذا حُلت التعارضات بالطريقة نفسها.

كيف تتخذ القرار في الفريق

أجب عن بعض الأسئلة. هل يعمل عدة أشخاص في الفرع نفسه؟ إذا كان الجواب نعم فالـ rebase لذلك الفرع مستبعد. ما مدى خبرة الفريق في Git؟ إذا كان المبتدئون كثيرين فابدأ بـ merge وsquash merge — فهما يغفران الأخطاء. هل تحتاج إلى تاريخ كامل للتدقيق وتحليل الحوادث؟ عندها تكون commits دمج الميزات مفيدة. وهل يهمك git log مقروء وgit bisect سريع؟ إذن فالـ rebase والتاريخ الخطي أنسب.

دوّن القرار في CONTRIBUTING أو README المستودع، وفعّل حماية الفرع الرئيسي: منع force push، وطلب pull request، وتحديد طريقة الدمج المسموحة. القاعدة المثبتة بالإعدادات أقوى من أي اتفاق شفهي.

مفاهيم خاطئة شائعة

  • «Rebase متقدم وmerge للمبتدئين». لا: هما أداتان لمهمتين مختلفتين. ويستخدم المطورون المتمرسون الاثنتين.
  • «commits الدمج قمامة». commits المزامنة نعم، غالباً هي ضجيج. أما commit دمج إدخال الميزة فهو نقطة مفيدة: بها تُرى المهمة كلها وتُتراجع عنها دفعة واحدة.
  • «بعد rebase تقل التعارضات». التعارضات نفسها، لأن الأسطر نفسها تتغير. يوزعها rebase على الـ commits، بينما يحلها merge دفعة واحدة.
  • «Rebase يفقد الـ commits». الـ commits القديمة تبقى في git reflog ويمكن استعادتها، إلى أن يحذفها جامع المهملات.

الخلاصة

الاختيار بين merge وrebase هو اختيار بين تاريخ أمين ومتتابع وتاريخ خطي مقروء. merge آمن ويحفظ السياق، وrebase يمنح النظافة لكنه يعيد كتابة الـ commits. وأفضل ممارسة هي الجمع: أعِد التأسيس ونظّف فرعك طالما هو لك، وأدخل العمل الجاهز إلى الفروع المشتركة بـ merge أو squash حسب قواعد فريقك. المهم أن تتفقوا على المخطط وتثبتوه في إعدادات المستودع. والاستراتيجيتان وعمل الفروع الجماعي مشروحان عملياً في دورة Git.

الأسئلة الشائعة

هل نستخدم git pull بـ merge أم بـ rebase؟

لتحديث فرعك المحلي يفضَّل rebase: فـ commits غير المنشورة تستقر فوق الـ commits البعيدة الحديثة دون commit دمج زائد. ويُفعَّل بالأمر git config --global pull.rebase true.

هل يمكن الجمع بين merge وrebase في مشروع واحد؟

نعم، وهذا النهج الأشيع: rebase لفروعك الخاصة وmerge للفروع المشتركة. المهم أن تكون القاعدة معروفة لكل الفريق.

ماذا يختار المبتدئ؟

ابدأ بـ merge: فهو لا يعيد كتابة التاريخ، ويصعب أن تكسر شيئاً به. تعلّم rebase على فروعك المحلية بعد أن تفهم reflog وforce push.

هل يؤثر اختيار الاستراتيجية في الكود النهائي؟

لا، فعند حل التعارضات بالطريقة نفسها تتطابق حالة الملفات النهائية. الفرق الوحيد هو في تاريخ الـ commits المسجَّل.

ما الفرق بين squash merge وrebase؟

يضغط squash merge الفرع كله في commit جديد واحد في الفرع الرئيسي، بينما ينقل rebase كل commit على حدة. وكلاهما يعطي تاريخاً خطياً، لكن squash يفقد التفاصيل خطوة بخطوة.

تعلّم هذا الموضوع بالفيديو

يُشرح هذا الموضوع بالتفصيل مع التطبيق العملي في دورة «Git من الصفر إلى مطور واثق».

الانتقال إلى الدورة