65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 C# 进行常量预订和 Git Hooks

starIconstarIconstarIconstarIconstarIcon

5.00/5 (11投票s)

2020年1月23日

CPOL

13分钟阅读

viewsIcon

12320

在本文中,我将解释如何使用 C# 构建一个健壮的 Git hooks 系统。此外,我还会展示如何使用它来解决开发协作中的一些棘手问题。

引言

让我给你讲个故事。很久以前,有两个开发者:Sam 和 Bob。他们在一个有数据库的项目上工作。当开发者想要对数据库结构进行更改时,她/他必须创建一个步骤文件stepNNN.sql,其中NNN是某个数字。为了避免不同开发者之间的数字冲突,他们有一个简单的 Web 应用程序。每个开发者在开始编写 SQL 文件之前,都必须访问该应用程序并预订一个新数字以供其修改使用。

那是 Sam 和 Bob 进行数据库更改的时候。Sam 听话地去 Web 应用程序预订了数字 333。但 Bob 忘了这样做。他直接将 333 用于他的新步骤文件。碰巧 Bob 是第一个将更改提交到版本控制系统的人。当 Sam 准备提交时,发现step333.sql已经存在。他联系了 Bob,解释说 333 号已经被预订了,并要求 Bob 修复冲突。但 Bob 回答说:

- 嘿,哥们。你知道,我的代码已经在“master”分支上了,很多开发者已经取用了。而且,它已经上线了。你能否自己修改你的代码?

你注意到了吗?遵守所有规则的人反而是受惩罚的人。Sam 不得不更改他的文件,修改他的本地数据库等等。我个人很讨厌这种情况。让我们看看如何避免它。

总体思路

我们如何才能防止此类事情发生?如果 Bob 没有在 Web 应用程序上预订相应的数字就无法提交更改,那会怎么样?

这是可以实现的。我们可以使用 Git hooks 在每次提交之前执行自定义代码。这段代码将检查开发者想要提交的所有更改。如果这些更改包含新的步骤文件,代码将联系 Web 应用程序并检查该步骤文件的编号是否已被当前开发者预订。如果该编号未被预订,代码将阻止提交。

这就是主要思想。现在让我们深入细节。

C# 中的 Git Hooks

Git 不限制你使用哪种语言来编写 hooks。作为一名 C# 开发者,我希望为此目的使用熟悉的 C#。我可以做到吗?

是的,我可以。我从 Max Hamulyák 的这篇文章中得到了主要想法。这需要我们使用dotnet-script全局工具。此工具需要在开发者的机器上安装 .NET Core 2.1+ SDK。我认为如果您正在进行 .NET 开发,安装它并不算过分。dotnet-script的安装非常直接。

> dotnet tool install -g dotnet-script

现在我们可以使用 C# 编写 Git hooks 了。要做到这一点,在你的项目文件夹中,转到.git\hooks目录并创建一个pre-commit文件(没有任何扩展名)。

#!/usr/bin/env dotnet-script


Console.WriteLine("Git hook");

从此刻起,每次运行git commit命令时,你都会在控制台中看到Git hook消息。

一个 Hook 的多个处理器

嗯,这只是一个开始。现在我们可以在pre-commit文件中编写任何内容。但我不太喜欢这个想法。

首先,编写脚本文件不是很方便。我宁愿使用我最喜欢的 IDE 及其所有功能。并且我想将复杂代码分成多个文件。

但还有另一件事我不喜欢。考虑以下情况。你创建了一个pre-commit文件并进行了一些检查。但后来,你决定添加更多检查。你必须打开文件,决定在哪里插入新代码,决定如何与旧代码交互等等。我个人更喜欢编写新代码,而不是修改现有代码。

让我们一个一个地解决这些问题。

调用外部代码

这是我们将要做的。我们将创建一个文件夹(例如,gitHookAssemblies)。在这个文件夹中,我将放置一个 .NET Core 程序集(例如 GitHooks)。我pre-commit文件中的脚本将仅调用此程序集中的某个方法。

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

我可以使用我喜欢的 IDE 和任何我想要的工具来创建程序集。

现在在pre-commit文件中,我可以这样写:

#!/usr/bin/env dotnet-script

#r "../../gitHookAssemblies/GitHooks.dll"

GitHooks.RunHooks.RunPreCommitHook();

看这有多酷!现在我只需要在GitHooks程序集中进行更改。pre-commit文件的代码将永远不变。任何时候我需要新的检查,我都会更改RunPreCommitHook方法的代码,重新编译程序集并将其放置在gitHookAssemblies文件夹中。就是这样!

