Reserviere Konstanten und Git Hooks in C #

Lass mich dir eine Geschichte erzählen. Es waren einmal zwei Entwickler: Sam und Bob. Sie arbeiteten zusammen an einem Projekt, in dem sich eine Datenbank befand. Wenn der Entwickler Änderungen daran vornehmen wollte, musste er eine Datei stepNNN.sql erstellen, wobei NNN eine bestimmte Zahl ist. Um Konflikte dieser Nummern zwischen verschiedenen Entwicklern zu vermeiden, verwendeten sie einen einfachen Webdienst. Jeder Entwickler musste vor dem Schreiben der SQL-Datei zu diesem Dienst gehen und eine neue Nummer für die Schrittdatei reservieren.


Diesmal mussten sowohl Sam als auch Bob Änderungen an der Datenbank vornehmen. Sam ging gehorsam zum Gottesdienst und reservierte die Nummer 333. Und Bob vergaß es zu tun. Er hat gerade 333 für seine Schrittdatei verwendet. Diesmal hat Bob seine Änderungen als erster in das Versionskontrollsystem hochgeladen. Als Sam zum Fluten bereit war, stellte er fest, dass die Datei step333.sql bereits vorhanden ist. Er kontaktierte Bob, erklärte ihm, dass die Nummer 333 für ihn reserviert sei und bat ihn, den Konflikt zu lösen. Aber Bob antwortete:


- Alter, mein Code befindet sich bereits im 'Master', eine Reihe von Entwicklern verwenden ihn bereits. Außerdem wurde es bereits in die Produktion abgepumpt. Repariere einfach alles, was du brauchst.


Ich hoffe du hast gemerkt was passiert ist. Die Person, die alle Regeln befolgt hat, wurde bestraft. Sam musste seine Dateien ändern, seine lokale Datenbank bearbeiten usw. Persönlich hasse ich solche Situationen. Mal sehen, wie wir das vermeiden können.


Hauptidee


Wie vermeiden wir solche Dinge? Was ist, wenn Bob seinen Code nicht eingeben konnte, ohne die entsprechende Nummer im Webdienst zu reservieren?


Und das können wir tatsächlich erreichen. Wir können Git-Hooks verwenden, um vor jedem Commit benutzerdefinierten Code auszuführen. Dieser Code überprüft alle übertragenen Änderungen. Wenn sie eine neue Schrittdatei enthalten, kontaktiert der Code den Webdienst und prüft, ob die Nummer der Schrittdatei für den aktuellen Entwickler reserviert ist. Und wenn die Nummer nicht reserviert ist, verhindert der Code das Ausfüllen.


Das ist die Hauptidee. Kommen wir zu den Details.


Git-Hooks in C #


Git schränkt Sie nicht ein, in welchen Sprachen Sie Hooks schreiben sollten. Als C # -Entwickler würde ich für diese Zwecke lieber das vertraute C # verwenden. Kann ich das machen


Ja, ich kann. Die Grundidee habe ich aus diesem Artikel von Max Hamulyák übernommen. Dazu müssen wir das globale Dotnet-Script- Tool verwenden. Für dieses Tool ist ein .NET Core 2.1 + SDK auf dem Entwicklercomputer erforderlich. Ich halte dies für eine vernünftige Voraussetzung für diejenigen, die an der .NET-Entwicklung beteiligt sind. Die Installation von dotnet-script sehr einfach:


 > dotnet tool install -g dotnet-script 

Jetzt können wir Git-Hooks in C # schreiben. Wechseln Sie dazu in den Ordner .git\hooks Ihres Projekts und erstellen Sie eine pre-commit Datei (ohne Erweiterung):


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

Von nun an sehen Sie bei jedem git commit den Git hook Text in Ihrer Konsole.


Mehrere Handler pro Haken


Nun, es wurde ein Anfang gemacht. Jetzt können wir alles in die pre-commit Datei schreiben. Aber ich mag diese Idee nicht wirklich.


Erstens ist das Arbeiten mit einer Skriptdatei nicht sehr praktisch. Ich würde lieber meine Lieblings-IDE mit all ihren Funktionen verwenden. Und ich wäre eher in der Lage, komplexen Code in mehrere Dateien aufzuteilen.


