Réservation de constantes et de crochets Git en C #

Permettez-moi de vous raconter une histoire. Il était une fois deux développeurs: Sam et Bob. Ils ont travaillé ensemble sur un projet dans lequel il y avait une base de données. Lorsque le développeur a voulu y apporter des modifications, il a dû créer un fichier stepNNN.sql , où NNN est un certain nombre. Pour éviter les conflits de ces nombres entre différents développeurs, ils ont utilisé un simple service Web. Chaque développeur, avant de commencer à écrire le fichier SQL, devait se rendre sur ce service et réserver un nouveau numéro pour le fichier d'étape.


Cette fois, Sam et Bob devaient tous deux apporter des modifications à la base de données. Sam est allé docilement au service et a réservé le numéro 333. Et Bob a oublié de le faire. Il vient d'utiliser 333 pour son fichier de pas. Il se trouve que cette fois, Bob a été le premier à télécharger ses modifications dans le système de contrôle de version. Lorsque Sam était prêt à inonder, il a découvert que le fichier step333.sql déjà. Il a contacté Bob, lui a expliqué que le numéro 333 lui était réservé et lui a demandé de régler le conflit. Mais Bob a répondu:


- Mec, mon code est déjà dans le «maître», un tas de développeurs l'utilisent déjà. De plus, il a déjà été pompé vers la production. Il suffit donc de réparer tout ce dont vous avez besoin.


J'espère que vous avez remarqué ce qui s'est passé. La personne qui a suivi toutes les règles a été punie. Sam a dû changer ses fichiers, éditer sa base de données locale, etc. Personnellement, je déteste de telles situations. Voyons comment nous pouvons l'éviter.


Idée principale


Comment éviter de telles choses? Et si Bob ne pouvait pas entrer son code s'il n'avait pas réservé le numéro correspondant sur le service Web?


Et nous pouvons réellement y parvenir. Nous pouvons utiliser des hooks Git pour exécuter du code personnalisé avant chaque commit. Ce code vérifiera toutes les modifications poussées. S'ils contiennent un nouveau fichier d'étape, le code contactera le service Web et vérifiera si le numéro du fichier d'étape est réservé au développeur actuel. Et si le numéro n'est pas réservé, le code interdira le remplissage.


Telle est l'idée principale. Passons aux détails.


Git hooks en C #


Git ne vous limite pas dans quelles langues vous devriez écrire des hooks. En tant que développeur C #, je préfère utiliser le C # familier à ces fins. Puis-je faire ça?


Oui, je peux. L'idée de base a été prise par moi à partir de cet article écrit par Max Hamulyák. Il nous oblige à utiliser l' outil global de script dotnet . Cet outil nécessite un .NET Core 2.1 + SDK sur la machine du développeur. Je pense que c'est une exigence raisonnable pour ceux qui sont impliqués dans le développement .NET. L'installation de dotnet-script très simple:


 > dotnet tool install -g dotnet-script 

Nous pouvons maintenant écrire des hooks Git en C #. Pour ce faire, accédez au dossier .git\hooks de votre projet et créez un fichier de pre-commit (sans aucune extension):


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

À partir de maintenant, chaque fois que vous effectuez un git commit , vous verrez le texte du Git hook dans votre console.


Plusieurs gestionnaires par crochet


Eh bien, un début a été fait. Maintenant, nous pouvons écrire n'importe quoi dans le fichier de pre-commit . Mais je n'aime pas vraiment cette idée.


Tout d'abord, travailler avec un fichier script n'est pas très pratique. Je préfère utiliser mon IDE préféré avec toutes ses fonctionnalités. Et je préfère être capable de diviser du code complexe en plusieurs fichiers.


Mais il y a encore une chose que je n'aime pas. Imaginez la situation suivante. Vous avez créé un pre-commit avec une sorte de vérification. Mais plus tard, vous deviez ajouter plus de contrôles. Vous devrez ouvrir le fichier, décider où coller votre code, comment il va interagir avec l'ancien code, etc. Personnellement, je préfère écrire un nouveau code et ne pas creuser dans l'ancien.


Traitons ces problèmes un par un.


Appeler un code externe


Voilà ce que nous allons faire. Créons un dossier séparé (par exemple gitHookAssemblies ). Dans ce dossier, je mettrai l'assembly .NET Core (par exemple GitHooks ). Mon script dans le fichier de pre-commit appellera simplement une méthode de cet assemblage.


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

Je peux créer cet assemblage dans mon IDE préféré et utiliser n'importe quel outil.


Maintenant, dans le fichier de pre-commit , je peux écrire:


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

Super, n'est-ce pas! Maintenant, je ne peux apporter des modifications qu'à ma version de GitHooks . Le code du fichier de pre-commit ne changera jamais. Lorsque je dois ajouter une vérification, je vais changer le code de la méthode RunPreCommitHook , reconstruire l'assembly et le placer dans le dossier gitHookAssemblies . Et c'est tout!


Enfin, pas vraiment.


Combattre le cache


