git 갈고리에 C# 코드 사용

14187 단어 programmingcsharpgit

Why use hooks?
개발자로서 GitHub, GitLab, Atlassian, Azure DevOps 등 플랫폼을 우리의 위탁 관리git 시스템과 협업 플랫폼으로 좋아합니다.우리도 깨끗한 코드를 좋아하며, 끊임없이 새로운 들보와 규칙을 발명하여 그것을 집행한다.내가 보기에 매번 제출할 때마다 코드 라이브러리를 생산 환경에 배치하는 것을 허용해야 한다.'고정 스타일 오류' 나 '고정 구축' 같은 제출보다 더 나쁜 것은 없다.이것은 보통 개발 주기에 가능한 한 빨리 알고 싶은 작은 오류입니다.다음 개발자가 당신의 오류를 범하거나 CI 서버의 귀중한 구축 시간을 낭비해서 구축을 파괴하고 싶지 않습니다.가령 당신의 동료에게 당신의 코드를 검사하라고 요구했다면;또한 구축 서버가 코드를 거부합니다.이것은 당신이 이 문제를 해결하기 위해 돌아가야 하고, 당신의 동료가 돌아와야 하며, 변경된 후에 다시 심사해야 한다는 것을 의미한다. (즉, 새로 제출할 때 비준을 리셋해야 한다.)이렇게 하면 많은 시간과 정력을 낭비할 것이다.

note : I favour server-side hooks, but when using a SaaS solution, this is not always a possibility. I know I would not want someone to run arbitrary code on my servers. Unfortunately, a developer can bypass the client-side hooks. Until we can run, possibly sandboxed, server-side hooks on our prefered platform, we have to make the best of it by using client-side hooks.


Githooks는 git 라이프 사이클의 일부에서 실행할 수 있는 스크립트입니다.갈고리는 반드시 실행할 수 있어야 하지만 그 외에 갈고리의 기능은 개발자의 상상력에만 한정된다.나는 자바스크립트 (node) 로 작성된 갈고리 예시를 많이 보았는데, 그것들은 huskycommitlint 등의 도구를 사용하여 어떤 작업 방식을 강제로 집행하는 것을 보았다.내가 다가올 것을 훑어보면NET Core 3.0이 발표됐을 때local-tools라는 개념이 나를 생각하게 했다.나는 dotnet-script의 존재를 알고 있다. 이것이 나의 GitHooks의 C#를 가능하게 할 수 있을까?

note : in the past I have used a set-up with node since I occasionally work with front-end frameworks like Angular. Since I had node installed I could use it even in my pure backend projects to enforce commit messages and such. For me it felt dirty, since that would require team members to have node installed. Using the dotnet cli feels less as a forced decision since members are likely to have it installed already.



Let’s get started!
git 메모리 라이브러리를 만들 때 hooks라는 폴더가 있습니다. 이 폴더에는 모든git 갈고리가 설치되어 있습니다.각 이벤트에는 일정한 예제 게시물이 있습니다.각 연결 가능성의 예를 표시합니다.이 디렉터리는 원본 코드의 제어를 받지 않기 때문에 팀과 이 갈고리를 공유할 수 있도록 디렉터리를 만들 것입니다.
mkdir git-hooks-example  
cd git-hooks-example  
git init  
dotnet new gitignore  
dotnet new tool-manifest  
dotnet tool install dotnet-script  
dotnet tool install dotnet-format  
mkdir .githooks


Pre-Commit Hook
시범을 보이기 위해서, 우리는 일반적인 연결을 만들 것이다.git commit-m "이 실행 중인지 확인하십시오. (빈 제출 메시지를 사용하면 제출이 중단됩니다.)인쇄된 줄pre-commit hook을 보셔야 합니다.
#!/usr/bin/env dotnet dotnet-script
Console.WriteLine("pre-commit hook");

실행할 수 있도록 하려면 다음과 같이 하십시오.
find .git/hooks -type f -exec rm {} \;
find .githooks -type f -exec chmod +x {} \;
find .githooks -type f -exec ln -sf ../../{} .git/hooks/ \;

csx에서 다른 파일을 인용할 수 있기 때문에, 갈고리 사이에서 코드를 다시 사용할 수 있도록 몇 개의 파일을 만들 것입니다.
logger라는 파일을 만듭니다.csx
public class Logger
{
    public static void LogInfo(string message)
    {
        Console.ForegroundColor = ConsoleColor.White;
        Console.Error.WriteLine(message);
    }
    public static void LogError(string message)
    {
        Console.ForegroundColor = ConsoleColor.Red;
        Console.Error.WriteLine(message);
    }
}

명령줄이라는 파일을 만듭니다.csx
#load "logger.csx"
public class CommandLine
{
    public static string Execute(string command)
    {
        // according to: https://stackoverflow.com/a/15262019/637142
        // thans to this we will pass everything as one command
        command = command.Replace("\"", "\"\"");
        var proc = new Process
        {
            StartInfo = new ProcessStartInfo
            {
                FileName = "/bin/bash",
                Arguments = "-c \"" + command + "\"",
                UseShellExecute = false,
                RedirectStandardOutput = true,
                CreateNoWindow = true
            }
        };
        proc.Start();
        proc.WaitForExit();
        if (proc.ExitCode != 0)
        {
            Logger.LogError(proc.StandardOutput.ReadToEnd());
            return proc.ExitCode.ToString();
        }
        return proc.StandardOutput.ReadToEnd();
    }
}

