تخطَّ إلى المحتوى

٢٠ يوليو ٢٠٢٦ · آخر تحديث: ١٩ أكتوبر ٢٠٢٦

الفرق بين Git Rebase و Merge بالعربي

شارك المقال:

أمامك فرع feature-login فيه commitان، وmain تقدّم بـ commit جديد منذ أن تفرّعت منه. تريد جمعهما. Git يعطيك طريقتين: merge وrebase. الكود الناتج في النهاية واحد، والفرق في ثلاثة أشياء: شكل التاريخ، وهل تتغيّر الـ commits الموجودة، وكيف تواجه التعارضات.

كل المخرجات هنا من مستودع حقيقي على Git 2.43.

نقطة البداية

main:            أول commit ← إصلاح على main
feature-login:   أول commit ← صفحة الدخول ← تحقق من المدخلات

merge: commit جديد يربط الفرعين

git switch main
git merge feature-login
*   7e7d476 Merge branch 'feature-login'
|\
| * ece0990 تحقق من المدخلات
| * a2a4945 صفحة الدخول
* | 4161258 إصلاح على main
|/
* 2bb2a39 أول commit

كل commit بقي بنفس الـ hash، وأضاف Git commit دمج (7e7d476) له أبوان. التاريخ يحكي ما حدث فعلًا: عملان جريا بالتوازي ثم اجتمعا. تفاصيل الأنواع (fast-forward والدمج الثلاثي) في درس الدمج.

rebase: إعادة إنشاء commits فرعك فوق main

git switch feature-login
git rebase main

قبل:

ece0990 تحقق من المدخلات
a2a4945 صفحة الدخول

بعد:

* 41d92f5 تحقق من المدخلات
* ec85645 صفحة الدخول
* 4161258 إصلاح على main
* 2bb2a39 أول commit

لاحظ أمرين:

  1. التاريخ خطي: كأنك بدأت العمل بعد إصلاح على main مباشرة.
  2. الـ hashes تغيّرت: a2a4945 صار ec85645. هذه ليست نفس الـ commits منقولة، بل commits جديدة بنفس التغييرات والرسائل. الـ hash يُحسب من محتوى الـ commit ومن أبيه، وأبوها تغيّر.

بعد rebase، دمج الفرع في main يصبح fast-forward بلا commit دمج:

git switch main
git merge feature-login     # Fast-forward

التعارضات: لا أحد منهما يمنعها

إن عدّل فرعك وmain نفس السطر، فسيتعارضان في الطريقتين. الفرق في التجربة:

  • merge يوقفك مرة واحدة بكل التعارضات، تحلّها، ثم commit دمج واحد.
  • rebase يطبّق commits فرعك واحدًا تلو الآخر، ويتوقف عند كل commit يتعارض:
Rebasing (1/1)Auto-merging config.txt
CONFLICT (content): Merge conflict in config.txt
error: could not apply 8acda71... f1
hint: Resolve all conflicts manually, mark them as resolved with
hint: "git add/rm <conflicted_files>", then run "git rebase --continue".
hint: You can instead skip this commit: run "git rebase --skip".
hint: To abort and get back to the state before "git rebase", run "git rebase --abort".

تحلّ التعارض، ثم git add ثم git rebase --continue لينتقل إلى الـ commit التالي. فرع فيه 10 commits تعدّل نفس المنطقة قد يوقفك عدة مرات. وإن تهت في المنتصف، فـ git rebase --abort يعيدك إلى ما قبل البدء تمامًا. قراءة علامات التعارض وحلها بالتفصيل في حل تعارضات الدمج.

فخ: ours وtheirs ينقلبان أثناء rebase

أثناء merge إلى main، كلمة ours تعني main (الفرع الذي أنت عليه) وtheirs تعني الفرع القادم. أثناء rebase ينعكس المعنى: ours هي القاعدة التي تعيد البناء فوقها (main)، وtheirs هي commits فرعك أنت. فإن كتبت git checkout --ours config.txt أثناء rebase وأنت تقصد "نسختي"، ستحصل على نسخة main. اقرأ الملف بعد الحل دائمًا قبل --continue.

بعد rebase: الدفع يُرفض

إن كان فرعك مرفوعًا مسبقًا، فأول git push بعد rebase:

 ! [rejected]        feature -> feature (non-fast-forward)
error: failed to push some refs to '...'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart.

الـ commits على الخادم لم تعد في تاريخ فرعك؛ rebase استبدلها. لا تتبع التلميح وتعمل git pull، فهذا سيدمج النسخ القديمة مع الجديدة وتحصل على كل commit مرتين. إن كان الفرع لك وحدك:

git push --force-with-lease
 + ec8ac4f...bf7bc3a feature -> feature (forced update)

--force-with-lease أأمن من --force: ترفض الدفع إن كان أحد غيرك قد أضاف commits إلى الفرع على الخادم منذ آخر مرة جلبت فيها، بدل أن تمسحها بصمت. باقي أسباب رفض الدفع في حل git push rejected.