嗯,也不是完全如此。

对抗缓存

让我们尝试遵循这个过程。让我们将Console.WriteLine的消息更改为不同的内容,重新编译程序集并将其放入gitHookAssemblies文件夹。之后,再次调用git commit。我们会看到什么?旧的消息。我们的更改未被找到。为什么会这样?

假设你的项目在c:\project文件夹中。这意味着 Git hooks 存储在c:\project\.git\hooks文件夹中。现在,如果你使用的是 Windows 10,请转到c:\Users\<UserName>\AppData\Local\Temp\scripts\c\project\.git\hooks\文件夹。这里的<UserName>应该是你当前用户的名字。这里有什么?当我们运行pre-commit脚本时,该文件夹中的脚本的编译版本将被创建。在这里,你还可以找到所有引用的程序集(包括我们的GitHooks.dll)。在execution-cache子文件夹中,你可以找到一个 SHA256 文件。我可以推测这个文件包含了我们pre-commit文件的 SHA256 哈希值。每次我们运行脚本时,运行时会将当前文件的哈希值与存储的哈希值进行比较。如果它们相等,将使用存储的已编译脚本版本。

这意味着,由于我们从不更改pre-commit文件,GitHooks.dll中的更改将永远不会进入缓存,也不会被使用。

我们能对此做些什么?嗯,反射会提供帮助。我将重写我的脚本文件,使用反射而不是直接引用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所做的唯一事情是向控制台写入某些字符串时,一切看起来都很好。但坦率地说,我们通常对编写字符串不感兴趣。我们需要做更复杂的事情。要做到这一点,我们需要引用其他程序集和 NuGet 包。让我们看看如何做到。

我将修改我的RunHooks.RunPreCommitHook以使用一些LibGit2Sharp包。

public static void RunPreCommitHook()
{
    using var repo = new Repository(Environment.CurrentDirectory);

    Console.WriteLine(repo.Info.WorkingDirectory);
}

现在,如果我尝试运行git commit,我会收到以下错误消息:

System.Reflection.TargetInvocationException: Exception has been thrown by 
the target of an invocation.
 ---> System.IO.FileLoadException: Could not load file or assembly 
'LibGit2Sharp, Version=0.26.0.0, Culture=neutral, PublicKeyToken=7cbde695407f0333'. 
General Exception (0x80131500)

因此,我们需要一种方法来提供所有引用的程序集。这里的主要思想如下。我将所有执行所需的程序集与GitHooks.dll一起放置在同一个gitHookAssemblies文件夹中。要获取 .NET Core 项目中的所有引用的程序集,可以使用dotnet publish命令。在我们的例子中,我们需要将LibGit2Sharp.dllgit2-7ce88e6.dll放置在此文件夹中。

此外,我们还必须修改我们的pre-commit脚本。我们将添加以下代码:

#!/usr/bin/env dotnet-script

#r "nuget: System.Runtime.Loader, 4.3.0"

using System.IO;
using System.Runtime.Loader;

var hooksDirectory = Path.Combine(Environment.CurrentDirectory, "gitHookAssemblies");

var assemblyPath = Path.Combine(hooksDirectory, "GitHooks.dll");

AssemblyLoadContext.Default.Resolving += (context, assemblyName) => {
    var assemblyPath = Path.Combine(hooksDirectory, $"{assemblyName.Name}.dll");
    if(File.Exists(assemblyPath))
    {
        return AssemblyLoadContext.Default.LoadFromAssemblyPath(assemblyPath);
    }

    return null;
};

...

这段代码将尝试在gitHookAssemblies文件夹中查找所有未知的程序集。

现在我们可以运行git commit,它将顺利执行。

提高可扩展性

现在我们的pre-commit已经完成了。我们不再需要修改它了。但在任何更改的情况下,我们都需要修改RunHooks.RunPreCommitHook方法。我们只是将这个问题移到了另一个层面。我个人更希望有一种插件系统。每次我想添加一个必须在提交前执行的操作时,我只需编写另一个插件而不修改任何内容。这很难实现吗?

一点也不难。让我们使用MEF。它的工作原理如下。

首先,我们为所有 hook 处理程序定义一个接口:

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

每个 Git hook 都可以通过 Git 传递一些string参数。这些参数将在args参数中。如果Process方法允许提交更改,则返回true,否则返回false

我们绝对可以为其他 hooks 定义类似的接口,但在本文中,我们将重点关注 pre-commit hook。

