为你的 GitLab 增加提交信息检测

最近准备对项目生成 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 这个钩子,当然他可以链式调用;所以一切操作就得从这里入手

二、pre-receive 实现

查阅了相关资料得出,在进行 push 时,GitLab 会调用这个钩子文件,这个钩子文件必须放在 /var/opt/gitlab/git-data/repositories/<group>/<project>.git/custom_hooks 目录中,当然具体路径也可能是 /home/git/repositories/<group>/<project>.git/custom_hookscustom_hooks 目录需要自己创建,具体可以参阅文档的 Setup

在进行 push 操作时,GitLab 会调用这个钩子文件,并且从 stdin 输入三个参数,分别为 之前的版本 commit ID、push 的版本 commit ID 和 push 的分支;根据 commit ID 我们就可以很轻松的获取到提交信息,从而实现进一步检测动作;根据 GitLab 的文档说明,当这个 hook 执行后以非 0 状态退出则认为执行失败,从而拒绝 push;同时会将 stderr 信息返回给 client 端;说了这么多,下面就可以直接上代码了,为了方便我就直接用 go 造了一个 pre-receive,官方文档说明了不限制语言

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
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 checkFailedMeassge = `##############################################################################
## ##
## 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 = false

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(odlCommitID, commitID string) []string {
getCommitMsgCmd := exec.Command("git", "log", odlCommitID+".."+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, checkFailedMeassge)
os.Exit(1)
}

三、安装 pre-receive

把以上代码编译后生成的 pre-receive 文件复制到对应项目的钩子目录即可;要注意的是文件名必须为 pre-receive,同时 custom_hooks 目录需要自建;custom_hooks 目录以及 pre-receive 文件用户组必须为 git:git;在删除分支时 commit ID 为 0000000000000000000000000000000000000000,此时不需要检测提交信息,否则可能导致无法删除分支/tag;最后效果如下所示

commit msg check


本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 国际许可协议进行许可,转载请注明出处。