الوحدة 2 · الفروع والدمج
الدمج: Fast-Forward و Merge Commits
15 دقراءة 4 داختر بحكمة
الدرس في لمحة
ادمج فرعًا مكتملًا في main، وتوقّع هل سينفّذ Git عملية fast-forward أم سينشئ merge commit، واختر بينهما عن قصد بـ --no-ff و --ff-only.
لا فيديو هنا، بل الشيء الحقيقي: درس مكتوب وأمثلة قابلة للتشغيل واختبار وتدريب.
لديك فرع footer مكتمل، وفرع main لا يحتويه بعد. الدمج (merge) هو الطريقة التي ينضمّ بها العمل على خط من السجلّ إلى خط آخر. لدى Git طريقتان لفعل ذلك، والطريقة التي تحدث ليست عشوائية: تستطيع توقّعها بالنظر إلى الرسم البياني قبل أن تكتب أي شيء.
قاعدة الاتجاه
أولًا، القاعدة التي تجنّبك أكبر قدر من الارتباك: أنت تدمج فرعًا آخر في الفرع الذي أنت عليه. الأمر git merge footer يعني «أدخِل عمل footer إلى فرعي الحالي». لا يتحرّك إلا الفرع الحالي. أما الفرع الذي سمّيته فيبقى تمامًا حيث كان.
إذن الروتين دائمًا: انتقل إلى الفرع الذي يجب أن يستقبل العمل، ثم ادمج.
git switch main
git merge footerالحالة الأولى: fast-forward
منذ أنشأت footer، لم ينشئ أحد أي commit على main. كل commit على main موجود أصلًا في سجلّ footer. لا يحتاج Git إلى جمع أي شيء؛ إنه ينزلق بـ main إلى الأمام حتى آخر commit في footer:
Updating c39be11..f1a3b07
Fast-forward
index.html | 8 ++++++++
styles.css | 12 ++++++++++++
2 files changed, 20 insertions(+)هذا هو الـ fast-forward (التقديم السريع): لا commit جديد، بل مؤشّر يتحرّك على commits موجودة. ويبقى السجلّ خطًا مستقيمًا، كأنك عملت على main طوال الوقت.
الحالة الثانية: دمج حقيقي
والآن الحالة الأكثر إثارة. بينما كنت تبني dark-mode، صحّحت أيضًا خطأً إملائيًا مباشرة على main. في كل فرع commits يفتقدها الآخر؛ لقد تباعدا (diverged). لا يوجد خط مستقيم يمكن الانزلاق عليه.
ينفّذ Git دمجًا ثلاثيًا (three-way merge). يجد الـ merge base، وهو أحدث commit يتشاركه الفرعان، ويقارن طرف كل فرع به. التغيير الذي جرى على طرف واحد فقط يُؤخذ. والتغييرات التي جرت على الطرفين في مواضع مختلفة تُؤخذ كلها. أما إن غيّر الطرفان الأسطر نفسها، فيتوقّف Git ويسألك؛ هذا هو التعارض (conflict)، وله الدرس التالي كاملًا.
git switch main
git merge dark-modeيفتح Git محرّرك بالرسالة Merge branch 'dark-mode'. احفظ وأغلق، وسترى:
Merge made by the 'ort' strategy.
styles.css | 24 ++++++++++++++++++++++++
1 file changed, 24 insertions(+)ort هو اسم خوارزمية الدمج الافتراضية في Git؛ ولا تحتاج إلى اختيارها. والنتيجة merge commit: commit له أبوان، طرف main السابق وطرف dark-mode. ولقطته تحتوي خطَّي العمل كليهما.
الاختيار عن قصد
النتيجتان تحتويان الملفات النهائية نفسها. الاختلاف في طريقة قراءة السجلّ، وللفرق آراء في ذلك.
- الـ fast-forward يُبقي السجلّ خطيًا وهادئًا. جيد للتغييرات الصغيرة، لكنك لاحقًا لا تستطيع أن ترى أيّ الـ commits كانت تنتمي إلى أيّ feature.
- الـ merge commits تُبقي كل feature مجمّعة بشكل ظاهر، وتسجّل متى وصلت. ويصبح الرسم أكثر ازدحامًا.
خياران يسمحان لك بالاختيار بدل ترك الرسم يقرّر:
git merge --no-ff footer # always create a merge commit, even if fast-forward is possible
git merge --ff-only footer # fast-forward or refuse; never create a merge commitخياري الافتراضي لموقع شخصي يعمل عليه شخص واحد: دع Git ينفّذ fast-forward للفروع الصغيرة، ولا ترهق نفسك بالتفكير في الأمر. أما في الفريق، فاتبع عُرف الفريق، وهو عادةً يُضبط على GitHub ولا يُكتب يدويًا. والأهمّ من السياسة بكثير أن يحمل كل فرع قطعة عمل واحدة متماسكة، فأيًّا كان الشكل الذي يتّخذه السجلّ، تكون كل خطوة فيه مفهومة.
--ff-only عادة أمان جيدة حين لا تتوقّع إلا اللحاق بالتحديثات، مثلًا عند تحديث main المحلي. إن رفض Git، فهذا خبر بحدّ ذاته: شيء ما تباعد دون علمك. وعلى GitHub ستقابل الاختيار نفسه في صورة طرق الدمج (merge methods) في الـ pull request في القسم الثالث.
تستطيع فحص الـ merge base بنفسك قبل الدمج، وهذا يخبرك كم ابتعد الفرعان:
git merge-base main dark-mode
git log --oneline main..dark-mode # commits on dark-mode that main lacks
git log --oneline dark-mode..main # and the reverseإن كان ناتج أمر الـ log الثاني فارغًا، فـ main لم يتحرّك وسيكون الدمج fast-forward.
بعد الدمج
بعد دمج footer، يكون اسم الفرع قد أدّى مهمّته. احذفه:
git branch -d footerينجح -d لأن الـ commits قابلة للوصول من main. الـ commits نفسها تبقى في السجلّ إلى الأبد؛ والذي يذهب هو الورقة اللاصقة فقط. نظّف الفروع المدموجة أولًا بأول، فيبقى ناتج git branch قائمة قصيرة بالعمل الجاري فعلًا.
حتى الآن مرّ كل دمج بسلاسة لأن الفرعين لمسا أسطرًا مختلفة. في الدرس التالي ستجعلهما يتصادمان عن قصد، وتحلّ التعارض خطوة بخطوة.
أهم النقاط
- أنت تدمج دائمًا فرعًا آخر في الفرع الذي أنت عليه؛ ولا يتحرّك إلا الفرع الحالي.
- إن لم يتحرّك الفرع الحالي منذ بدأ الفرع الآخر، يقدّم Git المؤشّر إلى الأمام (fast-forward) ولا ينشئ commit جديدًا.
- إن كان في الفرعين commits جديدة، ينفّذ Git دمجًا ثلاثيًا (three-way merge) انطلاقًا من سلفهما المشترك، ويسجّل merge commit له أبوان.
--no-ffيفرض إنشاء merge commit ليُبقي الـ feature مجمّعة؛ و--ff-onlyيرفض أي شيء غير الـ fast-forward.