mrsaynothing.dev
git merge مقابل rebase — القرار في عشر ثوانٍ

> git merge مقابل rebase — القرار في عشر ثوانٍ▋

merge أم rebase، سؤال واحد يحسمها: هل غادر هذا الـ commit جهازك؟ جدول القرار، وحالة fast-forward، وإنقاذ reflog عندما يسوء الـ rebase.

mrsaynothing· 5 أكتوبر 2026· 6 دقيقة قراءة

ستكتب أحد هذين الأمرين آلاف المرات في مسيرتك، ونصف النصائح على الإنترنت تحوّلهما إلى ديانة — من جهة أعد كل شيء بـ rebase، فالسجل يجب أن يكون نظيفًا، ومن الجهة الأخرى لا تلمس rebase أبدًا، فهو يدمّر العمل. الطرفان يتجاهلان القاعدة الحقيقية التي تتسع في عشر ثوانٍ. هذه التدوينة قرار لا لاهوت، وهي تنتمي إلى عنقود Undo Anything in Git — العائلة نفسها التي فيها git revert vs reset. كُتب Git نفسه من الصفر في عشرة أيام في أبريل 2005؛ وجدل merge ضد rebase مستمر منذ ذلك الحين.

قائمة الخمس دقائق

git merge مقابل rebase — السؤال الواحد commit جاهز للهبوط git status -sb يعرض التقدّم/التأخّر هل يسحب غيرك هذا الفرع؟ افحص الفرع لا حدسك git log origin/feature..feature قد يكون آخرون سحبوه → لك وحدك → git merge يسجّل ما حدث حقًا git rebase سجل خطي، مراجعة أسهل السجل يبدو خاطئًا بعدها؟ git reflog → reset --hard HEAD@{1}
fig 1 — شجرة merge-أو-rebase. انقر فرعك.

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

كلا الأمران يسلّمان الكود نفسه؛ وتختلفان عمّا ينبغي أن يقوله السجل بعدها. merge يكتب الخلاف بالحبر: commit جديد بأبوين، والتفرّع محفوظ لكل من يقرأ الرسم لاحقًا. rebase يتظاهر أنك بدأت من قاعدة اليوم: تُعاد commits الثلاثة الخاصة بك كثلاثة commits جديدة بـ SHA جديدة، ويصبح القديم بعيد المنال من أي فرع.

شغّلتُ الأمرين في bench يُرمى بعد الاستخدام على git 2.47.3، بالخمسة commits نفسها في المرهين. جولة merge حافظت على كل SHA أصلي وأضافت عقدة واحدة؛ جولة rebase غيّرت هوية commits الفرع — دخل c3 باسم 5d48940 وخرج 6b2be0a. الشجرة نفسها، والسجل مختلف:

بعد merge c1 c2 c3 · c4 c5 commit merge · أبوين بعد rebase c1 c2 c5 c3' 6b2be0a c4' 67c0442 خط واحد — بلا عقدة، الـ SHA القديمة اختفت
fig 2 — الشجرة نفسها، سجلّان. المشار إليها بفاصلة عليا هي التغييرات نفسها بمعرّفات جديدة.

تحقّق: git log --oneline --graph → عقدة |\ تعني merge، والخط المستقيم يعني أن rebase فاز

متى يصبح الـ merge بالتقسيط السريع (fast-forward)؟

إذا لم يتحرك main منذ أن أنشأت الفرع فلا شيء لدمجه — git يزيح العلامة إلى الأمام فحسب. هذا هو fast-forward، وهو سبب حصول أنصار rebase على خطهم النظيف دون إجبار أحد: أعد feature على main فيصبح merge سريعًا (fast-forward) ويبقى الرسم خطًا مستقيمًا. من التجربة:

* 67c0442 c4 feature 2      # بعد rebase ثم merge — fast-forward
* 6b2be0a c3 feature 1      # SHA جديدة — 5d48940 قبل الـ rebase
* 1398836 c5 main moved on
* facd5a4 c2 main work
* f45b000 c1 base

خمسة commits وبلا عقدة واحدة. الثمن: تلك الـ SHA الموشّمة لا توجد في أي مكان سوى جهازك. ارفعها، ونسخة زميلك تحتفظ بالأصلية — نسختان من الـ commit «نفسه»، وهي دقيق الفوضى الذي تعالجه القسم الخامس.

تحقّق: git merge --ff-only feature → تظهر Fast-forward في المخرجات دون إنشاء commit merge

أي commits ما زالت لك وحدك؟

الجواب أمر واحد لا شعور. git log origin/main..HEAD يسرد الـ commits التي لم يرها origin قط — وهي المؤهّلة لـ rebase. والمقابل git log origin/main..origin/main فارغ بالتعريف؛ المهم هو فحص الفرع الذي يبني عليه الآخرون. إذا ضمّت القائمة commit قد تملكه نسخة أخرى — push سابق، أو pull request مفتوح، أو فرع رُفع ثم رُفع بالقوة — فقد صار ليس لك وحدك، مهما زعم رسمك المحلي.

