دعني أخبرك قصة. ذات مرة كان هناك مطورين: سام وبوب. لقد عملوا معًا في مشروع كانت فيه قاعدة بيانات. عندما أراد المطور إجراء تغييرات عليه ، كان عليه إنشاء ملف stepNNN.sql
، حيث NNN هو رقم معين. لتجنب تعارض هذه الأرقام بين مطورين مختلفين ، استخدموا خدمة ويب بسيطة. كان على كل مطور ، قبل البدء في كتابة ملف SQL ، الانتقال إلى هذه الخدمة وحجز رقم جديد لملف الخطوة.
هذه المرة ، يحتاج كل من سام وبوب إلى إجراء تغييرات على قاعدة البيانات. ذهب سام بطاعة إلى الخدمة واحتفظ بالرقم 333. ونسى بوب القيام بذلك. لقد استخدم 333 فقط لملف الخطوة الخاص به. لقد حدث أن بوب هذه المرة كان أول من حمل تغييراته على نظام التحكم في الإصدار. عندما كان سام مستعدًا للفيضان ، اكتشف أن ملف step333.sql
موجود بالفعل. اتصل ببوب ، وأوضح له أن الرقم 333 محجوز له وطلب منه حل النزاع. لكن بوب أجاب:
- المتأنق ، الكود الخاص بي موجود بالفعل في "master" ، حفنة من المطورين يستخدمونه بالفعل. بالإضافة إلى ذلك ، لقد تم ضخه بالفعل إلى الإنتاج. لذلك فقط إصلاح كل ما تحتاجه هناك.
أتمنى أن تكون قد لاحظت ما حدث. تمت معاقبة الشخص الذي اتبع جميع القواعد. كان على سام تغيير ملفاته وتعديل قاعدة بياناته المحلية وما إلى ذلك. شخصيا ، أنا أكره مثل هذه الحالات. دعونا نرى كيف يمكننا تجنب ذلك.
الفكرة الرئيسية
كيف نتجنب مثل هذه الأشياء؟ ماذا لو لم يستطع بوب ملء كوده إذا لم يحتفظ بالرقم المناظر على خدمة الويب؟
ويمكننا تحقيق ذلك بالفعل. يمكننا استخدام السنانير Git لتنفيذ التعليمات البرمجية المخصصة قبل كل التزام. سوف يتحقق هذا الرمز من جميع التغييرات التي تم دفعها. إذا كانت تحتوي على ملف خطوة جديد ، فسوف يتصل الكود بخدمة الويب ويتحقق مما إذا كان رقم ملف الخطوة محجوزًا للمطور الحالي. وإذا كان الرقم غير محجوز ، فسيحظر الرمز التعبئة.
هذه هي الفكرة الرئيسية. دعنا ننتقل إلى التفاصيل.
السنانير بوابة في C #
بوابة لا يحدك في اللغات التي يجب أن تكتب السنانير. كمطور C # ، أفضل استخدام C # المألوف لهذه الأغراض. هل يمكنني فعل هذا؟
نعم استطيع أخذت الفكرة الأساسية بي من هذا المقال الذي كتبه ماكس هامولياك. يتطلب منا استخدام أداة dotnet-script العالمية. تتطلب هذه الأداة .NET Core 2.1 + SDK على جهاز المطور. أعتقد أن هذا مطلب معقول لأولئك المشاركين في تطوير .NET. تثبيت dotnet-script
بسيط للغاية:
> dotnet tool install -g dotnet-script
الآن يمكننا كتابة السنانير جيت في C #. للقيام بذلك ، انتقل إلى المجلد .git\hooks
الخاص .git\hooks
وإنشاء ملف pre-commit
(بدون أي امتداد):
#!/usr/bin/env dotnet-script Console.WriteLine("Git hook");
من الآن فصاعدًا ، كلما فعلت ذلك ، ستشاهد نص Git hook
في وحدة التحكم الخاصة بك.
معالجات متعددة لكل خطاف
حسنا ، لقد تم البدء. الآن يمكننا كتابة أي شيء في ملف pre-commit
. لكنني لا أحب هذه الفكرة حقًا.
أولاً ، العمل مع ملف نصي ليس ملائمًا للغاية. أفضل استخدام IDE المفضل لدي مع كل ميزاته. وأود أن أكون قادراً على تقسيم التعليمات البرمجية المعقدة إلى ملفات متعددة.
ولكن هناك شيء آخر لا أحبه. تخيل الموقف التالي. قمت بإنشاء pre-commit
مع نوع من الشيكات. ولكن في وقت لاحق كنت بحاجة لإضافة المزيد من الشيكات. سيتعين عليك فتح الملف وتحديد مكان لصق الشفرة وكيفية تفاعلها مع الشفرة القديمة وما إلى ذلك. أنا شخصياً أفضل أن أكتب رمزًا جديدًا ، وليس الحفر في الكود القديم.
دعونا نتعامل مع هذه المشاكل واحدة في وقت واحد.
اتصل بالرمز الخارجي
هذا هو ما سنفعله. لنقم بإنشاء مجلد منفصل (مثل gitHookAssemblies
). في هذا المجلد ، سأضع مجموعة .NET Core (مثل GitHooks
). سيقوم البرنامج النصي الخاص بي في ملف pre-commit
باستدعاء طريقة ما من هذا التجميع.
public class RunHooks { public static void RunPreCommitHook() { Console.WriteLine("Git hook from assembly"); } }
يمكنني إنشاء هذا التجميع في IDE المفضل لدي واستخدام أي أدوات.
الآن في ملف pre-commit
يمكنني الكتابة:
#!/usr/bin/env dotnet-script #r "../../gitHookAssemblies/GitHooks.dll" GitHooks.RunHooks.RunPreCommitHook();
عظيم ، أليس كذلك! الآن يمكنني فقط إجراء تغييرات في بناء GitHooks
بي. لن يتغير رمز ملف pre-commit
. عندما أحتاج إلى إضافة بعض التحقق ، سأقوم بتغيير رمز الأسلوب RunPreCommitHook
، وإعادة إنشاء التجميع ووضعه في مجلد gitHookAssemblies
. وهذا كل شيء!
حسنا ، ليس حقا.
محاربة ذاكرة التخزين المؤقت
دعونا نحاول متابعة عمليتنا. تغيير الرسالة في Console.WriteLine
إلى شيء آخر ، إعادة إنشاء التجميع ووضع النتيجة في مجلد gitHookAssemblies
. بعد ذلك ، ندعو git commit
مرة أخرى. ماذا سوف نرى؟ المنصب القديم. تغييراتنا لم يمسك. لماذا؟
دعنا ، بالتأكيد ، يقع c:\project
مجلد c:\project
. هذا يعني أن البرامج النصية Git hook موجودة في المجلد c:\project\.git\hooks
. الآن ، إذا كنت تستخدم نظام التشغيل Windows 10 ، فانتقل إلى المجلد c:\Users\<UserName>\AppData\Local\Temp\scripts\c\project\.git\hooks\
. هنا <UserName>
هو اسم المستخدم الحالي. ماذا سنرى هنا؟ عندما نقوم بتشغيل البرنامج النصي pre-commit
، يتم إنشاء نسخة مترجمة من هذا البرنامج النصي في هذا المجلد. هنا يمكنك العثور على جميع التجميعات المشار إليها بواسطة البرنامج النصي (بما في ذلك GitHooks.dll
). وفي مجلد execution-cache
يمكنك العثور على ملف SHA256. يمكنني افتراض أنه يحتوي على تجزئة SHA256 لملف pre-commit
بنا. عند تشغيل البرنامج النصي ، يقارن وقت التشغيل بين التجزئة الحالية للملف والتجزئة المخزنة. إذا كانت متساوية ، فسيتم استخدام الإصدار المحفوظ من البرنامج النصي المترجم.
هذا يعني أنه نظرًا لأننا لم نغير أبدًا ملف pre-commit
، فلن تصل التغييرات إلى GitHooks.dll
إلى ذاكرة التخزين المؤقت ولن يتم استخدامها أبدًا.
ماذا يمكننا أن نفعل في هذه الحالة؟ حسنا ، سوف يساعدنا التفكير. سأقوم بإعادة كتابة النص البرمجي GitHooks
Reflection" بدلاً من الرجوع مباشرة إلى مجموعة GitHooks
. إليك ما سيبدو عليه ملف pre-commit
بنا بعد هذا:
#!/usr/bin/env dotnet-script #r "nuget: System.Runtime.Loader, 4.3.0" using System.IO; using System.Runtime.Loader; var hooksDirectory = Path.Combine(Environment.CurrentDirectory, "gitHookAssemblies"); var assemblyPath = Path.Combine(hooksDirectory, "GitHooks.dll"); var assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(assemblyPath); if(assembly == null) { Console.WriteLine($"Can't load assembly from '{assemblyPath}'."); } var collectorsType = assembly.GetType("GitHooks.RunHooks"); if(collectorsType == null) { Console.WriteLine("Can't find entry type."); } var method = collectorsType.GetMethod("RunPreCommitHook", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static); if(method == null) { Console.WriteLine("Can't find method for pre-commit hooks."); } method.Invoke(null, new object[0]);
الآن يمكننا تحديث GitHook.dll
في مجلد gitHookAssemblies
في أي وقت ، وسيتم التقاط جميع التغييرات بواسطة البرنامج النصي نفسه. تعديل البرنامج النصي نفسه لم يعد ضروريا.
كل هذا يبدو رائعًا ، ولكن هناك مشكلة أخرى تحتاج إلى حل قبل المضي قدمًا. أنا أتحدث عن التجميعات المشار إليها بواسطة الكود.
المجالس المستخدمة
كل شيء يعمل بشكل جيد ، طالما أن الطريقة الوحيدة التي RunHooks.RunPreCommitHook
طريقة RunHooks.RunPreCommitHook
هي إخراج السلسلة إلى وحدة التحكم. ولكن ، بصراحة ، لا يعد عرض النص على الشاشة أمرًا مهمًا. نحن بحاجة إلى القيام بأشياء أكثر تعقيدًا. ولهذا نحن بحاجة إلى استخدام التجميعات الأخرى وحزم NuGet. دعونا نرى كيف نفعل هذا.
سوف أقوم RunHooks.RunPreCommitHook
بحيث يستخدم حزمة LibGit2Sharp
:
public static void RunPreCommitHook() { using var repo = new Repository(Environment.CurrentDirectory); Console.WriteLine(repo.Info.WorkingDirectory); }
الآن ، إذا قمت بتنفيذ git commit
، فسوف أحصل على رسالة الخطأ التالية:
System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.IO.FileLoadException: Could not load file or assembly 'LibGit2Sharp, Version=0.26.0.0, Culture=neutral, PublicKeyToken=7cbde695407f0333'. General Exception (0x80131500)
من الواضح أننا نحتاج إلى طريقة لضمان تحميل التجميعات التي نشير إليها. الفكرة الأساسية هنا هي. سوف أضع كل رمز التجميع المطلوب لتنفيذ التعليمات البرمجية في مجلد gitHookAssemblies
نفسه مع GitHooks.dll
. للحصول على جميع التجميعات المطلوبة ، يمكنك استخدام الأمر dotnet publish
. في حالتنا ، نحتاج إلى وضع LibGit2Sharp.dll
و git2-7ce88e6.dll
في هذا المجلد.
علينا أيضا أن نغير pre-commit
. سنضيف الكود التالي إليها:
#!/usr/bin/env dotnet-script #r "nuget: System.Runtime.Loader, 4.3.0" using System.IO; using System.Runtime.Loader; var hooksDirectory = Path.Combine(Environment.CurrentDirectory, "gitHookAssemblies"); var assemblyPath = Path.Combine(hooksDirectory, "GitHooks.dll"); AssemblyLoadContext.Default.Resolving += (context, assemblyName) => { var assemblyPath = Path.Combine(hooksDirectory, $"{assemblyName.Name}.dll"); if(File.Exists(assemblyPath)) { return AssemblyLoadContext.Default.LoadFromAssemblyPath(assemblyPath); } return null; }; ...
سيحاول هذا الرمز تحميل جميع التجميعات التي تعذر على وقت التشغيل العثور عليها من مجلد gitHookAssemblies
.
الآن يمكنك تشغيل git commit
وسيتم تشغيله دون مشاكل.
تحسين القابلية للتوسعة
ملف pre-commit
بنا كامل. لم نعد بحاجة إلى تغييره. ولكن إذا كنت بحاجة إلى إجراء تغييرات ، فسيتعين علينا تغيير الأسلوب RunHooks.RunPreCommitHook
. لذلك نحن فقط نقل المشكلة إلى مستوى آخر. شخصيا ، أنا أفضل أن يكون لدي نوع من نظام البرنامج المساعد. في كل مرة أحتاج إلى إضافة بعض الإجراءات التي يجب تنفيذها قبل ملء الرمز ، سأكتب فقط مكونًا إضافيًا جديدًا ولن يلزم تغيير أي شيء. ما مدى صعوبة تحقيق هذا؟
ليس من الصعب على الإطلاق. دعنا نستخدم MEF . هذه هي الطريقة التي يعمل بها.
نحتاج أولاً إلى تحديد واجهة لمعالجات الخطافات لدينا:
public interface IPreCommitHook { bool Process(IList<string> args); }
يمكن لكل معالج تلقي بعض وسائط السلسلة من Git. سيتم تمرير هذه الوسائط من خلال المعلمة args
. ستعود طريقة Process
إلى true
إذا سمحت بتغييرات الصب. وإلا سوف تعاد false
.
يمكن تعريف واجهات مماثلة لجميع السنانير ، ولكن في هذه المقالة سوف نركز فقط على الالتزام المسبق.
أنت الآن بحاجة إلى كتابة تطبيق لهذه الواجهة:
[Export(typeof(IPreCommitHook))] public class MessageHook : IPreCommitHook { public bool Process(IList<string> args) { Console.WriteLine("Message hook..."); if(args != null) { Console.WriteLine("Arguments are:"); foreach(var arg in args) { Console.WriteLine(arg); } } return true; } }
يمكن إنشاء هذه الفئات في مجموعات مختلفة إذا كنت ترغب في ذلك. لا توجد قيود حرفيًا. يتم أخذ سمة Export
من حزمة System.ComponentModel.Composition
NuGet.
أيضًا ، دعنا ننشئ طريقة مساعدة من شأنها أن تجمع كل IPreCommitHook
واجهة IPreCommitHook
Export
، وقم بتشغيلها جميعًا وإرجاع معلومات حول ما إذا كانت جميعها قد سمحت بالتعبئة. وضعت GitHooksCollector
في مجموعة GitHooksCollector
منفصلة ، لكن هذا ليس مهمًا جدًا:
public class Collectors { private class PreCommitHooks { [ImportMany(typeof(IPreCommitHook))] public IPreCommitHook[] Hooks { get; set; } } public static int RunPreCommitHooks(IList<string> args, string directory) { var catalog = new DirectoryCatalog(directory, "*Hooks.dll"); var container = new CompositionContainer(catalog); var obj = new PreCommitHooks(); container.ComposeParts(obj); bool success = true; foreach(var hook in obj.Hooks) { success &= hook.Process(args); } return success ? 0 : 1; } }
يستخدم هذا الرمز أيضًا حزمة System.ComponentModel.Composition
NuGet. أولاً ، نقول أننا *Hooks.dll
جميع التجميعات التي يطابق *Hooks.dll
قالب *Hooks.dll
في مجلد directory
. يمكنك استخدام أي قالب تريده هنا. ثم نقوم بتجميع جميع التطبيقات المصدرة لواجهة IPreCommitHook
في كائن PreCommitHooks
. وأخيرًا ، نبدأ جميع معالجات الخطافات ونجمع نتيجة تنفيذها.
آخر شيء نحتاج إلى القيام به هو تغيير بسيط في ملف pre-commit
:
#!/usr/bin/env dotnet-script #r "nuget: System.Runtime.Loader, 4.3.0" using System.IO; using System.Runtime.Loader; var hooksDirectory = Path.Combine(Environment.CurrentDirectory, "gitHookAssemblies"); var assemblyPath = Path.Combine(hooksDirectory, "GitHooksCollector.dll"); AssemblyLoadContext.Default.Resolving += (context, assemblyName) => { var assemblyPath = Path.Combine(hooksDirectory, $"{assemblyName.Name}.dll"); if(File.Exists(assemblyPath)) { return AssemblyLoadContext.Default.LoadFromAssemblyPath(assemblyPath); } return null; }; var assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(assemblyPath); if(assembly == null) { Console.WriteLine($"Can't load assembly from '{assemblyPath}'."); } var collectorsType = assembly.GetType("GitHooksCollector.Collectors"); if(collectorsType == null) { Console.WriteLine("Can't find collector's type."); } var method = collectorsType.GetMethod("RunPreCommitHooks", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static); if(method == null) { Console.WriteLine("Can't find collector's method for pre-commit hooks."); } int exitCode = (int) method.Invoke(null, new object[] { Args, hooksDirectory }); Environment.Exit(exitCode);
ولا تنسَ أن تضع كل المجموعات المشاركة في مجلد gitHookAssemblies
.
نعم ، لقد كانت مقدمة طويلة. ولكن الآن لدينا حل موثوق به تمامًا لإنشاء معالجات ربط Git في C #. كل ما هو مطلوب منا هو تغيير محتويات مجلد gitHookAssemblies
. يمكن وضع محتوياته في نظام التحكم في الإصدار ، وبالتالي ، يمكن توزيعه بين جميع المطورين.
في أي حال ، لقد حان الوقت للعودة إلى مشكلتنا الأصلية.
خدمة الويب للحجز المستمر
أردنا التأكد من أن المطورين لن يتمكنوا من ملء بعض التغييرات إذا نسوا حجز الثابت المطابق على خدمة الويب. لنقم بإنشاء خدمة ويب بسيطة حتى تتمكن من العمل معها. أنا أستخدم خدمة ASP.NET Core Web مع مصادقة Windows. ولكن في الواقع ، هناك خيارات مختلفة.
using System.Collections.Generic; using System.Linq; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace ListsService.Controllers { public sealed class ListItem<T> { public ListItem(T value, string owner) { Value = value; Owner = owner; } public T Value { get; } public string Owner { get; } } public static class Lists { public static List<ListItem<int>> SqlVersions = new List<ListItem<int>> { new ListItem<int>(1, @"DOMAIN\Iakimov") }; public static Dictionary<int, List<ListItem<int>>> AllLists = new Dictionary<int, List<ListItem<int>>> { {1, SqlVersions} }; } [Authorize] public class ListsController : Controller { [Route("/api/lists/{listId}/ownerOf/{itemId}")] [HttpGet] public IActionResult GetOwner(int listId, int itemId) { if (!Lists.AllLists.ContainsKey(listId)) return NotFound(); var item = Lists.AllLists[listId].FirstOrDefault(li => li.Value == itemId); if(item == null) return NotFound(); return Json(item.Owner); } } }
هنا ، لأغراض الاختبار ، استخدمت فئة " Lists
الثابتة" كآلية لتخزين القوائم. سيكون لكل قائمة معرف عدد صحيح. ستحتوي كل قائمة على قيم صحيحة ومعلومات حول الأشخاص الذين تم حجز هذه القيم لهم. تسمح لك طريقة GetOwner
لفئة GetOwner
بالحصول على معرف الشخص المحجوز لعنصر القائمة هذا.
التحقق من صحة ملفات الخطوة SQL
نحن الآن على استعداد للتحقق مما إذا كان بإمكاننا تحميل ملف خطوة جديد أم لا. للتأكد من صحتنا ، افترض أننا نقوم بتخزين ملفات الخطوة على النحو التالي. يحتوي المجلد الجذر sql
دليل sql
. في ذلك ، يمكن لكل مطور إنشاء مجلد verXXX
، حيث XXX
هو رقم معين يجب حجزه مسبقًا على خدمة الويب. داخل الدليل verXXX
، قد يكون verXXX
ملف .sql
واحد أو أكثر يحتوي على إرشادات لتعديل قاعدة البيانات. لن نناقش مشكلة ضمان ترتيب تنفيذ هذه الملفات .sql
هنا. هذا ليس مهما لمناقشتنا. نريد فقط أن نفعل ما يلي. إذا كان أحد المطورين يحاول تحميل ملف جديد موجود في sql/verXXX
، فيجب علينا التحقق مما إذا كان XXX
الثابت محجوزًا لهذا المطور.
إليك ما يبدو عليه رمز معالج ربط Git المقابل:
[Export(typeof(IPreCommitHook))] public class SqlStepsHook : IPreCommitHook { private static readonly Regex _expr = new Regex("\\bver(\\d+)\\b"); public bool Process(IList<string> args) { using var repo = new Repository(Environment.CurrentDirectory); var items = repo.RetrieveStatus() .Where(i => !i.State.HasFlag(FileStatus.Ignored)) .Where(i => i.State.HasFlag(FileStatus.NewInIndex)) .Where(i => i.FilePath.StartsWith(@"sql")); var versions = new HashSet<int>( items .Select(i => _expr.Match(i.FilePath)) .Where(m => m.Success) .Select(m => m.Groups[1].Value) .Select(d => int.Parse(d)) ); foreach(var version in versions) { if (!ListItemOwnerChecker.DoesCurrentUserOwnListItem(1, version)) return false; } return true; } }
نحن هنا نستخدم فئة Repository
من باقة LibGit2Sharp
. سيحتوي متغير items
على جميع الملفات الجديدة في فهرس Git الموجودة داخل مجلد sql
. يمكنك تحسين إجراء البحث عن هذه الملفات إذا كنت ترغب في ذلك. في متغير versions
نجمع الثوابت المختلفة XXX
من مجلدات verXXX
. أخيرًا ، يتحقق أسلوب ListItemOwnerChecker.DoesCurrentUserOwnListItem
لمعرفة ما إذا كانت هذه الإصدارات مسجلة لدى المستخدم الحالي في خدمة الويب في القائمة 1.
تطبيق ListItemOwnerChecker.DoesCurrentUserOwnListItem
بسيط جدًا:
class ListItemOwnerChecker { public static string GetListItemOwner(int listId, int itemId) { var handler = new HttpClientHandler { UseDefaultCredentials = true }; var client = new HttpClient(handler); var response = client.GetAsync($"https://localhost:44389/api/lists/{listId}/ownerOf/{itemId}") .ConfigureAwait(false) .GetAwaiter() .GetResult(); if (response.StatusCode == System.Net.HttpStatusCode.NotFound) { return null; } var owner = response.Content .ReadAsStringAsync() .ConfigureAwait(false) .GetAwaiter() .GetResult(); return JsonConvert.DeserializeObject<string>(owner); } public static bool DoesCurrentUserOwnListItem(int listId, int itemId) { var owner = GetListItemOwner(listId, itemId); if (owner == null) { Console.WriteLine($"There is no item '{itemId}' in the list '{listId}' registered on the lists service."); return false; } if (owner != WindowsIdentity.GetCurrent().Name) { Console.WriteLine($"Item '{itemId}' in the list '{listId}' registered by '{owner}' and you are '{WindowsIdentity.GetCurrent().Name}'."); return false; } return true; } }
نطلب هنا من خدمة الويب معرف المستخدم الذي سجل الثابت المحدد (طريقة GetListItemOwner
). ثم تتم مقارنة النتيجة مع اسم مستخدم Windows الحالي. هذه مجرد واحدة من العديد من الطرق الممكنة لتنفيذ هذه الوظيفة. على سبيل المثال ، يمكنك استخدام اسم المستخدم أو البريد الإلكتروني من تكوين Git.
هذا كل شيء. مجرد تجميع التجميع المناسب ووضعه في مجلد gitHookAssemblies
جنبا إلى جنب مع جميع التبعيات. وكل شيء سوف يعمل تلقائيا.
التحقق من قيم التعداد
هذا رائع! لن يتمكن أي شخص الآن من تحميل التغييرات على قاعدة البيانات دون الاحتفاظ مسبقًا لنفسه بالثبات المقابل في خدمة الويب. ولكن يمكن استخدام طريقة مماثلة في أماكن أخرى حيث يتطلب الحجز الدائم.
على سبيل المثال ، في مكان ما في رمز المشروع لديك التعداد. يمكن لكل مطور إضافة أعضاء جدد إليه بقيم عدد صحيح محدد:
enum Constants { Val1 = 1, Val2 = 2, Val3 = 3 }
نريد تجنب الاصطدام في القيم لأعضاء هذا التعداد. لذلك ، نطلب حجزًا أوليًا للثوابت المقابلة على خدمة الويب. ما مدى صعوبة تنفيذ التحقق من هذا التحفظ؟
هنا هو رمز معالج ربط Git الجديد:
[Export(typeof(IPreCommitHook))] public class ConstantValuesHook : IPreCommitHook { public bool Process(IList<string> args) { using var repo = new Repository(Environment.CurrentDirectory); var constantsItem = repo.RetrieveStatus() .Staged .FirstOrDefault(i => i.FilePath == @"src/GitInteraction/Constants.cs"); if (constantsItem == null) return true; if (!constantsItem.State.HasFlag(FileStatus.NewInIndex) && !constantsItem.State.HasFlag(FileStatus.ModifiedInIndex)) return true; var initialContent = GetInitialContent(repo, constantsItem); var indexContent = GetIndexContent(repo, constantsItem); var initialConstantValues = GetConstantValues(initialContent); var indexConstantValues = GetConstantValues(indexContent); indexConstantValues.ExceptWith(initialConstantValues); if (indexConstantValues.Count == 0) return true; foreach (var version in indexConstantValues) { if (!ListItemOwnerChecker.DoesCurrentUserOwnListItem(2, version)) return false; } return true; } ... }
أولاً نتحقق مما إذا كان الملف الذي يحتوي على تعدادنا قد تم تعديله. ثم نقوم باستخراج محتويات هذا الملف من أحدث إصدار تم تحميله ومن فهرس Git باستخدام GetIndexContent
و GetIndexContent
. هنا هو تنفيذها:
private string GetInitialContent(Repository repo, StatusEntry item) { var blob = repo.Head.Tip[item.FilePath]?.Target as Blob; if (blob == null) return null; using var content = new StreamReader(blob.GetContentStream(), Encoding.UTF8); return content.ReadToEnd(); } private string GetIndexContent(Repository repo, StatusEntry item) { var id = repo.Index[item.FilePath]?.Id; if (id == null) return null; var itemBlob = repo.Lookup<Blob>(id); if (itemBlob == null) return null; using var content = new StreamReader(itemBlob.GetContentStream(), Encoding.UTF8); return content.ReadToEnd(); }
. GetConstantValues
. Roslyn
. NuGet- Microsoft.CodeAnalysis.CSharp
.
private ISet<int> GetConstantValues(string fileContent) { if (string.IsNullOrWhiteSpace(fileContent)) return new HashSet<int>(); var tree = CSharpSyntaxTree.ParseText(fileContent); var root = tree.GetCompilationUnitRoot(); var enumDeclaration = root .DescendantNodes() .OfType<EnumDeclarationSyntax>() .FirstOrDefault(e => e.Identifier.Text == "Constants"); if(enumDeclaration == null) return new HashSet<int>(); var result = new HashSet<int>(); foreach (var member in enumDeclaration.Members) { if(int.TryParse(member.EqualsValue.Value.ToString(), out var value)) { result.Add(value); } } return result; }
Roslyn
. , , Microsoft.CodeAnalysis.CSharp
3.4.0
. gitHookAssemblies
, , . . , dotnet-script
Roslyn
. , - Microsoft.CodeAnalysis.CSharp
. 3.3.1
. NuGet-, .
, , Process
hook`, Web-.
. . , .
pre-commit
, , .git\hooks
. --template
git init
. - :
git config init.templatedir git_template_dir git init
core.hooksPath
Git, Git 2.9 :
git config core.hooksPath git_template_dir
.
dotnet-script
. .NET Core, .
, . , gitHookAssemblies
, , . , LibGit2Sharp
. git2-7ce88e6.dll
, Win-x64. , .
Web-. Windows-, . Web- UI .
, Git hook' . , .
استنتاج
Git hook` .NET. , .
, . حظا سعيدا
PS GitHub .