现在我们来实现这个接口:

[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程序集中,但这并不重要。

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文件夹中。你可以使用任何你想要的模式。然后,我们将所有导出的IPreCommitHook接口实现收集到PreCommitHooks对象中。最后,我们运行所有处理程序并计算聚合执行结果。

最后一件事是稍微修改pre-commit文件:

#!/usr/bin/env dotnet-script

#r "nuget: System.Runtime.Loader, 4.3.0"

using System.IO;
using System.Runtime.Loader;

var hooksDirectory = Path.Combine(Environment.CurrentDirectory, "gitHookAssemblies");

var assemblyPath = Path.Combine(hooksDirectory, "GitHooksCollector.dll");

AssemblyLoadContext.Default.Resolving += (context, assemblyName) => {
    var assemblyPath = Path.Combine(hooksDirectory, $"{assemblyName.Name}.dll");
    if(File.Exists(assemblyPath))
    {
        return AssemblyLoadContext.Default.LoadFromAssemblyPath(assemblyPath);
    }
    return null;
};

var assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(assemblyPath);
if(assembly == null)
{
    Console.WriteLine($"Can't load assembly from '{assemblyPath}'.");
}

var collectorsType = assembly.GetType("GitHooksCollector.Collectors");
if(collectorsType == null)
{
    Console.WriteLine("Can't find collector's type.");
}

var method = collectorsType.GetMethod("RunPreCommitHooks", 
             System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
if(method == null)
{
    Console.WriteLine("Can't find collector's method for pre-commit hooks.");
}

int exitCode = (int) method.Invoke(null, new object[] { Args, hooksDirectory });

Environment.Exit(exitCode);

不要忘记将所有参与的程序集放置在gitHookAssemblies文件夹中。

哇,这真是一个漫长的介绍。但现在我们有了一个相当健壮的用 C# 编写 Git hooks 的解决方案。我们只需要修改gitHookAssemblies文件夹的内容。此文件夹的内容可以放置在版本控制系统中,从而分发给所有开发人员。

无论如何,是时候解决我们最初的问题了。

用于常量预订的 Web 服务

我们想确保开发人员在忘记在 Web 服务上注册相应常量之前无法提交更改。让我们为我们的需求创建一个简单的 Web 服务。我将使用 ASP.NET Core Web 服务和 Windows 身份验证。但实际上,有许多变体可供使用。

using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

namespace ListsService.Controllers
{
    public sealed class ListItem<T>
    {
        public ListItem(T value, string owner)
        {
            Value = value;
            Owner = owner;
        }

        public T Value { get; }
        public string Owner { get; }
    }

    public static class Lists
    {
        public static List<ListItem<int>> SqlVersions = new List<ListItem<int>>
        {
            new ListItem<int>(1, @"DOMAIN\Iakimov")
        };

        public static Dictionary<int, 
               List<ListItem<int>>> AllLists = new Dictionary<int, List<ListItem<int>>>
        {
            {1, SqlVersions}
        };
    }

    [Authorize]
    public class ListsController : Controller
    {
        [Route("/api/lists/{listId}/ownerOf/{itemId}")]
        [HttpGet]
        public IActionResult GetOwner(int listId, int itemId)
        {
            if (!Lists.AllLists.ContainsKey(listId))
                return NotFound();

            var item = Lists.AllLists[listId].FirstOrDefault(li => li.Value == itemId);
            if(item == null)
                return NotFound();

            return Json(item.Owner);
        }
    }
}

在这里,我仅出于测试目的将staticLists用作存储机制。每个列表将有一个整数标识符。每个列表将包含整数项,其中包含有关已注册它们的个人的信息。ListController类的方法GetOwner允许获取注册相应列表项的人的某个标识符。

检查 SQL 步骤文件

现在我们准备好检查是否可以提交新的 SQL 步骤文件了。假设我们以以下方式存储 SQL 步骤文件。在项目的主文件夹中,我们有一个sql子文件夹。在此文件夹中,每个开发人员都可以创建verXXX文件夹,其中XXX是必须在 Web 服务中注册的某个数字。并且在verXXX文件夹内应该有一个或多个.sql文件,它们提供对数据库的修改。我们在这里不讨论这些.sql文件的执行顺序问题。这与我们的讨论无关。我们只想做到这一点。如果开发人员想要提交sql/verXXX文件夹内的任何新文件,我们必须检查常量XXX是否已被该开发人员注册。

这是相应 Git hook 的代码:

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

在这里,我们使用LibGit2Sharp NuGet 包中的Repository类。items变量将包含 Git 索引中位于sql文件夹内的所有新文件。如果你愿意,可以改进查找此类文件的过程。在versions变量中,我们收集所有不同的XXX常量来自verXXX文件夹。最后,方法ListItemOwnerChecker.DoesCurrentUserOwnListItem检查版本是否由当前用户在 Web 服务上的列表 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://: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;
    }
}