dotnet commands라는 파일을 만듭니다.csx
#load "logger.csx"
#load "command-line.csx"
public class DotnetCommands
{
    public static int FormatCode() => ExecuteCommand("dotnet format");
    public static int BuildCode() => ExecuteCommand("dotnet build");

    public static int TestCode() => ExecuteCommand("dotnet test");

    private static int ExecuteCommand(string command)
    {
        string response = CommandLine.Execute(command);
        Int32.TryParse(response, out int exitCode);
        return exitCode;
    }

}

git commands라는 파일을 만듭니다.csx
#load "logger.csx"
#load "command-line.csx"
public class GitCommands
{
    public static void StashChanges()
    {
        CommandLine.Execute("git stash -q --keep-index");
    }
    public static void UnstashChanges()
    {
        CommandLine.Execute("git stash pop -q");
    }
}

GIT와dotnet 명령을 기록하고 실행하는 데 사용되는 프로그램이 생겼습니다.다음에 우리는 미리 제출한 갈고리부터 시작할 것이다.pre-commit이라는 파일을 만듭니다. 이 파일은 새로 만든 다른 파일과 달리 확장자를 지정하지 않고 Shebang을 사용하여dotnet 스크립트를 불러옵니다.각 갈고리에 대한 설명은 아래의 문장을 참조하시오.
Git Hooks | Atlassian Git Tutorial
#!/usr/bin/env dotnet dotnet-script
#load "logger.csx"
#load "git-commands.csx"
#load "dotnet-commands.csx"

// We'll only runchecks on changes that are a part of this commit so let's stash others
GitCommands.StashChanges();

int buildCode = DotnetCommands.BuildCode();

// We're done with checks, we can unstash changes
GitCommands.UnstashChanges();
if (buildCode != 0) {
    Logger.LogError("Failed to pass the checks");
    Environment.Exit(-1);
}
// All checks have passed

