يعرض هذا المقال أساسيات التفريع والدمج في Git. نبدأ بمثال سهل لسيناريو يحدث كثيرا أثناء تطوير البرمجيات ونرى كيفية تطبيقه على التفريع والدمج.
لنفترض حدوث الخطوات التالية:
- كنت تعمل على موقع ويب وأنجزت الجزء الأهم وهو الآن مفتوح للزوار.
- أنشأت تفريعا جديدا وبدأت العمل على ميزات ستضيفها لاحقا للموقع.
عند هذه النقطة وردك اتصال يخبرك عن مشكل في الموقع. المشكل خطير ويحتاج حلا سريعا.
-
تنتقل لنسخة الموقع الموجودة على بيئة الإنتاج (إصدار الموقع المفتوح للزوار). أي أنك انتقلت للتفريع الذي أطلقت منه الموقع (وليكن التفريع
master
). - تنشئ تفريعا جديدا انطلاقا من تفريع الإنتاج بهدف إصلاح الخلل (الترقيع).
- تعمل على التفريع الجديد وتختبر التعديلات حتى تتأكد من جاهزيتك لإضافتها إلى بيئة الإنتاج.
- تدمج تفريع الترقيع مع تفريع الإنتاج وتدفع التغييرات إلى الموقع.
- أصلحت المشكل وبإمكانك الآن العودة إلى تفريع تحسين الميزات الذي كنت تعمل عليه قبل ورود الاتصال.
المبادئ الأساسية للتفريع
نفرض أنك تعمل على مشروع سبق أن أضفت إليه إيداعين.
قررت أنك ستعمل على إضافة الميزة رقم 53. تنشئ لهذا الغرض تفريعا جديدا. لإنشاء تفريع والانتقال للعمل عليه في نفس الوقت نستخدم الأمر git checkout
مع الخيار b-
:
git checkout -b iss53
Switched to a new branch "iss53"
هذا الأمر هو اختصار للأمرين:
git branch iss53
git checkout iss53
تنفيذ أمر git checkout
يعني نقل المؤشر HEAD
ليحيل إلى التفريع iss53
.
استمريت في العمل على موقعك، وتنفيذ إيداعات:
vim index.html
git commit -a -m 'added a new footer [issue 53]'
وهو ما يجعل التفريع iss53
يتقدم بتتالي الإيداعات:
تقدّم التفريع 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(+)
إنشاء تفريع 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
ويمكن نشر الترقيع ليصبح متاحا للعموم.
التقدم السريع للتفريع 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(+)
يجب الانتباه هنا إلى أن العمل المضاف إلى التفريع 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 فيستخدم اللقطتين المشار إليهما في الصورة أدناه والإيداع المشترك بين الفرعين.
ينشئ Git، بدلا من تقديم المؤشر إلى آخر إيداع في التفريع iss53
مثل ما فعل مع التفريع hotfix
، ينشئ لقطة جديدة ناتجة عن الدمج الثلاثي، وينشئ تلقائيا إيداعا جديدا يشير إلى اللقطة الجديدة. يُسمّى هذا الإيداع بإيداع الدمج Merge commit، ويتميز بكونه جزءا من متتاليتي إيداعات (أي أن له سابقين).
تجدر الإشارة هنا إلى أن 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.
أفضل التعليقات
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.