git rebase

الفروع والدمج

إعادة تطبيق commits فوق أساس آخر


ينقل git rebase سلسلة من الـ commits أو يضمّها إلى commit أساس جديد. يُستخدم كثيرًا للحفاظ على تاريخ خطي بإعادة تطبيق commits فرع الميزة فوق أحدث main. وبخلاف merge، يعيد rebase كتابة التاريخ بإنشاء commits جديدة بـ SHA مختلف. ويتيح rebase التفاعلي (-i) ضم الـ commits أو إعادة ترتيبها أو تعديلها أو إسقاطها قبل إعادة تطبيقها.

الصيغة

git rebase [-i | --interactive] [<options>] [--exec <cmd>] [--onto <newbase> | --keep-base] [<upstream> [<branch>]]
git rebase [-i | --interactive] [<options>] [--exec <cmd>] [--onto <newbase>] --root [<branch>]
git rebase (--continue | --skip | --abort | --quit | --edit-todo | --show-current-patch)
[ ] اختياري| أو<arg> قيمة تُستبدل... قابل للتكرار--flag خيار

الخيارات

انقر على أي خيار لتوسيعه

مبتدئ (3)

استأنف rebase بعد حل تعارضات الدمج. شغّله بعد تجهيز الملفات المحلولة بـ git add.

موصى بهالطريقة العادية للاستئناف بعد حل التعارضات وتجهيزها

مثال

git rebase --continue

ألغِ rebase وعُد إلى الحالة الأصلية قبل بدئه. استخدمه حين تكون التعارضات معقّدة جدًا أو غيّرت رأيك.

موصى بهمخرج طوارئ آمن: يعيد فرعك إلى حالته قبل الـ rebase

مثال

git rebase --abort

تخطَّ الـ commit الحالي وتابع مع التالي. استخدمه حين يصبح commit فارغًا أو لم تعد تحتاج إلى تغييراته.

عند الحاجةيُسقط الـ commit الحالي — فقط عندما تكون تغييراته غير مرغوبة حقًا

مثال

git rebase --skip

متوسط (3)

افتح محررًا لإعادة ترتيب الـ commits أو ضمها أو تعديلها أو إسقاطها قبل إعادة تطبيقها. أقوى طريقة لتنظيف تاريخ الـ commits.

موصى بهأفضل طريقة لترتيب الـ commits المحلية قبل مشاركتها

مثال

git rebase -i HEAD~3

خبّئ التغييرات غير المحفوظة تلقائيًا قبل rebase واستعدها بعده. يغنيك عن التخبئة اليدوية.

عند الحاجةمفيد مع شجرة غير نظيفة، لكن إعادة تطبيق الـ stash قد تسبّب تعارضًا

مثال

git rebase --autostash main

افرض rebase حتى حين يكون الفرع محدَّثًا. ينشئ commits جديدة بـ hashes جديدة لكل الـ commits في النطاق.

عند الحاجةفقط عندما تحتاج hashes جديدة لـ commits محدَّثة أصلًا

مثال

git rebase -f main

متقدم (5)

انقل commits إلى أساس مختلف. يتيح لك إعادة تأسيس commits فرع ما على commit محدد آخر.

عند الحاجةينقل نطاقًا محددًا من الـ commits إلى قاعدة جديدة — تحقّق من النطاق جيدًا

مثال

git rebase --onto main feature~3 feature

انقل commits من نوع fixup!/squash! (المنشأة بـ git commit --fixup أو ‎--squash) وضمّها تلقائيًا إلى أهدافها. يعمل مع -i ودونه منذ Git 2.44.

موصى بهيعمل مع commit --fixup لتنظيف التاريخ دون عناء

مثال

git rebase -i --autosquash main

حافظ على commits الدمج وبنية الفروع أثناء rebase. يعيد إنشاء طوبولوجيا الدمج بدل تسطيح التاريخ.

عند الحاجةعندما يجب أن تنجو بنية الدمج في فرع من الـ rebase

مثال

git rebase -r main

نفّذ أمر shell بعد تطبيق كل commit. مفيد لتشغيل الاختبارات والتأكد أن كل commit يُبنى.

عند الحاجةيشغّل الاختبارات على كل commit يُعاد تطبيقه؛ بطيء في الفروع الطويلة

مثال

git rebase -i -x 'npm test' main

أعد تأسيس كل الـ commits التي يمكن الوصول إليها من HEAD، بما فيها الـ commit الجذر. يتيح لك إعادة كتابة التاريخ كله.

غير موصى بهيعيد كتابة التاريخ كله من أول commit؛ نادرًا ما تحتاجه

مثال

git rebase -i --root

اختيار الخيار المناسب

مقارنة git rebase بـ git merge

متى تستخدم rebase ومتى merge لدمج التغييرات

