Cadangan konstanta dan kait Git dalam C #

Biarkan saya menceritakan sebuah kisah. Alkisah ada dua pengembang: Sam dan Bob. Mereka bekerja bersama pada sebuah proyek di mana ada database. Ketika pengembang ingin membuat perubahan, ia harus membuat file stepNNN.sql , di mana NNN adalah angka tertentu. Untuk menghindari konflik angka-angka ini antara pengembang yang berbeda, mereka menggunakan layanan Web sederhana. Setiap pengembang, sebelum mulai menulis file SQL, harus pergi ke layanan ini dan memesan nomor baru untuk file langkah.


Kali ini, Sam dan Bob sama-sama perlu melakukan perubahan pada database. Dengan patuh Sam pergi ke layanan dan memesan nomor 333. Dan Bob lupa melakukannya. Dia hanya menggunakan 333 untuk file langkahnya. Kebetulan kali ini Bob adalah yang pertama mengunggah perubahannya ke sistem kontrol versi. Ketika Sam siap membanjiri, ia menemukan bahwa file step333.sql sudah ada. Dia menghubungi Bob, menjelaskan kepadanya bahwa nomor 333 disediakan untuknya dan meminta untuk memperbaiki konflik. Tetapi Bob menjawab:


- Bung, kode saya sudah ada di 'master', banyak pengembang sudah menggunakannya. Selain itu, sudah dipompa keluar untuk produksi. Jadi perbaiki semua yang Anda butuhkan di sana.


Saya harap Anda memperhatikan apa yang terjadi. Orang yang mengikuti semua aturan dihukum. Sam harus mengubah file-nya, mengedit database lokalnya, dll. Secara pribadi, saya benci situasi seperti itu. Mari kita lihat bagaimana kita bisa menghindarinya.


Ide utama


Bagaimana kita menghindari hal-hal seperti itu? Bagaimana jika Bob tidak dapat mengisi kodenya jika ia tidak memesan nomor yang sesuai di layanan Web?


Dan kita benar-benar dapat mencapai ini. Kita dapat menggunakan kait Git untuk mengeksekusi kode khusus sebelum setiap komit. Kode ini akan memeriksa semua perubahan yang didorong. Jika mereka berisi file langkah baru, kode akan menghubungi layanan Web dan memeriksa apakah jumlah file langkah dicadangkan untuk pengembang saat ini. Dan jika nomornya tidak dicadangkan, kode akan melarang isinya.


Itulah ide utamanya. Mari kita beralih ke detailnya.


Git kait di C #


Git tidak membatasi Anda dalam bahasa apa yang harus Anda tulis. Sebagai pengembang C #, saya lebih suka menggunakan C # yang akrab untuk tujuan ini. Bisakah saya melakukan ini?


Ya saya bisa. Ide dasar diambil oleh saya dari artikel ini yang ditulis oleh Max Hamulyรกk. Ini mengharuskan kita untuk menggunakan alat skrip dotnet global. Alat ini membutuhkan .NET Core 2.1 + SDK pada mesin pengembang. Saya percaya ini adalah persyaratan yang wajar bagi mereka yang terlibat dalam pengembangan .NET. Instalasi dotnet-script sangat sederhana:


 > dotnet tool install -g dotnet-script 

Sekarang kita bisa menulis kait Git di C #. Untuk melakukannya, buka folder .git\hooks proyek Anda dan buat file pre-commit (tanpa ekstensi apa pun):


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

Mulai sekarang, setiap kali Anda melakukan git commit , Anda akan melihat teks Git hook di konsol Anda.


Beberapa penangan per kait


Yah, sebuah awal telah dibuat. Sekarang kita dapat menulis apa saja di file pre-commit . Tapi saya tidak terlalu suka ide ini.


Pertama, bekerja dengan file skrip sangat tidak nyaman. Saya lebih suka menggunakan IDE favorit saya dengan semua fitur-fiturnya. Dan saya lebih suka dapat memecah kode kompleks menjadi beberapa file.


Tetapi ada satu hal lagi yang tidak saya sukai. Bayangkan situasi berikut. Anda membuat pre-commit dengan semacam cek. Tetapi nanti Anda perlu menambahkan lebih banyak cek. Anda harus membuka file, memutuskan di mana akan menempelkan kode Anda, bagaimana ia akan berinteraksi dengan kode lama, dll. Secara pribadi, saya lebih suka menulis kode baru, dan tidak menggali yang lama.


