轻松为 Java / Kotlin + Gradle 项目配置 commit 规范控制和其它 git hooks 操作

前言

在使用 git 作为版本管理工具的软件项目的协作开发中,经常要涉及到控制团队的 commit message 规范的问题,或者希望项目成员在提交代码前保证代码可以通过所有静态检查,并通过所有测试,能够较为高质量地正常运行。一般来说,这种需求都是利用 git hooks^1^ 脚本实现。但 git hooks 脚本文件们保存在 .git/hooks 文件夹下,该文件夹通常情况下不会被 git 管理版本,更不会被推送到远程仓库,所以没法简单地直接利用 git 保证其应用给每个项目成员。

对于 Web 项目开发中的前端同学来说,有像 husky、commitlint、commitizen 这样的被广泛使用的工具可以比较简单地利用包管理工具保证团队中的每个人都能够强制应用由 git hooks 实现的 git 使用流程中的检查操作。

但对于后端同学来说,似乎就没有这种如此方便的工具来实现一个较为强制的 git 控制行为了。但是好在,在 JVM 开发生态中,Gradle 支持用 Groovy/Kotlin Script 很轻松地编写构建任务^2^,从而轻松地实现项目初始化时/构建时的一些自定义操作。

本文便是利用 Gradle(kts) 编写一个必然会被执行的构建任务,在这个构建任务中,将提前准备好的 git hook 脚本文件复制到 .git/hooks 文件夹下,从而保证项目初始化后/重新构建后,你所编写的自定义 git hook 会正确地应用到所有的 git 操作上。

对于 Gradle(Groovy) 的同学: 虽然本文用的是 Gradle(kts),但是原理是一样的,只要找到一个必然会执行的 Gradle task,然后将安装 git hooks 的 task 设置为对应任务的依赖即可达成一样的效果。剩下的就是用 Groovy 代码实现我用 Kotlin Script 实现的效果。

实现

  1. 本文不对 git hook 进行过多的解释,仅对 commit message 规范控制所涉及到的部分进行简述。如果有兴趣深入了解 git hook,请参考本文脚注^1^。
  2. 本文所使用的 commit message 规范参考 Angular 贡献规范中的 Commit Message Format 一节^3^,但并不完全是其检查实现,所使用的用于检查 commit message 的正则表达式仅为抛砖引玉,请按照自己的切实需求自行编写检查逻辑。

创建并实现 Git Hooks 安装 task

.git/hooks 文件夹复制到项目目录下的 .githooks 文件夹中,作为本项目应用的 git hooks 脚本。

build.gradle.kts 中添加以下代码,创建 installProjectGitHooks task:

kts 复制代码
// build.gradle.kts
tasks.register("installProjectGitHooks") {
    doLast {
        val projectGitHooksPath = project.file(".githooks").toPath()
        // 检查 .githooks 文件夹正确性
        if (projectGitHooksPath.notExists() || !projectGitHooksPath.isDirectory()) {
            throw IllegalStateException("The .githooks folder is missing, please pull the project again, or clone the project, or roll back to the commit before the .githooks folder was missing.")
        }
        // 删除原 .git/hooks 文件夹并将 .githooks 文件夹整体复制过去
        (project.file(".git").toPath() / "hooks").also {
            it.deleteRecursively()
            projectGitHooksPath.copyToRecursively(it, followLinks = false, overwrite = false)
        }
    }
}

保证 installProjectGitHooks task 的执行

当使用 kts 作为 gradle 的构建脚本编写语言,并使用任何的 IDE 与 gradle 进行集成索引时,IDE 一定会令 gradle 首先执行一个名为 prepareKotlinBuildScriptModel 的 task,用以让 gradle 生成可供 IDE 利用的元数据。所以我们这里的思路是,修改 prepareKotlinBuildScriptModel task,令其依赖于我们刚刚创建的 installProjectGitHooks task,从而保证 gradle 一定会执行 installProjectGitHooks task。

build.gradle.kts 中添加以下代码:

kts 复制代码
// build.gradle.kts
tasks.named("prepareKotlinBuildScriptModel") {
    dependsOn("installProjectGitHooks")
}

如果你使用的是 IntelliJ IDEA 作为你的 IDE,现在执行 Reload Gradle Project,你将会在 Build 标签页的日志中看到 gradle 执行了名为 installProjectGitHooks 的 task。

