前言
在使用 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 实现的效果。
实现
创建并实现 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 ~