利用 Git Hook 做团队代码的质量管理

为什么要使用 Git Hook

团队人员增加,以及项目复杂后,对于 Git 开发工作流都会有一套规范,一般都有管理代码,统一风格的类似需求。

这时候,仅仅靠成员自觉操作,出现遗漏操作的问题在所难免。

显然通过机器去每次检查会更可靠。

这次也并没有上级要求我去做这件事,现在仅仅是要求规范 git commit 的提交内容格式。格式如:

[功能] 完成聊天模块
[bug] 修复滑动越界崩溃
[修改] 修改个人文案

刚好在之前写其它脚本,听同事提过 Git Hook 的用处,我认为很适合这个场景运用。

学会使用之后,将来更可以应对更复杂的 Git 开发工作流要求当中去。

Git Hook 介绍

类似 Jenkins 之类的 CI 系统一样,Git 能在特定的重要动作发生时,去触发脚本。

借助 Git Hook 能力,可以根据各个团队自己的代码质量需求,整合到 Git 开发工作流中。

Git Hook 分类

Git Hook 也分为两个端: 客户端和服务端。

客户端的 Git Hook 就是工作在我们本地机器的,服务端的 Git Hook 则是工作在我们提交到远程的服务器仓库中。

客户端 Git Hook:

  • pre-commit: 执行git commit命令时触发,常用于检查代码风格
  • prepare-commit-msg: 触发于 commit message 编辑器呼起前 ,default commit message创建后,常用于生成默认的标准化的提交说明
  • commit-msg: 开发者编写完并确认commit message后触发,常用于校验提交说明是否标准
  • post-commit: 整个git commit完成后触发,常用于邮件通知、提醒
  • applypatch-msg: 执行git am命令时触发,常用于检查命令提取出来的提交信息是否符合特定格式
  • pre-applypatch: git am提取出补丁并应用于当前分支后,准备提交前触发,常用于执行测试用例或检查缓冲区代码
  • post-applypatch: git am提交后触发,常用于通知、或补丁邮件回复(此钩子不能停止git am过程)
  • pre-rebase: 执行git rebase命令时触发
  • post-rewrite: 执行会替换commit的命令时触发,比如git rebase或git commit –amend
  • post-checkout: 执行git checkout命令成功后触发,可用于生成特定文档,处理大二进制文件等
  • post-merge: 成功完成一次 merge行为后触发
  • pre-push: 执行git push命令时触发,可用于执行测试用例
  • pre-auto-gc: 执行垃圾回收前触发

服务端 Git Hook:

  • pre-receive: 当服务端收到一个 push 操作请求时触发,可用于检测 push 的内容

  • update: update 脚本和 pre-receive 脚本十分类似,不同之处在于它会为每一个准备更新的分支各运行一次。 假如推送者同时向多个分支推送内容,pre-receive 只运行一次,相比之下 update 则会为每一个被推送的分支各运行一次。

  • post-receive: post-receive 挂钩在整个过程完结以后运行,可以用来更新其他系统服务或者通知用户。它的用途包括给某个邮件列表发信,通知持续集成(continous integration)的服务器,或者更新问题追踪系统(ticket-tracking system) —— 甚至可以通过分析提交信息来决定某个问题(ticket)是否应该被开启,修改或者关闭。

使用 Git Hook

Git Hook 本身自带有脚本,会存放在仓库 .git/hooks 文件夹中,目录一般是这样的:

1
2
3
4
5
6
- YourGitRepo
|- .git
|- hooks
|- hooks--commit-msg.sample
|- hooks--post-update.sample
...

完整的几个 .sample 文件在可以在 Git 的开源仓库中查看: Git Hooks Sample .

可能有的人和我一样,公司的 GitLab 仓库中不会自动生成 hooks 目录,不用担心,按照上面的目录结果,在 .git 目录下建立一个 hooks 文件夹就好。

到这里,我们就可以编写 shell 脚本,针对需要的时机,来对 git 进行操作了。

注意如果是 sample 文件,要去掉 .sample 后缀,变成前面 Git Hook 分类 中提到对应的操作来作为文件名。

比如我要做的校验 commit 的提交信息,那么使用的 Git Hook 脚本名应该为 commit-msg.

hooks--commit-msg.sample 里的内容为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/bin/sh
#
# An example hook script to check the commit log message.
# Called by "git commit" with one argument, the name of the file
# that has the commit message. The hook should exit with non-zero
# status after issuing an appropriate message if it wants to stop the
# commit. The hook is allowed to edit the commit message file.
#
# To enable this hook, rename this file to "commit-msg".

# Uncomment the below to add a Signed-off-by line to the message.
# Doing this in a hook is a bad idea in general, but the prepare-commit-msg
# hook is more suited to it.
#
# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p')
# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1"

# This example catches duplicate Signed-off-by lines.

test "" = "$(grep '^Signed-off-by: ' "$1" |
sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || {
echo >&2 Duplicate Signed-off-by lines.
exit 1
}

上面说明里有一些基本说明, 这里获得的 $1 参数,其实是存放 commit msg 内容的文件路径,为 .git/COMMIT_EDITMSG.

利用命令:

1
msg=$(cat $1)

就能取到 commit 的信息,稍作修改,就能达到我说的,校验团队 commit 信息的规范的目的了。

Git Hook 不生效

如果遇到不起作用的,可能脚本没有打开执行权限,导致没办法执行。我就是这样的情况。

cd 到 .git/hooks 目录下,执行 chmod 777 命令即可,如:

1
chmod 777 commit-msg

由于每个团队的需求不同,就不再赘述。

我已经将 commit-msg 代码上传到 GitHub 中,感兴趣的同学可以看一看具体实现。

这一次只是很简单的对 Git Hook 的实践,它还有更多可以使用的场景,更多具体的情况还需要具体对待。

参考

自定义 Git - Git 钩子

git hook实践心得