مقدمة عن التفريع (Branching) في Git


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

يدعم Git على غرار غالبية أنظمة إدارة النسخ التفريع Branching.

git-branching.png.9207636d12d0a3f8291a73

يُقصَد بالتفريع الانتقال للعمل على خط تطوير مغاير لخط التطوير الرئيس والاستمرار في العمل على هذا الخط دون تداخل مع الخط الرئيس. يتطلّب التفريع في كثير من أنظمة إدارة النسخ إنشاء نسخة جديدة من مجلد الشفرة المصدرية، وهو أمر مكلّف ويأخذ الكثير من الوقت في المشاريع الكبيرة.

أساسيات التفريع

نحتاج لفهم آلية التفريع للعودة قليلا إلى الوراء ومراجعة الكيفية التي يخزّن بها Git بياناته. ذكرنا في درس مبادئ Git الأساسية أن البرنامج لا يخزّن البيانات على هيئة مجموعة فروق أو تغييرات؛ لكنه بدلا من ذلك يخزّن متتالية من اللقطات Snapshots.

ما يحدُث عند إيداع البيانات هو أن Git يخزّن كائن إيداع Commit object يحوي مؤشرا على لقطة للمحتوى الذي أدرجته. يوجد بهذا الكائن أيضا اسمُ كاتب الإيداع Author وبريده الإلكتروني، الرسالة التي أضفتها مع الإيداع ومؤشرات Pointers على الإيداع أو الإيداعات التي تأتي مباشرة قبل الإيداع الذي يمثّله الكائن المذكور (الإيداع أو الإيداعات السابقة): لا سابق بالنسبة لأول إيداع، سابق واحد لإيداع عاديّ وإيداعات سابقة متعدّدة لإيداع ناتج عن دمج Merge تفريعين أو أكثر.

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

git add README test.rb LICENSE
git commit -m 'The initial commit of my project'

ينشئ Git عند تنفيذ أمر commit جمع تحقق لكل مجلّد فرعي (المجلّد الجذر فقط في المثال الحالي) ويخزّن كائنات الشجرة في مستودع Git؛ ثم ينشئ بعد ذلك كائن إيداع لديه بيانات وصفية Metadata ومؤشرًا يحيل إلى شجرة جذر المشروع مما يسمح له بإنشاء لقطة من المجلد عند الحاجة.

يحوي مستودع Git الآن خمسة كائنات: كتلة واحدة لمحتوى كلٍّ من الملفات الثلاثة، شجرة واحدة تسرُد لائحة بمحتوى المجلّد وتحدّد الكتل التي تخزِّن أسماء الملفات وكائن إيداع يوجد فيه مؤشر إلى جذر الشجرة إضافة لكلّ البيانات الوصفية الخاصة بالإيداع.

01_commits_and_tree.thumb.png.de574b7405
شجرة البيانات الخاصة بالإيداع

إن أجريت تعديلات ثم نفذت أمر commit من جديد فإن الإيداع الجديد سيخزّن مؤشرا على الإيداع الذي سبقه.

02_commits_parents.thumb.png.62fe04b207e
إيداع والإيداعات السابقة عليه

تفريع Git ليس سوى مؤشر على واحد من هذه الإيداعات؛ يتميز هذا المؤشر بكونه قابلا للنقل. يدعى التفريع المبدئي في Git بـmaster. ينشئ Git عند بدء الإيداعات تفريعا يؤشِّر على آخر إيداع؛ وفي كلّ مرة تضيف إيداعا جديدا ينتقل المؤشر تلقائيا إليه.

ملحوظة: تفريع master ليس تفريعًا خاصًّا فهو مماثل لأي تفريع آخر في Git. يعود السبب في كون كلّ مستودعات Git تقريبا تحوي تفريعًا بهذا الاسم إلى أنّ أمر git init ينشئ مبدئيا تفريعا بهذا الاسم، والكثيرون لا يكلّفون أنفسهم عناء تغييره.

03_branch_history.thumb.png.d6ba0f269e11
تفريع وسجل الإيداعات الخاصة به

إنشاء تفريع جديد في Git

مالذي يحدث بالضبط عندما تنشئ تفريعا جديدا؟ يعني هذا أنك تنشئ مؤشرا جديدا للتنقل به. فلنفترض أننا أنشأنا تفريعا جديدا باسم testing باستخدام أمر git branch التالي:

git branch testing

ينشئ الأمر مؤشرا جديدا يحيل إلى نفس الإيداع الذي توجد عليه. أي أن لدينا مؤشرين يحيلان إلى نفس المتتالية من الإيداعات.

04_two_branches.thumb.png.3e306d0e9b1c07
تفريعان يشيران إلى نفس متتالية الإيداعات

كيف يعرف Git التفريع الذي توجد عليه الآن؟ يحتفظ Git لهذا الغرض بمؤشر خاص يُسمّى HEAD (المقدّمة). ينبغي الانتباه إلى أن HEAD في Git مختلف تماما عنه في أنظمة إدارة نسخ أخرى مثل Subversion و CVS. يحيل HEAD إلى التفريع المحلي الذي تعمل عليه الآن. في المثال أعلاه فنحن لا زلنا على التفريع master حتى بعد إنشاء تفريع testing الجديد؛ فأمر git branch ينشئ تفريعا جديدا ولكنّه لا ينقُل إلى التفريع الجديد.