만약 우리가 이번에git commit-m "을 다시 실행한다면, 우리는 검사를 통과하지 못했다는 오류 메시지를 받을 것입니다. 이것은 일리가 있습니다. 왜냐하면 우리는 아직 프로젝트가 없기 때문입니다.우리는 하나의 클래스 라이브러리와 테스트 라이브러리로 구성된 간단한 sln을 만들 것입니다.
dotnet new sln  
dotnet new classlib --framework netstandard2.1 --langVersion 8 --name SomeLib --output src/SomeLib  
dotnet new xunit --output tests/SomeLibTests  
dotnet sln add **/*.csproj 
cd tests/SomeLibTests/  
dotnet add reference ../../src/SomeLib/SomeLib.csproj  
cd ../../  
dotnet build

git commit-m "을 다시 사용하면 제출 중지에 대한 메시지를 받을 수 있습니다.제출할 때마다 최소한 컴파일됩니다: -) 예를 들어, 클라스1에서 이름 공간의 끝에 있는 괄호를 삭제하면 잘못된 클라스1을 얻을 수 있습니다.cs(7,6): 오류 CS1513:}.만약 우리가 예비 제출 갈고리를 더 확장한다면, 우리는 매번 제출할 때마다dotnet-format와dotnet 테스트를 실행할 수 있습니다.만약 우리가 일부러 실패한 테스트 (1이 0이나 유사한 것) 를 작성한다면, 구축은 통과되지 않을 것이다.
#!/usr/bin/env dotnet dotnet-script
#load "logger.csx"
#load "git-commands.csx"
#load "dotnet-commands.csx"

Logger.LogInfo("pre-commit hook");

// We'll only runchecks on changes that are a part of this commit so let's stash others
GitCommands.StashChanges();

int formatCode = DotnetCommands.FormatCode();
int buildCode = DotnetCommands.BuildCode();
int testCode = DotnetCommands.TestCode();

// We're done with checks, we can unstash changes
GitCommands.UnstashChanges();
int exitCode = formatCode + buildCode + testCode;
if (exitCode != 0) {
    Logger.LogError("Failed to pass the checks");
    Environment.Exit(-1);
}
// All checks have passed


Prepare-commit-message hook
지금까지 우리는 C#가 필요한 어떤 것도 실제로 사용하지 않았다.물론 C#을 사용하여 셸 명령을 실행하고 있습니다.우리의 다음 갈고리에 대해 우리는 시스템을 사용할 것이다.이오.팀으로서 메시지를 제출하겠다는 약속을 상상해 보세요.모든 제출 메시지가 문제 추적기에 대한 인용을 포함하기를 원한다면.
type(scope?): subject #scope is optional

이 갈고리에 prepare commit msg 파일을 만듭니다. 사용자가 정보를 제공하지 않으면, 우리는 편리한 commit 메시지 차지 문자를 제공할 수 있습니다.메시지를 실제로 실행하려면commit-msg 갈고리가 필요합니다.이 예에서, 우리는 단지 요소 지점을 위한 메시지를 만들 뿐이다.
#!/usr/bin/env dotnet dotnet-script
#load "logger.csx"
#load "util.csx"
#load "git-commands.csx"

Logger.LogInfo("prepare-commit-msg hook");

string commitMessageFilePath = Util.CommandLineArgument(Args, 0);
string commitType = Util.CommandLineArgument(Args, 1);
string commitHash = Util.CommandLineArgument(Args, 2);

if (commitType.Equals("message")) {
    // user supplied a commit message, no need to prefill.
    Logger.LogInfo("commitType message");
    Environment.Exit(0);
}

string[] files = GitCommands.ChangedFiles();
for(int i = 0; i < files.Length; i++) {
    // perhaps determine scope based on what was changed.
    Logger.LogInfo(files[i]);
}

string branch = GitCommands.CurrentBranch();
if (branch.StartsWith("feature")) {
    string messageToBe = "feat: ISS-XXX";
    PrepareCommitMessage(commitMessageFilePath, messageToBe);
}

public static void PrepareCommitMessage(string messageFile, string message)
{
     string tempfile = Path.GetTempFileName();
    using (var writer = new StreamWriter(tempfile))
    using (var reader = new StreamReader(messageFile))
    {
        writer.WriteLine(message);
        while (!reader.EndOfStream)
            writer.WriteLine(reader.ReadLine());
    }
    File.Copy(tempfile, messageFile, true);
}

util이라는 새 조수를 만듭니다.csx
public class Util
{
    public static string CommandLineArgument(IList<string> Args, int position)
    {
        if (Args.Count() >= position + 1)
        {
            return Args[position];
        }
        return string.Empty;
    }

}


Commit-msg Hook
마지막 로컬 git 갈고리는commit-msg 갈고리입니다.이것은 제출 메시지가 지정한 형식에 부합되는지 확인하기 위해 정규 표현식을 사용합니다.
#!/usr/bin/env dotnet dotnet-script
#load "logger.csx"
#load "util.csx"
#load "git-commands.csx"
using System.Text.RegularExpressions;

Logger.LogInfo("commit-msg hook");

string commitMessageFilePath = Util.CommandLineArgument(Args, 0);
string branch = GitCommands.CurrentBranch();
Logger.LogInfo(commitMessageFilePath);
Logger.LogInfo(branch);
string message = GetCommitedMessage(commitMessageFilePath);
Logger.LogInfo(message);

const string regex = @"\b(feat|bug)\b(\({1}\b(core)\b\){1})?(:){1}(\s){1}(ISS-[0-9]{0,3}){1}";
var match = Regex.Match(message, regex);

if (!match.Success) {
    Logger.LogError("Message does not match commit format");
    Environment.Exit(1);
}

public static string GetCommitedMessage(string filePath) {
    return File.ReadAllLines(filePath)[0];
}


pre push Hook
NuGet 패키지는 갈고리에서도 사용할 수 있습니다.예를 들어 우리는 주인에 대한 밀치기를 막고 싶다.Newtonsoft를 사용하여 프로필을 읽을 수 있습니다.Json이 보호된 분기를 찾아 중지합니다.
#!/usr/bin/env dotnet dotnet-script
#r "nuget: Newtonsoft.Json, 12.0.2"
#load "logger.csx"
#load "config.csx"
#load "git-commands.csx"
using Newtonsoft.Json;

string currentBranch = GitCommands.CurrentBranch().Trim();
Config currentConfig = GetConfig();
bool lockedBranch = currentConfig.ProtectedBranches.Contains(currentBranch);

if (lockedBranch) {
    Logger.LogError($"Trying to commit on protected branch '{currentBranch}'");
    Environment.Exit(1);
}

public static Config GetConfig()
{
    return JsonConvert.DeserializeObject<Config>(File.ReadAllText(".githooks/config.json"));
}


Conclusion
나의 현재 갈고리는 아주 좋은 것이 아니다. 아마도 C#는git 갈고리 중에서 가장 빠른 언어를 사용하지 않을 것이다.그러나 나는 이 실험이 성공했다고 생각한다.나는 셸 스크립트가 아닌 C#로 코드를 작성하는 것을 더 좋아한다.한층 더 개선할 생각은
  • 변경 목록에 따라 변경 범위를 확정한다(즉, 하나의 디렉터리만 변경되었으므로 범위를 알 수 있다)
  • 정규 표현식, 허용된 역할 영역과 허용된 유형 설정
  • 더 많은 장면에 대한 개선 제출 전 메시지
  • 강제 사용자 연결
  • 갈고리 버전을 관리하고 구/다른 버전의pull(갈고리 업데이트)를 서명할 때 디렉터리를 동기화합니다.(perhaps githook location)
  • 당신의 생각을 알려주세요. -)
    maxhamulyak/git-hooks-example
    즐거움 코드🍻

    좋은 웹페이지 즐겨찾기