在这里,我们向 Web 服务询问注册所需常量(GetListItemOwner方法)的用户的标识符。然后将其与当前 Windows 用户的名字进行比较。这只是实现此功能的一种可能方法。例如,你可以使用 Git 配置中的用户名或电子邮件。

就是这样。只需构建相应的程序集并将其与所有引用的程序集一起放置在gitHookAssemblies文件夹中。一切都会自动工作。

检查枚举值

嗯,这很棒。现在没有人可以在 Web 服务中注册相应常量之前提交新的 SQL 数据库更改。但我们可以在其他地方使用此方法,其中一些常量需要预订。

例如,在代码的某个地方,可能有一个enum。每个开发人员都可以向enum添加一个成员并为该成员分配一个整数值:

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

我们想避免此enum成员值的冲突。这就是为什么我们要求先在 Web 服务中注册相应的整数常量。实现此类常量的注册检查有多难?

这是新的 Git hook 的代码:

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

    ...
}

首先,我们检查我们enum的相应文件是否已被修改。然后我们使用GetInitialContentGetIndexContent方法从 Git 存储(先前提交的版本)和 Git 索引中提取该文件的内容。以下是它们的实现:

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

然后我们从enum的两个版本中提取enum成员的整数值。这是在GetConstantValues方法中完成的。我使用了Roslyn来实现此功能。你可以从Microsoft.CodeAnalysis.CSharp NuGet 包中获取。

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 NuGet 包的最新版本是 3.4.0。我将程序集放在gitHookAssemblies文件夹中,但代码说找不到相应的程序集版本。原因如下。你看,dotnet-script也使用Roslyn来工作。这意味着,某个版本的Microsoft.CodeAnalysis.CSharp程序集已被加载到域中。对我来说,那是 3.3.1 版本。当我开始使用此版本的 NuGet 包时,问题就消失了。

最后,在我们 hook 处理程序的Process方法中,我们选择所有新值并在我们的 Web 服务上检查它们的拥有者。

关注点

好了。我们的常量预订检查系统已经构建完成。最后,我想谈谈一些我们应该考虑的问题。

  1. 我们创建了一个pre-commit hook 文件,但我们还没有讨论如何在所有开发者的计算机上将其放置到.git\hooks文件夹中。我们可以使用git init命令的--template参数。或者类似的东西:
    git config init.templatedir git_template_dir
    
    git init

    或者,如果你有 Git 2.9 或更高版本,可以使用core.hooksPath Git 配置选项:

    git config core.hooksPath git_template_dir

    或者我们可以将其作为我们项目构建过程的一部分。

  2. dotnet-script的安装也存在同样的问题。我们可以预先在所有开发者的机器上安装它,并指定某个版本的 .NET Core,或者我们可以将其作为构建过程的一部分进行安装。
  3. 我个人认为最大的问题在于引用的程序集的位置。我们同意将所有程序集都放在gitHookAssemblies文件夹中,但我不能确定这在所有情况下都有帮助。例如,LibGit2Sharp包附带了许多适用于不同操作系统的原生库。在这里,我使用了适合 Win-x64 的git2-7ce88e6.dll。但是,如果不同的开发人员使用不同的操作系统,我们可能会遇到一些问题。
  4. 我们几乎没有谈论 Web 服务的实现。在这里,我们使用了 Windows 身份验证,但还有许多可能的选项。此外,Web 服务应该提供一些 UI 来预订新常量和创建新列表。
  5. 也许你已经注意到,在我们的 Git hook 处理程序中使用异步操作很麻烦。我认为应该实现对这些操作更好的支持。

结论

在本文中,我们学习了如何使用 .NET 语言构建一个健壮的 Git hooks 系统。在此基础上,我们编写了几个 hook 处理程序,可以检查不同常量的预订情况,并在违反规定时阻止提交。

我希望这些信息对你有所帮助。祝你好运!

你可以在我的博客上阅读更多我的文章。

附注:你可以在GitHub上找到本文的代码。

历史

  • 2020年1月23日:初始版本
© . All rights reserved.