الدمج · فصل ٤

merge ولا rebase؟

fast-forward · merge commit · rebase · new hashes · golden rule · squash
🎯 الهدف: تفهم الاتنين بيعملوا إيه في التاريخ بالظبط، وتعرف تختار بينهم.

عندك branch اسمه feature/cart، وفي نفس الوقت زمايلك رفعوا commits جديدة على main. عايز تجمع الشغلتين. Git بيدّيك طريقتين، والاتنين بيوصلوا لنفس الكود في الآخر. الفرق كله في شكل التاريخ.

merge بيحافظ على التاريخ زي ما حصل، ويضيف commit بيربط الطريقين. rebase بيعيد كتابة الـ commits بتاعتك كأنك بدأت شغلك من آخر main.

نقطة البداية

A ← B ← C ← D        ← main     ↖       E ← F         ← feature/cart

إنت بدأت من B وعملت E و F. وفي نفس الوقت main اتحرك لـ C و D.

الـ merge

git switch feature/cartgit merge main
A ← B ← C ← D ←─────── M   ← feature/cart     ↖                 ╱       E ← F ─────────

Git عمل commit جديد اسمه M (merge commit)، وليه اتنين parents: D و F. الـ commits بتاعتك E و F زي ما هي، بنفس الـ hashes.

مميزات
  • مش بيغيّر أي commit موجود، فآمن على أي branch حتى لو مشترك.
  • التاريخ بيقول الحقيقة: الشغل ده اتعمل بالتوازي واتدمج هنا.
  • لو فيه conflicts بتحلها مرة واحدة.
عيوب
  • لو بتعمل merge من main كل يوم، التاريخ بيتملي merge commits ملهاش معنى.
  • الـ git log بيبقى شكله زي شبكة، وصعب تقراه.

الـ fast-forward

لو main متحركش من ساعة ما بدأت، مفيش طريقين أصلًا:

A ← B                ← main     ↖       E ← F         ← feature/cart

git switch main && git merge feature/cart هنا مش بيعمل merge commit. بيزق main لقدام لحد F وخلاص. ده اسمه fast-forward. ولو عايز merge commit حتى في الحالة دي، عشان يفضل باين إن ده كان feature لوحده، استخدم git merge --no-ff.

الـ rebase

git switch feature/cartgit rebase main
A ← B ← C ← D        ← main              ↖                E' ← F'   ← feature/cart

Git شال E و F، وعمل commits جديدة E' و F' فوق D، فيها نفس التعديلات. كأنك بدأت شغلك النهارده من آخر main.

مميزات
  • التاريخ خط واحد مستقيم، سهل تقراه.
  • مفيش merge commits ملهاش معنى.
  • لما تعمل merge لـ main بعد كده، بيبقى fast-forward.
عيوب
  • بيغيّر الـ hashes، فخطر على أي branch حد تاني شغال عليه.
  • لو عندك commits كتير، ممكن تحل نفس الـ conflict أكتر من مرة، مرة لكل commit.
  • التاريخ مبقاش بيقول اللي حصل بالظبط.

القاعدة الذهبية

طب لو عملت rebase لـ branch أنا رفعته؟

لو الـ branch بتاعك لوحدك (زي feature branch محدش شغال عليه غيرك)، عادي. بس الـ push العادي هيترفض، لأن التاريخ اللي على GitHub مختلف عن بتاعك. هنا محتاج force:

git push --force-with-lease

--force-with-lease أأمن من --force. بيرفض الـ push لو حد رفع حاجة جديدة على الـ branch من آخر مرة عملت fetch. يعني مش هتمسح شغل حد من غير ما تاخد بالك.

git pull بيستخدم أنهي واحد؟

git pull = fetch + دمج. لو عندك commits محلية ومحدش رفع حاجة، الدمج بيبقى fast-forward. لو الاتنين اتحركوا، بيعمل merge commit، وده سبب commits زي “Merge branch ‘main’ of github.com/…” اللي بتملى التاريخ.

لو عايز pull يعمل rebase بدل كده:

git pull --rebase# أو خليه الافتراضيgit config --global pull.rebase true

ده آمن، لأنه بيعمل rebase للـ commits المحلية بتاعتك بس، اللي لسه مترفعتش.

الـ squash merge

في GitHub، لما تدوس “Squash and merge” على pull request، كل الـ commits بتاعة الـ feature بتتجمع في commit واحد على main. كده main بيفضل نضيف، commit لكل feature، حتى لو جوه الـ branch كان فيه 20 commit زي “fix typo” و “wip”.

تختار إزاي؟

خلّصت الفصل لما

🧠 اختبر نفسك
١) بعد merge وبعد rebase، الكود في الآخر مختلف؟
لا، نفس الكود. اللي بيختلف هو شكل التاريخ.
٢) ليه الـ rebase خطر على branch زمايلك سحبوه؟
لأنه بيعمل commits جديدة بـ hashes جديدة، والـ commits القديمة اللي عندهم مبقتش موجودة عندك، فالتاريخ بيتلخبط.
٣) امتى merge مش بيعمل merge commit؟
لما يبقى fast-forward، يعني الـ branch التاني متحركش من ساعة ما بدأت.
٤) عملت rebase لـ feature branch بتاعك ورفعته قبل كده. ترفعه تاني إزاي؟
git push —force-with-lease.