Aber es gibt noch eine Sache, die ich nicht mag. Stellen Sie sich folgende Situation vor. Sie haben ein pre-commit mit einigen Überprüfungen erstellt. Später mussten Sie jedoch weitere Prüfungen hinzufügen. Sie müssen die Datei öffnen, entscheiden, wo Sie Ihren Code einfügen möchten, wie er mit dem alten Code interagiert usw. Persönlich ziehe ich es vor, neuen Code zu schreiben und nicht in den alten zu graben.


Beschäftigen wir uns nacheinander mit diesen Problemen.


Externen Code anrufen


Das werden wir tun. Erstellen wir einen separaten Ordner (z. B. gitHookAssemblies ). In diesen Ordner lege ich die .NET Core-Assembly (z. B. GitHooks ). Mein Skript in der pre-commit Datei ruft nur eine Methode aus dieser Assembly auf.


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

Ich kann diese Assembly in meiner bevorzugten IDE erstellen und beliebige Tools verwenden.


Jetzt kann ich in die pre-commit Datei schreiben:


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

Großartig, nicht wahr? Jetzt kann ich nur noch Änderungen in meinem GitHooks Build GitHooks . Der pre-commit Dateicode wird sich niemals ändern. Wenn ich eine Überprüfung hinzufügen muss, ändere ich den Code der RunPreCommitHook Methode, RunPreCommitHook die Assembly neu und gitHookAssemblies sie in den gitHookAssemblies Ordner ein. Und alle!


Na ja, nicht wirklich.


Den Cache bekämpfen


Lassen Sie uns versuchen, unseren Prozess zu verfolgen. Ändern Sie die Nachricht in Console.WriteLine in eine andere, gitHookAssemblies die Assembly neu und gitHookAssemblies das Ergebnis im Ordner gitHookAssemblies . Rufen Sie danach git commit erneut auf. Was werden wir sehen? Alter Beitrag. Unsere Veränderungen haben sich nicht durchgesetzt. Warum?


Lassen Sie Ihr Projekt zur Sicherheit im Ordner c:\project . Dies bedeutet, dass sich Git-Hook-Skripte im Ordner c:\project\.git\hooks . Wenn Sie Windows 10 verwenden, c:\Users\<UserName>\AppData\Local\Temp\scripts\c\project\.git\hooks\ . Hier ist <UserName> der Name Ihres aktuellen Benutzers. Was werden wir hier sehen? Wenn wir das pre-commit Skript ausführen, wird in diesem Ordner eine kompilierte Version dieses Skripts erstellt. Hier finden Sie alle Assemblys, auf die das Skript verweist (einschließlich unserer GitHooks.dll ). Und im execution-cache Unterordner finden Sie die SHA256-Datei. Ich kann davon ausgehen, dass es den SHA256-Hash unserer pre-commit Datei enthält. In dem Moment, in dem wir das Skript ausführen, vergleicht die Laufzeit den aktuellen Hash der Datei mit dem gespeicherten Hash. Wenn sie gleich sind, wird die gespeicherte Version des kompilierten Skripts verwendet.


Dies bedeutet, dass Änderungen an GitHooks.dll niemals in den Cache gelangen und niemals verwendet werden, da wir die pre-commit Datei niemals ändern.


Was können wir in dieser Situation tun? Reflexion wird uns helfen. Ich werde mein Skript so umschreiben, dass es Reflection verwendet, anstatt direkt auf die GitHooks Assembly zu GitHooks . So sieht unsere pre-commit Datei danach aus:


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

Jetzt können wir GitHook.dll in unserem gitHookAssemblies Ordner jederzeit aktualisieren und alle Änderungen werden von demselben Skript übernommen. Das Skript selbst muss nicht mehr geändert werden.


Das hört sich alles gut an, aber es gibt ein weiteres Problem, das gelöst werden muss, bevor Sie fortfahren. Ich spreche von Assemblys, auf die unser Code verweist.


Gebrauchte Baugruppen


Alles funktioniert RunHooks.RunPreCommitHook , solange die RunHooks.RunPreCommitHook Methode die Zeichenfolge nur an die Konsole ausgibt. Aber ehrlich gesagt ist es normalerweise nicht von Interesse, Text auf dem Bildschirm anzuzeigen. Wir müssen komplexere Dinge tun. Und dafür müssen wir andere Assemblys und NuGet-Pakete verwenden. Mal sehen, wie das geht.


Ich werde RunHooks.RunPreCommitHook so RunHooks.RunPreCommitHook , dass es das LibGit2Sharp Paket verwendet:


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

Wenn ich nun git commit ausführe, bekomme ich folgende Fehlermeldung:


 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) 

