之前写过一篇 SVN服务端集成Checkstyle实现代码自动静态检查。最近部门打算试点GIT作为SCM,因此需要将相应的服务端检测功能进行一次迁移,于是就有了本篇博文。
1. 概述
对于Git通过Hook实现静态代码检测,大致分为两个方向:
- 借助Client-Side-Hook来实现。此方法对应于研发人员工作机上的
${PROJECT_ROOT}/.git/hooks/pre-commit
脚本实现。 - 借助Server-Side-Hook来实现。此方法对应于Git服务端的
/var/opt/gitlab/git-data/repositories/<group>/<project>.git/custom_hooks/pre-receive
脚本。
相比较之下,两种实现方式之中,参考资料丰富度层面前一种胜出,但笔者所在部门希望尽可能地促使研发人员将问题原地解决,因此我们最终决定求助于第二种方案 —— Server-Side-Hook。
2. Server-Side-Hook
关于Server-Side-Hook
,GitLab - Server_Hooks已经有了详尽的介绍,这里我们不出意外地将目光集中到了 pre-receive
。
2.1 准备检测工具
准备CheckStyle/PMD相关JAR和XML规则文件。
将下载的JAR和对应的XML推送到服务器对应位置,笔者这里是直接放到了对应项目根目录下:
/var/opt/gitlab/git-data/repositories/<group>/<project>.git/checkstyle
/var/opt/gitlab/git-data/repositories/<group>/<project>.git/pmd-bin-6.20.0
2.2 创建pre-receive脚本文件
首先我们需要在对应位置创建一个空白的pre-receive文件(注意该文件没有后缀)。
# 切换到服务端项目目录下
cd /var/opt/gitlab/git-data/repositories/<group>/<project>.git
mkdir custom_hooks
cd custom_hooks
vi pre-receive
接下来就是重头戏。
2.3 pre-receive脚本内容
注意这里的脚本实现方式相当粗暴,因为笔者打算借鉴自其它产品来对此类功能做一次封装,因此这里的要求只是生效即可。
#!/usr/bin/env bash
# From
#加载环境变量, 让下面的java指令生效
source /etc/profile
# 创建临时目录
TMP_DIR=$(mktemp -d)
# CheckStyle
CHECKSTYLE_BASE_PATH="/var/opt/gitlab/git-data/repositories/<group>/<project>.git/checkstyle"
CHECKSTYLE_JAR_FILE="$CHECKSTYLE_BASE_PATH/checkstyle-8.17-all.jar"
CHECKSTYLE_XML_FILE="$CHECKSTYLE_BASE_PATH/checkstyle-OLD.xml"
# PMD
PMD_BASE_PATH="/var/opt/gitlab/git-data/repositories/<group>/<project>.git/pmd-bin-6.20.0"
PMD_CLASSPATH="$PMD_BASE_PATH/lib/*"
PMD_XML_FILE="$PMD_BASE_PATH/kanq-pmd-bestpratice-OLD.xml"
# 错误数
errors_count=0
# 空hash
EMPTY_REF='0000000000000000000000000000000000000000'
while read oldrev newrev ref
do
# 当push新分支的时候oldrev会不存在,删除时newrev就不存在
if [[ $oldrev != $EMPTY_REF && $newrev != $EMPTY_REF ]]; then
# 被检查了的文件数
checked_file_count=0
# 找出哪些文件被更新了
for line in $(git diff-tree -r $oldrev..$newrev | grep -oP '.*\.(java)' | awk '{print $5$6}')
do
# 文件状态. 本地查询命令: git status --help
# D: deleted
# A: added
# M: modified
status=$(echo $line | grep -o '^.')
# 不检查被删除的文件
if [[ $status == 'D' ]]; then
continue
fi
# 文件名
file=$(echo $line | sed 's/^.//')
# 为文件创建目录
mkdir -p $(dirname $TMP_DIR/$file)
# 保存文件内容
git show $newrev:$file > $TMP_DIR/$file
output=$(java -jar $CHECKSTYLE_JAR_FILE -c $CHECKSTYLE_XML_FILE $TMP_DIR/$file)
outputPMD=$(java -classpath "$PMD_CLASSPATH" net.sourceforge.pmd.PMD -R $PMD_XML_FILE -dir $TMP_DIR)
# 以下命名管道是为了将形如"Checkstyle ends with 89 errors."字符串中的 89 找出来
error=$(echo $output | grep -oP '([0-9]+) errors' | grep -oP '[0-9]+')
errorPMD=$(echo $outputPMD | grep -oP '[0-9]{2,}' | wc -l)
if [[ $errorPMD || $error ]]; then
msg="${file}: "
if [[ $errorPMD > 0 ]]; then
let "warnings_count = warnings_count + 1"
echo $outputPMD
rm -rf $TMP_DIR
exit 1
fi
if [[ $error > 0 ]]; then
msg="$msg has checkstyle [${error}] errors"
echo $output
rm -rf $TMP_DIR
exit 1
let "errors_count = errors_count + 1"
fi
echo -e " $msg"
fi
let "checked_file_count = checked_file_count + 1";
done
if [[ $checked_file_count == 0 ]]; then
echo -e " No file was checked."
elif [[ $warnings_count == 0 && $errors_count == 0 ]]; then
echo -e "'Congratulations!!! no error, no warning'"
elif [[ $errors_count == 0 ]]; then
echo -e " Good job, no errors were found!!!"
fi
fi
done
# 删除临时目录
rm -rf $TMP_DIR
if [[ $warnings_count > 0 || $errors_count > 0 ]]; then
echo 'check fail. please check yourself local'
exit 1
fi
exit 0
3. 效果
错误提示不如 SVN集成PMD实现代码自动静态检查 有条理,但有个消息是"错误提示的行数正常了"。
4. 建议
- 可以考虑使用global server hook,然后针对特例再作 single repository server hook。
- 鉴于SVN和GIT设计思路的不同,在使用GIT时候如果出现检测失败,修复之后一定要记得再commit一次,然后再次push。其它方式还有建立Client Site Hook - pre commit 以及 取消本地commit。
5. Links
- SVN集成Checkstyle实现代码自动静态检查
- Atlassian - Bitbucket - Server-Side-Hooks 笔者个人认为相较于下面的文档,Atlassian文档至少在这一点上更为丰富。
- GitLab - Server_Hooks
- Git - Server-Side-Hooks
- Git 使用 pre commit 实现代码自动静态检查 //需要BASH支持
- CheckStyle Git hook 简单配置 // 需要Python支持