git 服务端钩子做代码检查
需求分析在代码修改后可以对代码进行检查,比如代码规范检查、代码构建、单元测试等。我们需要禁止成员推送不符合规范的代码到服务端。Git 钩子能在特定的重要动作发生时触发自定义脚本。钩子分为客户端和服务器端两类。使用客服端钩子可以在commit时,对本地代码进行检查,可以参考使用git钩子对提交代码进行检查。考虑到客服端钩子需要每个成员单独配置,或者说不是一种强制手段,无法避免某成员跳过钩子,强制pu
需求分析
在代码修改后可以对代码进行检查,比如代码规范检查、代码构建、单元测试等。我们需要禁止成员推送不符合规范的代码到服务端。
Git 钩子能在特定的重要动作发生时触发自定义脚本。钩子分为客户端和服务器端两类。使用客服端钩子可以在commit时,对本地代码进行检查,可以参考:使用git钩子对提交代码进行检查。考虑到客服端钩子需要每个成员单独配置,或者说不是一种强制手段,无法避免某成员跳过钩子,强制push代码到远程的行为,我们将主要研究如何使用服务端钩子来拦截非法push的问题。
服务端钩子介绍
钩子都被存储在 Git 目录下的 hooks
子目录中。 也即绝大部分项目中的 .git/hooks
。
下图是使用gitolite搭建的git服务端,里面有一些示例钩子。
如需使钩子生效,需把文件.sample后缀去掉,如update.sample改成update,并确保文件有可执行权限。钩子脚本可以使用Shell、Python、Perl等脚本实现。这些脚本会在特定的动作发生时被调用,还会传递一些重要的参数,方便后续使用。
各个钩子的介绍
pre-receive
处理来自客户端的推送操作时,最先被调用的脚本是 pre-receive
。 它从标准输入获取一系列被推送的引用。如果它以非零值退出,所有的推送内容都不会被接受。 你可以用这个钩子阻止对引用进行非快进(non-fast-forward)的更新,或者对该推送所修改的所有引用和文件进行访问控制。
update
update
脚本和 pre-receive
脚本十分类似,不同之处在于它会为每一个准备更新的分支各运行一次。 假如推送者同时向多个分支推送内容,pre-receive
只运行一次,相比之下 update
则会为每一个被推送的分支各运行一次。 它不会从标准输入读取内容,而是接受三个参数:引用的名字(分支),推送前的引用指向的内容的 SHA-1 值,以及用户准备推送的内容的 SHA-1 值。 如果 update 脚本以非零值退出,只有相应的那一个引用会被拒绝;其余的依然会被更新。
post-receive
post-receive
挂钩在整个过程完结以后运行,可以用来更新其他系统服务或者通知用户。 它接受与 pre-receive
相同的标准输入数据。 它的用途包括给某个邮件列表发信,通知持续集成(continous integration)的服务器, 或者更新问题追踪系统(ticket-tracking system) —— 甚至可以通过分析提交信息来决定某个问题(ticket)是否应该被开启,修改或者关闭。 该脚本无法终止推送进程,不过客户端在它结束运行之前将保持连接状态, 所以如果你想做其他操作需谨慎使用它,因为它将耗费你很长的一段时间。
实现需求
从钩子的介绍来看,我们需要拦截推送,可以在pre-receive和update中通过返回值来标示是否需要拦截。
服务端钩子会接受到三个参数:
-
被推送的引用的名字
-
推送前分支的修订版本(revision)
-
用户准备推送的修订版本(revision)
我们使用pre-receive
来实现,可以使用shell脚本获取到传递的进来的参数,可以参考gitlab pre-receive钩子。
# pre-receive
#!/bin/bash
while read oldVersion newVersion branch; do
echo ${oldVersion}
echo ${newVersion}
echo ${branch}
done
此时我们虽然可以拿到最新的commit id,也就是最新提交的ref,但是如何把代码取出来呢?
在服务端仓库中,不能直接拿到提交文件,而是以git object存储的。
可以使用git diff 拿到修改内容的差异文件。但是当我们需要对整个代码做检查时,需要的不仅是差异文件,可以在服务器上再clone出一套代码存放在build_code文件夹中,再把本次更新的差异应用到clone出来的代码上。
git@xxx:~/build_code$ git clone ~/repositories/testing.git
Cloning into 'testing'...
done.
git@xxx:~/build_code$ cd testing/
git@xxx:~/build_code/testing$ git branch
* master
git@xxx:~/build_code/testing$
注意:clone代码后,需确认当前代码的分支,这里默认使用master,如需改成其它分支,需checkout到对应的分支。
由于git diff 生成的文件不能记录新增和删除的文件,这里我们可以用尝试使用git patch打包,然后在clone的代码端apply patch。
# pre-receive
#!/bin/bash
while read oldVersion newVersion branch; do
# 只对master分支做检查
result=$(echo ${branch}| grep "master")
if [ "$result" != "" ];then
echo 开始检查代码
# 生成patch
git format-patch $oldVersion..$newVersion
cd ~/build_code/testing/
unset GIT_DIR
unset GIT_QUARANTINE_PATH
git reset HEAD --hard
# 应用patch
git apply ~/repositories/testing.git/*.patch
# 清空patch
rm -rf ~/repositories/testing.git/*.patch
#开始验证代码
bash check_code.sh # 自定义的检查脚本
if [ $? -ne 0 ]; then
echo 检查代码失败
exit 1
fi
echo 检查代码成功
fi
done
到这里,我们就做到了对push到远程的代码进行检查检查的功能,若检查失败会exit 1,使成员在客服端上push时,操作失败。
后续问题
简单测试使用,并未发现异常,但是当客户端push的代码和当前ref差异较大时(如有分支分叉情况),apply patch会失败,导致检查的代码并不是最新代码。所以我们必须改变思路,放弃使用patch的方式。
修改思路
通过git diff 获取修改、删除、增加、重命名文件的名字,然后在clone到build_code中的代码中,重现对应的操作。
查看修改文件的记录git diff --name-status $oldVersion $newVersion,可参考Git pre-commit hook : changed/added files。
修改后的代码:
# pre-receive
#!/bin/bash
mkdir_and_cp_file(){
result=$(echo $1 | grep "/")
if [ "$result" != "" ];then
mkdir -p $2/${1%/*}
echo mkdir_and_cp_file:$1
fi
# tempDir=temp
# mkdir -p $tempDir
# git archive --format tar.gz --output "$tempDir/output.tar.gz" $newVersion $1
# tar zxf $tempDir/output.tar.gz -C $tempDir
# cp $tempDir/$1 $2/$1
# rm -rf $tempDir
git show $newVersion:$1 > $2/$1
}
while read oldVersion newVersion branch; do
# 只对master分支做检查
result=$(echo ${branch}| grep "master")
if [ "$result" != "" ];then
echo 开始检查代码
desPath=~/build_code/testing/
# echo -e "\ncp file"
gitDiff=`git diff --name-status $oldVersion $newVersion | awk '$1 == "M" { print $2 }'`
for var in ${gitDiff}; do
mkdir_and_cp_file ${var} $desPath
done
# echo -e "\nmkdir and add cp file"
gitDiff=`git diff --name-status $oldVersion $newVersion | awk '$1 == "A" { print $2 }'`
for var in ${gitDiff}; do
mkdir_and_cp_file ${var} $desPath
done
gitDiff=`git diff --name-status $oldVersion $newVersion | awk '$1 ~ "R" { print $3}'`
for var in ${gitDiff}; do
mkdir_and_cp_file ${var} $desPath
done
# echo -e "\ndelete file"
gitDiff=`git diff --name-status $oldVersion $newVersion | awk '$1 == "D" { print $2 }'`
for var in ${gitDiff}; do
rm $desPath/${var}
done
gitDiff=`git diff --name-status $oldVersion $newVersion | awk '$1 ~ "R" { print $2}'`
for var in ${gitDiff}; do
rm $desPath/${var}
done
cd $desPath
unset GIT_DIR
unset GIT_QUARANTINE_PATH
#开始验证代码
bash check_code.sh # 自定义的检查脚本
if [ $? -ne 0 ]; then
echo 检查代码失败
exit 1
fi
echo 检查代码成功
fi
done
另外需要注意,build_code中clone的代码,在pre-receive中检查后,需要在代码确认和入后(也就是post-receive后
)将此份代码同步到最新状态,以便在下次使用git diff重现对应操作时,代码是干净且最新状态。
以上是使用服务端钩子检查代码的一种方式,有更好的方法,欢迎提出。
更多推荐
所有评论(0)