之前寫過一篇 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 https://bbs.csdn.net/topics/392558612
#加載環境變量, 讓下面的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支持