وهنا تأتي القاعدة الذهبية: إن كان زميلك يعمل على نفس الفرع، فدفعك القسري يستبدل التاريخ الذي بنى عليه. لهذا: لا rebase لفرع يسحبه غيرك.

أخطأت؟ rebase قابل للتراجع

الـ commits القديمة لا تُحذف فورًا. git reflog يسجّل كل مكان كان فيه HEAD:

bf7bc3a HEAD@{0}: rebase (finish): returning to refs/heads/feature
bf7bc3a HEAD@{1}: rebase (pick): f2
f875053 HEAD@{2}: rebase (continue): f1
1240274 HEAD@{3}: rebase (start): checkout main
ec8ac4f HEAD@{4}: checkout: moving from main to feature

ec8ac4f هو آخر commit في فرعك قبل rebase. للرجوع إليه:

git reset --hard ec8ac4f
# أو مباشرة بعد rebase: git reset --hard ORIG_HEAD

ORIG_HEAD يحفظه Git تلقائيًا قبل العمليات الكبيرة مثل rebase وmerge. التفاصيل في درس reflog.

إذن، أيهما تستخدم؟

الاختيار يتحدد بسؤالين: هل الفرع مشترك؟ وماذا تريد أن يحكي التاريخ؟

الموقفالأنسبالسبب
فرعك تأخّر عن main وتريد آخر التحديثات قبل فتح PRgit rebase mainتحل التعارضات الآن في فرعك، والـ PR يُدمج نظيفًا
git pull على فرعك وزميلك دفع إليه commitsgit pull --rebaseتتجنّب commit دمج بلا معنى مثل Merge branch 'feature' of ...
commits صغيرة كثيرة ("إصلاح"، "إصلاح ثانٍ") قبل المراجعةgit rebase -iتجمعها في commits واضحة (rebase التفاعلي)
دمج فرع مكتمل في mainmerge (أو زر الدمج في PR)يبقى تاريخ واضح لمتى دخلت الميزة ومن راجعها
فرع طويل يعمل عليه عدة أشخاصmerge فقطrebase يكسر تاريخ كل من سحب الفرع
أنت غير متأكدmergeلا يغيّر شيئًا موجودًا، ولا يحتاج دفعًا قسريًا

لاحظ أن الفريق نفسه غالبًا يستخدم الاثنين: rebase لتحديث الفروع الشخصية، وmerge لإدخالها إلى main. وكثير من الفرق تضبط زر الدمج في GitHub على Squash and merge، فيتحوّل الـ PR كله إلى commit واحد في main، وعندها يصبح شكل تاريخ فرعك قبل الدمج أقل أهمية.

لتطبيق عملي خطوة بخطوة مع فرع حقيقي، راجع درس Rebase في مسار Git بالعربي.

📚 مصادر رسمية للتعمّق: التوثيق الرسمي لـ Git

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

هل rebase خطير؟

على فرعك المحلي الذي لم يسحبه أحد، لا. الخطر في حالة واحدة: rebase لفرع رفعته ويعمل عليه غيرك، لأن الـ commits القديمة تُستبدل بأخرى جديدة بـ hashes مختلفة، فيجد زملاؤك تاريخهم لا يطابق تاريخك. وحتى لو أخطأت، فالـ commits القديمة ما زالت في git reflog ويمكن الرجوع إليها.

هل rebase يمنع التعارضات؟

لا. إذا عدّل فرعك وmain نفس السطر، سيحدث تعارض في الحالتين. الفرق أن merge يعرض كل التعارضات مرة واحدة، بينما rebase يطبّق commits فرعك واحدًا تلو الآخر وقد يتوقف عند أكثر من واحد. ما يفعله rebase هو أنك تحل التعارض الآن في فرعك، فيصبح دمج الـ Pull Request لاحقًا بلا تعارض.

لماذا رُفض git push بعد rebase؟

لأن الـ commits على الخادم لم تعد موجودة في تاريخ فرعك المحلي: rebase استبدلها بنسخ جديدة. Git يرفض الدفع حتى لا تمسح شيئًا. إن كان الفرع لك وحدك، ادفع بـ git push --force-with-lease، فهي ترفض الدفع إن أضاف أحد غيرك commits إلى الفرع منذ آخر سحب.

ما هو rebase التفاعلي؟

git rebase -i يعرض لك commits فرعك في قائمة قبل إعادة تطبيقها، فتستطيع دمج عدة commits صغيرة في واحد (squash)، أو تعديل رسالة، أو حذف commit، أو إعادة ترتيبها. يُستخدم غالبًا لتنظيف الفرع قبل فتح Pull Request.

اقرأ أيضًا

تصفّح كل المقالات في المدوّنة، أو ابدأ التعلّم من المسارات و خرائط الطريق.