实现在 commit 前对于 commit message 格式的检查

在 "创建并实现 Git Hooks 安装 task" 一节中创建的 .githooks 文件夹中复制一份 commit-msg.sample,并将其改名为 commit-msg,以启用该钩子脚本。

commit-msg 钩子接收一个参数,此参数即为存有当前提交信息的临时文件的路径。 如果该钩子脚本以非零值退出,Git 将放弃提交,因此,可以用来在提交通过前验证项目状态或提交信息。

------ 节选自 Git - Git 钩子 (git-scm.com)

在网络上的很多文章、包括 Git 官方文档中,这里的检查脚本通常不是用 POSIX sh 实现的,而是使用 bash、python、ruby 等语言实现。通常情况下我们没法保证客户端拥有以上运行环境,但可以确定的是,客户端一定能够运行 gradle 的构建脚本,再加上本人不会写 POSIX sh 脚本,所以此处我们依然采用 gradle task 实现 commit msg 的检查逻辑。

build.gradle.kts 中添加如下代码,创建新构建任务 checkGitCommitMessage

kts 复制代码
// build.gradle.kts
tasks.register("checkGitCommitMessage") {
    group = "verification"
    doLast {
        // 读取项目参数 gitMessage 并读取其文件内容
        val message = File(project.findProperty("gitMessage") as String).readText().trimEnd()
        val regex = Regex("^(feat|fix|docs|style|refactor|perf|test|chore|revert|ci)(\(.*\))?: [^\n]{1,30}(\n(\n- .{1,100})+)?(\n\n(Fixes|Closes): #[0-9]+)?$")
        if (!regex.matches(message)) {
            throw IllegalStateException("git commit message format is incorrect, please refer to the specification")
        }
    }
}

修改 .githooks/commit-msg 中的代码,令其执行刚刚创建的 checkGitCommitMessage task:

shellscript 复制代码
#!/bin/sh

./gradlew checkGitCommitMessage -PgitMessage="$1"

Reload Gradle Project,尝试进行 git commit,附以不符合上述脚本中的正则表达式的 commit message,可以发现提交被阻止;附以符合上述脚本中的正则表达式的 commit message,提交成功。由此,成功实现对于 commit message 的规范控制。

尾声

对于其它的需求,比如令提交前代码必须能够通过所有测试和客户端静态检查,只需要启用 pre-commit 钩子,并在其中执行 ./gradlew check 等 task,令 gradle 检查失败后阻止 git 提交即可。

欢迎在评论区留言讨论,如果觉得本篇文章有点用,还请点赞收藏一条龙 QwQ ~

Footnotes

  1. Git - Git 钩子 (git-scm.com) ↩^2^

  2. Gradle Document: Part 6: Writing Tasks (gradle.org)

  3. angular/CONTRIBUTING.md#Commit Message Format · angular/angular (github.com)

相关推荐
XiaoYu20022 天前
21.JS高级-ES6之模板字符串与剩余参数
前端·javascript·代码规范
Vinkan@3 天前
前端规范工程-2:JS代码规范(Prettier + ESLint)
前端·javascript·代码规范·前端工程
Vinkan@5 天前
前端规范工程-5:Git提交信息规范(commitlint + czg)
前端·git·代码规范·前端工程
Vinkan@6 天前
前端工程规范-3:CSS规范(Stylelint)
前端·css·代码规范·前端工程
香菜的开发日记8 天前
JavaScript 中变量命名的最佳实践
前端·javascript·代码规范
凭栏听雨客10 天前
【JS代码规范】如何优化if-else代码规范
开发语言·javascript·代码规范
Parasoft中国13 天前
Parasoft助力Joby Aviation符合DO-178B标准
c++·代码规范·测试覆盖率
帅次14 天前
基于 Web 的工业设备监测系统:非功能性需求与标准化数据访问机制的架构设计
设计模式·重构·软件工程·软件构建·需求分析·代码规范·设计规范
DT——14 天前
Vite项目中eslint的简单配置
前端·javascript·代码规范
帅次20 天前
重塑在线软件开发新纪元:集成高效安全特性,深度解析与评估支持浏览器在线编程的系统架构设计
性能优化·重构·软件工程·软件构建·个人开发·代码规范·规格说明书