عندك 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”.