Natürlich müssen wir sicherstellen, dass die Assemblys, auf die wir verweisen, geladen werden. Die Grundidee hier ist. Ich werde den gesamten Assembler-Code, der zum Ausführen des Codes erforderlich ist, zusammen mit meiner GitHooks.dll in denselben gitHookAssemblies Ordner GitHooks.dll . Um alle erforderlichen Assemblys dotnet publish können Sie den Befehl dotnet publish . In unserem Fall müssen wir LibGit2Sharp.dll und git2-7ce88e6.dll in diesem Ordner platzieren.


Wir müssen auch das pre-commit ändern. Wir werden den folgenden Code hinzufügen:


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

Dieser Code versucht, alle Assemblys zu laden, die die Laufzeit nicht alleine aus dem Ordner gitHookAssemblies .


Jetzt können Sie git commit ausführen und es wird ohne Probleme ausgeführt.


Verbesserung der Erweiterbarkeit


Unsere pre-commit Datei ist vollständig. Wir müssen es nicht mehr ändern. Wenn Sie jedoch Änderungen vornehmen müssen, müssen wir die RunHooks.RunPreCommitHook Methode ändern. Also haben wir das Problem auf eine andere Ebene verschoben. Persönlich würde ich es vorziehen, eine Art Plugin-System zu haben. Jedes Mal, wenn ich eine Aktion hinzufügen muss, bevor ich den Code ausfülle, schreibe ich einfach ein neues Plugin und nichts muss geändert werden. Wie schwer ist das zu erreichen?


Gar nicht so schwer. Lassen Sie uns MEF verwenden . So funktioniert es.


Zuerst müssen wir eine Schnittstelle für unsere Hook-Handler definieren:


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

Jeder Handler kann einige String-Argumente von Git erhalten. Diese Argumente werden über den Parameter args . Die Process Methode gibt true wenn sie Änderungen zulässt. Anderenfalls wird false zurückgegeben.


Ähnliche Schnittstellen können für alle Hooks definiert werden. In diesem Artikel konzentrieren wir uns jedoch nur auf das Pre-Commit.


Jetzt müssen Sie eine Implementierung dieser Schnittstelle schreiben:


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

Solche Klassen können auf Wunsch in verschiedenen Assemblys erstellt werden. Es gibt buchstäblich keine Einschränkungen. Das Export Attribut stammt aus dem System.ComponentModel.Composition NuGet-Paket.


Erstellen wir außerdem eine IPreCommitHook , mit der alle mit dem Export Attribut gekennzeichneten IPreCommitHook Schnittstellenimplementierungen IPreCommitHook , alle ausgeführt und Informationen darüber zurückgegeben werden, ob alle das Ausfüllen zugelassen haben. Ich habe meinen Handler in einer separaten GitHooksCollector Assembly GitHooksCollector , aber das ist nicht so wichtig:


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

Dieser Code verwendet auch das Paket System.ComponentModel.Composition NuGet. Zuerst sagen wir, dass wir alle Assemblys *Hooks.dll deren Name mit der *Hooks.dll Vorlage im directory *Hooks.dll . Sie können hier jede beliebige Vorlage verwenden. Anschließend sammeln wir alle exportierten Implementierungen der IPreCommitHook Schnittstelle in einem PreCommitHooks Objekt. Und schließlich starten wir alle Hook-Handler und sammeln das Ergebnis ihrer Ausführung.


Das Letzte, was wir tun müssen, ist eine kleine Änderung an der pre-commit Datei:


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

Und vergessen Sie nicht, alle beteiligten Assemblys in den Ordner gitHookAssemblies zu legen.


Ja, es war eine lange Einführung. Aber jetzt haben wir eine absolut zuverlässige Lösung zum Erstellen von Git-Hook-Handlern in C #. Wir müssen gitHookAssemblies den Inhalt des Ordners gitHookAssemblies . Seine Inhalte können in ein Versionskontrollsystem gestellt und so an alle Entwickler verteilt werden.


In jedem Fall ist es Zeit, dass wir zu unserem ursprünglichen Problem zurückkehren.


Webservice für ständige Reservierung


Wir wollten sicherstellen, dass Entwickler bestimmte Änderungen nicht vornehmen können, wenn sie vergessen, die entsprechende Konstante im Webdienst zu reservieren. Erstellen wir einen einfachen Webdienst, damit Sie damit arbeiten können. Ich verwende den ASP.NET Core-Webdienst mit Windows-Authentifizierung. Tatsächlich gibt es jedoch verschiedene Optionen.


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