Mari kita selesaikan masalah ini satu per satu.


Panggil kode eksternal


Ini yang akan kita lakukan. Mari kita buat folder terpisah (mis. gitHookAssemblies ). Di folder ini, saya akan meletakkan perakitan .NET Core (mis. GitHooks ). Skrip saya di file pre-commit hanya akan memanggil beberapa metode dari majelis ini.


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

Saya dapat membuat perakitan ini di IDE favorit saya dan menggunakan alat apa pun.


Sekarang di file pre-commit saya bisa menulis:


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

Bagus, bukan! Sekarang saya hanya bisa melakukan perubahan pada build GitHooks saya. Kode file pre-commit tidak akan pernah berubah. Ketika saya perlu menambahkan beberapa verifikasi, saya akan mengubah kode metode RunPreCommitHook , membangun kembali perakitan dan meletakkannya di folder gitHookAssemblies . Dan itu dia!


Yah, tidak juga.


Memerangi cache


Mari kita coba ikuti proses kita. Ubah pesan di Console.WriteLine ke sesuatu yang lain, gitHookAssemblies kembali rakitan dan masukkan hasilnya di folder gitHookAssemblies . Setelah itu, panggil git commit lagi. Apa yang akan kita lihat? Posting lama. Perubahan kami tidak berhasil. Mengapa


Biarkan, untuk kepastian, proyek Anda berada di folder c:\project . Ini berarti bahwa skrip Git hook berada di folder c:\project\.git\hooks . Sekarang, jika Anda menggunakan Windows 10, buka c:\Users\<UserName>\AppData\Local\Temp\scripts\c\project\.git\hooks\ . Di sini <UserName> adalah nama pengguna Anda saat ini. Apa yang akan kita lihat di sini? Saat kami menjalankan skrip pre-commit , versi kompilasi dari skrip ini dibuat di folder ini. Di sini Anda dapat menemukan semua majelis yang dirujuk oleh skrip (termasuk GitHooks.dll kami). Dan dalam subfolder execution-cache Anda dapat menemukan file SHA256. Saya dapat berasumsi bahwa itu berisi hash SHA256 dari file pre-commit . Saat kita menjalankan skrip, runtime membandingkan hash file saat ini dengan hash yang disimpan. Jika sama, versi skrip terkompilasi yang disimpan akan digunakan.


Ini berarti bahwa karena kita tidak pernah mengubah file pre-commit , perubahan ke GitHooks.dll tidak akan pernah mencapai cache dan tidak akan pernah digunakan.


Apa yang bisa kita lakukan dalam situasi ini? Nah, Refleksi akan membantu kita. Saya akan menulis ulang skrip saya sehingga menggunakan Refleksi alih-alih merujuk langsung ke perakitan GitHooks . Inilah yang akan terlihat seperti file pre-commit kami setelah ini:


 #!/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]); 

Sekarang kita dapat memperbarui GitHook.dll di folder gitHookAssemblies kapan saja, dan semua perubahan akan diambil oleh skrip yang sama. Memodifikasi skrip itu sendiri tidak lagi diperlukan.


Semua ini terdengar hebat, tetapi ada masalah lain yang perlu diselesaikan sebelum melanjutkan. Saya sedang berbicara tentang majelis yang dirujuk oleh kode kami.


Majelis yang digunakan


Semuanya berfungsi dengan baik, selama satu-satunya yang RunHooks.RunPreCommitHook metode RunHooks.RunPreCommitHook adalah mengeluarkan string ke konsol. Tapi, terus terang, biasanya menampilkan teks di layar tidak menarik. Kita perlu melakukan hal-hal yang lebih kompleks. Dan untuk ini kita perlu menggunakan paket Majelis dan NuGet lainnya. Mari kita lihat bagaimana melakukan ini.


Saya akan RunHooks.RunPreCommitHook sehingga menggunakan paket LibGit2Sharp :


 public static void RunPreCommitHook() { using var repo = new Repository(Environment.CurrentDirectory); Console.WriteLine(repo.Info.WorkingDirectory); } 

Sekarang, jika saya menjalankan git commit , saya akan mendapatkan pesan kesalahan berikut:


 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) 

