EduFlow
Git و GitHub
⁦0%⁩

الوحدة 2 · الفروع والدمج

الدمج: Fast-Forward و Merge Commits

15 دقراءة 4 داختر بحكمة

الدرس في لمحة

ادمج فرعًا مكتملًا في main، وتوقّع هل سينفّذ Git عملية fast-forward أم سينشئ merge commit، واختر بينهما عن قصد بـ --no-ff و --ff-only.

  1. 1قاعدة الاتجاه
  2. 2الحالة الأولى: fast-forward
  3. 3الحالة الثانية: دمج حقيقي
  4. 4الاختيار عن قصد
  5. 5بعد الدمج

لا فيديو هنا، بل الشيء الحقيقي: درس مكتوب وأمثلة قابلة للتشغيل واختبار وتدريب.

لديك فرع footer مكتمل، وفرع main لا يحتويه بعد. الدمج (merge) هو الطريقة التي ينضمّ بها العمل على خط من السجلّ إلى خط آخر. لدى Git طريقتان لفعل ذلك، والطريقة التي تحدث ليست عشوائية: تستطيع توقّعها بالنظر إلى الرسم البياني قبل أن تكتب أي شيء.

قاعدة الاتجاه

أولًا، القاعدة التي تجنّبك أكبر قدر من الارتباك: أنت تدمج فرعًا آخر في الفرع الذي أنت عليه. الأمر git merge footer يعني «أدخِل عمل footer إلى فرعي الحالي». لا يتحرّك إلا الفرع الحالي. أما الفرع الذي سمّيته فيبقى تمامًا حيث كان.

إذن الروتين دائمًا: انتقل إلى الفرع الذي يجب أن يستقبل العمل، ثم ادمج.

Terminal
git switch main
git merge footer

الحالة الأولى: fast-forward

منذ أنشأت footer، لم ينشئ أحد أي commit على main. كل commit على main موجود أصلًا في سجلّ footer. لا يحتاج Git إلى جمع أي شيء؛ إنه ينزلق بـ main إلى الأمام حتى آخر commit في footer:

Text
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)، وله الدرس التالي كاملًا.

Terminal
git switch main
git merge dark-mode

يفتح Git محرّرك بالرسالة Merge branch 'dark-mode'. احفظ وأغلق، وسترى:

Text
Merge made by the 'ort' strategy.
 styles.css | 24 ++++++++++++++++++++++++
 1 file changed, 24 insertions(+)

ort هو اسم خوارزمية الدمج الافتراضية في Git؛ ولا تحتاج إلى اختيارها. والنتيجة merge commit: commit له أبوان، طرف main السابق وطرف dark-mode. ولقطته تحتوي خطَّي العمل كليهما.

في الأعلى: main يشير إلى b2e0 و footer متقدّم بـ commitين على الخط نفسه، فينزلق main عند الدمج إلى f1a3 دون commit جديد. في الأسفل: لكل من main و dark-mode commit جديد بعد b2e0، فينشئ الدمج الـ merge commit ذا المعرّف m7c1 وله أبوان، ويتحرّك main إليه. Fast-forward: لم يتحرّك main b2e0 e9d2 f1a3 footer main (بعد) كان main (قبل) هنا، على b2e0 Merge commit: تحرّك الفرعان كلاهما b2e0 c39b d4a7 m7c1 main dark-mode للـ commit m7c1 أبوان: c39b (main) و d4a7
الـ fast-forward مقابل الـ merge commit

الاختيار عن قصد

النتيجتان تحتويان الملفات النهائية نفسها. الاختلاف في طريقة قراءة السجلّ، وللفرق آراء في ذلك.

  • الـ fast-forward يُبقي السجلّ خطيًا وهادئًا. جيد للتغييرات الصغيرة، لكنك لاحقًا لا تستطيع أن ترى أيّ الـ commits كانت تنتمي إلى أيّ feature.
  • الـ merge commits تُبقي كل feature مجمّعة بشكل ظاهر، وتسجّل متى وصلت. ويصبح الرسم أكثر ازدحامًا.

خياران يسمحان لك بالاختيار بدل ترك الرسم يقرّر:

Terminal
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 بنفسك قبل الدمج، وهذا يخبرك كم ابتعد الفرعان:

Terminal
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، يكون اسم الفرع قد أدّى مهمّته. احذفه:

Terminal
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.

قراءات إضافية

تحقّق من فهمك

4 أسئلة

السؤال 1 من 4

أنت على main. منذ أنشأت footer منه، لم يكسب main أي commit، بينما كسب footer ثلاثة. ماذا يفعل git merge footer؟

السؤال 2 من 4

كنت تقصد إدخال dark-mode إلى main، لكنك كنت على dark-mode وشغّلت git merge main. ما الذي حدث؟

السؤال 3 من 4

أيّ commit يستخدمه Git نقطة انطلاق حين ينفّذ دمجًا ثلاثيًا لـ dark-mode في main؟

السؤال 4 من 4

يريد فريقك أن تظهر كل feature في سجلّ main على شكل merge commit واحد، حتى حين يكون الـ fast-forward ممكنًا. أيّ خيار يحقّق ذلك؟

تدريب

اختر بحكمة

توقّع نتيجة الدمج

هذا هو الموقع الشخصي الآن. الأسهم تتّجه من كل commit إلى أبيه.

:::figure قبل الدمج

الـ commit ذو المعرّف a1 ثم b2. من b2 يكمل main إلى c3، ويشير إليه main والـ HEAD. ومن b2 أيضًا، في فرع projects-grid الـ commit ذو المعرّف d4 ثم e5، ويشير projects-grid إلى e5. a1 b2 c3 d4 e5 main HEAD projects-grid :::

لمس الفرعان ملفات مختلفة. تشغّل git merge projects-grid. كيف يبدو الرسم بعد ذلك؟

توقّع نتيجة الدمج