الأمرالجانبGit RebaseGit Merge
Historyخطي (يعيد كتابة الـ commits)يحافظ على التاريخ المتباعد
Commitsينشئ commits جديدة بـ SHA مختلفيحافظ على الـ commits الأصلية + commit دمج
Destructiveنعم - يغيّر hashes الـ commitsلا - يضيف commit دمج فقط
Conflictsتُحل لكل commit أثناء إعادة التطبيقتُحل مرة واحدة عند نقطة الدمج
Best forالفروع المحلية/الخاصة قبل الدفعالفروع العامة/المشتركة
Traceabilityيصعب رؤية نقاط الدمجسجل واضح لدمج الفروع

أمثلة على سير العمل

1
git fetch origin

احصل على أحدث التغييرات من المستودع البعيد

2
git checkout feature

انتقل إلى فرع ميزتك

3
git rebase origin/main

أعد تطبيق commits الخاصة بك فوق أحدث main

4
git push --force-with-lease

ادفع الفرع المُعاد تأسيسه (فقط إن لم يكن مشتركًا)

1
git log --oneline

احسب عدد الـ commits المراد ضمها

2
git rebase -i HEAD~5

rebase تفاعلي لآخر 5 commits

3
(in editor) pick, squash, squash...

أبقِ الأول وضمّ البقية إليه

4
(edit combined message)

اكتب رسالة commit نظيفة

1
(make your fix)

أصلح المشكلة في الشيفرة

2
git commit --fixup=<commit-hash>

أنشئ commit من نوع fixup للهدف

3
git rebase --autosquash main

يرتّب autosquash ويضم تلقائيًا (لا حاجة إلى -i منذ Git 2.44)

1
git rebase -i HEAD~4

rebase تفاعلي لآخر 4 commits

2
(in editor) reorder lines

انقل أسطر الـ commits إلى مواضع جديدة

3
save and close editor

يعيد Git تطبيقها بالترتيب الجديد

1
git rebase -i HEAD~3

ابدأ rebase تفاعليًا

2
(mark commit with 'edit')

توقّف عند الـ commit المراد تقسيمه

3
git reset HEAD^

ألغِ الـ commit مع إبقاء التغييرات غير مجهّزة في مجلد العمل

4
git add file1 && git commit -m 'Part 1'

أنشئ أول commit أصغر

5
git add file2 && git commit -m 'Part 2'

أنشئ ثاني commit أصغر

6
git rebase --continue

تابع مع الـ commits المتبقية

1
git reflog

اعثر على الـ commit الذي كان قبل rebase

2
git reset --hard HEAD@{n}

عُد إلى حالة ما قبل rebase

حل المشكلات

تعارض أثناء rebase

السبب: تغييرات الـ commit الخاص بك تتعارض مع الفرع الأساس

عدّل الملفات لحل التعارضات، ونفّذ git add عليها، ثم git rebase --continue

git add <resolved-files> && git rebase --continue

Cannot rebase: You have unstaged changes

السبب: في مجلد العمل تعديلات غير محفوظة

احفظها في commit، أو خبّئها، أو استخدم ‎--autostash للتعامل معها

git rebase --autostash main

صار الـ commit فارغًا بعد rebase

السبب: تغييرات هذا الـ commit موجودة أصلًا في الفرع الهدف

يُسقطه rebase العادي تلقائيًا؛ ويتوقف التفاعلي - شغّل ‎--skip لإسقاطه، أو 'git commit --allow-empty' لإبقائه

git rebase --skip

ينتج عن rebase تعارضات كثيرة

السبب: تباعد الفرع كثيرًا عن الأساس

فكّر في استخدام merge بدلًا منه، أو ألغِ ونفّذ rebase على خطوات أصغر

git rebase --abort

ضياع commits بعد rebase

السبب: الـ commits الأصلية يتيمة (لم تُحذف، بل لم يعد شيء يشير إليها)

استخدم الـ reflog لإيجاد الـ commits الأصلية واستعادتها

git reflog && git branch recovery HEAD@{n}

رُفض الدفع (non-fast-forward) بعد rebase

السبب: أعاد rebase كتابة الـ commits، فلم يعد فرعك يحتوي تاريخ النسخة البعيدة

استخدم ‎--force-with-lease لدفع بالإجبار أكثر أمانًا (يرفض إن دفع شخص آخر في الأثناء)

git push --force-with-lease

يقول git rebase ‏«Current branch is up to date»

السبب: لا commits لإعادة تطبيقها - الفرع مؤسَّس على الهدف أصلًا

استخدم -f لفرض rebase رغم ذلك إن احتجت إلى hashes جديدة

git rebase -f main

قائمة مهام rebase التفاعلي فارغة

السبب: لا commits في النطاق المحدد

تحقّق من النطاق - استخدم HEAD~n أو حدد الـ upstream الصحيح

git log --oneline main..HEAD

نصائح وأخطاء شائعة

