Mercurial
معمارية تطبيقات المصادر المفتوحة (المجلد الأول)Mercurial
معمارية تطبيقات المصادر المفتوحة (المجلد الأول) Mercurial
Dirkjan Ochtman
إذا استمتعت بهذه الكتب، فقد تستمتع أيضًا بـ التصميم البرمجي بالأمثلة في Python والتصميم البرمجي بالأمثلة في JavaScript.
Mercurial هو نظام تحكّم بالإصدارات موزّع حديث (VCS)، كُتب معظمه بلغة Python مع أجزاء هنا وهناك بلغة C من أجل الأداء. وفي هذا الفصل سأناقش بعض القرارات التي تنطوي عليها خوارزميات Mercurial وهياكل بياناته. أولًا، اسمحوا لي ألقي نظرة على تاريخ موجز لأنظمة التحكّم بالإصدارات، لتوفير السياق الضروري.
12.1. نبذة تاريخية قصيرة عن التحكّم بالإصدارات
على الرغم من أنّ هذا الفصل يتناول أساسًا البنية المعمارية لبرمجيات Mercurial، فإنّ كثيرًا من المفاهيم يشترك فيها مع أنظمة التحكّم بالإصدارات الأخرى. ولمناقشة Mercurial على نحو مثمر، أودّ أن أبدأ بتسمية بعض المفاهيم والإجراءات في مختلف أنظمة التحكّم بالإصدارات. ولوضع كلّ هذا في سياقه، سأقدّم أيضًا تاريخًا موجزًا لهذا المجال.
اختُرعت أنظمة التحكّم بالإصدارات لمساعدة المطوّرين على العمل على الأنظمة البرمجية في الوقت نفسه، دون تبادل نسخ كاملة ودون أن يتولّى كلٌّ منهم متابعة تغيّرات الملفات بنفسه. لنعمّم هذا من شيفرة المصدر البرمجية إلى أي شجرة ملفات. ومن الوظائف الأساسية للتحكّم بالإصدارات تبادل التغييرات على الشجرة. والدورة الأساسية شيءٌ من هذا القبيل:
- الحصول على أحدث شجرة ملفات من شخص آخر
- العمل على مجموعة تغييرات على هذه النسخة من الشجرة
- نشر التغييرات بحيث يتمكن الآخرون من استرجاعها
الإجراء الأول، أي الحصول على شجرة ملفات محلّية، يسمّى الاستخراج (checkout). أمّا المخزن الذي نسترجع منه تغييراتنا وننشرها فيسمّى المستودع (repository)، بينما تسمّى نتيجة الاستخراج مجلّد العمل (working directory) أو شجرة العمل (working tree) أو نسخة العمل (working copy). وتحديث نسخة العمل بأحدث الملفات من المستودع يسمّى ببساطة تحديث (update)؛ وأحيانًا يستلزم ذلك دمج (merging، أي دمج تغييرات مستخدمين مختلفين في ملف واحد. ويتيح لنا أمر المقارنة (diff) مراجعة التغييرات بين نسختين من شجرة أو ملف، وأشهر أنماطه هو فحص التغييرات المحلّية (غير المنشورة) في نسخة العمل. أمّا التغييرات فتُنشر بإصدار أمر التزام (commit)، الذي يحفظ تغييرات مجلّد العمل في المستودع.
12.1.1. التحكّم المركزي بالإصدارات
كان أوّل نظام تحكّم بالإصدارات هو نظام التحكّم بشيفرة المصدر (Source Code Control System، SCCS)، الذي وُصِف أوّل مرّة عام 1975. وكان في جوهره وسيلة لحفظ الفروق (deltas) في ملفات مفردة أكثر كفاءةً من مجرد الاحتفاظ بنسخ، ولم يكن يساعد في نشر هذه التغييرات للآخرين. وتلاه عام 1982 نظام التحكّم بالمراجعات (Revision Control System، RCS)، وهو بديل أكثر تطوّرًا ومجانيّ من SCCS (ولا يزال مشروع GNU يصونه حتى اليوم).
بعد RCS جاء CVS، أي نظام الإصدارات المتزامنة (Concurrent Versioning System)، الذي صدر أوّل مرّة عام 1986 في صورة مجموعة نصوص برمجية (scripts) للتعامل مع ملفات مراجعات RCS على شكل مجموعات. والابتكار الكبير في CVS هو اعتبار أنّ مستخدمين متعدّدين يمكنهم التحرير في وقت واحد، مع إجراء عمليات الدمج لاحقًا (تحرير متزامن). وقد استلزم هذا أيضًا مفهوم تعارضات التحرير. ولا يستطيع المطوّرون إصدار نسخة جديدة من ملف ما إلا إذا كانت مبنية على أحدث نسخة متاحة في المستودع. وإذا كانت هناك تغييرات في المستودع وفي مجلّد عملي، فعليّ حلّ أي تعارضات ناتجة عن تلك التغييرات (تعديلات تغيّر الأسطر نفسها).
كما رائد CVS أيضًا مفهومي الفروع (branches)، التي تتيح للمطوّرين العمل على أمور مختلفة بالتوازي، والوسوم (tags)، التي تتيح تسمية لقطة متّسقة للإشارة إليها بسهولة. وعلى أنّ فروق CVS كانت في البداية تُتبادل عبر المستودع الموجود على نظام ملفات مشترك، فإنّ CVS نفّذ لاحقًا بنية عميل-خادم (client-server) للاستخدام عبر الشبكات الواسعة (مثل الإنترنت).
في عام 2000 اجتمع ثلاثة مطوّرين لبناء نظام تحكّم بالإصدارات جديد سمّياه Subversion، بقصد إصلاح بعض العيوب الكبرى في CVS. والأهم من ذلك أنّ Subversion يعمل على أشجار كاملة في كل مرّة، ما يعني أنّ التغييرات في المراجعة ينبغي أن تكون ذرّية (atomic) ومتّسقة ومعزولة ودائمة. كما تحتفظ نسخ عمل Subversion بنسخة أصلية (pristine) من المراجعة المُستخرَجة في مجلّد العمل، بحيث تكون عملية المقارنة (diff) الشائعة (مقارنة الشجرة المحلّية بمجموعة تغييرات مُستخرَجة) محلّية وبالتالي سريعة.
من المفاهيم المثيرة في Subversion أنّ الوسوم والفروع جزء من شجرة المشروع. فمشروع Subversion يُقسَّم عادةً إلى ثلاث مناطق: tags وbranches وtrunk. وقد أثبت هذا التصميم حدسيًّا جدًّا للمستخدمين غير المعتادين على أنظمة التحكّم بالإصدارات، وإن كانت المرونة الكامنة في هذا التصميم قد سبّبت عددًا كبيرًا من المشاكل لأدوات التحويل، والسبب في معظمها أنّ tags وbranches لهما تمثيل بنيويّ أكثر في الأنظمة الأخرى.
توصف جميع الأنظمة السابقة بأنها مركزية؛ إذ تعتمد، حتى في قدرتها على تبادل التغييرات (ابتداءً من CVS)، على حاسوب آخر لمتابعة تاريخ المستودع. أمّا أنظمة التحكّم بالإصدارات الموزّعة فتحتفظ بنسخة من كامل تاريخ المستودع أو معظمه على كل حاسوب لديه مجلّد عمل لذلك المستودع.
12.1.2. التحكّم الموزّع بالإصدارات
على أنّ Subversion كان تحسّنًا واضحًا على CVS، فإنّ عددًا من أوجه القصور ما زالت قائمة. أولًا، في جميع الأنظمة المركزية، فإنّ إصدار مجموعة تغييرات ونشرها أمر واحد عمليًا، إذ إنّ تاريخ المستودع مركزيّ في مكان واحد. وهذا يعني استحالة إصدار تغييرات دون الوصول إلى الشبكة. ثانيًا، يحتاج الوصول إلى المستودع في الأنظمة المركزية دائمًا إلى رحلة ذهاب وإياب واحدة أو أكثر عبر الشبكة، ممّا يجعله بطيئًا نسبيًا مقارنةً بالوصولات المحلّية التي تحتاجها الأنظمة الموزّعة. ثالثًا، لم تكن الأنظمة التي ناقشناها أعلاه جيّدة جدًّا في تتبّع عمليات الدمج (وقد تحسّن بعضُها في ذلك منذ ذلك الحين). وفي المجموعات الكبيرة التي تعمل بالتوازي، من المهمّ أن يسجّل نظام التحكّم بالإصدارات ما الذي أُدخل من تغييرات في مراجعة جديدة، بحيث لا يضيع شيءٌ وتستفيد عمليات الدمج اللاحقة من هذه المعلومات. رابعًا، تبدو أحيانًا المركزية التي يتطلّبها أنظمة التحكّم بالإصدارات التقليدية مصطنعة، وهي تُفضّي إلى مكان واحد للدمج. ويُجادل أنصار أنظمة التحكّم الموزّعة بأنّ نظامًا أكثر توزّعًا يتيح تنظيمًا أكثر عضوية، يستطيع فيه المطوّرون أن يدفعوا بالتغييرات ويدمجوها حسب ما يتطلّبه المشروع في كل لحظة.
طُوّرت أدوات عديدة لتلبية هذه الاحتياجات. ومن موقعي (عالم البرمجيات مفتوحة المصدر)، فإنّ أبرز ثلاث منها في عام 2011 هي Git وMercurial وBazaar. وقد انطلق كلٌّ من Git وMercurial عام 2005 حين قرّر مطوّرو نواة Linux التوقف عن استخدام نظام BitKeeper المملوك. وقد بدأهما مطوّرو نواة Linux (على الترتيب Linus Torvalds وMatt Mackall) لمعالجة الحاجة إلى نظام تحكّم بالإصدارات قادر على معالجة مئات الآلاف من مجموعات التغييرات في عشرات الآلاف من الملفات (النواة مثلًا). كما تأثّر كلٌّ من Matt وLinus كثيرًا بنظام التحكّم بالإصدارات Monotone. أمّا Bazaar فقد طُوّر بمعزل عنهما، لكنه اكتساب انتشارًا واسعًا في الوقت نفسه حين تبنّته Canonical لاستخدامه في جميع مشاريعها.
إنّ بناء نظام تحكّم بالإصدارات موزّع يستتبع بطبيعة الحال تحديات، وكثير منها متأصّل في أي نظام موزّع. أولًا، بينما كان خادم التحكّم بالمصادر في الأنظمة المركزية يوفّر دائمًا رؤية معتمدة (canonical) للتاريخ، فإنّ الأمر لا يقابله شيءٌ في نظام التحكّم الموزّع. إذ يمكن إصدار مجموعات التغييرات على التوازي، ممّا يجعل ترتيب المراجعات زمنيًا في أي مستودع بعينه مستحيلًا.
الحل الذي تبنّته الأنظمة كلها تقريبًا هو استخدام رسم بياني موجَّه خالٍ من الدورات (directed acyclic graph، DAG) لمجموعات التغييرات بدلًا من ترتيب خطّي (الشكل 12.1). أي أنّ مجموعة التغييرات المُصدَرة حديثًا هي المراجعة الفرعية للمراجعة التي استُنِدَت إليها، ولا يمكن لأي مراجعة أن تعتمد على نفسها أو على مراجعاتها التفرعية. وفي هذا المخطّط لدينا ثلاثة أنواع خاصة من المراجعات: المراجعات الجذرية (root revisions) التي لا أب لها (ويمكن أن يكون للمستودع جذور متعدّدة)، ومراجعات الدمج (merge revisions) التي لها أكثر من أب، ومراجعات الرؤوس (head revisions) التي لا أبناء لها. يبدأ كل مستودع من مراجعة جذرية فارغة ثم يمضي من هناك على امتداد خطّ مجموعات التغييرات، لينتهي برأس أو أكثر. وحين يكون مستخدمان قد أصدرا تغييراتهما بصورة مستقلة وأراد أحدهما سحب تغييرات الآخر، فإنّه سيضطر إلى دمج تغييرات الآخر صراحةً في مراجعة جديدة، ثم يصدرها لاحقًا كمراجعة دمج.

الشكل 12.1: الرسم البياني الموجَّه الخالي من الدورات للمراجعات
لاحظ أنّ نموذج DAG يساعد في حلّ بعض المشكلات الصعبة في أنظمة التحكّم المركزي بالإصدارات: إذ تُستخدم مراجعات الدمج لتسجيل معلومات عن الفروع التي دُمجت حديثًا في DAG. ويمكن بالرسم البياني الناتج أن يمثّل بصورة مفيدة مجموعة كبيرة من الفروع المتوازية، تندمج في مجموعات أصغر، ثم تندمج في النهاية في فرع واحد خاص يُعدّ معتمدًا (canonical).
يتطلّب هذا الأسلوب أن يتابع النظام علاقات النسب (ancestry) بين مجموعات التغييرات؛ ولتسهيل تبادل بيانات مجموعات التغييرات، يُنفَّذ ذلك عادةً بجعل كل مجموعة تغييرات تتابع آباءها. ولهذا تحتاج مجموعات التغييرات بطبيعة الحال إلى نوع من المعرّفات. وبينما تستخدم بعض الأنظمة UUID أو مخطّطًا مشابهًا، فإنّ Git وMercurial قد اختارا استخدام تجزئات SHA1 لمحتويات مجموعات التغييرات. وهذه خاصية مفيدة إضافية: إذ يمكن استخدام معرّف مجموعة التغييرات للتحقّق من محتواها. وفي الواقع، لأنّ الآباء مُدرَجون ضمن البيانات المُجزَّأة، يمكن التحقّق من كامل التاريخ المؤدّي إلى أي مراجعة باستخدام تجزئتها. كما أنّ أسماء المؤلّفين ورسائل الإصدار والطوابع الزمنية وسائر البيانات الوصفية لمجموعة التغييرات تُجزَّأ تمامًا مثل محتويات الملفات الفعلية للمراجعة الجديدة، بحيث يمكن التحقّق منها هي الأخرى. وبما أنّ الطوابع الزمنية تُسجَّل لحظة الإصدار، فإنّها أيضًا لا تتقدّم بالضرورة خطيًا في أي مستودع بعينه.
قد يكون كلّ هذا صعبًا على من اعتاد سابقًا على الأنظمة المركزية وحدها: إذ لا يوجد عدد صحيح أنيق لتسمية المراجعة عالميًا، بل مجرّد سلسلة سداسية عشرية من 40 محرفًا. فضلًا عن ذلك، لم يعد هناك أي ترتيب عالمي، بل ترتيب محلّي فقط؛ والترتيب «العالمي» الوحيد هو DAG بدلًا من خطّ. وقد يكون بدء رأس جديد من التطوير بالخطأ، عبر الإصدار مقابل مراجعة أب كانت لها بالفعل مجموعة تغييرات فرعية أخرى، مربكًا لمن اعتاد أن يصدِّر له نظام التحكّم بالإصدارات تحذيرًا عند حدوث هذا الأمر.
لحسن الحظّ، هناك أدوات تساعد على تصوّر ترتيب الشجرة، ويوفّر Mercurial نسخة مختصرة غير ملتبسة من تجزئة مجموعة التغييرات و رقمًا خطيًا محليًا فقط يساعدان على التعرّف. والأخير عدد صحيح يتزايد رتيبًا ويبيّن الترتيب الذي دخلت به مجموعات التغييرات إلى النسخة المستنسَخة (clone). ولأنّ هذا الترتيب قد يختلف من نسخة إلى أخرى، فلا يمكن الاعتماد عليه في العمليات غير المحلّية.
12.2. هياكل البيانات
الآن وقد صار مفهوم DAG واضحًا إلى حدٍّ ما، فلنرَ كيف تُخزَّن هذه الرسوم في Mercurial. فنموذج DAG محوري في الآلية الداخلية لـ Mercurial، ونحن فعليًا نستخدم عدة رسوم بيانية مختلفة في تخزين المستودع على القرص (وكذلك في بنية الشيفرة في الذاكرة). ويشرح هذا القسم ما هي وكيف تتكامل معًا.
12.2.1. التحديات
قبل أن نغوص في هياكل البيانات الفعلية، أودّ تقديم بعض السياق عن البيئة التي تطوّر فيها Mercurial. ويمكن العثور على أوّل فكرة عن Mercurial في رسالة إلكترونية أرسلها Matt Mackall إلى القائمة البريدية لنواة Linux (Linux Kernel Mailing List) في 20 أبريل 2005. وقد وقع ذلك بعد وقت قصير من القرار بأنّ BitKeeper لم يعد صالحًا لتطوير النواة. وبدأ Matt رسالته بعرض بعض الأهداف: أن يكون بسيطًا وقابلًا للتوسّع وكفؤًا.
وفي Mac06، ادّعى Matt أنّ نظام التحكّم بالإصدارات الحديث يجب أن يتعامل مع أشجار تضمّ ملايين الملفات، وأن يعالج ملايين مجموعات التغييرات، وأن يتوسّع عبر آلاف المستخدمين الذين ينشئون مراجعات جديدة على التوازي على مدى عقود. وبعد أن حدّد الأهداف، استعرض العوامل التقنية المانعة:
- السرعة: المعالج (CPU)
- السعة: القرص والذاكرة
- عرض النطاق: الذاكرة والشبكة المحلّية والقرص والشبكة الواسعة
- معدل انتقال رأس القرص
إنّ معدل انتقال رأس القرص وعرض نطاق الشبكة الواسعة هما العاملان المانعان اليوم، ومن ثمّ ينبغي تحسينهما. وتستعرض الورقة بعد ذلك سيناريوهات أو معايير شائعة لتقييم أداء مثل هذا النظام على مستوى الملف:
- ضغط التخزين: أيّ نوع من الضغط أنسب لحفظ تاريخ الملف على القرص؟ وبعبارة أخرى، ما الخوارزمية التي تستثمر أكثر ما يمكن من أداء الإدخال/الإخراج (I/O) مع منع وقت المعالج من أن يصبح عنق زجاجة؟
- استرجاع مراجعات اعتباطية للملفات: ستخزّن عدد من أنظمة التحكّم بالإصدارات مراجعة معيّنة بطريقة تُلزم بقراءة عدد كبير من المراجعات الأقدم لإعادة بناء الأحدث منها (باستخدام الفروق deltas). نريد ضبط ذلك للتأكّد من أن استرجاع المراجعات القديمة ما زال سريعًا.
- إضافة مراجعات للملفات: نضيف مراجعات جديدة بصورة منتظمة. ولا نريد إعادة كتابة المراجعات القديمة في كل مرّة نضيف فيها مراجعة جديدة، لأن ذلك سيصبح بطيئًا جدًّا عندما تكون المراجعات كثيرة.
- عرض تاريخ الملف: نريد أن نتمكّن من مراجعة تاريخ كل مجموعات التغييرات التي لمست ملفًا معيّنًا. وهذا يتيح لنا أيضًا إجراء التعليقات الشرحية (annotations) (التي كانت تُسمّى
blameفي CVS، لكن أُعيدت تسميتهاannotateفي أنظمة لاحقة للتخلّص من الدلالة السلبية): مراجعة مجموعة التغييرات التي أنشأت كل سطر حالي في الملف.
وتستعرض الورقة بعد ذلك سيناريوهات مماثلة على مستوى المشروع. والعمليات الأساسية على هذا المستوى هي استخراج مراجعة، وإصدار مراجعة جديدة، وإيجاد الفروق في مجلّد العمل. والعملية الأخيرة تحديدًا قد تكون بطيئة مع الأشجار الكبيرة (مثل أشجار مشروعي Mozilla وNetBeans، وكلاهما يستخدم Mercurial لتلبية احتياجاته في التحكّم بالإصدارات).
12.2.2. تخزين سريع للمراجعات: سجلّات المراجعات
الحل الذي جاء به Matt لـ Mercurial يسمّى سجلّ المراجعات (revlog) (اختصارًا لعبارة revision log). وسجلّ المراجعات وسيلة لتخزين مراجعات محتويات الملفات بكفاءة (كل مراجعة منها تحمل قدرًا من التغييرات مقارنةً بالنسخة السابقة). ويجب أن يكون كفؤًا في زمن الوصول (وبالتالي أن يُحسَّن لانتقالات القرص) وفي مساحة التخزين، تبعًا للسيناريوهات الشائعة التي رسمها القسم السابق. ولتحقيق ذلك، فإنّ سجلّ المراجعات ملفّان على القرص في الواقع: فهرس وملف بيانات.
| 6 بايت | إزاحة الكتلة |
|---|---|
| 2 بايت | الأعلام |
| 4 بايت | طول الكتلة |
| 4 بايت | الطول غير المضغوط |
| 4 بايت | المراجعة الأساس |
| 4 بايت | مراجعة الارتباط |
| 4 بايت | المراجعة الأم الأولى |
| 4 بايت | المراجعة الأم الثانية |
| 32 بايت | التجزئة |
الجدول 12.1: تنسيق سجلّات Mercurial
يتكوّن الفهرس من سجلّات ذات طول ثابت، تفاصيل محتواها في الجدول 12.1. والسجلّات ذات الطول الثابت ميزة جيّدة، لأنّها تعني أنّ معرفة رقم المراجعة المحلّي تتيح الوصول المباشر (أي في زمن ثابت) إلى المراجعة: يمكننا ببساطة القراءة حتى الموضع (index-length×revision) في ملف الفهرس لتحديد موقع البيانات. ويعني فصل الفهرس عن البيانات أيضًا أننا نستطيع قراءة بيانات الفهرس بسرعة من دون أن نضطر إلى تحريك رأس القرص في جميع بيانات الملفات.
تحدّد إزاحة الكتلة وطولها مقطعًا من ملف البيانات الذي يُقرأ للحصول على البيانات المضغوطة لتلك المراجعة. للحصول على البيانات الأصلية، علينا أن نبدأ بقراءة المراجعة الأساس ثم نطبّق الفروق حتى الوصول إلى هذه المراجعة. والحيلة هنا هي القرار المتعلّق بزمن تخزين مراجعة أساس جديدة. ويستند هذا القرار إلى الحجم التراكمي للفروق مقارنةً بالطول غير المضغوط للمراجعة (تُضغط البيانات باستخدام zlib لاستهلاك مساحة أقل على القرص). وبتحديد طول سلسلة الفروق بهذه الطريقة، نتأكّد من أنّ إعادة بناء بيانات مراجعة معيّنة لا تتطلّب قراءة وتطبيق كمّ كبير من الفروق.
تُستخدم مراجعات الارتباط (link revisions) كي تُشير سجلّات المراجعات التابعة إلى سجلّ المراجعات في أعلى المستويات (وسنعود إلى هذا بعد قليل)، وتُخزَّن المراجعات الآباء باستخدام رقم المراجعة العددي المحلّي. وهذا أيضًا يجعل من السهل العثور على بياناتها في سجلّ المراجعات المعنيّ. أمّا التجزئة (hash) فتُستخدم لحفظ المعرّف الفريد لمجموعة التغييرات هذه. ولدينا 32 بايت بدلًا من الـ 20 بايت التي يتطلّبها SHA1، من أجل السماح بالتوسّع مستقبلًا.
12.2.3. سجلّات المراجعات الثلاثة
ولما كانت سجلّات المراجعات توفّر بنية عامّة للبيانات التاريخية، يمكننا أن نبني نموذج بيانات شجرة الملفات فوق ذلك. ويتكوّن من ثلاثة أنواع من سجلّات المراجعات: سجلّ التغييرات (changelog)، والبيانات الوصفية (manifests، وتُعرف بالرمز man)، وسجلّات الملفات (filelogs). ويحتوي سجلّ التغييرات على البيانات الوصفية لكل مراجعة، مع مؤشّر إلى سجلّ البيانات الوصفية (أي معرّف عقدة لمراجعة واحدة فيه). وفي المقابل، البيانات الوصفية ملف يحوي قائمة بأسماء الملفات إضافةً إلى معرّف العقدة لكل ملف، ويشير إلى مراجعة في سجلّ ملفات ذلك الملف. أمّا في الشيفرة، لدينا أصناف (classes) لسجلّ التغييرات والبيانات الوصفية وسجلّ الملفات، وهي أصناف فرعية من الصنف العام revlog، ما يوفّر طبقة أنظف للمفهومين معًا.

الشكل 12.2: بنية السجلّات
تبدو مراجعة في سجلّ التغييرات هكذا:
0a773e3480fe58d62dcc67bd9f7380d6403e26fa
Dirkjan Ochtman <dirkjan@ochtman.nl>
1276097267 -7200
mercurial/discovery.py
discovery: fix description line
هذه هي القيمة التي تحصل عليها من طبقة سجلّ المراجعات؛ أمّا طبقة سجلّ التغييرات فتحوّلها إلى قائمة بسيطة من القيم. ويوفّر السطر الأول تجزئة البيانات الوصفية، ثم يأتي اسم المؤلّف والتاريخ والوقت (في صورة طابع زمني من نوع Unix وإزاحة منطقة زمنية)، ثم قائمة بالملفات المتأثّرة، ثم رسالة الوصف. وهناك أمر مخفي هنا: نسمح ببيانات وصفية اعتباطية داخل سجلّ التغييرات، ولأجل الحفاظ على التوافق مع الإصدارات السابقة أضفنا تلك البتات لتأتي بعد الطابع الزمني.
ثم تأتي البيانات الوصفية:
.hgignore\x006d2dc16e96ab48b2fcca44f7e9f4b8c3289cb701
.hgsigs\x00de81f258b33189c609d299fd605e6c72182d7359
.hgtags\x00b174a4a4813ddd89c1d2f88878e05acc58263efa
CONTRIBUTORS\x007c8afb9501740a450c549b4b1f002c803c45193a
COPYING\x005ac863e17c7035f1d11828d848fb2ca450d89794
…
هذه هي مراجعة البيانات الوصفية التي تشير إليها مجموعة التغييرات 0a773e (تتيح واجهة Mercurial تقصير المعرّف إلى أي بادئة غير ملتبسة). وهي قائمة بسيطة بجميع ملفات الشجرة، ملف واحد في كل سطر، يتبع فيه اسم الملف بايت NULL، ثم معرّف العقدة المُرمَّز ست عشريًا الذي يشير إلى سجلّ ملفات ذلك الملف. ولا تُمثَّل المجلّدات في الشجرة بصفة منفصلة، بل يُستنتجها مجرّدًا من وجود مائلات في مسارات الملفات. وتذكّر أنّ البيانات الوصفية تُخزَّن بالفروق (diff) تمامًا مثل كل سجلّ مراجعات، لذا ينبغي أن يجعل هذا البنيةَ سهلةً على طبقة سجلّ المراجعات كي تخزّن فقط الملفات المتغيّرة وتجزئاتها الجديدة في أي مراجعة بعينها. وبالعادة تُمثَّل البيانات الوصفية في شيفرة Mercurial المكتوبة بـ Python على هيئة بنية شبيهة بجدول التجزئة (hashtable)، بأسماء الملفات كمفاتيح وبالعُقد كقيم.
النوع الثالث من سجلّات المراجعات هو سجلّ الملفات. تُخزَّن سجلّات الملفات في مجلّد store الداخلي في Mercurial، حيث تُسمّى تكاد تمامًا كما يُسمّى الملف الذي تتبّعه. وتُرمَّز الأسماء قليلًا لضمان عمل كل شيء عبر جميع أنظمة التشغيل الرئيسية. فعلى سبيل المثال، علينا التعامل مع أنظمة الملفات التي تُطوي حالة الأحرف (casefolding) في Windows وMac OS X، ومع أسماء الملفات الممنوعة تحديدًا في Windows، ومع ترميزات المحارف المختلفة التي تستخدمها أنظمة الملفات المختلفة. ويمكنك تخيّل أن فعل ذلك بموثوقية عبر أنظمة التشغيل قد يكون مؤلمًا إلى حدٍّ كبير. وعلى النقيض من ذلك، فإنّ محتويات مراجعة في سجلّ الملفات ليست مثيرة للاهتمام بهذا القدر: مجرّد محتويات الملف، عدا بادئة اختيارية من البيانات الوصفية (نستخدمها لتتبّع نسخ الملفات وإعادة تسميتها، بين أمور أخرى ثانوية).
يمنحنا نموذج البيانات هذا وصولًا كاملًا إلى مخزن البيانات في مستودع Mercurial، لكنه ليس مريحًا في كل الأحوال. فبينما يكون النموذج الأساسي القائم في الخلفية موجَّهًا رأسيًا (سجلّ ملفات لكل ملف)، وجد مطوّرو Mercurial أنفسهم في مناسبات كثيرة يريدون التعامل مع كل تفاصيل مراجعة واحدة، بدءًا من مجموعة تغييرات في سجلّ التغييرات ورغبةً في وصول سهل إلى البيانات الوصفية وسجلّات الملفات الخاصة بتلك المراجعة. ولاحقًا اخترعوا مجموعة أخرى من الأصناف، مبنيّة بنظافة فوق سجلّات المراجعات، تفعل ذلك بالضبط. وتُسمّى هذه الأصناف contexts.
ومن الأمور الجيدة في طريقة إعداد سجلّات المراجعات المنفصلة هو الترتيب. فبت ترتيب عمليات الإلحاق بحيث تُلحَق سجلّات الملفات أوّلًا، ثم البيانات الوصفية، وأخيرًا سجلّ التغييرات، يبقى المستودع دائمًا في حالة متّسقة. وأي عملية تبدأ بقراءة سجلّ التغييرات يمكنها أن تطمئن إلى أنّ جميع المؤشّرات إلى سجلّات المراجعات الأخرى صالحة، ممّا يعالج عددًا من المشكلات في هذا المجال. ومع ذلك، يملك Mercurial أيضًا بعض الأقفال (locks) الصريحة للتأكّد من عدم وجود عمليتين تُلحقان بسجلّات المراجعات على التوازي.
12.2.4. مجلّد العمل
آخر بنية بيانات مهمّة هي ما نسمّيه حالة المجلّد (dirstate). وهي تمثيل لما في مجلّد العمل في أي لحظة. والأهم من ذلك أنها تتابع أي مراجعة جرى استخراجها: وهذه هي خطّ الأساس لكل المقارنات التي تجريها أوامر status أو diff، كما تحدّد أيضًا أبا (أو آباء) مجموعة التغييرات التالية التي ستُصدر. وستكون في حالة المجلّد أُبوان محدَّدان كلما صدر أمر merge في محاولة لدمج مجموعة تغييرات في الأخرى.
ولأنّ status وdiff عمليتان شديدتي شيوعًا (فهما تساعدانك على قياس مدى ما أنجزته حتى الآن مقارنةً بآخر مجموعة تغييرات)، فإنّ حالة المجلّد تحتوي أيضًا على ذاكرة مؤقتة (cache) لحالة مجلّد العمل في آخر مرّة اجتازه فيها Mercurial. فمتابعة أوقات آخر تعديل وأحجام الملفات تجعل من الممكن تسريع اجتياز الشجرة. ونحتاج أيضًا إلى متابعة حالة الملف: هل أُضيف، أم أُزيل، أم دُمج في مجلّد العمل. وسيؤدي هذا بدوره إلى تسريع اجتياز مجلّد العمل، ويجعل من السهل الحصول على هذه المعلومات وقت الإصدار.
12.3. آليات الإصدارات
الآن وقد ألفت نموذج البيانات الأساسي وبنية الشيفرة في المستويات الدنيا من Mercurial، فلنصعد قليلًا لنفحص كيف ينفّذ Mercurial مفاهيم التحكّم بالإصدارات فوق الأساس الموصوف في القسم السابق.
12.3.1. الفروع
تُستخدم الفروع عادةً لفصل خطوط تطوير مختلفة ستُدمج لاحقًا. وقد يكون ذلك لأنّ أحدهم يجرّب منهجًا جديدًا، لمجرد القدرة على إبقاء خطّ التطوير الرئيسي في حالة جاهزة للإطلاق دائمًا (فروع الميزات)، أو القدرة على إصدار إصلاحات سريعة لإصدار قديم (فروع الصيانة). والنهجان شائعان وتدعمهما جميع أنظمة التحكّم بالإصدارات الحديثة. وبينما تكون الفروع الضمنية (implicit) شائعة في أنظمة التحكّم بالإصدارات القائمة على DAG، فإنّ الفروع المسمّاة (named branches) (حيث يُحفظ اسم الفرع في البيانات الوصفية لمجموعة التغييرات) ليست شائعة بالقدر نفسه.
في الأصل، لم يكن لدى Mercurial وسيلة لتسمية الفروع صراحةً. بل كانت الفروع تُعالَج بإنشاء نسخ مستنسَخة (clones) مختلفة ونشرها بصورة منفصلة. وهذه طريقة فعّالة وسهلة الفهم، ومفيدة خصوصًا لفروع الميزات، لأن التكاليف الثانوية فيها قليلة. لكنّها، في المشاريع الكبيرة، قد تظلّ باهظة: فرغم أنّ مخزن المستودع سيُربط بروابط صلبة (hardlinks) على معظم أنظمة الملفات، فإنّ إنشاء شجرة عمل منفصلة عملية بطيئة وقد تستهلك قدرًا كبيرًا من مساحة القرص.
ونظرًا لهذه العيوب، أضاف Mercurial طريقة ثانية للتعامل مع الفروع: إدراج اسم الفرع في البيانات الوصفية لمجموعة التغييرات. وقد أُضيف أمر branch يمكنه تعيين اسم الفرع لمجلّد العمل الحالي، بحيث يُستخدم اسم الفرع ذلك في الإصدار التالي. ويمكن استخدام أمر update المعتاد للتحديث إلى اسم فرع، وستكون أي مجموعة تغييرات مُصدَرة على فرع مرتبطة بذلك الفرع دائمًا. وتُسمّى هذه الطريقة الفروع المسمّاة (named branches). غير أنّه استغرق Mercurial بضعة إصدارات أخرى قبل أن يبدأ في توفير وسيلة لإغلاق هذه الفروع مجددًا (وإغلاق الفرع يُخفيه من العرض في قائمة الفروع). ويُنفَّذ إغلاق الفرع بإضافة حقلٍّ إضافي في البيانات الوصفية لمجموعة التغييرات، يُفيد بأنّ مجموعة التغييرات هذه تُغلق الفرع. وإذا كان للفرع أكثر من رأس، فيجب إغلاقها جميعها قبل أن يختفي الفرع من قائمة الفروع في المستودع.
وهناك بالطبع أكثر من طريقة للقيام بذلك. فلدى Git طريقة مختلفة لتسمية الفروع، تقوم على المراجع (references). والمراجع أسماء تشير إلى كائن آخر في تاريخ Git، عادةً مجموعة تغييرات. وهذا يعني أنّ فروع Git عابرة (ephemeral): فبمجرد إزالة المرجع لا يبقى أي أثر للفرع بأنّه كان موجودًا أصلًا، تمامًا كما يحدث عند استخدام نسخة مستنسَخة منفصلة من Mercurial ثم دمجها في نسخة أخرى. ويجعل هذا التعامل مع الفروع محلّيًا سهلًا وخفيفًا جدًّا، ويمنع ازدحام قائمة الفروع.
وتبيّن أنّ طريقة التفرّع هذه شديدة الشعبية، بل أكثر شعبيةً بكثير من الفروع المسمّاة أو نسخ الفروع في Mercurial. وقد أدّى ذلك إلى ظهور امتداد bookmarksq، الذي يُرجَّح أن يُدمج في Mercurial مستقبلًا. وهو يستخدم ملفًّا بسيطًا غير مُصدَّر لإصدار (unversioned) لمتابعة المراجع. وقد جرى توسيع بروتوكول الاتصال (wire protocol) المستخدم لتبادل بيانات Mercurial ليتيح التواصل بشأن الإشارات المرجعية (bookmarks)، ما يجعل من الممكن دفعها من مكان إلى آخر.
12.3.2. الوسوم
للوهلة الأولى قد تبدو طريقة تنفيذ Mercurial للوسوم مربكة بعض الشيء. عند إضافتك وسمًا أوّل مرّة (باستخدام أمر tag)، يُضاف ملف اسمه .hgtags إلى المستودع ويُلتزَم به. وسيحتوي كل سطر في ذلك الملف على معرّف عقدة مجموعة تغييرات واسم الوسم الخاص بتلك العقدة. وبذلك يُعامَل ملف الوسوم معاملة أي ملف آخر في المستودع.
وهناك ثلاثة أسباب مهمّة لهذا. أولًا، يجب أن يكون من الممكن تغيير الوسوم؛ فالأخطاء تحدث، وينبغي أن يكون من الممكن تصحيحها أو حذف الخطأ. ثانيًا، ينبغي أن تكون الوسوم جزءًا من تاريخ مجموعات التغييرات: فمن المفيد أن ترى متى أُنشئ الوسم ومن أنشأه ولماذا، بل وأن يُغيَّر لاحقًا. ثالثًا، ينبغي أن يكون من الممكن وسم مجموعة تغييرات بأثر رجعي. فعلى سبيل المثال، تخضع بعض المشاريع لاختبارات تشغيل مكثّفة لمخرجات إصدار مُصدَّرة من نظام التحكّم بالإصدارات قبل إطلاقها.
وتنبع كلّ هذه الخصائص بسهولة من تصميم .hgtags. ومع أنّ وجود ملف .hgtags في مجلّدات العمل يربك بعض المستخدمين، فإنّه يجعل دمج آلية الوسوم مع بقية أجزاء Mercurial (مثلًا، المزامنة مع نسخ المستودع الأخرى) بسيطًا جدًّا. لو كانت الوسوم موجودة خارج شجرة المصدر (كما هي الحال في Git مثلًا)، لكان لا بدّ من آليات منفصلة لتدقيق مصدر الوسوم وللتعامل مع تعارضات الوسوم المكرّرة (المُنشأة على التوازي). وحتى وإن كانت الأخيرة نادرة، فإنّ من اللطيف أن يكون هناك تصميم لا تنشأ فيه هذه المسائل أصلًا.
ولضبط كل هذا، لا يقتصر Mercurial إلّا على إلحاق أسطر جديدة بملف .hgtags. وهذا يبسّط أيضًا دمج الملف إذا أُنشئت الوسوم على التوازي في نسخ مختلفة. فأحدث معرّف عقدة لأي وسم بعينه يتقدّم دائمًا، وإضافة معرّف العقدة الفارغ (الذي يمثّل المراجعة الجذرية الفارغة المشتركة بين جميع المستودعات) يكون له أثر حذف الوسم. كما سيأخذ Mercurial في الحسبان الوسوم من جميع الفروع في المستودع، مستخدمًا حسابات الحداثة لتحديد الأسبقية بينها.
12.4. البنية العامة
كُتب Mercurial بالكامل تقريبًا بلغة Python، مع مجرّد أجزاء صغيرة هنا وهناك بلغة C لأنها حرجة بالنسبة لأداء التطبيق ككل. ورُئي أن Python خيار أنسب لمعظم الشيفرة لأنّ من الأسهل بكثير التعبير عن المفاهيم عالية المستوى في لغة ديناميكية مثل Python. وبما أنّ الكثير من الشيفرة ليست حرجة بالأداء فعليًا، فلا نرى بأسًا في تحمّل تلك الكلفة مقابل جعل البرمجة أسهل لأنفسنا في معظم الأجزاء.
تقابل وحدة Python (module) ملف شيفرة واحدًا. ويمكن أن تحتوي الوحدات على قدر ما نحتاج من الشيفرة، وهي إذن وسيلة مهمّة لتنظيم الشيفرة. وقد تستخدم الوحدات أنواعًا أو تستدعي دوالّ من وحدات أخرى عبر استيراد تلك الوحدات صراحةً. ويُقال عن المجلّد الذي يحتوي وحدة __init__.py إنّه حزمة (package)، وسيكشف جميع الوحدات والحزم التي يحتويها لجالب (importer) في Python.
يهيّئ Mercurial افتراضيًا حزمتين في مسار Python: mercurial وhgext. تحتوي حزمة mercurial على الشيفرة الأساسية اللازمة لتشغيل Mercurial، بينما تحتوي hgext على عدد من الامتدادات التي رُئي أنها مفيدة بما يكفي لتُشحن مع الشيفرة الأساسية. غير أنّه لا بدّ من تفعيلها يدويًا في ملف إعدادات إن رُغب في ذلك (وسنناقش ذلك لاحقًا).
ولنكن واضحين، فإنّ Mercurial تطبيق يعمل من سطر الأوامر. وهذا يعني أنّ لدينا واجهة بسيطة: يستدعي المستخدم نصّ hg بأمر ما. وقد يأخذ هذا الأمر (مثل log أو diff أو commit) عددًا من الخيارات والوسائط؛ وهناك أيضًا بعض الخيارات الصالحة لجميع الأوامر. بعد ذلك، يمكن أن تحدث ثلاثة أمور مختلفة على مستوى الواجهة.
- غالبًا ما يُخرج
hgما طلبه المستخدم أو يعرض رسائل حالة - يمكن لـ
hgأن يطلب مزيدًا من المدخلات عبر مطالبات في سطر الأوامر - قد يشغّل
hgبرنامجًا خارجيًا (مثل محرّر لرسالة الإصدار أو برنامج للمساعدة في دمج تعارضات الشيفرة)

الشكل 12.3: رسم بياني للاستيراد
يمكن ملاحظة بداية هذه العملية بوضوح من رسم الاستيراد في الشكل 12.3. إذ تُمرَّر جميع وسائط سطر الأوامر إلى دالة في وحدة dispatch. وأول ما يحدث هو إنشاء كائن ui. وسيحاول صنف ui أوّلًا العثور على ملفات الإعداد في عدد من الأماكن المعروفة (مثل مجلّد المنزل الخاص بك)، وحفظ خيارات الإعداد في كائن ui. وقد تحتوي ملفات الإعداد أيضًا على مسارات إلى امتدادات، ويجب تحميلها هي أيضًا في هذه اللحظة. كما تُحفظ أي خيارات عامة ممرَّرة في سطر الأوامر في كائن ui عند هذه اللحظة أيضًا.
وبعد أن يتمّ ذلك، علينا أن نقرّر ما إذا كنّا سننشئ كائن مستودع. فبينما تتطلّب معظم الأوامر مستودعًا محلّيًا (يمثّله الصنف localrepo من الوحدة localrepo)، قد تعمل بعض الأوامر على مستودعات بعيدة (إما عبر HTTP أو SSH أو شكل آخر مسجَّل)، بينما يمكن لبعض الأوامر أن تنجز عملها دون الإشارة إلى أي مستودع. وتشمل هذه الفئة الأخيرة مثلًا أمر init المستخدَم لتهيئة مستودع جديد.
تُمثَّل جميع الأوامر الأساسية بدالة واحدة في وحدة commands؛ وهذا يجعل العثور على شيفرة أي أمر بعينه سهلًا جدًّا. كما تحتوي وحدة commands على جدول تجزئة (hashtable) يربط اسم الأمر بالدالة ويصف الخيارات التي يأخذها. وتتيح طريقة التنفيذ هذه أيضًا مشاركة مجموعات شائعة من الخيارات (فالكثير من الأوامر لها خيارات تشبه خيارات أمر log). ويتيح وصف الخيارات لوحدة dispatch التحقّق من الخيارات المُعطاة لأي أمر، وتحويل أي قيم ممرَّرة إلى النوع الذي تتوقّعه دالة الأمر. ويحصل كل دالة تقريبًا أيضًا على كائن ui وكائن repository للعمل بهما.
12.5. قابلية التوسيع
من الأمور التي تجعل Mercurial قويًا قدرتك على كتابة امتدادات (extensions) له. ولأنّ Python لغة سهلة نسبيًا على المبتدئين، ولأنّ واجهة Mercurial البرمجية مصمَّمة تصمييمًا جيدًا في معظمها (مع أنّ التوثيق ناقص قطعًا في بعض المواضع)، فقد تعلّم فعليًا عدد من الناس Python لأنّهم أرادوا توسيع Mercurial.
12.5.1. كتابة الامتدادات
يجب تفعيل الامتدادات بإضافة سطر إلى أحد ملفات الإعداد التي يقرأها Mercurial عند بدء التشغيل؛ ويُقدَّم مع السطر مفتاح مع مسار أي وحدة Python. وهناك عدة طرق لإضافة وظائف:
- إضافة أوامر جديدة؛
- تغليف الأوامر الموجودة؛
- تغليف المستودع المستخدَم؛
- تغليف أي دالة في Mercurial؛ و
- إضافة أنواع مستودعات جديدة.
ويمكن إضافة أوامر جديدة ببساطة بإضافة جدول تجزئة اسمه cmdtable إلى وحدة الامتداد. وسيلتقطه محمِّل الامتدادات، الذي سيضيفه إلى جدول الأوامر المستخدم عند توزيع أي أمر. وبالمثل، يمكن للامتدادات أن تعرّف دوالّ اسمها uisetup وreposetup، تُستدعى من شيفرة التوزيع بعد إنشاء واجهة المستخدم والمستودع. ومن السلوكيات الشائعة استخدام دالة reposetup لتغليف المستودع داخل صنف مستودع فرعي يقدّمه الامتداد. ويتيح هذا للامتداد تعديل أنواع كثيرة من السلوك الأساسي. فعلى سبيل المثال، كتبتُ امتدادًا يتدخّل في uisetup ويضبط خاصية الإعداد ui.username استنادًا إلى بيانات مصادقة SSH المتاحة من البيئة.
ويمكن كتابة امتدادات أكثر جسامًا لإضافة أنواع مستودعات. فعلى سبيل المثال، يسجّل مشروع hgsubversion (غير المُدرَج ضمن Mercurial) نوع مستودع لمستودعات Subversion. ويجعل ذلك من الممكن الاستنساخ (clone) من مستودع Subversion كأنّه مستودع Mercurial تقريبًا. بل ومن الممكن الدفع (push) إلى مستودع Subversion أيضًا، وإن كان هناك عدد من الحالات الحدّية بسبب عدم التطابق (impedance mismatch) بين النظامين. أمّا واجهة المستخدم، من جهة أخرى، فشفافة تمامًا.
أمّا من يريد تغيير Mercurial جذريًا، فهناك ما يُسمّى عادةً بـ«الترقيع القِرَدي» (monkeypatching) في عالم اللغات الديناميكية. ولأنّ شيفرة الامتداد تعمل في فضاء العناوين نفسه الذي يعمل فيه Mercurial، ولأنّ Python لغة مرنة إلى حدٍّ كبير مع قدرات عميقة على الانعكاس (reflection)، فإنّه من الممكن (بل سهل جدًّا) تعديل أي دالة أو صنف معرَّف في Mercurial. وعلى أنّ هذا قد يؤدّي إلى حِيَل قبيحة نوعًا ما، فهو كذلك آلية قوية جدًّا. فعلى سبيل المثال، يعدّل الامتداد highlight الموجود في hgext خادم الويب المدمج لإضافة تلوين بناء الجمل (syntax highlighting) إلى الصفحات في متصفّح المستودع التي تتيح لك فحص محتويات الملفات.
وهناك طريقة أخرى لتوسيع Mercurial، وهي أبسط بكثير: الأسماء المستعارة (aliases). إذ يمكن لأي ملف إعداد أن يعرّف اسمًا مستعارًا بوصفه اسمًا جديدًا لأمر موجود مع مجموعة محدّدة من الخيارات مضبوطة مسبقًا. ويجعل هذا أيضًا من الممكن منح أسماء أقصر لأي أوامر. وتشمل الإصدارات الحديثة من Mercurial القدرة على استدعاء أمر صدفة (shell) كاسم مستعار، بحيث تتمكّن من تصميم أوامر معقّدة باستخدام برمجة الصدفة وحدها.
12.5.2. الخطّافات
وفّرت أنظمة التحكّم بالإصدارات منذ زمن طويل الخطّافات (hooks) كوسيلة لتفاعل أحداث نظام التحكّم بالإصدارات مع العالم الخارجي. ومن الاستخدامات الشائعة إرسال إشعار إلى نظام تكامل مستمر (continuous integration system) أو تحديث مجلّد العمل على خادم ويب بحيث تصبح التغييرات مرئية للعالم. وبالطبع، يضمّ Mercurial أيضًا نظامًا فرعيًا لاستدعاء خطّافات من هذا النوع.
وفي الواقع، يضمّ هذا النظام الفرعي بدوره نوعين. أحدهما أقرب إلى الخطّافات التقليدية في أنظمة التحكّم بالإصدارات الأخرى، إذ يستدعي نصوصًا برمجية في الصدفة. والآخر أكثر إثارة للاهتمام، لأنّه يتيح للمستخدمين استدعاء خطّافات Python عبر تحديد وحدة Python واسم دالة لاستدعائها من تلك الوحدة. وليس هذا أسرع فحسب لأنّه يعمل في العملية نفسها، بل يمرّر أيضًا كائنَي repo وui، ما يعني أنك تستطيع بسهولة إطلاق تفاعلات أكثر تعقيدًا داخل نظام التحكّم بالإصدارات.
يمكن تقسيم خطّافات Mercurial إلى خطّافات ما قبل الأمر، وما بعد الأمر، والخطّافات الضابطة (controlling)، وخطّافات متنوّعة. النوعان الأولان يُعرَّبان لأي أمر بسهولة عبر تحديد مفتاح pre-command أو post-command في قسم hooks من ملف إعدادات. أمّا النوعان الآخران فهناك مجموعة معرّفة مسبقًا من الأحداث. والفرق في الخطّافات الضابطة أنها تُنفَّذ قبل وقوع شيءٍ ما مباشرة، وقد لا تسمح بذلك الحدث بالتقدّم أكثر من ذلك. ويُستخدم هذا عادةً للتحقّق من مجموعات التغييرات بطريقة ما على خادم مركزي؛ وبسبب طبيعة Mercurial الموزّعة، لا يمكن فرض أي فحوص كهذه وقت الإصدار. فعلى سبيل المثال، يستخدم مشروع Python خطّافًا لضمان فرض بعض جوانب أسلوب البرمجة في جميع أنحاء قاعدة الشيفرة—فإذا أضافت مجموعة تغييرات شيفرة بأسلوب غير مسموح به، فإنّ المستودع المركزي سيرفضها.
ومن الاستخدامات المثيرة للاهتمام للخطّافات سجلّ الدفع (pushlog، ويستخدمه مشروع Mozilla وعدد من المؤسسات التجارية). فسجلّ الدفع يسجّل كل عملية دفع (لأن الدفع قد يضمّ أي عدد من مجموعات التغييرات) ويسجّل من قام بهذه العملية ومتى، ما يوفّر نوعًا من مسارات التدقيق (audit trail) للمستودع.
12.6. الدروس المستفادة
كان أحد أوّل القرارات التي اتخذها Matt عند بدء تطوير Mercurial هو تطويره بلغة Python. وقد أثبتت Python تفوّقها في قابلية التوسيع (عبر الامتدادات والخطّافات) وسهولة البرمجة فيها إلى حدٍّ كبير. كما أنها تتولّى عنك الكثير من أعمال التوافق بين المنصّات المختلفة، ممّا يجعل من الممكن نسبيًا أن يعمل Mercurial جيدًا عبر أنظمة التشغيل الرئيسية الثلاثة. ومن الجهة الأخرى، فإنّ Python بطيئة مقارنةً بغيرها من اللغات المُصرَّفة (compiled) العديدة؛ وعلى وجه التحديد فإنّ بدء تشغيل المفسّر (interpreter) بطيء نسبيًا، وهو أمر سيّئ بصفة خاصة بالأدوات التي لها استدعاءات قصيرة كثيرة (مثل نظام التحكّم بالإصدارات) بدلًا من عمليات طويلة التشغيل.
واتُّخذ قرار مبكّر بجعل تعديل مجموعات التغييرات بعد إصدارها صعبًا. ولأنّ من المستحيل تغيير مراجعة دون تعديل تجزئة هويتها، فإنّ «سحب» مجموعات التغييرات بعد نشرها على الإنترنت العام أمر عسير، ويجعل Mercurial من الصعب فعل ذلك. غير أنّه يُسمح عادةً بتغيير المراجعات غير المنشورة، وقد ظلّ المجتمع يحاول جعل ذلك أسهل منذ وقت قصير بعد الإصدار. وهناك امتدادات تحاول حلّ المشكلة، لكنها تتطلّب خطوات تعلّم ليست بديهية جدًّا للمستخدمين الذين استخدموا Mercurial الأساسي من قبل.
وتُعدّ سجلّات المراجعات جيّدة في تقليل انتقالات رأس القرص، وقد أتت البنية الطبقية لسجلّ التغييرات والبيانات الوصفية وسجلّات الملفات بنتيجة ممتازة. فالإصدار سريع، ولا تستهلك المراجعات سوى مساحة قرص قليلة نسبيًا. لكنّ بعض الحالات مثل إعادة تسمية الملفات ليست كفؤة تامّة بسبب التخزين المنفصل للمراجعات لكل ملف؛ وسيُعالَج ذلك في النهاية، لكنه سيتطلّب نوعًا ما من انتهاك الطبقية بأسلوب حيلة. وعلى نحو مماثل، فإنّ DAG لكل ملف المستخدم في توجيه تخزين سجلّ الملفات لا يُستخدَم كثيرًا في الواقع، بحيث يمكن القول إنّ بعض الشيفرة المستخدَمة لإدارة تلك البيانات تُعَدّ عبئًا إضافيًا.
وكان محور أساسي آخر لـ Mercurial هو جعل تعلّمه سهلًا. نحاول توفير معظم الوظائف المطلوبة في مجموعة صغيرة من الأوامر الأساسية، بخيارات متّسقة عبر الأوامر. والنية أن يُتعلَّم Mercurial في الغالب على مراحل، خصوصًا لمن سبق له استخدام نظام تحكّم بالإصدارات آخر؛ وتمتدّ هذه الفلسفة إلى فكرة أنّ الامتدادات يمكن أن تُستخدم لتخصيص Mercurial أكثر من ذلك لاستعمال بعينه. ولهذا السبب حاول المطوّرون أيضًا إبقاء واجهة المستخدم متوافقة مع أنظمة التحكّم بالإصدارات الأخرى، وSubversion على وجه الخصوص. وعلى نحو مماثل، حاول الفريق توفير توثيق جيّد يمكن الوصول إليه من التطبيق نفسه، مع إحالات متقاطعة إلى مواضيع مساعدة وأوامر أخرى. ونبذل جهدًا كبيرًا لتقديم رسائل خطأ مفيدة، تتضمّن تلميحات بما ينبغي تجربته بدلًا من العملية التي فشلت.
وقد تكون بعض الخيارات الأصغر التي اتُّخذت مفاجئة للمستخدمين الجدد. فعلى سبيل المثال، فإنّ معالجة الوسوم (كما ناقشنا في قسم سابق) بوضعها في ملف منفصل داخل مجلّد العمل أمر يكرهه كثيرون في البداية، لكنّ للآلية خصائص مرغوبة جدًّا (مع أنّ لها عيوبها كذلك بالطبع). وعلى نحو مماثل، اختارت أنظمة التحكّم بالإصدارات الأخرى أن ترسل افتراضيًا إلى مضيف بعيد مجموعة التغييرات المُستخرَجة فقط وأسلافها، في حين يرسل Mercurial كل مجموعة تغييرات مُصدَرة لا يمتلكها المضيف البعيد. ولكلا النهجين قدر من المعقولية، ويعتمد أيّهما الأنسب لك على أسلوب التطوير لديك.
وكما في أي مشروع برمجيات، هناك مفاضلات كثيرة يجب اتخاذها. وأظنّ أنّ Mercurial اتخذ قرارات جيّدة، وإن كان من المتيسّر، بنظرةٍ مريحة إلى الماضي، أن تكون بعض الخيارات الأخرى أكثر ملاءمة. تاريخيًا، يبدو أنّ Mercurial ينتمي إلى الجيل الأول من أنظمة التحكّم الموزّع بالإصدارات، وقد نضج بما يكفي ليكون جاهزًا للاستخدام العام. وأنا على الأقلّ أتطلّع إلى رؤية شكل الجيل القادم.