Essayons de suivre notre processus. Remplacez le message dans Console.WriteLine par autre chose, reconstruisez l'assembly et placez le résultat dans le dossier gitHookAssemblies . Après cela, appelez à nouveau git commit . Que verrons-nous? Ancien poste. Nos changements n'ont pas pris. Pourquoi?


Que, pour être sûr, votre projet se trouve dans le dossier c:\project . Cela signifie que les scripts de hook Git se trouvent dans le dossier c:\project\.git\hooks . Maintenant, si vous utilisez Windows 10, accédez au dossier c:\Users\<UserName>\AppData\Local\Temp\scripts\c\project\.git\hooks\ . Ici, <UserName> est le nom de votre utilisateur actuel. Que verrons-nous ici? Lorsque nous exécutons le script de pre-commit , une version compilée de ce script est créée dans ce dossier. Ici vous pouvez trouver tous les assemblys référencés par le script (y compris notre GitHooks.dll ). Et dans le sous execution-cache dossier d' execution-cache vous pouvez trouver le fichier SHA256. Je peux supposer qu'il contient le hachage SHA256 de notre fichier de pre-commit . Au moment où nous exécutons le script, le runtime compare le hachage actuel du fichier avec le hachage stocké. S'ils sont égaux, la version enregistrée du script compilé sera utilisée.


Cela signifie que puisque nous ne modifions jamais le fichier de pre-commit , les modifications apportées à GitHooks.dll n'atteindront jamais le cache et ne seront jamais utilisées.


Que pouvons-nous faire dans cette situation? Eh bien, la réflexion nous aidera. Je vais réécrire mon script pour qu'il utilise Reflection au lieu de référencer directement l'assembly GitHooks . Voici à quoi ressemblera notre fichier de 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]); 

Nous pouvons maintenant mettre à jour GitHook.dll dans notre dossier gitHookAssemblies à tout moment, et toutes les modifications seront récupérées par le même script. La modification du script lui-même n'est plus nécessaire.


Tout cela sonne bien, mais il y a un autre problème qui doit être résolu avant de continuer. Je parle des assemblys référencés par notre code.


Assemblages d'occasion


Tout fonctionne bien, tant que la seule chose que fait la méthode RunHooks.RunPreCommitHook est de sortir la chaîne sur la console. Mais, franchement, afficher généralement du texte à l'écran n'a aucun intérêt. Nous devons faire des choses plus complexes. Et pour cela, nous devons utiliser d'autres assemblys et packages NuGet. Voyons comment procéder.


Je vais RunHooks.RunPreCommitHook pour qu'il utilise le package LibGit2Sharp :


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

Maintenant, si j'exécute git commit , j'obtiendrai le message d'erreur suivant:


 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) 

De toute évidence, nous avons besoin d'un moyen de nous assurer que les assemblages auxquels nous nous référons sont chargés. L'idée de base ici est. Je mettrai tout le code d'assembly requis pour exécuter le code dans le même dossier gitHookAssemblies avec mon GitHooks.dll . Pour obtenir tous les assemblages requis, vous pouvez utiliser la commande dotnet publish . Dans notre cas, nous devons placer LibGit2Sharp.dll et git2-7ce88e6.dll dans ce dossier.


Nous devons également modifier le pre-commit . Nous y ajouterons le code suivant:


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

Ce code essaiera de charger tous les assemblys que le runtime n'a pas pu trouver par lui-même à partir du dossier gitHookAssemblies .


Vous pouvez maintenant exécuter git commit et il fonctionnera sans problème.


Amélioration de l'extensibilité


Notre fichier de pre-commit est terminé. Nous n'avons plus besoin de le changer. Mais si vous devez apporter des modifications, nous devrons changer la méthode RunHooks.RunPreCommitHook . Nous avons donc simplement déplacé le problème à un autre niveau. Personnellement, je préférerais avoir une sorte de système de plugins. Chaque fois que je dois ajouter une action qui doit être effectuée avant de remplir le code, je vais simplement écrire un nouveau plugin et rien ne devra être changé. Est-ce difficile à réaliser?


Pas du tout difficile. Utilisons MEF . Voilà comment ça marche.


Nous devons d'abord définir une interface pour nos gestionnaires de hook:


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

Chaque gestionnaire peut recevoir des arguments de chaîne de Git. Ces arguments seront transmis via le paramètre args . La méthode Process renverra true si elle permet de verser des modifications. Sinon, false sera retourné.


Des interfaces similaires peuvent être définies pour tous les hooks, mais dans cet article, nous nous concentrerons uniquement sur la pré-validation.


Vous devez maintenant écrire une implémentation de cette interface:


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

Ces classes peuvent être créées dans différents assemblys si vous le souhaitez. Il n'y a littéralement aucune restriction. L'attribut Export est extrait du package System.ComponentModel.Composition NuGet.


IPreCommitHook également une méthode d'assistance qui collectera toutes les IPreCommitHook interface IPreCommitHook marquées avec l'attribut Export , les exécutera toutes et renverra des informations pour savoir si elles ont toutes autorisé le remplissage. J'ai mis mon gestionnaire dans un assemblage GitHooksCollector séparé, mais ce n'est pas si important:


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