Jelas, kita perlu beberapa cara untuk memastikan bahwa majelis yang kita rujuk telah dimuat. Ide dasarnya di sini adalah. Saya akan meletakkan semua kode perakitan yang diperlukan untuk mengeksekusi kode ke folder gitHookAssemblies sama bersama dengan GitHooks.dll saya. Untuk mendapatkan semua perangkat yang diperlukan, Anda dapat menggunakan perintah dotnet publish . Dalam kasus kami, kami perlu menempatkan LibGit2Sharp.dll dan git2-7ce88e6.dll dalam folder ini.


Kami juga harus mengubah pre-commit . Kami akan menambahkan kode berikut ke dalamnya:


 #!/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; }; ... 

Kode ini akan mencoba memuat semua majelis yang runtime tidak dapat menemukan sendiri dari folder gitHookAssemblies .


Sekarang Anda dapat menjalankan git commit dan itu akan berjalan tanpa masalah.


Peningkatan Extensibility


File pre-commit selesai. Kami tidak perlu lagi mengubahnya. Tetapi jika Anda perlu melakukan perubahan, kita harus mengubah metode RunHooks.RunPreCommitHook . Jadi kami baru saja memindahkan masalah ke tingkat lain. Secara pribadi, saya lebih suka memiliki semacam sistem plugin. Setiap kali saya perlu menambahkan beberapa tindakan yang perlu dilakukan sebelum mengisi kode, saya hanya akan menulis plugin baru dan tidak ada yang perlu diubah. Seberapa sulit ini untuk dicapai?


Sama sekali tidak sulit. Mari kita gunakan MEF . Beginilah cara kerjanya.


Pertama, kita perlu mendefinisikan antarmuka untuk penangan kait kita:


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

Setiap handler dapat menerima beberapa argumen string dari Git. Argumen ini akan diteruskan melalui parameter args . Metode Process akan kembali true jika memungkinkan menuangkan perubahan. Jika tidak, false akan dikembalikan.


Antarmuka yang serupa dapat didefinisikan untuk semua kait, tetapi dalam artikel ini kami hanya akan fokus pada pra-komit.


Sekarang Anda perlu menulis implementasi antarmuka ini:


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

Kelas semacam itu dapat dibuat di majelis yang berbeda jika diinginkan. Secara harfiah tidak ada batasan. Atribut Export diambil dari paket NuGet System.ComponentModel.Composition .


Selain itu, mari kita buat metode pembantu yang akan mengumpulkan semua implementasi antarmuka IPreCommitHook ditandai dengan atribut Export , jalankan semuanya dan kembalikan informasi tentang apakah mereka semua mengizinkan isi. Saya meletakkan handler saya di majelis GitHooksCollector terpisah, tapi ini tidak begitu penting:


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

Kode ini juga menggunakan paket NuGet System.ComponentModel.Composition . Pertama, kita mengatakan bahwa kita akan melihat semua majelis yang namanya cocok dengan template *Hooks.dll di folder directory . Anda dapat menggunakan template apa pun yang Anda suka di sini. Kemudian kami mengumpulkan semua implementasi yang diekspor dari antarmuka IPreCommitHook ke objek PreCommitHooks . Dan akhirnya, kami memulai semua penangan kait dan mengumpulkan hasil eksekusi mereka.


Hal terakhir yang perlu kita lakukan adalah perubahan kecil ke file 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); 

Dan jangan lupa untuk meletakkan semua majelis yang terlibat dalam folder gitHookAssemblies .


Ya, itu adalah pengantar yang panjang. Tapi sekarang kami memiliki solusi yang sepenuhnya dapat diandalkan untuk membuat penangan kait Git di C #. Semua yang diperlukan dari kami adalah mengubah isi folder gitHookAssemblies . Isinya dapat ditempatkan dalam sistem kontrol versi dan, dengan demikian, didistribusikan di antara semua pengembang.


Bagaimanapun, sudah saatnya bagi kita untuk kembali ke masalah awal kita.


Layanan web untuk reservasi konstan


