这个工具肯定不完美,未来至少还会经历一个月的时间进行稳定和功能增强,现阶段发出来只是单纯的打算 Build in Public (另外,也确实可以正式使用一些功能了),我其实挺想等等完美了再发的,但是怕时间久了又没有继续去做/去写文档的动力了,所以虽然不完美,但还是提前发下
https://github.com/ImSingee/kitty
如果你用过 Node.js 的生态,肯定对 husky 和 lint-staged 不陌生,简单来说,这个工具就是这俩的整合 & 增强,给任何语言增加了 git hook 管理和基于要提交的文件进行 lint 的功能
实话实说,这个工具写出来主要还是为了解决我自己的痛点的
痛点一:无法共享 hook
之前的方案都是通过 Makefile 进行 hook 拷贝或者干脆文档说明要手动安装 hook 的,但是如果 hook 规则更新了那么总归会产生问题
在撰写 hook 前,可以通过 kitty install --direnv 来生成给 direnv 使用的 sh 文件(并提交),这样只要文档说明下让协作者执行 direnv allow 就可以完成每次进入时自动安装/更新 hook 了
目前 kitty install 会自动生成一个 .kittyrc 配置文件,这个文件可以删除,如果要保留的话也建议使用 .kittyrc.json (方便直接配置 lint-staged 规则给 json 做格式化)
痛点二:无法只基于特定文件运行 lint
例如 gofmt 、golines 等工具,之前想要实现对于只打算提交的文件来执行特定命令只能在 pre-commit 中写一段长长的 git diff 命令,并且难以做到自动化的应用修改、也难以处理某些既被 add 又再次修改的文件
这一切现在可以简单通过 lint-staged 来实现
比如对所有 .go 文件执行 gofmt ,之前需要
changed=$(git diff --cached --name-only --diff-filter=ACM | grep -v vendor | grep '.go$')
gofmt -s -l $changed
git add $changed
现在只需要配置 kitty 的配置文件
{
"lint-staged": {
"*.go": "gofmt -s -l"
}
}
痛点三:无法基于特定目录运行 lint
主要是 Go 场景下 golangci-lint ,传递时(如果出现多目录修改)需要指定 go package ,用 shell 来做到会非常的麻烦,但是用 kitty lint-staged 只需要
{
"lint-staged": {
"*.go": "[dir] @golangci-lint run"
}
}
这里用的 @golangci-lint 而不是 golangci-lint 证明使用的是 kitty tools ,见👇
痛点四:难以管理依赖工具的版本
某些依赖需要锁死版本,例如 protoc-gen-go 生成的文件会带有所使用的工具版本、golangci-lint 升级后可能会产生新的 lint error ,又或者 gci 这个鬼东西平均每两个版本就会有一个 breaking change 🙃
因此顺便在 kitty 中集成了一个简单的 tools manager ,将依赖工具锁死
例如使用 golangci-lint 时,只需要
kitty tools install [email protected]
然后使用 kitty @golangci-lint 执行这个工具,就可以保证始终使用的是 1.54.2 这个版本
并且这个版本信息会保存在 kitty config 中,协作者也会始终使用最新的版本
如果配置了 direnv ,kitty 的 PATH 会被自动提高到最高优先级,即在项目目录下直接运行 golangci-lint 而不是 kitty @golangci-lint 也会使用被 kitty 管理的版本
目前的 tools manager 还有很多优化空间,例如不支持本地 registry 、只支持下载二进制和使用 go install 、没有保存 checksum 、不会自动下载缺失的工具(而是需要手动执行 kitty install )等
附上一份我自己给 go 项目所配置的 kitty config
.kitty/pre-commit
kitty @lint-staged
.kitty/pre-push
go test -tags test -trimpath -race -timeout=2m -short -failfast ./...
.kittyrc.json
{
"lint-staged": {
"files": {
"*.go": [
"gofmt -s -w -l",
"@gci write --skip-generated --skip-vendor -s Standard -s Default -s 'Prefix(github.com/ImSingee/SOME_PKG)' -s Blank -s Dot",
"@golines -w --max-len=180 --reformat-tags --shorten-comments --ignore-generated",
"[dir] @golangci-lint run"
],
"*.json": "@format",
"go.mod": "go mod edit -fmt"
}
},
"tools": {
"gci": "0.11.1",
"golangci-lint": "1.54.2",
"golines": "0.11.0"
}
}