العمل غير المُزوّد هو سبب وجود الـ rebase بالضبط: أعده فوق القاعدة المحدّثة فيقرأ pull request الخاص بك تسلسلًا نظيفًا واحدًا. ولهذا فـ «pull مع rebase» افتراض سليم للفروع الخاصة — git pull --rebase يعيد commits المحلية الخاصة بك فوق ما وصل، بدل نسج عقدة merge في كل مزامنة. حالة stash تعيش في git stash a single file؛ ونسخة fork في sync fork with upstream.

ما الذي يخرّبه rebase على فرع مشترك؟

كل نسخة clone تملك الـ commits القديمة تصبح منذ تلك اللحظة على خلاف مع جهازك حول الواقع. يمنح rebase الـ commits معرّفات جديدة؛ فلم يعد بين فرعك المرفوع وفرعك المُعاد كتابته أيّ سلف مشترك، وبالتالي يتطلب الـ push التالي --force — وعلى كل من سحب النسخة القديمة أن يدمج نسخته من commits «لم تعد موجودة». وستعود مكرّراتها للسطح في كل merge قادم. ليست فرضية نظرية: دمج الـ commits المكرّرة هو العاقبة الكلاسيكية، وفكّها يكلف بعد الظهر.

القاعدة جملة واحدة، والكتاب الرسمي يحملها بوصفها تحذيرًا لا اقتراحًا:

Do not rebase commits that exist outside your repository and that people may have based work on.

— Pro Git، الطبعة الثانية، «Git Branching — Rebasing»

تعليقات المراجعة المثبّتة على SHA بعينها تنفصل في الحركة نفسها — أعد فرع pull request مفتوح وسيتبخّر سياق المراجعة مع المعرّفات القديمة.

كيف تتراجع عن rebase؟

الـ commits القديمة تنجو من rebase — reflog هو مخبأها. الـ rebase يحرّك مؤشرات الفروع ولا يحذف الكائنات. إنقاذ التجربة، بعد reset --hard رمى commitين:

$ git reflog | head -4
1398836 HEAD@{0}: reset: moving to HEAD~2
67c0442 HEAD@{1}: merge feature: Fast-forward
1398836 HEAD@{2}: checkout: moving from feature to main
67c0442 HEAD@{3}: rebase (finish): returning to refs/heads/feature

$ git reset --hard HEAD@{1}
HEAD is now at 67c0442 c4 feature 2

سطر واحد والفرع مستعاد، مع العمل المُدرج في stage وغير المُدرج. مدخلات reflog تنتهي صلاحيتها بعد نحو 90 يومًا افتراضيًا، فالإنقاذ مقيّد بساعة — الآلية نفسها في git undo last commit، وقرار revert/reset الكامل يعيش في git revert vs reset.

تحقّق: git reflog | head -3 → قمة ما قبل rebase تنتظر عند HEAD@{1}، على بعد reset واحد

أي أمر تحتاجه حالتك؟

الجدول هو التدوينة كلها في ستة أسطر — رشّحها حسب حالة فرعك.

الكل feature مشترك محلي fork إنقاذ
الحالة الأمر لماذا يفوز
فرع feature الخاص بك، لم يُرفع قط git rebase main سجل خطي، تسلسل نظيف واحد للمراجعة
فرع يسحبه آخرون git merge بلا إعادة كتابة SHA — بلا تكرارات في أي نسخة clone
main محلي متباعد، لم يُرفع شيء git pull --rebase يزامن دون عقدة؛ تبقى commitsك في الأعلى
fork يلحق upstream git merge upstream/main يتسق مع تدفق fork sync؛ يبقى PR موصولًا
إصلاح عاجل يجب أن يهبط الآن git merge --no-ff علامة ظاهرة لدخول الإصلاح حتى على فرع يقبل fast-forward
rebase ساء git reset --hard HEAD@{1} الـ reflog ما زال يحتفظ بالقمة السابقة (~90 يومًا)
وأين يقع --squash من هذا؟

git merge --squash يأخذ تغييرات الفرع ويضعها في stage ككتلة واحدة غير مُدمجة في commit — بلا commit merge وبلا ربط بسجل الفرع. يقول الاقتراح التلقائي إن «merge vs rebase vs squash» يُبحث يوميًا: squash هو الجواب الثالث، لإسقاط فرع بعد هبوط صافي تغييره. لا يعيد كتابة شيء؛ يرفض فقط توثيق الخطوات الداخلية للفرع.

أيمكن أن يختار الفريق واحدًا فقط؟

كثيرون يفعلون: rebase-للمحلي وmerge-إلى-المشترك هو السياسة الأكثر شيوعًا، وزر merge في GitHub يبقي merge افتراضيًا. وسياسة «rebase لكل شيء» تنجح أيضًا — طالما لم يُعاد كتابة فرع مشترك أبدًا، وهي الجملة الوحيدة التي تنجو من كل جدالات workflow.

نسخة العشر ثوانٍ، مرة أخيرة: هل غادر الـ commit جهازك؟ لا → rebase. نعم → merge. وإذا فاز الخطأ رغم ذلك فالـ reflog هو المخرج — وبقية خريطة التراجع تعيش في hub git undo.

faq

— mrsaynothing

$ احصل على الشرح التطبيقي التالي بالبريد

رسالة واحدة لكل مقال. أصلح المشكلة وامضِ.

self-hosted · بلا أطراف ثالثة · إلغاء الاشتراك بنقرة واحدة

ما هذا؟