أفضل الممارسات

  • ✓لا تنفّذ rebase أبدًا على commits دُفعت إلى مستودع مشترك - فهذا يجبر الجميع على إصلاح فروعهم يدويًا
  • ✓استخدم دائمًا 'git rebase --abort' إن صارت التعارضات معقّدة جدًا - يمكنك تجربة نهج آخر
  • ✓استخدم rebase التفاعلي (-i) لتنظيف الـ commits قبل الدفع أو إنشاء pull request
  • ✓فكّر في استخدام '--autostash' للتعامل تلقائيًا مع التغييرات غير المحفوظة أثناء rebase
  • ✓استخدم 'git commit --fixup=<hash>' مع '--autosquash' لسير عمل إصلاح أنظف
  • ✓شغّل الاختبارات بعد rebase للتأكد أن الـ commits المُعاد تأسيسها ما زالت تعمل
  • ✓استخدم '-x npm test' لتشغيل الاختبارات تلقائيًا بعد كل commit يُعاد تطبيقه
  • ✓أبقِ عمليات rebase صغيرة - نفّذ rebase كثيرًا بدل ترك الفروع تتباعد كثيرًا
  • ✓استخدم 'git reflog' للتعافي إن ساءت عملية rebase - لا تُحذف الـ commits الأصلية فورًا أبدًا
  • ✓فضّل merge على rebase في الفروع العامة/المشتركة للحفاظ على التاريخ
  • ✓استخدم '--force-with-lease' بدل '--force' عند دفع فروع أُعيد تأسيسها، من باب الأمان
  • ✓عند الضم، اكتب رسالة commit جديدة وصفية تلخّص كل الـ commits المضمومة

أخطاء متكررة

  • ✗إعادة تأسيس commits دُفعت أصلًا إلى مستودع مشترك. يعيد هذا كتابة التاريخ ويجبر الآخرين على إصلاح فروعهم يدويًا.
  • ✗نسيان تجهيز التعارضات المحلولة قبل تشغيل ‎--continue. شغّل دائمًا 'git add' على الملفات المحلولة أولًا.
  • ✗استخدام ‎--skip دون تأنٍّ، فيُسقط تغييرات الـ commit من الفرع المُعاد تأسيسه. لا تستخدمه إلا حين تريد عمدًا إزالة commit.
  • ✗عدم فهم أن rebase ينشئ commits جديدة بـ hashes مختلفة. تصبح الـ commits الأصلية يتيمة.
  • ✗تنفيذ rebase مع تغييرات غير محفوظة في مجلد العمل. استخدم ‎--autostash أو خبّئها يدويًا أولًا.

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

A:

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

A:

يغيّر rebase hashes الـ commits. إن بنى آخرون عملهم على commits الأصلية، فسيتباعد تاريخهم عن تاريخك. وسيحتاجون إلى إصلاح فروعهم يدويًا، وهذا مرهق وعرضة للأخطاء.

A:

استخدم 'git reflog' لإيجاد الـ commit الذي كان قبل rebase (ابحث عن الرأس القديم للفرع)، ثم 'git reset --hard HEAD@{n}' لاستعادته. افتراضيًا تبقى الـ commits الأصلية قابلة للوصول من الـ reflog لمدة 30 يومًا. وبعد rebase مباشرة يعمل أيضًا 'git reset --hard ORIG_HEAD'.

A:

في rebase التفاعلي: 'pick' يُبقي الـ commit كما هو، و'squash' يضمّه إلى الـ commit السابق (مع إبقاء الرسالتين)، و'fixup' يضمّه لكنه يتخلّص من رسالته، و'reword' يُبقي الـ commit لكنه يتيح لك تعديل رسالته.

A:

شغّل 'git rebase -i HEAD~n' حيث n عدد الـ commits. في المحرر أبقِ 'pick' على أول commit وغيّر البقية إلى 'squash' أو 'fixup'. احفظ وعدّل رسالة الـ commit المدموج.

A:

يتيح ‎--onto نقل الـ commits إلى أساس مختلف. مثلًا، «git rebase --onto main feature~3 feature» ينقل آخر 3 commits فقط من feature فوق main، متخطّيًا ما قبلها.

A:

عدّل الملفات المتعارضة لحلّها، وشغّل 'git add' على الملفات المحلولة، ثم 'git rebase --continue'. وإن صار الأمر فوضويًا جدًا، فإن 'git rebase --abort' يعيدك إلى حالة ما قبل rebase.

A:

أُزيل ‎--preserve-merges في Git 2.34 (يفشل git الآن برسالة «--preserve-merges was replaced by --rebase-merges»). استخدم ‎--rebase-merges ‏(-r) بدلًا منه للحفاظ على commits الدمج وبنية الفروع أثناء rebase. يعيد إنشاء طوبولوجيا الدمج فوق الأساس الجديد.

A:

نعم، استخدم 'git rebase -i' وعلّم الـ commits التي تريد إبقاءها بـ 'pick' والتي تريد إزالتها بـ 'drop'. أو استخدم ‎--onto لتحديد نطاق الـ commits المراد نقله بدقة.

A:

يُحسب hash الـ commit من محتواه ورسالته ومؤلفه وتاريخه والـ commit الأب. وبما أن rebase يغيّر الأب، تحصل كل الـ commits اللاحقة على hashes جديدة حتى لو كان محتواها متطابقًا.