GitLab Server 增加提交信息检测
December 4, 2020 默认分类
最近准备对项目生成 Change Log,然而发现提交格式不统一根本没法处理;so 后来大家约定式遵循 GitFlow,并使用 Angular 社区规范的提交格式,同时扩展了一些前缀如 hotfix 等;但是时间长了发现还是有些提交为了 “方便” 不遵循 Angular 社区规范的提交格式,这时候我唯一能做的就是想办法在服务端增加一个提交检测;以下记录了 GitLab 增加自定义 Commit 提交格式检测的方案
一、相关文章资料
最开始用 Google 搜索到的方案是使用 GitLab 的 Push Rules 功能,具体文档见 这里,看完了我才发现这是企业版独有的,作为比较有逼格(qiong)的我们是不可能接受这种 “没技术含量” 的方式的;后来找了好多资料,发现还得借助 Git Hook 功能,文档见 Custom Git Hooks;简单地说 Git Hook 就是在 git 操作的不同阶段执行的预定义脚本,GitLab 目前支持 pre-receive
update
post-receive
这几个钩子,当然他可以链式调用;所以一切操作就得从这里入手
二、pre-receive 实现
查阅了相关资料得出,在进行 push 时,GitLab 会调用这个钩子文件,
单独项目配置:
这个钩子文件必须放在 /var/opt/gitlab/git-data/repositories/<group>/<project>.git/custom_hooks
目录中,当然具体路径也可能是 /home/git/repositories/<group>/<project>.git/custom_hooks
;custom_hooks
目录需要自己创建,具体可以参阅文档的 Setup;
全局项目配置: (参考)
对于从源安装的默认目录通常是
/home/git/gitlab-shell/hooks
。在此位置创建一个新目录。
对于Omnibus GitLab
(docker 部署的gitlab也是在此路径),通常是安装/opt/gitlab/embedded/service/gitlab-shell/hooks
取决于钩的类型,它可以是一个 pre-receive.d
, post-receive.d
或update.d
目录, hooks
有可能需要自己创建. 具体路径示例/opt/gitlab/embedded/service/gitlab-shell/hooks/pre-receive.d/
(本人基于docker跑的gitlab....懒)
在进行 push 操作时,GitLab 会调用这个钩子文件,并且从 stdin 输入三个参数,分别为 之前的版本 commit ID、push 的版本 commit ID 和 push 的分支;根据 commit ID 我们就可以很轻松的获取到提交信息,从而实现进一步检测动作;根据 GitLab 的文档说明,当这个 hook 执行后以非 0 状态退出则认为执行失败,从而拒绝 push;同时会将 stderr 信息返回给 client 端;说了这么多,下面就可以直接上代码了,为了方便我就直接用 go 造了一个 pre-receive,官方文档说明了不限制语言
package main
import (
"fmt"
"io/ioutil"
"os"
"os/exec"
"regexp"
"strings"
)
type CommitType string
const (
FEAT CommitType = "feat"
FIX CommitType = "fix"
DOCS CommitType = "docs"
STYLE CommitType = "style"
REFACTOR CommitType = "refactor"
TEST CommitType = "test"
CHORE CommitType = "chore"
PERF CommitType = "perf"
HOTFIX CommitType = "hotfix"
)
const CommitMessagePattern = `^(?:fixup!\s*)?(\w*)(\(([\w\$\.\*/-].*)\))?\: (.*)|^Merge\ branch(.*)`
const checkFailedMessage = `##############################################################################
## ##
## Commit message style check failed! ##
## ##
## Commit message style must satisfy this regular: ##
## ^(?:fixup!\s*)?(\w*)(\(([\w\$\.\*/-].*)\))?\: (. *)|^Merge\ branch(.*) ##
## ##
## Example: ##
## feat(test): test commit style check. ##
## ##
##############################################################################`
// 是否开启严格模式,严格模式下将校验所有的提交信息格式(多 commit 下)
const strictMode = true
var commitMsgReg = regexp.MustCompile(CommitMessagePattern)
func main() {
input, _ := ioutil.ReadAll(os.Stdin)
param := strings.Fields(string(input))
// allow branch/tag delete
if param[1] == "0000000000000000000000000000000000000000" {
os.Exit(0)
}
commitMsg := getCommitMsg(param[0], param[1])
for _, tmpStr := range commitMsg {
commitTypes := commitMsgReg.FindAllStringSubmatch(tmpStr, -1)
if len(commitTypes) != 1 {
checkFailed()
} else {
switch commitTypes[0][1] {
case string(FEAT):
case string(FIX):
case string(DOCS):
case string(STYLE):
case string(REFACTOR):
case string(TEST):
case string(CHORE):
case string(PERF):
case string(HOTFIX):
default:
if !strings.HasPrefix(tmpStr, "Merge branch") {
checkFailed()
}
}
}
if !strictMode {
os.Exit(0)
}
}
}
func getCommitMsg(oldCommitID, commitID string) []string {
getCommitMsgCmd := exec.Command("git", "log", oldCommitID+".."+commitID, "--pretty=format:%s")
getCommitMsgCmd.Stdin = os.Stdin
getCommitMsgCmd.Stderr = os.Stderr
b, err := getCommitMsgCmd.Output()
if err != nil {
fmt.Print(err)
os.Exit(1)
}
commitMsg := strings.Split(string(b), "\n")
return commitMsg
}
func checkFailed() {
_, _ = fmt.Fprintln(os.Stderr, checkFailedMessage)
os.Exit(1)
}
三、安装 pre-receive
把以上代码编译后生成的 pre-receive
文件复制到对应项目的钩子目录/opt/gitlab/embedded/service/gitlab-shell/hooks/pre-receive.d/
即可;要注意的是文件名必须为 pre-receive
,同时 custom_hooks
目录需要自建;custom_hooks
目录以及 pre-receive
文件用户组必须为 git:git
;在删除分支时 commit ID 为 0000000000000000000000000000000000000000
,此时不需要检测提交信息,否则可能导致无法删除分支/tag;最后效果如下所示