सी # में स्थिरांक और गिट हुक का निर्माण

आपको एक कहानी सुनाता हूं। एक बार दो डेवलपर्स थे: सैम और बॉब। उन्होंने एक परियोजना पर एक साथ काम किया जिसमें एक डेटाबेस था। जब डेवलपर इसमें बदलाव करना चाहता था, तो उसे एक फाइल stepNNN.sql , जहाँ NNN एक निश्चित संख्या है। विभिन्न डेवलपर्स के बीच इन नंबरों के टकराव से बचने के लिए, उन्होंने एक साधारण वेब सेवा का उपयोग किया। प्रत्येक डेवलपर, SQL फ़ाइल लिखना शुरू करने से पहले, इस सेवा में जाता था और चरण फ़ाइल के लिए एक नया नंबर आरक्षित करता था।


इस बार, सैम और बॉब दोनों को डेटाबेस में बदलाव करने की आवश्यकता थी। सैम आज्ञाकारी रूप से सेवा में चला गया और 333 नंबर को आरक्षित कर दिया। और बॉब इसे करना भूल गया। उन्होंने अपनी स्टेप फाइल के लिए सिर्फ 333 का इस्तेमाल किया। ऐसा हुआ कि इस बार बॉब ने सबसे पहले संस्करण नियंत्रण प्रणाली में अपने परिवर्तन अपलोड किए। जब सैम बाढ़ के लिए तैयार था, तो उसने पाया कि step333.sql फ़ाइल पहले से मौजूद है। उन्होंने बॉब से संपर्क किया, उन्हें समझाया कि संख्या 333 उनके लिए आरक्षित थी और उन्होंने संघर्ष को ठीक करने के लिए कहा। लेकिन बॉब ने जवाब दिया:


- यार, मेरा कोड पहले से ही 'मास्टर' में है, डेवलपर्स का एक समूह पहले से ही इसका उपयोग कर रहा है। इसके अलावा, यह पहले से ही उत्पादन के लिए पंप किया गया है। इसलिए आप अपनी जरूरत की हर चीज को ठीक करें।


मुझे आशा है कि आपने देखा कि क्या हुआ। सभी नियमों का पालन करने वाले को दंडित किया गया। सैम को अपनी फ़ाइलों को बदलना पड़ा, अपने स्थानीय डेटाबेस को संपादित करना पड़ा, आदि। निजी तौर पर, मुझे ऐसी स्थितियों से नफरत है। आइए देखें कि हम इससे कैसे बच सकते हैं।


मुख्य विचार


हम ऐसी चीजों से कैसे बचें? यदि वह वेब सेवा पर संबंधित संख्या आरक्षित नहीं करता है तो बॉब अपने कोड में क्या नहीं भर सकता है?


और हम वास्तव में इसे प्राप्त कर सकते हैं। हम प्रत्येक कमिट से पहले कस्टम कोड निष्पादित करने के लिए Git हुक का उपयोग कर सकते हैं। यह कोड सभी पुश किए गए परिवर्तनों की जाँच करेगा। यदि उनके पास एक नई चरण फ़ाइल है, तो कोड वेब सेवा से संपर्क करेगा और जांच करेगा कि क्या चरण फ़ाइल की संख्या वर्तमान डेवलपर के लिए आरक्षित है। और यदि संख्या आरक्षित नहीं है, तो कोड भरने पर रोक लगाएगा।


यही मुख्य विचार है। चलिए डिटेल्स पर चलते हैं।


C # में गिट हुक


Git आपको सीमित नहीं करता है कि आपको किन भाषाओं में हुक लिखना चाहिए। C # डेवलपर के रूप में, मैं इन उद्देश्यों के लिए परिचित C # का उपयोग करूंगा। क्या मैं ऐसा कर सकता हूँ?


हाँ, मैं कर सकता हूँ। मैक्स हैम्युलक द्वारा लिखे गए इस लेख से मूल विचार मेरे द्वारा लिया गया था। इसके लिए हमें वैश्विक डॉटनेट-स्क्रिप्ट टूल का उपयोग करना होगा। इस उपकरण को डेवलपर की मशीन पर .NET Core 2.1 + SDK की आवश्यकता होती है। मेरा मानना ​​है कि यह .NET विकास में शामिल लोगों के लिए एक उचित आवश्यकता है। dotnet-script स्थापित करना बहुत सरल है:


 > dotnet tool install -g dotnet-script 

अब हम C # में Git हुक लिख सकते हैं। ऐसा करने के लिए, अपनी परियोजना के .git\hooks फ़ोल्डर पर जाएं और एक pre-commit फ़ाइल (बिना किसी एक्सटेंशन के) बनाएं:


 #!/usr/bin/env dotnet-script Console.WriteLine("Git hook"); 