Hier habe ich zu Testzwecken die statische Klasse Lists als Mechanismus zum Speichern von Listen verwendet. Jede Liste hat eine Ganzzahlkennung. Jede Liste enthält ganzzahlige Werte und Informationen zu den Personen, für die diese Werte reserviert sind. Mit der GetOwner Methode der GetOwner Klasse können Sie die GetOwner der Person GetOwner , für die dieses Listenelement reserviert ist.


Überprüfen von SQL-Step-Dateien


Jetzt können wir prüfen, ob wir eine neue Step-Datei hochladen können oder nicht. Angenommen, wir speichern Schrittdateien wie folgt. Der Stammordner unseres Projekts hat ein sql Verzeichnis. Darin kann jeder Entwickler einen verXXX Ordner erstellen, wobei XXX eine bestimmte Nummer ist, die zuvor für den Webdienst reserviert werden sollte. Im verXXX Verzeichnis verXXX möglicherweise eine oder mehrere .sql Dateien, die Anweisungen zum Ändern der Datenbank enthalten. Wir werden hier nicht auf das Problem der Sicherstellung der Ausführungsreihenfolge dieser .sql Dateien .sql . Dies ist für unsere Diskussion nicht wichtig. Wir möchten nur Folgendes tun. Wenn ein Entwickler versucht, eine neue Datei im sql/verXXX , müssen wir prüfen, ob die Konstante XXX für diesen Entwickler reserviert XXX .


So sieht der Code für den entsprechenden Git-Hook-Handler aus:


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

Hier verwenden wir die Repository Klasse aus dem NuGet-Paket LibGit2Sharp . Die Variablen items enthält alle neuen Dateien im Git-Index, die sich im Ordner sql . Sie können das Suchverfahren für solche Dateien verbessern, wenn Sie dies wünschen. In der Variablen versions sammeln wir verschiedene Konstanten XXX aus den verXXX Ordnern. Schließlich überprüft die ListItemOwnerChecker.DoesCurrentUserOwnListItem Methode, ob diese Versionen für den aktuellen Benutzer im Webdienst in Liste 1 registriert sind.


Die Implementierung von ListItemOwnerChecker.DoesCurrentUserOwnListItem ziemlich einfach:


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

Hier fragen wir den Webdienst nach der Kennung des Benutzers, der die angegebene Konstante registriert hat ( GetListItemOwner Methode). Das Ergebnis wird dann mit dem Namen des aktuellen Windows-Benutzers verglichen. Dies ist nur eine von vielen Möglichkeiten, diese Funktionalität zu implementieren. Beispielsweise können Sie den Benutzernamen oder die E-Mail-Adresse aus der Git-Konfiguration verwenden.


Das ist alles. Kompilieren Sie einfach die entsprechende Assembly und platzieren Sie sie mit all ihren Abhängigkeiten im Ordner gitHookAssemblies . Und alles wird automatisch funktionieren.


Überprüfen von Aufzählungswerten


Es ist toll! Jetzt kann niemand mehr Änderungen in die Datenbank hochladen, ohne zuvor die entsprechende Konstante im Webdienst für sich reserviert zu haben. Eine ähnliche Methode kann jedoch auch an anderen Orten angewendet werden, an denen eine ständige Reservierung erforderlich ist.


Zum Beispiel irgendwo in dem Projektcode, den Sie enum haben. Jeder Entwickler kann neue Mitglieder mit zugewiesenen ganzzahligen Werten hinzufügen:


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

Wir möchten eine Kollision von Werten für Mitglieder dieser Aufzählung vermeiden. Daher benötigen wir eine Vorreservierung der entsprechenden Konstanten im Web-Service. Wie schwierig ist es, eine solche Reservierung zu überprüfen?


Hier ist der Code für den neuen Git-Hook-Handler:


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

Zuerst prüfen wir, ob die Datei, die unsere Aufzählung enthält, geändert wurde. Anschließend extrahieren wir den Inhalt dieser Datei mit den GetIndexContent GetInitialContent und GetIndexContent aus der zuletzt hochgeladenen Version und aus dem Git-Index. Hier ist ihre Implementierung:


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



Das ist alles. . , .


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



Fazit


Git hook` .NET. , .


, . Viel glück


PS GitHub .

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


All Articles