أساسيات التفريع (Branching) والدمج (Merging) في Git


محمد أحمد العيل

يعرض هذا المقال أساسيات التفريع والدمج في Git. نبدأ بمثال سهل لسيناريو يحدث كثيرا أثناء تطوير البرمجيات ونرى كيفية تطبيقه على التفريع والدمج.

git-branching-merging.png.300fb59d8e1e94

لنفترض حدوث الخطوات التالية:

  • كنت تعمل على موقع ويب وأنجزت الجزء الأهم وهو الآن مفتوح للزوار.
  • أنشأت تفريعا جديدا وبدأت العمل على ميزات ستضيفها لاحقا للموقع.

عند هذه النقطة وردك اتصال يخبرك عن مشكل في الموقع. المشكل خطير ويحتاج حلا سريعا.

  • تنتقل لنسخة الموقع الموجودة على بيئة الإنتاج (إصدار الموقع المفتوح للزوار). أي أنك انتقلت للتفريع الذي أطلقت منه الموقع (وليكن التفريع master).
  • تنشئ تفريعا جديدا انطلاقا من تفريع الإنتاج بهدف إصلاح الخلل (الترقيع).
  • تعمل على التفريع الجديد وتختبر التعديلات حتى تتأكد من جاهزيتك لإضافتها إلى بيئة الإنتاج.
  • تدمج تفريع الترقيع مع تفريع الإنتاج وتدفع التغييرات إلى الموقع.
  • أصلحت المشكل وبإمكانك الآن العودة إلى تفريع تحسين الميزات الذي كنت تعمل عليه قبل ورود الاتصال.

المبادئ الأساسية للتفريع

نفرض أنك تعمل على مشروع سبق أن أضفت إليه إيداعين.

01_basic_branching.thumb.png.5cc577e12c9
سجل إيداعات مستقيم

قررت أنك ستعمل على إضافة الميزة رقم 53. تنشئ لهذا الغرض تفريعا جديدا. لإنشاء تفريع والانتقال للعمل عليه في نفس الوقت نستخدم الأمر git checkout مع الخيار b-:

git checkout -b iss53
Switched to a new branch "iss53"

هذا الأمر هو اختصار للأمرين:

git branch iss53
git checkout iss53

02_basic_branching.thumb.png.dcec227ac42
إنشاء مؤشر تفريع جديد

تنفيذ أمر git checkout يعني نقل المؤشر HEAD ليحيل إلى التفريع iss53.

استمريت في العمل على موقعك، وتنفيذ إيداعات:

vim index.html
git commit -a -m 'added a new footer [issue 53]'

وهو ما يجعل التفريع iss53 يتقدم بتتالي الإيداعات:

03_basic_branching.thumb.png.bf39b4e3a5e
تقدّم التفريع iss53 بتوالي الإيداعات

يأتي الآن الاتصال الذي ينبئ بوجود مشكل في الموقع؛ ويجب التغلب على هذا المشكل في أقرب وقت. لا تحتاج مع Git لنشر الترقيع العاجل مع ترقيع الميزة 53؛ كما أنك لا تحتاج لبذل الكثير من الجهد للتراجع عن التعديلات التي أضفتها أثناء عملك على الميزة 53 حتى تعود إلى حالة الموقع الموجود الآن أمام الزوار. كل ما عليك فعله هو العودة إلى التفريع الرئيس master وترك التفريع iss53 لحين الفراغ من العمل العاجل.

انتبه إلى أن Git لن يسمح لك بالانتقال إلى تفريع جديد إن كان مجلد العمل أو منطقة الإدراج يحوي تغييرات تتعارض مع التفريع الذي تريد الانتقال إليه. توجد طرق للالتفاف حول هذا الأمر، وهي ادّخار الإيداعات Stashing وتصحيحها Amending وهو ما سنتطرّق إليه لاحقا عند الحديث عن الادّخار والتنظيف Cleaning. سنفترض في الوقت الحالي أن جميع التعديلات أودعت وبإمكننا الانتقال إلى التفريع الرئيس:

git checkout master
Switched to branch 'master'