अब से, जब भी आप कोई git commit , तो आप अपने कंसोल में Git hook टेक्स्ट देखेंगे।


प्रति हुक एकाधिक हैंडलर


खैर, एक शुरुआत की गई है। अब हम pre-commit फाइल में कुछ भी लिख सकते हैं। लेकिन मुझे वास्तव में यह विचार पसंद नहीं है।


सबसे पहले, स्क्रिप्ट फ़ाइल के साथ काम करना बहुत सुविधाजनक नहीं है। मैं इसकी सभी विशेषताओं के साथ अपनी पसंदीदा आईडीई का उपयोग करूंगा। और मैं इसके बजाय कई फ़ाइलों में जटिल कोड को तोड़ने में सक्षम हो जाएगा।


लेकिन एक और चीज है जो मुझे पसंद नहीं है निम्नलिखित स्थिति की कल्पना करें। आपने कुछ तरह के चेक के साथ pre-commit बनाया। लेकिन बाद में आपको और चेक जोड़ने की जरूरत पड़ी। आपको फ़ाइल खोलनी होगी, यह तय करना होगा कि अपना कोड कहाँ पेस्ट करना है, यह पुराने कोड के साथ कैसे इंटरैक्ट करेगा, आदि। निजी तौर पर, मैं नया कोड लिखना पसंद करता हूं, और पुराने में खुदाई नहीं करता।


आइए एक बार में इन समस्याओं से निपटें।


बाहरी कोड को कॉल करें


यही हम करेंगे। आइए एक अलग फ़ोल्डर बनाएं (जैसे gitHookAssemblies )। इस फ़ोल्डर में, मैं .NET कोर असेंबली (जैसे GitHooks ) GitHookspre-commit फ़ाइल में मेरी स्क्रिप्ट इस विधानसभा से कुछ विधि कहेगी।


 public class RunHooks { public static void RunPreCommitHook() { Console.WriteLine("Git hook from assembly"); } } 

मैं अपनी पसंदीदा आईडीई में यह असेंबली बना सकता हूं और किसी भी टूल का उपयोग कर सकता हूं।


अब pre-commit फ़ाइल में मैं लिख सकता हूँ:


 #!/usr/bin/env dotnet-script #r "../../gitHookAssemblies/GitHooks.dll" GitHooks.RunHooks.RunPreCommitHook(); 

महान, यह नहीं है! अब मैं केवल अपने GitHooks बिल्ड में बदलाव कर सकता हूं। pre-commit फ़ाइल कोड कभी नहीं बदलेगा। जब मुझे कुछ सत्यापन जोड़ने की आवश्यकता होती है, तो मैं RunPreCommitHook विधि के कोड को बदल RunPreCommitHook , विधानसभा को फिर से RunPreCommitHook और इसे gitHookAssemblies फ़ोल्डर में डाल gitHookAssemblies । और यह बात है!


खैर, वास्तव में नहीं।


कैश से लड़ रहे हैं


आइए हमारी प्रक्रिया का पालन करने का प्रयास करें। संदेश को Console.WriteLine में कुछ और बदलें, असेंबली का पुनर्निर्माण करें और परिणाम को gitHookAssemblies फ़ोल्डर में gitHookAssemblies । उसके बाद, फिर से कॉल git commit करें। हम क्या देखेंगे? पुरानी पोस्ट। हमारे बदलाव नहीं पकड़े। क्यों?


चलो, निश्चितता के लिए, आपकी परियोजना c:\project फ़ोल्डर में स्थित होगी। इसका मतलब यह है कि Git हुक स्क्रिप्ट c:\project\.git\hooks फ़ोल्डर में स्थित हैं। अब, यदि आप विंडोज 10 का उपयोग कर रहे हैं, तो c:\Users\<UserName>\AppData\Local\Temp\scripts\c\project\.git\hooks\ फ़ोल्डर पर जाएं। यहां <UserName> आपके वर्तमान उपयोगकर्ता का नाम है। हम यहां क्या देखेंगे? जब हम pre-commit स्क्रिप्ट चलाते हैं, तो इस स्क्रिप्ट का एक संकलित संस्करण इस फ़ोल्डर में बनाया जाता है। यहां आप स्क्रिप्ट द्वारा संदर्भित सभी असेंबली (हमारे GitHooks.dll सहित) पा सकते हैं। और execution-cache सबफ़ोल्डर में आप SHA256 फ़ाइल पा सकते हैं। मैं यह मान सकता हूं कि इसमें हमारी pre-commit फ़ाइल का SHA256 हैश शामिल है। जिस क्षण हम स्क्रिप्ट चलाते हैं, रनटाइम फ़ाइल की वर्तमान हैश की तुलना संग्रहीत हैश से करता है। यदि वे समान हैं, तो संकलित स्क्रिप्ट के सहेजे गए संस्करण का उपयोग किया जाएगा।