Kami ingin memastikan bahwa pengembang tidak akan dapat mengisi perubahan tertentu jika mereka lupa memesan konstanta yang sesuai pada layanan Web. Mari kita membuat layanan Web sederhana sehingga Anda dapat bekerja dengannya. Saya menggunakan layanan ASP.NET Core Web dengan otentikasi Windows. Namun nyatanya, ada berbagai pilihan.


 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); } } } 

Di sini, untuk tujuan pengujian, saya menggunakan kelas Lists statis sebagai mekanisme untuk menyimpan daftar. Setiap daftar akan memiliki pengenal integer. Setiap daftar akan berisi nilai integer dan informasi tentang orang-orang yang nilai-nilai ini dicadangkan. Metode GetOwner kelas GetOwner memungkinkan Anda untuk mendapatkan pengidentifikasi dari orang yang item daftar ini dicadangkan.


Memvalidasi File Langkah SQL


Sekarang kami siap untuk memeriksa apakah kami dapat mengunggah file langkah baru atau tidak. Untuk kepastian, anggaplah bahwa kita menyimpan file langkah sebagai berikut. Folder root dari proyek kami memiliki direktori sql . Di dalamnya, setiap pengembang dapat membuat folder verXXX , di mana XXX adalah nomor tertentu yang sebelumnya harus disediakan di layanan Web. Di dalam direktori verXXX , mungkin ada satu atau lebih file .sql berisi instruksi untuk memodifikasi database. Kami tidak akan membahas masalah memastikan urutan eksekusi file .sql ini di sini. Ini tidak penting untuk diskusi kita. Kami hanya ingin melakukan hal berikut. Jika seorang pengembang mencoba mengunggah file baru yang terkandung dalam sql/verXXX , kita harus memeriksa apakah XXX konstan disediakan untuk pengembang ini.


Berikut ini kode untuk penangan Git hook yang sesuai:


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

Di sini kita menggunakan kelas Repository dari paket LibGit2Sharp . Variabel items akan berisi semua file baru di indeks Git yang terletak di dalam folder sql . Anda dapat meningkatkan prosedur pencarian untuk file seperti itu jika Anda mau. Dalam variabel versions kami mengumpulkan berbagai konstanta XXX dari folder verXXX . Akhirnya, metode ListItemOwnerChecker.DoesCurrentUserOwnListItem memeriksa untuk melihat apakah versi ini terdaftar dengan pengguna saat ini di layanan Web dalam daftar 1.


Implementasi ListItemOwnerChecker.DoesCurrentUserOwnListItem cukup sederhana:


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

Di sini kami meminta layanan Web untuk pengidentifikasi pengguna yang mendaftarkan konstanta yang ditentukan (metode GetListItemOwner ). Kemudian hasilnya dibandingkan dengan nama pengguna Windows saat ini. Ini hanyalah salah satu dari banyak cara yang mungkin untuk mengimplementasikan fungsi ini. Misalnya, Anda dapat menggunakan nama pengguna atau email dari konfigurasi Git.


Itu saja. Cukup kompilasi rakitan yang sesuai dan letakkan di folder gitHookAssemblies beserta semua dependensinya. Dan semuanya akan bekerja secara otomatis.


Memeriksa nilai enum


Ini bagus! Sekarang tidak seorang pun akan dapat mengunggah perubahan ke basis data tanpa sebelumnya telah mencadangkan sendiri konstanta yang sesuai pada layanan Web. Tetapi metode serupa dapat digunakan di tempat lain di mana diperlukan reservasi konstan.


Misalnya, di suatu tempat dalam kode proyek Anda memiliki enum. Setiap pengembang dapat menambahkan anggota baru ke dalamnya dengan nilai integer yang ditetapkan:


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

Kami ingin menghindari tabrakan nilai untuk anggota enumerasi ini. Oleh karena itu, kami memerlukan reservasi awal dari konstanta yang sesuai pada layanan Web. Seberapa sulit untuk mengimplementasikan verifikasi reservasi semacam itu?


Berikut adalah kode untuk penangan kait Git yang baru:


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

Pertama-tama kami memeriksa apakah file yang berisi enumerasi kami telah dimodifikasi. Kemudian kami mengekstrak konten file ini dari versi yang diunggah terbaru dan dari indeks Git menggunakan metode GetInitialContent dan GetIndexContent . Inilah implementasinya:


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



Kesimpulan


Git hook` .NET. , .


, . Semoga beruntung


PS GitHub .

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


All Articles