الوحدة 2 · الفروع والدمج
الـ Rebase مقابل الـ Merge، والقاعدة الذهبية
17 دقراءة 4 داملأ الفراغات
الدرس في لمحة
نفّذ rebase لفرع feature على main لتُبقي السجلّ خطيًا، وتعامل مع التعارضات في منتصف الـ rebase، ورتّب الـ commits تفاعليًا، واعرف متى تقول القاعدة الذهبية إن عليك الدمج بدلًا من ذلك.
- 1ما الذي يفعله الـ rebase
- 2التعارضات أثناء الـ rebase
- 3الترتيب بالـ rebase التفاعلي
- 4القاعدة الذهبية
- 5الـ merge أم الـ rebase؟
لا فيديو هنا، بل الشيء الحقيقي: درس مكتوب وأمثلة قابلة للتشغيل واختبار وتدريب.
في الدرسين السابقين، كان جمع الفروع يعني الدمج، وكان السجلّ المتباعد يكسب merge commit. هذا صادق وآمن، لكن في repository مزدحم يبدأ git log --graph بالظهور كخريطة قطارات. الـ rebase (إعادة التأسيس) هو الطريقة الأخرى لجمع العمل: بدل أن يصل خطّين، ينقل خطك بحيث لا يبقى إلا خط واحد.
ما الذي يفعله الـ rebase
انفصل فرعك projects-grid عن main عند b2. ومنذ ذلك الحين كسب main الـ commit ذا المعرّف c3، وفي فرعك d4 و e5. من الفرع، شغّل:
git switch projects-grid
git rebase mainSuccessfully rebased and updated refs/heads/projects-grid.وجد Git الـ commits الموجودة على فرعك وليست على main (d4 و e5)، ووضعها جانبًا، ونقل فرعك إلى طرف main، ثم أعاد تشغيل كل تغيير فوقه، commitًا تلو الآخر.
الـ commits المُعاد تشغيلها commits جديدة. التغييرات والرسائل نفسها، لكن الآباء مختلفون، فالمعرّفات مختلفة. هذه ليست تفصيلة؛ إنها القصة كلها لمعرفة متى يكون الـ rebase آمنًا. معرّف الـ commit يتضمّن أباه، لذا لا تستطيع نقل commit، بل تصنع نسخة منه في مكان آخر.
الآن صار main سلفًا لفرعك، فإدخال العمل يتمّ بـ fast-forward:
git switch main
git merge projects-grid # Fast-forwardالسجلّ خط مستقيم، كأنك بدأت العمل على الشبكة (grid) بعد تصحيح الخطأ الإملائي.
التعارضات أثناء الـ rebase
لأن الـ rebase يعيد تشغيل الـ commits واحدًا تلو الآخر، فإن التعارض يوقفه عند commit بعينه. الروتين هو نفسه الذي تعلّمته في الدمج، مع كلمات مختلفة في النهاية:
# Git stops: CONFLICT (content): Merge conflict in projects.html
# edit projects.html, remove the markers, check it
git add projects.html
git rebase --continue # finish this commit, replay the nextgit rebase --abort يعيد الفرع تمامًا إلى حيث كان قبل أن تبدأ. و git rebase --skip يُسقط الـ commit المتعارض، ونادرًا ما يكون هذا ما تريده.
مفاجأة واحدة: أثناء الـ rebase يتبادل “ours” و “theirs” موقعيهما. أنت تعيد تشغيل الـ commits الخاصة بك فوق main، لذا فالـ HEAD (النصف العلوي من التعارض) هو طرف main، والـ commit الخاص بك هو النصف السفلي. اقرأ الأسماء الدالّة، لا حدسك.
الترتيب بالـ rebase التفاعلي
قبل أن تشارك فرعًا، تستطيع إعادة كتابة الـ commits الخاصة به. يفتح git rebase -i قائمة مهام في محرّرك:
git rebase -i mainpick 4b1e2c0 Add projects grid
pick 9a3d7f1 fix typo
pick c2e8b44 Grid gap on mobileغيّر الكلمة التي أمام الـ commit واحفظ. reword يعدّل رسالته، و squash أو fixup يطويه في الـ commit الذي فوقه (مع الاحتفاظ برسالته أو التخلّص منها)، و drop يحذفه، وإعادة ترتيب الأسطر تعيد ترتيب الـ commits. تغيير pick 9a3d7f1 إلى fixup 9a3d7f1 يحوّل ثلاثة commits إلى اثنين نظيفين. هكذا يُبقي المحترفون commits من نوع “fix typo” خارج الـ pull request.
القاعدة الذهبية
الـ rebase يستبدل الـ commits بنسخ منها. هذا غير ضارّ حين تكون الوحيد الذي يملكها. لكنه فوضى حين يكون شخص آخر قد بنى على الأصلية: سجلّه ما زال يحتوي d4 و e5، وسجلّك يحتوي d4' و e5'، والـ pull التالي ينتج commits مكرّرة وتعارضات محيّرة.
إذن: لا تنفّذ rebase على commits موجودة خارج الـ repository الخاص بك وقد يكون الناس بنوا عليها عملًا. وعمليًا:
- نفّذ rebase بحرية على فروعك الخاصة قبل أن ترفعها أو قبل أن يلمسها أحد غيرك.
- لا تنفّذ rebase أبدًا على
mainأو على أي فرع يسحب منه آخرون. - تنفيذ rebase على فرع الـ pull request الخاص بك بعد رفعه أمر شائع ومقبول إن لم يكن أحد غيرك ينشئ commits عليه. سيرفض Git الرفع العادي لأن السجلّ أُعيدت كتابته، فاستخدم:
git push --force-with-lease--force-with-lease يكتب فوق الفرع البعيد فقط إن كان ما زال يشير إلى حيث رأيته آخر مرة. وإن رفع زميل شيئًا في الأثناء، يرفض بدل أن يمحو عمله.
الـ merge أم الـ rebase؟
| Merge | Rebase | |
|---|---|---|
| السجلّ | سجلّ صادق، مع merge commits | خطّي، كأن العمل جرى بالتتابع |
| معرّفات الـ commits | لا تتغيّر | تُعاد كتابتها |
| آمن على الفروع المشتركة | نعم | لا |
| التعارضات | تُحلّ مرة واحدة | قد تتكرّر مع كل commit يُعاد تشغيله |
خيار افتراضي معقول: نفّذ rebase لفرع الـ feature الخاص بك على main لتبقى محدَّثًا ولترتّبه قبل المراجعة؛ وادمجه (غالبًا عبر pull request) لإيصاله. وكثير من الفرق تضبط أيضًا git pull ليعيد تأسيس الـ commits التي لم تُرفع بعد، وهذا ما ستضبطه في القسم التالي، حين يحصل موقعك الشخصي أخيرًا على remote.
أهم النقاط
git rebase mainيعيد تشغيل commits فرعك فوق طرفmain، فينشئ commits جديدة بمعرّفات جديدة.- بعد الـ rebase، يصبح دمج الفرع في
mainعملية fast-forward، فيبقى السجلّ خطًا مستقيمًا. - أثناء تعارض الـ rebase، أصلح الملف، ونفّذ
git addله، ثمgit rebase --continue؛ وgit rebase --abortيلغي الـ rebase بكامله. - القاعدة الذهبية ألّا تنفّذ rebase أبدًا على commits ربما بنى عليها آخرون؛ نفّذه فقط على عملك الخاص غير المشارَك.
- إن اضطررت إلى تحديث فرع رفعته بالفعل، فاستخدم
git push --force-with-lease، ولا تستخدم--forceالمجرّد أبدًا.