इसका मतलब यह है कि चूंकि हम pre-commit फ़ाइल को कभी नहीं बदलते हैं, GitHooks.dll परिवर्तन कभी भी कैश तक नहीं पहुंचेंगे और इसका उपयोग कभी नहीं किया जाएगा।


इस स्थिति में हम क्या कर सकते हैं? खैर, प्रतिबिंब हमारी मदद करेगा। मैं अपनी स्क्रिप्ट फिर से GitHooks वह 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]); 

अब हम किसी भी समय अपने gitHookAssemblies फ़ोल्डर में GitHook.dll को अपडेट कर सकते हैं, और सभी परिवर्तन एक ही स्क्रिप्ट द्वारा उठाए जाएंगे। स्क्रिप्ट को संशोधित करना अब आवश्यक नहीं है।


यह सब बहुत अच्छा लगता है, लेकिन एक और समस्या है जिसे आगे बढ़ने से पहले हल करने की आवश्यकता है। मैं हमारे कोड द्वारा संदर्भित असेंबलियों के बारे में बात कर रहा हूं।


विधानसभाओं का इस्तेमाल किया


सब कुछ ठीक काम करता है, जब तक कि केवल एक चीज RunHooks.RunPreCommitHook विधि RunHooks.RunPreCommitHook है आउटपुट स्ट्रिंग को कंसोल पर आउटपुट RunHooks.RunPreCommitHook है। लेकिन, स्पष्ट रूप से, आमतौर पर स्क्रीन पर पाठ प्रदर्शित करना कोई दिलचस्पी नहीं है। हमें और अधिक जटिल चीजें करने की जरूरत है। और इसके लिए हमें अन्य विधानसभाओं और NuGet पैकेजों का उपयोग करने की आवश्यकता है। आइए देखें कि यह कैसे करना है।


मैं RunHooks.RunPreCommitHook को 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) 

स्पष्ट रूप से, हमें यह सुनिश्चित करने के लिए किसी तरह की आवश्यकता है कि जिन विधानसभाओं को हम संदर्भित करते हैं, वे भरी हुई हैं। मूल विचार यहाँ है। मैं अपने GitHooks.dll के साथ समान 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 चला सकते git commit और यह बिना किसी समस्या के चलेगा।


व्यापकता में सुधार


हमारी pre-commit फ़ाइल पूर्ण हो गई है। हमें अब इसे बदलने की जरूरत नहीं है। लेकिन अगर आपको बदलाव करने की आवश्यकता है, तो हमें RunHooks.RunPreCommitHook विधि को बदलना होगा। इसलिए हमने समस्या को दूसरे स्तर पर ले जाया। व्यक्तिगत रूप से, मैं किसी तरह का प्लगइन सिस्टम रखना पसंद करूंगा। हर बार कोड में भरने से पहले मुझे कुछ कार्रवाई करने की आवश्यकता होती है, मैं सिर्फ एक नया प्लगइन लिखूंगा और कुछ भी बदलने की आवश्यकता नहीं होगी। इसे हासिल करना कितना कठिन है?


मुश्किल बिल्कुल नहीं। आइए MEF का उपयोग करें। यह इसी तरह काम करता है।


पहले हमें अपने हुक हैंडलर के लिए एक इंटरफ़ेस परिभाषित करने की आवश्यकता है:


 public interface IPreCommitHook { bool Process(IList<string> args); } 

प्रत्येक हैंडलर गिट से कुछ स्ट्रिंग तर्क प्राप्त कर सकता है। इन तर्कों को args पैरामीटर के माध्यम से पारित किया जाएगा। यदि यह परिवर्तन डालने की अनुमति देता true तो 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 पैकेज से ली गई है।


इसके अलावा, आइए एक सहायक विधि बनाएं जो Export विशेषता के साथ चिह्नित सभी IPreCommitHook इंटरफ़ेस कार्यान्वयन को इकट्ठा करेगा, उन सभी को चलाएगा और इस बारे में जानकारी लौटाएगा कि क्या उन्होंने सभी को भरने की अनुमति दी थी। मैंने अपना हैंडलर एक अलग 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 जिनके नाम directory फ़ोल्डर में *Hooks.dll टेम्पलेट से मेल खाते हैं। आप यहाँ किसी भी टेम्पलेट का उपयोग कर सकते हैं। फिर हम एक PreCommitHooks ऑब्जेक्ट में IPreCommitHook इंटरफ़ेस के सभी निर्यातित कार्यान्वयन एकत्र करते हैं। और अंत में, हम सभी हुक हैंडलर शुरू करते हैं और उनके निष्पादन का परिणाम एकत्र करते हैं।


