Git Hook 的小实践

先贴上代码

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
#!/usr/bin/env ruby
# encoding: UTF-8

$refname = ARGV[0]
$oldrev = ARGV[1]
$newrev = ARGV[2]


$test_domain_regex = /\.test\.51offer\.com/

def check_test_domain_in_template

change_files = `git rev-list --objects #{$oldrev}..#{$newrev} | git cat-file --batch-check='%(rest)' | egrep '\.(jsp|vm|html)$'`.split("\n")

change_files.each do |path|

file_content = `git show #{$newrev}:#{path}`

if $test_domain_regex.match(file_content)

puts "[POLICY] 不允许在 vm、jsp、html 等文件中包含 'test.51offer.com' 这样的字符 > #{path}"
exit 1

end
end
end

check_test_domain_in_template

需求背景

我们发生过几次线上事故,就是带有 .test.51offer.com 的内容发布到正式环境,在内网很难主动发现这样的问题,所以需要对提交的代码做一些检查,自然的想到了 Git Hook

什么是 Git Hook

Hook 指的是钩子,这里可以理解成某种事件所触发的程序。比如:你在本地做 commit 或者 pull 时都可以触发 Git 中相应的 Hook 脚本。Hook 脚本又分客户端和服务端。它们响应不同的事件。 Hook脚本可以使用 Ruby, Python 和 Shell 来编写。因为分发的问题,解决上面的需求我选择了服务端 Hook,并把代码写在 update 事件中,希望仓库的分支在被更新时触发检查。

Git Hook 的详情介绍可以查看下面的地址:
https://git-scm.com/book/zh/v1/%E8%87%AA%E5%AE%9A%E4%B9%89-Git-Git%E6%8C%82%E9%92%A9

技术关键点

最关键的是要在客户端在一次 push 中所有 commit 对象中找到你要的文件。连 Git 基本命令还在熟悉的我只能请教 stackoverflow.com 了。原文地址:http://stackoverflow.com/questions/1595631/how-to-get-a-list-of-all-blobs-in-a-repository-in-git

代码如下:

1
$ git rev-list --objects --all | git cat-file --batch-check='%(objectname) %(objecttype) %(rest)' | grep '^[^ ]* blob' | cut -d" " -f1,3-

简化成我需要的:

1
git rev-list --objects #{$oldrev}..#{$newrev} | git cat-file --batch-check='%(rest)' | egrep '\.(jsp|vm|html)$'

使用了 rev-listcat-file 两个 git 的底层命令,含义就是找出两个 commit id 之间所有类型的提交对象(commit,tree,blob),并一一查看他们,显示出他们对应的文件路径(除 blod 对象,其它类型是没有路径的),然后只拿带有那几项扩展名的路径。

有了这些路径,我只需要使用 git show 命令从最新版本中捞出这些文件内容就可。

这里一个细节:grep 需要加 -E 才能支持正则表达式,否则是使用通配符,egrep 是直接使用正则表达式来做匹配的。

如何部署

因为我们使用了 Git Lab ,我从官方上找来了安装指南:

http://doc.gitlab.com/ce/hooks/custom_hooks.html

setup 比较简单,创建后如下图:

一个 git 仓库目录中的文件

需要给 custom_hooks 目录及文件给于 git 用户执行权限:

1
2
3
4
5
mkdir custom_hooks
chmod 777 custom_hooks
## 上传 update 文件
chmod 775 custom/update
chown git.git custom_hooks/ -R

在本地提交验证 hook 是否工作

执行 push 检查 Hook 有效性

以上方案还是只在一个仓库中部署,需要所有仓库布置这样的 Hook 如何处理?

第一图上已经给出答案:/opt/gitlab/embedded/service/gitlab-shell/hooks 这里是 Gitlab 自带的一些 hook 如下图:

查看gitlab hook

显然使用 require_relative 就可以把我们要加入的内容塞进去。

至此,我们用 hook 处理了这样的需求。暂时只部署在下面的仓库中,大家可以玩一下: git@gitlab.51offer.inner:libo/pack-test.git

后记

整理了一个比较适合在 gitlib 中使用的版本,这里再次遇到一个编码问题,为了统一源码编码格式和读取外部文件的编码格式引用了 nkf 模块。

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
#!/usr/bin/env ruby
# encoding: UTF-8

require 'nkf'

class Git51offerCustomHook

def checkTestDomainOnUpate(oldrev, newrev)

testDomainRegex = /\.test\.51offer\.com/

changeFiles = `git rev-list --objects #{oldrev}..#{newrev} | git cat-file --batch-check='%(rest)' | egrep '\.(jsp|vm)$'`.split("\n")

perfect = true

changeFiles.each do |path|

fileContent = `git show #{newrev}:#{path}`
fileContent = NKF.nkf("-w", fileContent)

if testDomainRegex.match(fileContent)
puts "[POLICY] 不允许在 vm、jsp 等文件中包含 'test.51offer.com' 这样的字符 > #{path}"
perfect = false
break
end

end

return perfect

end

end

注意

update hook 只能对已经存在的文件进行处理,新提交的文件不会触发

update

新版的gitlab已经使用自定义 update.d 文件来支持自定义hook了,这样的好处是,即使更新 gitlab 你的自定义文件还是存在的,不需要手动从系统全局的 hook 文件中添加