05_head_master.thumb.png.1fd914ecf3c9745
مؤشر HEAD يشير إلى تفريع master

تسهُل رؤية هذا الأمر بتنفيذ أمر git log الذي يعرض عند تحديد الخيار decorate-- الإيداعات التي تحيل إليها مؤشرات التفريعات:

git log --oneline --decorate
f30ab (HEAD  -> master, testing) add feature #32 - ability to add new formats to the central interface
34ac2 Fixed bug #1328 - stack overflow under certain conditions
98ca9 The initial commit of my project

يظهر اسما التفريعين master وtesting بجانب الإيداع f30ab.

التبديل بين التفريعات

يُستخدم أمر git checkout للانتقال إلى تفريع وبدء العمل عليه. ننفذ الأمر التالي للانتقال إلى التفريع testing الذي أنشأناه للتو:

git checkout testing

ينقل أمر git checkout أعلاه مؤشرَ HEAD إلى تفريع testing.

06_head_testing.thumb.png.44f889b9e029c1
يشير HEAD الآن إلى تفريع testing

ما دلالة نقل مؤشّر HEAD؟ سنضيف إيداعا جديدا وسنرى: 

vim test.rb
git commit -a -m 'made a change'

07_advance_testing.thumb.png.57d8809ff37
يتقدم مؤشر HEAD إلى الإيداع الأخير في التفريع

ينتقل مؤشر HEAD بتنفيذ الأمر commit. أي أن تفريع testing تقدم بإيداع بينما لا زال تفريع master على ما كان عليه عند تنفيذ أمر الانتقال git checkout. نعود إلى التفريع الرئيس master بتنفيذ الأمر التالي:

git checkout master

08_checkout_master.thumb.png.bab60e8625b
ينتقل مؤشر HEAD إلى التفريع الرئيس

ينقل الأمر السابق مؤشر HEAD ليحيل إلى التفريع الرئيس ثم يرجع الملفات الموجودة في مجلد العمل إلى اللقطة التي يشير إليها التفريع master. يعني هذا أيضا أن التعديلات من الآن فصاعدا ستكون على نسخة قديمة من المشروع. يبدو الأمر كما لو أنك تراجعت عن التعديلات التي أجريتها بعد الانتقال إلى تفريع testing؛ وبدأت في تغييرات جديدة.

ملحوظة: الانتقال إلى تفريع يغيّر ملفات مجلد العمل.

ينبغي الانتباه إلى أن الانتقال إلى تفريع يؤدي إلى تغير الملفات الموجودة في مجلد العمل. إن انتقلت إلى تفريع قديم فسيعود محتوى مجلد العمل إلى ما كان عليه بعد آخر إيداع على هذا التفريع. إن لم يستطع Gitفعل ذلك فلن يسمح لك بتاتا بالانتقال إلى التفريع الجديد

نعدّل على أحد الملفات ثم نضيف إيداعا جديدا:

vim test.rb
git commit -a -m 'made other changes'

للتذكير نحن نعمل على التفريع master. بالإيداع أعلاه يبدأ سجلّ التفريعين بالتباعد كما هو موضح في الشكل أدناه. أنشأنا تفريعا جديدا وانتقلنا للعمل عليه، أجرينا بضعة تغييرات ثم عدنا من جديد للعمل على التفريع الرئيس. كل من هذه التغييرات معزول عن الآخر في تفريع مختلف: يمكن العمل على تفريع، ثم الانتقال إلى تفريع آخر والعمل عليه ثم العودة إلى التفريع الأول والعمل عليه أيضا؛ وعندما تكون جاهزا يمكن أن تدمج الاثنين. يؤدّى كل هذا العمل بسهولة بالأوامر checkout ،branch وcommit.

09_advance_master.thumb.png.c35c4ecf1a3e
تباعد التفريعات عن بعضها

يمكن استخدام الأمر التالي لعرض سجل التغييرات على شكل مخطّط يوضّح إلى أين تحيل مؤشرات التفريعات وكيف تباعدت عن بعضها:

git log --oneline --decorate --graph --all

مثال على النتيجة:

* c2b9e (HEAD, master) made other changes
| * 87ab2 (testing) made a change
|/
* f30ab add feature #32 - ability to add new formats to the
* 34ac2 fixed bug #1328 - stack overflow under certain conditions
* 98ca9 initial commit of my project

يسهُل إنشاء تفريعات ومحوها في Git، إذ لا يتطلّب ذلك سوى إنشاء ملفّ من مجموع تحقق ذي 40 محرفا يمثّل الإيداع الذي يحيل إليه مؤشّر التفريع. يختلف Git عن نظم إدارة نسخ أخرى يتطلب التفريع فيها نسخ جميع ملفات المشروع إلى مجلد ثان ممّا يدوم ثواني عدّة وأحيانا دقائق حسب حجم المشروع؛ بينما يكاد يكون الأمر في Git لحظيا. زيادة على ذلك فإن تخزين سوابق الإيداع تجعل من العثور على قاعدة مناسبة لدمج تفريعين أسهل وفي كثير من الأحيان تلقائيا. تشجّع هذه الميزة التي سنتطرّق إليها في المقال التالي المطوّرين على إنشاء التفريعات واستخدامها أكثر.

ترجمة -بتصرف- للفصل Git Branching - Branches in a Nutshell من كتاب Pro Git لصاحبه Scott Chacon.





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


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



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

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

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


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

تسجيل الدخول

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


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