يكون مجلد العمل في المشروع بالوصول إلى هذه النقطة مماثلا تماما لما كان عليه قبل بدء العمل على الميزة 53، ويمكنك الآن التركيز على العلة العاجلة. من المهم تذكر هذا الأمر: يعيد Git عند الانتقال إلى تفريع، مجلد العمل إلى ما كان عليه بعد آخر مرة نفّذت فيها إيداعا على هذا التفريع. فيضيف ملفات وينقل أخرى أو يغيرها تلقائيا للتأكد من أن نسخة مجلد العمل مطابقة لما كان عليه بعد آخر عملية إيداع على التفريع.

لدينا ترقيع عاجل يجب إصداره. ننشئ تفريعا خاصا hotfix للعمل على إصدار ترقيع في أقرب وقت:

git checkout -b hotfix
Switched to a new branch 'hotfix'
vim index.html
git commit -a -m 'fixed the broken email address'
[hotfix 1fb7853] fixed the broken email address
1 file changed, 2 insertions(+)

04_basic_branching.thumb.png.05e5154d400
إنشاء تفريع hotfix انطلاقا من التفريع master

يمكن الآن اختبار التعديلات والتأكد من أن الترقيع يؤدي العمل المطلوب ثم بعد ذلك ندمج التفريع hotfix (باستخدام أمر git merge) مع التفريع الرئيس لنشره على بيئة الإنتاج. ننفذ الأوامر التالية لهذا الغرض:

git checkout master
git merge hotfix
Updating f42c576..3a0874c
Fast-forward
index.html | 2 ++
1 file changed, 2 insertions(+)

لاحظ جملة fast-forward (تقدّم سريع) بعد تنفيذ أمر الدمج. يعود السبب في ذلك إلى أن الإيداع الذي يشير إليه التفريع الذي دمجت فيه يوجد ضمن سوابق الإيداع الذي دمجته. بعبارة أخرى؛ عندما تريد دمج إيداع “أ” مع إيداع “ب” يوجد من بين سوابق الإيداع المدموج ("أ") فإن Git يسهّل الأمر بتقديم مؤشر التفريع “ب” ليحيل إلى الإيداع “أ”؛ يفعل Git هذا الأمر بسبب عدم وجود إيداعات متنافرة بين التفريعين لدمجها. بمعنى أنه في حالتنا لم يطرأ أي إيداع على التفريع master منذ إنشاء التفريع hotfix. نقول في هذه الحالة إن Git أجرى تقدما سريعا fast-forward.

توجد التعديلات الآن في اللقطة التي يشير إليها التفريع master ويمكن نشر الترقيع ليصبح متاحا للعموم.

05_basic_branching.thumb.png.849ecaa059c
التقدم السريع للتفريع master إلى التفريع hotfix

أنت جاهز بعد نشر الترقيع العاجل للعودة إلى العمل الذي انقطعت عنه. لكن يجب أولا حذفُ تفريع hotfix الذي لم تعد تحتاج إليه؛ تفريع master يشير الآن إلى نفس الإيداع. يمكن حذف التفريع hotfix باستخدام أمر git branch مع خيار d-:

git branch -d hotfix
Deleted branch hotfix (3a0874c).

يمكن العودة إلى التفريع iss53 واستكمال العمل على الميزة 53:

git checkout iss53
Switched to branch "iss53"
vim index.html
git commit -a -m 'finished the new footer [issue 53]'
[iss53 ad82d7a] finished the new footer [issue 53]
1 file changed, 1 insertion(+)

06_basic_branching.thumb.png.d0539e311c0
استكمال العمل على الميزة 53

يجب الانتباه هنا إلى أن العمل المضاف إلى التفريع hotfix غير موجود في ملفات التفريع iss53. يوجد لديك خياران لإضافة تعديلات hotfix؛ إما دمج التفريع الرئيس في تفريع iss53 أو الانتظار حتى تكمل العمل على التفريع iss53 ثم تدمجه في التفريع الرئيس.

المبادئ الأساسية لدمج التفريعات

أكملت العمل على الميزة 53 وأنت جاهز لدمجها في التفريع الرئيس. يجري دمج التفريع iss53 في التفريع الرئيس بنفس الطريقة التي دمجنا بها التفريع hotfix في التفريع الرئيس: الانتقال إلى التفريع الرئيس بالأمر git checkout ثم تنفيذ أمر الدمج:

git checkout master
Switched to branch 'master'
git merge iss53
Merge made by the 'recursive' strategy.
index.html |    1 +
1 file changed, 1 insertion(+)

يوجد اختلاف بين الدمج في هذه الحالة ودمج التفريع hotfix السابق. مصدر الاختلاف هو حدوث إيداعات على كل من التفريعين بالتوازي؛ الإيداع الذي يشير إليه التفريع الرئيس الآن ليس هو الإيداع الذي كان يشير إليه عند إنشاء التفريع iss53. يعني هذا أن على Git اتخاذ طريقة مغايرة للتقدم السريع آنفة الذكر. ينفذ Git في هذه الحالة ما يعرف بالدمج الثلاثي Three-way merge فيستخدم اللقطتين المشار إليهما في الصورة أدناه والإيداع المشترك بين الفرعين.

07_basic_branching.thumb.png.cb4d3f8cfda
3 لقطات مستخدمة في الدمج

ينشئ Git، بدلا من تقديم المؤشر إلى آخر إيداع في التفريع iss53 مثل ما فعل مع التفريع hotfix، ينشئ لقطة جديدة ناتجة عن الدمج الثلاثي، وينشئ تلقائيا إيداعا جديدا يشير إلى اللقطة الجديدة. يُسمّى هذا الإيداع بإيداع الدمج Merge commit، ويتميز بكونه جزءا من متتاليتي إيداعات (أي أن له سابقين).

08_basic_branching.thumb.png.2ffe172376c
إيداع دمج

تجدر الإشارة هنا إلى أن Git يحدّد الإيداع الأفضل من بين الإيداعات السابقة لاستخدامه قاعدة للدمج. يختلف الأمر عن نظم إدارة نسخ أخرى مثل CVS وSubversion (قبل الإصدار 1.5) يُطلب فيها من المطور تحديد أفضل قاعدة إيداعات للدمج وفقا لها؛ وهو ما يجعل دمج التفريعات في Git أسهل كثيرا من النظم الأخرى.

لم نعد نحتاج الآن، بعد دمج الميزة في التفريع الرئيس، للتفريعة 53. يمكن إغلاق التذكرة Ticket في نظام إدارة التذاكر لديك وحذف التفريع:

git branch -d iss53

التعارض في عمليات الدمج

لا تجري الأمور دائما بنفس السهولة المذكورة في الفقرة السابقة. إن غيّرت نفس الأجزاء من نفس الملف في فرعين تريد دمجهما فلن يكون بمقدور Git دمجهما بطريقة صحيحة. مثلا، إن عدّل العمل على الميزة 53 والترقيع في التفريع hotfix نفس الجزء من نفس الملف فسيظهر لديك تعارض Conflict في الدمج كما في المثال أدناه:

git merge iss53
Auto-merging index.html
CONFLICT (content): Merge conflict in index.html
Automatic merge failed; fix conflicts and then commit the result.

لم ينشئ Git في المثال أعلاه إيداعا للدمج؛ بل أوقف عملية الدمج إلى أن تحل مشكلة التعارض. إن أردت رؤية الملفات التي لم تطلها عملية الدمج بعد فيمكنك تنفيذ الأمر git status:

git status
On branch master
You have unmerged paths.
(fix conflicts and run "git commit")

Unmerged paths:
(use "git add <file>..." to mark resolution)

    both modified:      index.html

no changes added to commit (use "git add" and/or "git commit -a")

تظهر جميع الملفات التي يوجد بها تعارض يمنع دمجها تحت بند Unmerged (غير مدموج). يضيف Git علامات قياسية لحل التعارض إلى الملفات المعنية ليمكن للمطور فتحها ومن ثم حلّ التعارضات. يحوي الملف فقرة تشبه ما يلي:

<<<<<<< HEAD:index.html
<div id="footer">contact : email.support@github.com</div>
=======
<div id="footer">
please contact us at support@github.com
</div>
>>>>>>> iss53:index.html