Ce code utilise également le package System.ComponentModel.Composition NuGet. Tout d'abord, nous disons que nous allons afficher tous les assemblys dont le nom correspond au modèle *Hooks.dll dans le dossier du répertoire. Vous pouvez utiliser n'importe quel modèle que vous aimez ici. Ensuite, nous collectons toutes les implémentations exportées de l'interface IPreCommitHook dans un objet PreCommitHooks . Et enfin, nous démarrons tous les gestionnaires de hook et collectons le résultat de leur exécution.


La dernière chose que nous devons faire est une petite modification du fichier de 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); 

Et n'oubliez pas de mettre tous les assemblys impliqués dans le dossier gitHookAssemblies .


Oui, ce fut une longue introduction. Mais maintenant, nous avons une solution complètement fiable pour créer des gestionnaires de hook Git en C #. Tout ce qui nous est demandé est de modifier le contenu du dossier gitHookAssemblies . Son contenu peut être placé dans un système de contrôle de version et, ainsi, distribué à tous les développeurs.


Dans tous les cas, il est temps pour nous de revenir à notre problème d'origine.


Service Web pour réservation constante


Nous voulions nous assurer que les développeurs ne pourraient pas apporter certaines modifications s'ils oubliaient de réserver la constante correspondante sur le service Web. Créons un service Web simple pour pouvoir travailler avec. J'utilise le service Web ASP.NET Core avec l'authentification Windows. Mais en fait, il existe différentes options.


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

Ici, à des fins de test, j'ai utilisé la classe Lists statique comme mécanisme de stockage des listes. Chaque liste aura un identifiant entier. Chaque liste contiendra des valeurs entières et des informations sur les personnes à qui ces valeurs sont réservées. La méthode GetOwner de la classe GetOwner vous permet d'obtenir l'identifiant de la personne à laquelle cet élément de liste est réservé.


Validation des fichiers d'étape SQL


Nous sommes maintenant prêts à vérifier si nous pouvons télécharger un nouveau fichier étape ou non. Pour être précis, supposons que nous stockions les fichiers d'étapes comme suit. Le dossier racine de notre projet possède un répertoire sql . Dans ce document, chaque développeur peut créer un dossier verXXX , où XXX est un certain nombre qui devrait être réservé auparavant sur le service Web. Dans le répertoire verXXX , verXXX peut verXXX avoir un ou plusieurs fichiers .sql contenant des instructions de modification de la base de données. Nous .sql ici le problème de garantir l'ordre d'exécution de ces fichiers .sql . Ce n'est pas important pour notre discussion. Nous voulons simplement faire ce qui suit. Si un développeur essaie de télécharger un nouveau fichier contenu dans le sql/verXXX , nous devons vérifier si la constante XXX réservée à ce développeur.


Voici à quoi ressemble le code du gestionnaire de hook Git correspondant:


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

Ici, nous utilisons la classe Repository du package LibGit2Sharp . La variable items contiendra tous les nouveaux fichiers de l'index Git qui se trouvent dans le dossier sql . Vous pouvez améliorer la procédure de recherche de ces fichiers si vous le souhaitez. Dans la variable versions , nous collectons diverses constantes XXX partir des dossiers verXXX . Enfin, la méthode ListItemOwnerChecker.DoesCurrentUserOwnListItem vérifie si ces versions sont enregistrées auprès de l'utilisateur actuel dans le service Web dans la liste 1.


L'implémentation de ListItemOwnerChecker.DoesCurrentUserOwnListItem assez simple:


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

Ici, nous demandons au service Web l'identifiant de l'utilisateur qui a enregistré la constante spécifiée (méthode GetListItemOwner ). Le résultat est ensuite comparé au nom de l'utilisateur Windows actuel. Ce n'est là qu'une des nombreuses façons possibles d'implémenter cette fonctionnalité. Par exemple, vous pouvez utiliser le nom d'utilisateur ou l'e-mail de la configuration Git.


C’est tout. gitHookAssemblies simplement l'assembly approprié et placez-le dans le dossier gitHookAssemblies avec toutes ses dépendances. Et tout fonctionnera automatiquement.


Vérification des valeurs d'énumération


C'est super! Désormais, personne ne pourra télécharger des modifications dans la base de données sans s'être préalablement réservé la constante correspondante sur le service Web. Mais une méthode similaire peut être utilisée dans d'autres endroits où une réservation constante est requise.


Par exemple, quelque part dans le code du projet, vous avez enum. Chaque développeur peut y ajouter de nouveaux membres avec des valeurs entières attribuées:


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

Nous voulons éviter une collision de valeurs pour les membres de cette énumération. Par conséquent, nous demandons une réservation préalable des constantes correspondantes sur le service Web. Est-il difficile de mettre en œuvre la vérification d'une telle réservation?


Voici le code du nouveau gestionnaire de hook 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; } ... } 

Nous vérifions d'abord si le fichier contenant notre énumération a été modifié. Ensuite, nous extrayons le contenu de ce fichier à partir de la dernière version téléchargée et de l'index Git à l'aide des GetIndexContent GetInitialContent et GetIndexContent . Voici leur implémentation:


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



Conclusion


Git hook` .NET. , .


, . Bonne chance


PS GitHub .

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


All Articles