आखिरी चीज जो हमें करने की जरूरत है वह है 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 फ़ोल्डर में सभी शामिल असेंबलियों को रखना न भूलें।


हाँ, यह एक लंबा परिचय था। लेकिन अब हमारे पास C # में Git हुक हैंडलर बनाने के लिए पूरी तरह से विश्वसनीय समाधान है। हम सभी की आवश्यकता है कि gitHookAssemblies फ़ोल्डर की सामग्री को बदलना है। इसकी सामग्री को एक संस्करण नियंत्रण प्रणाली में रखा जा सकता है और इस प्रकार, सभी डेवलपर्स के बीच वितरित किया जाता है।


किसी भी स्थिति में, हमें अपनी मूल समस्या पर लौटने का समय है।


निरंतर आरक्षण के लिए वेब सेवा


हम यह सुनिश्चित करना चाहते थे कि अगर वे वेब सेवा पर संबंधित स्थिरांक को आरक्षित करना भूल गए, तो डेवलपर्स कुछ परिवर्तनों को नहीं भर पाएंगे। आइए एक सरल वेब सेवा बनाएं ताकि आप इसके साथ काम कर सकें। मैं Windows प्रमाणीकरण के साथ ASP.NET कोर वेब सेवा का उपयोग कर रहा हूं। लेकिन वास्तव में, विभिन्न विकल्प हैं।


 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 निर्देशिका है। इसमें, प्रत्येक डेवलपर एक verXXX फ़ोल्डर बना सकता है, जहां XXX एक निश्चित संख्या है जिसे पहले वेब सेवा पर आरक्षित किया जाना चाहिए। 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; } } 

यहाँ हम NuGet पैकेज LibGit2Sharp से Repository वर्ग का उपयोग करते हैं। items वेरिएबल में Git इंडेक्स में सभी नई फाइलें होंगी जो sql फोल्डर के अंदर स्थित हैं। आप चाहें तो ऐसी फ़ाइलों के लिए खोज प्रक्रिया में सुधार कर सकते हैं। versions वेरिएबल में हम verXXX फोल्डर से विभिन्न verXXX XXX एकत्र करते हैं। अंत में, 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 विधि) को पंजीकृत किया है। फिर परिणाम की वर्तमान विंडोज उपयोगकर्ता के नाम के साथ तुलना की जाती है। यह इस कार्यक्षमता को लागू करने के कई संभावित तरीकों में से एक है। उदाहरण के लिए, आप Git कॉन्फ़िगरेशन से उपयोगकर्ता नाम या ईमेल का उपयोग कर सकते हैं।


वह सब है। बस उपयुक्त विधानसभा संकलित करें और इसे अपने सभी निर्भरता के साथ gitHookAssemblies फ़ोल्डर में gitHookAssemblies । और सब कुछ अपने आप काम करेगा।


Enum मान की जाँच करना


यह महान है! अब कोई भी वेब सेवा पर संबंधित स्थिरांक को पहले से आरक्षित किए बिना डेटाबेस में परिवर्तन अपलोड करने में सक्षम नहीं होगा। लेकिन एक समान विधि का उपयोग अन्य स्थानों पर किया जा सकता है जहां निरंतर आरक्षण की आवश्यकता होती है।


उदाहरण के लिए, कहीं न कहीं आपके पास प्रोजेक्ट कोड है। प्रत्येक डेवलपर निर्दिष्ट पूर्णांक मानों के साथ इसमें नए सदस्य जोड़ सकता है:


 enum Constants { Val1 = 1, Val2 = 2, Val3 = 3 } 

हम इस गणना के सदस्यों के लिए मूल्यों की टक्कर से बचना चाहते हैं। इसलिए, हमें वेब सेवा पर संबंधित स्थिरांक के प्रारंभिक आरक्षण की आवश्यकता है। ऐसे आरक्षण के सत्यापन को लागू करना कितना कठिन है?


यहाँ नए गिट हुक हैंडलर के लिए कोड है:


 [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; } ... } 

पहले हम जांचते हैं कि हमारे एन्यूमरेशन वाली फाइल को संशोधित किया गया है या नहीं। फिर हम इस फ़ाइल की सामग्री को नवीनतम अपलोड किए गए संस्करण से और गेट इंडेक्स से GetInitialContent और GetIndexContent का उपयोग करके GetInitialContent । यहाँ उनका कार्यान्वयन है:


 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-.



. . , .


  1. 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 

    .


  2. dotnet-script . .NET Core, .


  3. , . , gitHookAssemblies , , . , LibGit2Sharp . git2-7ce88e6.dll , Win-x64. , .


  4. Web-. Windows-, . Web- UI .


  5. , Git hook' . , .



निष्कर्ष


Git hook` .NET. , .


, . सौभाग्य है


PS GitHub .

Source: https://habr.com/ru/post/hi485218/


All Articles