前语

这周,我决定完结由于工作而推迟了一周的TODO事项来改进我的Git工作流程。

为了在提交的时分尽或许多的带着上下文信息,咱们让提交信息包括了正在处理的JIRA编号。这样,将来假如有人回到咱们现在正在提交的源代码,输入git blame,就能很简单的找出JIRA的编号。

每次提交都包括这些信息或许会有点乏味(假如你运用了类似TDD之类的办法,您会提交的更加频繁),而且,虽然像Tower这样的git客户端会让此变得简单一些,但是您仍然需求手动将问题编号复制粘贴到提交音讯中,而且记住这样做,这是我最难以解决的问题。

出于这个原因,我开端寻求了解git hooks,企图自动化这项使命。我的想法是能够从git分支获取JIRA编号(咱们有一个分支命名约定,形如:story/ISSUE-1234_branch-name),然后将提交音讯更改为以JIRA编号为前缀,然后生成终究成果音讯:ISSUE-1234-其他原本的提交信息。

用git hooks自动生成提交信息

Git Hooks 提供了一种在运转某些重要的git指令时触发自定义操作的办法,例如在一次commit或者push之前履行一些操作。

在本例中,我运用了**commit-msg**钩子,它能够在当时提交信息生效前修正此信息。钩子由一个参数调用,该参数是指向包括用户输入的提交音讯的文件的途径。这意味着,为了改动提交音讯,咱们只需求从文件中读取、修正其内容,然后写回调用挂钩的文件。

要创立git钩子,咱们需求在**.git/hooks路经下提供一个可履行脚本。我的钩子放在了.git/hooks/commit-msg**路经之下。

为什么我运用Swift?

Git hooks能够运用任何你了解的,而且在主机上装置了解释器(经过shebang来指定)的脚本语言来编写。

虽然有很多更受欢迎的选项,比方bashruby等等,但我仍是决定运用Swift。由于我对Swift更了解,由于我每天都在运用它,而且我真的十分喜爱它强壮的类型语法以及低内存占用。

让咱们开端吧

你能够运用任何你喜爱的IDE编写Swift脚本。但是假如你想要有适当的代码补全以及调试才能,你能够为其创立一个Xcode项目。为此,在**macOS下挑选Command Line Tool**创立一个新的项目。

在 Swift 中编写脚本:Git Hooks

在创立的文件顶部加上Swift shebang,引入Foundation库。

#!/usr/bin/swift
import Foundation

这样当git履行文件时,shebang将确保运用文件作为输入数据调用/usr/bin/swift二进制文件。

编写git钩子

项目现已全部设置好,所以现在能够编写git挂钩了。让咱们走完一切的过程。

检索提交音讯

要做的第一件事就是从脚本传进来的参数检索暂时提交文件的途径然后读取文件内容。

let commitMessageFile = CommandLine.arguments[1]
guard let data = FileManager.default.contents(atPath: commitMessageFile),
      let commitMessage = String(data: data, encoding: .utf8) else {
    exit(1)
}

在上面的代码片段中,咱们首要拿到了提交文件的途径(git传递给脚本),然后经过FileManagerAPI读取了文件内容。假如由于一些原因检索失利了,咱们退出(exit)脚本一起回来状况码1,这将告知git停止此次提交。


注意:

依据git hooks文档,假如任何钩子脚本回来的状况码大于0,它都将停止即将要要产生的操作。这将在本文后面的部分中运用,以便在不需求做任何修正而优雅地退出。


检索问题编号

既然提交信息的字符串现已可用,接下来就需求找到当时分支并从中检索到问题编号。正如本文前面提到的,这只或许是由于团队对分支命名的严格格式,在其名称中一直包括JIRA编号(例如,story/ISSUE-1234_some-awesome-feature-work)。

为了完成这一点,咱们有必要检索当时的工作分支,然后用正则表达式从中检索问题编号。

让咱们从增加脚本调用zsh shell指令的才能开端。经过运用Processapi,脚本能够与git指令行界面交互。

func shell(_ command: String) -> String {
    let task = Process()
    let outputPipe = Pipe()
    let errorPipe = Pipe()
    task.standardOutput = outputPipe
    task.standardError = errorPipe
    task.arguments = ["-c", command]
    task.executableURL = URL(fileURLWithPath: "/bin/zsh")
    do {
        try task.run()
        task.waitUntilExit()
    } catch {
        print("There was an error running the command: \(command)")
        print(error.localizedDescription)
        exit(1)
    }
    guard let outputData = try? outputPipe.fileHandleForReading.readToEnd(),
          let outputString = String(data: outputData, encoding: .utf8) else {
        // Print error if needed
        if let errorData = try? errorPipe.fileHandleForReading.readToEnd(),
           let errorString = String(data: errorData, encoding: .utf8) {
            print("Encountered the following error running the command:")
            print(errorString)
        }
        exit(1)
    }
    return outputString
}

现在完成了shell指令,那么就能够运用它询问git当时分支是什么,然后尽或许的从中提取出问题编号。

let gitBranchName = shell("git rev-parse --abbrev-ref HEAD")
    .trimmingCharacters(in: .newlines)
let stringRange = NSRange(location: 0, length: gitBranchName.utf16.count)
guard let regex = try? NSRegularExpression(pattern: #"(\w*-\d*)"#, options: .anchorsMatchLines),
    let match = regex.firstMatch(in: gitBranchName, range: stringRange) else {
    exit(0)
}
let range = match.range(at: 1)
let ticketNumber = (gitBranchName as NSString)
    .substring(with: range)
    .trimmingCharacters(in: .newlines)

请注意,假如没有匹配项(即分支名称中不包括JIRA问题编号),脚本将以0的状况退出,允许提交继续进行,而不进行任何更改。这是为了不破坏比如main或其他测验/调查分支中的工作流。

修正提交信息

为了更改提交音讯,有必要将脚本开头读取的文件内容(包括提交音讯)写回同一途径。

在这种情况下,只需求做一个更改,即在提交信息的前面加上JIRA编号和(-),以将其与提交信息的其余部分很好地分开。还有必要确保检查了提交信息字符串,仅在编号不存在时才增加编号:

if !commitMessage.contains(ticketNumber) {
    do {
        try "\(ticketNumber) - \(commitMessage.trimmingCharacters(in: .newlines))"
            .write(toFile: commitMessageFile, atomically: true, encoding: .utf8)
    } catch {
        print("Could not write to file \(commitMessageFile)")
        exit(1)
    }
}

设置git钩子

现在脚本现已预备好了,是时分把它放在git能够找到它的方位了。Git钩子能够大局设置,也能够根据单个repo设置。

我个人对这类脚本的偏好是根据单个repo设置,由于这样能够在出现问题时为您提供更多的控制和可见性,而且假如钩子开端失利,它会在它设置的repo中失利,而不是大局都失利。

要设置它们,咱们只需求使文件可履行,重命名并将其复制到所要设置repo的**.git/hooks/**途径之下:

chmod +x main.swift
mv main.swift <path_to_your_repo>/.git/hooks/commit-msg

测验成果

现在repo现已全部设置好了,剩下的就是对布置的脚本进行测验。在下面的截屏中,创立了两个分支,一个带有问题编号,一个没有,它们有着相同的提交信息。能够看出脚本运转正常,而且只在需求时才更改提交音讯!

在 Swift 中编写脚本:Git Hooks

关于咱们

咱们是由 Swift 爱好者共同保护,咱们会共享以 Swift 实战、SwiftUI、Swift 基础为核心的技能内容,也整理搜集优秀的学习材料。

我正在参与技能社区创作者签约计划招募活动,点击链接报名投稿。