يعني هذا أن النسخة التي يحيل إليها المؤشر HEAD هي تلك الموجودة في الأعلى (كل ما يوجد فوق الخط ======)؛ بينما يظهر محتوى الملف الموجود في التفريع iss53 في الأسفل. للتذكير، يحيل المؤشر HEAD إلى التفريع الرئيس الآن، ويعود السبب في ذلك إلى أننا انتقلنا إليه بتنفيذ الأمر git checkout قبل محاولة الدمج.

يجب اختيار محتوى أحد الملفين أو دمجهما يدويا. يمكن على سبيل المثال تغيير كامل الجزء المعلّم (يبدأ بـ<<<<<<< وينتهي بـ >>>>>>>) ووضع محتوى جديد مكانه:

<div id="footer">
please contact us at email.support@github.com
</div>

يحوي هذا الحل جزءًا من كل فقرة، ويُلاحظ أن الأسطر >>>>>>>، <<<<<<< و ======= اختفت بالكامل. نفذ أمر git add على كل ملف بعد التخلص من التعارض. إضافة ملف به تعارض إلى منطقة الإدراج في Git يعني أن التعارض في الملف حُلّ.

نفّذ أمر git mergetool إن أردت استخدام أداة رسومية لحل التعارضات. يُظهِر الأمر الأداة الرسومية المناسبة لحل التعارضات:

git mergetool

This message is displayed because 'merge.tool' is not configured.
See 'git mergetool --tool-help' or 'git help config' for more details.
'git mergetool' will now attempt to use one of the following tools:
opendiff kdiff3 tkdiff xxdiff meld tortoisemerge gvimdiff diffuse diffmerge ecmerge p4merge araxis bc3 codecompare vimdiff emerge
Merging:
index.html

Normal merge conflict for 'index.html':
{local}: modified file
{remote}: modified file
Hit return to start merge resolution tool (meld):

اضغط زر Enter للبدء في استخدام الأداة الافتراضية (في المثال أعلاه يظهر اسم الأداة meld بين قوسين لأن المستخدم يشغّل نظام لينكس). إن أردت استخدام أداة مغايرة فيمكنك الاختيار بين الأدوات المدعومة التي يسردها أمر git mergetool مباشرة بعد جملة one of the following tools ثم كتابة اسم الأداة والضغط على زر Enter.

يسألك Git بعد الخروج من الأداة ما إذا كان الدمج ناجحا. فإن أجبت بنعم (زر y على لوحة المفاتيح) فإنه يعلمّ الملفات للإشارة لتجاوز التعارض ويضيفها بالتالي إلى منطقة الإدراج. يمكن تنفيذ أمر git statusمرة أخرى للتأكد من أن جميع التعارضات حُلّت:

git status
On branch master
All conflicts fixed but you are still merging.
(use "git commit" to conclude merge)

Changes to be committed:

    modified:   index.html

ثم تنفيذ أمر الإيداع git commit لاستكمال الدمج، بعد التأكد من أن جميع الملفات التي يوجد بها تعارض أضيفت إلى منطقة الإدراج. تبدو رسالة الإيداع المبدئية على النحو التالي (عند تنفيذ أمر git commit بعد دمج الملفات المتعارضة):

Merge branch 'iss53'

Conflicts:
    index.html
#
# It looks like you may be committing a merge.
# If this is not correct, please remove the file
#   .git/MERGE_HEAD
# and try again.


# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# On branch master
# All conflicts fixed but you are still merging.
#
# Changes to be committed:
#   modified:   index.html
#

يمكنك تعديل الرسالة لإضافة تفصيلات عن كيفية حل التعارض إن كنت ترى أن ذلك سيفيد الآخرين عند النظر في هذا الدمج في المستقبل. لماذا فعلت ما فعلت، خصوصا إن لم يكن بديهيا.

ترجمة -بتصرف- للفصل Git Branching - Basic Branching and Merging من كتاب Pro Git لصاحبه Scott Chacon.





تفاعل الأعضاء


لا توجد أيّة تعليقات بعد



يجب أن تكون عضوًا لدينا لتتمكّن من التعليق

انشاء حساب جديد

يستغرق التسجيل بضع ثوان فقط


سجّل حسابًا جديدًا

تسجيل الدخول

تملك حسابا مسجّلا بالفعل؟


سجّل دخولك الآن