企业项目管理、ORK、研发管理与敏捷开发工具平台

网站首页 > 精选文章 正文

Gitlab+P3C-PMD(阿里云插件)标准化你团队的代码和提交信息

wudianyun 2025-01-29 18:23:13 精选文章 19 ℃

背景简介

项目越来越大,团队越来越大,代码越来越多,然后就是各种五花八门的代码格式、代码规范。要做到代码规范我们可以采取代码交叉Review、IDEA/Eclipse安装插件自检、以及代码管理服务端做校验,但是自查基本上相当于不查,所以必须有一种强制的手段,不折不扣的执行代码规范,不符合规范的无法提交到代码仓库,从而杜绝了不符合规范的代码产生。同时,团队内的代码提交信息也需要遵循一定的格式,一并做校验。

gitlab 服务端hooks机制

利用gitlab的 (钩子)hooks机制,关于钩子有客户端、服务端的,本文仅描述服务端的钩子,更多关于钩子的资料参考:

https://blog.csdn.net/allensandy/article/details/121422607

gitlab服务端会有三个主要钩子:

  1. pre-receive(处理客户端push动作时最先被调用的脚本,以非0值退出拒绝Push,可以用来做注释标准化、代码标准化等);
  2. update(与pre-receive功能类似,为每个准备更新的分支各运行一次);
  3. post-receive(是在push之后执行的脚本,可以用来调用后续的持续集成、发邮件通知等)。

而本文的用到的做代码规则校验的就是pre-receive的钩子,通过此钩子执行java –cp命令调用aliyun p3c-pmd的规则校验,然后检查如不符合规范返回错误信息到客户端(idea,git bash等)。

P3C-PMD

P3C阿里云的插件,github地址:https://github.com/alibaba/p3c

此处我们用到p3c-pmd插件,需要打包成jar文件,以便gitlab服务端使用,下载源码之后,直接打包(gradlemaven均可),得到 p3c-pmd-2.1.1-jar-with-dependencies.jar ,后面备用;已经打包好的可以直接下载使用:https://download.csdn.net/download/TangKai_java/86539113

代码提交信息格式

提交信息即 git commit message,在常规开发中,可能很多人会忽略提交信息的重要性,对于提交信息的内容会相当随意地进行输入,其实这样在很大的程度上会影响后续的维护和必要情况下的故障的追踪。所以,在此我们需要对提交信息做一个初步规范。

首先,提交信息必须格式化,即提交信息需要按照一定的格式来撰写,当团队中的所有成员都遵循一定的格式的时候,团队的其他成员可以非常方便的阅读内容。且不允许多个task一个提交,可以一个task完整提交,也可以局部提交,但是不允许跨task提交,每次提交不能让项目跑不起来,养成每日多次提交的习惯,我们约定提交信息的格式为:(任务类型后的为英文冒号!!!)

{{Task #}} // 项目管理软件中的任务 ID

{{任务类型 (feat/fix/docs/style/refactor/perf/test/build/chore/revert/merge)}}:{{任务描述}}

再者,我们可以通过项目管理软件中自动从提交信息中完成和任务的关联,提高大家的工作效率。

针对日常开发中可能存在一些并没有实际task的零碎的工作,引入了NOTASK标识,即在提交时备注信息的地方原本需要填入task编号的地方填入NOTASK,用以和普通开发任务进行区分,如:

NOTASK // 没有实际任务情况下的细小改动

{{任务类型 (feat/fix/docs/style/refactor/perf/test/build/chore/revert/merge)}}: {{任务描述}}

任务分类:

  • feat:新增功能
  • fix:bug修复
  • docs:仅仅修改了文档,比如README,CHANGELOG等等
  • style:代码格式改变(修改空白字符,格式缩进,补全缺失的分号等,没有改代码的逻辑)
  • refactor:重构代码(没有新增功能或bug修复)
  • perf:优化,比如:提升性能、体验、算法等
  • test:增加/修改测试用例,包括单元测试、集成测试等
  • build:改变了build工具
  • revert:还原先前提交的更改
  • chore:改变构建流程或者增加依赖库、工具等
  • merge:代码合并
  • ci:与CI(持续集成)有关的改动

配置钩子(所有项目生效,如只需针对单个项目,请自行看网上教程)

1、gitlab所在服务器创建目录:/opt/gitlab/embedded/service/gitlab-shell/hooks/pre-receive.d

2、将 p3c-pmd-2.1.1-jar-with-dependencies.jar上传到该目录;

3、在该目录新建 pre-receive 文件,文件内容如下:

#!/bin/bash

##脚本提供功能:Commit提交的Message和代码规范是否符合统一规范
##分三个部分:
# 1.变量定义部分
# 2.校验部分:注释校验&代码分析
# 3.初始化入口
## 校验流程:
# 1.先做提交注释校验,校验的规则:是否已${TYPE_LIST}定义的开头,且内容长度是否大于	${COMMIT_MESSAGE_MIN_LENGTH}
# 2.如果是master分之,修改了pom文件还会校验是否存在snapshot版本的jar
# 3.最后代码规范校验
## (单个项目校验)文件放置目录
# 1./var/opt/gitlab/git-data/repositories/@hashed/xx/xx/xx.git或者/var/opt/gitlab/git-data/repositories/${group}/${project_name}.git/
# 2.创建custom_hooks目录
# 3.在custom_hooks目录下创建pre-receive.d目录,并在pre-receive.d目下创建pre-receive文件,并保持776可执行权限,且保持该文件权限:chown git:git pre-receive 以及阿里云的p3c-pmd的jar包权限
# 4.给chown -R git:git custom_hooks
# 5.官方文档说明:https://docs.gitlab.com/ee/administration/custom_hooks.html#setup

# 
# @date 2022-06-23
# @description 在服务端接收提交文件时检查备注规范以及代码规范,如不符合规范会被退回


####### 初始化变量部分 #########

## 定义java_home变量 需要修改你配置的java_home
JAVA_HOME=/etc/alternatives/java_sdk_1.8.0
## 是否开启commit message的校验:0是,1否
CHECK_COMMIT_MESSAGE_ON=0
## 是否开启代码检查:0是,1否
CHECK_CODE_RULE_ON=0
## 是否校验master上的pom文件是否包含snapshot:0是,1否
CHECK_MASTER_POM_SNAPSHOT_ON=1
## 注释内容最小长度,默认20
COMMIT_MESSAGE_MIN_LENGTH=40
### 代码校验规则:0使用阿里云P3C规则,1使用checkStyle
CODE_RULE_TYPE=0

export PATH=$JAVA_HOME/bin:$PATH

# 检测结果国际化语言:en、zh
PMD_LANGUAGE=zh
# 文件字符编码
FILE_ENCODING=utf-8

## 定义提交开头类型字符规则
## e.g: fix:测试提交bug修复,Bug编号#12
TYPE_LIST=(
     'feat:'   #新功能feature
     'fix:'  #修补bug
     'docs:'  #文档
     'style:' #格式化,不影响代码运行的变动
     'refactor:' #重构
     'perf:'  #性能优化
     'test:'  #增加测试
     'chore:'  #构建过程或辅助工具的变动
     'build:'  #改变了build工具
     'revert:'  #构建过程或辅助工具的变动
     'merge:'  #代码合并
     'chore:'  #构建过程或辅助工具的变动
     #'[ci skip]'  #忽略校验
)

## 获取当前路径
BASE_PATH=$(cd `dirname $0`; pwd)
#echo 'BASE_PATH: '$BASE_PATH

#定义和组装校验规则,拼接头规则,第一行必须是任务编号,只允许:大写字母、数字、中划线、下划线 外加回车换行,另外的行必须以上面定义的标记开头
declare -a regex_list
arrLen=${#TYPE_LIST[@]}
for ((i=0;i<$arrLen;i++)) do
	regex_list[i]='^[A-Z0-9_-]{4,}[ ]{1,}'${TYPE_LIST[i]}
done
#ci相关的提交自动跳过验证,此处暂时屏蔽
#regex_list[$arrLen+1]='^[A-Z0-9_-]{4,}[\s].*[ci skip]:'
#echo "reg_list=== "${regex_list[@]}
separator="|"
## 合并成一个完整的正则表达式
regex="$( printf "${separator}%s" "${regex_list[@]}" )"
#echo "type regex: "$regex
## 去除头部的 |
regex=${regex:${#separator}}
#echo "regex: "$regex

## 定义注释出错提示信息
tips_msg="$( printf "${separator}%s" "${TYPE_LIST[@]}" )"
tips_msg=${tips_msg:${#separator}}
####### 初始化变量部分 #########

####### 校验部分:注释校验&代码分析###########
## 校验commit message
validate_commit_message()
{
	 oldrev=$(git rev-parse $1)
	newrev=$(git rev-parse $2)
	refname="$3"
	#echo 'Old version: '$oldrev
	#echo 'New version: '$newrev
	#echo 'Branch: '$refname

	## git 命令
	#GITCMD="git"
	## 按时间倒序列出 commit  找出两个版本之间差异的版本号集合  oldrev~newrev
	commitList=`git rev-list $oldrev..$newrev`
	#echo 'commitList: '$commitList

	split=($commitList)
	#echo 'split: '$split

 	# 遍历数组
	for s in ${split[@]}
	do
  		#echo “$s”
  		#通过版本号获取仓库中对象实体的类型、大小和内容的信息
      #比如提交人、作者、邮件、提交时间、提交内容等
      currentContent=`git cat-file commit $s`
      #echo 'Commit obj: '$currentContent
      #echo 's==: '$s
      #获取提交内容
      msg=`git cat-file commit $s | sed '1,/^$/d'`
      echo 'commitid:'$s',msg: '$msg

    	## merge合并分之直接放行
   		if [[ $msg == *"Merge branch"* ]] || [[ $msg == *"Merge remote-tracking branch"* ]]; then
    		echo "Merge branch...skip the checking"
    	else  
		    ## 做内容校验
		    match=`echo $msg | grep -nE "(${regex})"`
		    #echo 'Match result: '$match

	    	## 找到匹配说明是符合规范的
		    if [ "${match}" != "" ]; then
      			## 校验注释长度
      			msg_length=${#msg}
      			#echo "Msg length: ${msg_length}"
            if [[ ${msg_length} -lt ${COMMIT_MESSAGE_MIN_LENGTH} ]]; then
              #echo -e "Error: Commit message should be bigger than ${COMMIT_MESSAGE_MIN_LENGTH} and current commit message length: ${msg_length}"
                  echo -e "错误: 提交备注信息的长度应该大于 ${COMMIT_MESSAGE_MIN_LENGTH} ,本次提交的备注长度为: ${msg_length} ,请修改后再提交!"
              exit 1
            fi

            ### 找到匹配内容做相应处理,如fix ,校验pom文件等
            #if [[ "${match}" =~ "fix:" ]]; then
              ## 如果是修补bug,规范有点获取到fix中的ID,然后调用禅道对外的API关闭,其他场景类似
            #fi

            # 是否开启校验和main分支
            isMaster=$(echo $refname | grep "main#34;)
            if [ $CHECK_MASTER_POM_SNAPSHOT_ON == 0 ] && [ -n "$isMaster" ]; then
              # 如果是main分之,并且pom文件发生了变更,判断pom文件是否含有sonapshot的引用
              pomfile=`git diff --name-only ${oldrev} ${newrev} | grep -e "pom\.xml"`
              if [[ "${pomfile}" != "" ]]; then
                #echo $pomfile
                ## 获取pom文件更新的内容
                pomcontent=`git show $newrev:$pomfile`
                #echo $pomcontent
                ## 校验pom文件是否包含snapshot版本
                if [[ $pomcontent =~ 'SNAPSHOT' ]]; then
                  #echo -e "Error: Snapshot version cannot exist in main branch!"
                  echo -e "错误: main 分支中不能存在快照版本!"
                  exit 1
                fi
              fi
            fi

            ## 其他操作
            #echo "Commit comments validate Success!"
            echo "提交备注格式检查通过!"
        else
            #echo -e "Error: Commit comments message should be started with [${tips_msg}]..."
            echo -e "错误: 提交备注格式错误,请修改后再提交,具体规范请参考团队规范文档"
            exit 1
        fi
      
	    fi
done
}

## 代码校验
validate_code_rules()
{
echo 'Start code analysis!'
oldrev=$(git rev-parse $1)
newrev=$(git rev-parse $2)
refname="$3"
#echo 'Old version: '$oldrev
#echo 'New version: '$newrev
#echo 'Branch: '$refname

TEMPDIR=$BASE_PATH/"tmp"

FILES=`git diff --name-only ${oldrev} ${newrev}  | grep -e "\.java#34;`

committer=`git log -1 $newrev --pretty=%ce`

if [ -n "$FILES" ]; then
  for FILE in ${FILES}; do
      mkdir -p "${TEMPDIR}/`dirname ${FILE}`" >/dev/null
      git show $newrev:$FILE > ${TEMPDIR}/${FILE}
  done;

  MAIN_JAVA_PATH=$TEMPDIR'/src/main'
  #echo 'Temp update files path: '$MAIN_JAVA_PATH

  #FILES_TO_CHECK=`find $MAIN_JAVA_PATH -name '*.java'`

  #echo 'Check files:'${FILES_TO_CHECK}
  echo 'Aliyun p3c-pmd check starting.....'

  #echo 'Current shell Path:' $BASE_PATH
  #echo 'JAVA_HOME:' $JAVA_HOME
  #echo 'Root directory for java sources: '$MAIN_JAVA_PATH

  #管理员提交暂时过滤掉检查
  if [ $committer != 'tangkai2009fei@163.com' ]; then
      if [[ $CODE_RULE_TYPE == 0 ]]; then
        ## 需要阿里云P3C的插件包p3c-pmd-2.0.0.jar与该脚本在同级目录下
        echo 'Code analysis for Aliyun-p3c..'
        #$JAVA_HOME/bin/java -Dpmd.language=en -cp $BASE_PATH/p3c-pmd-2.0.0.jar net.sourceforge.pmd.PMD -d $MAIN_JAVA_PATH -R rulesets/java/ali-comment.xml,rulesets/java/ali-concurrent.xml,rulesets/java/ali-constant.xml,rulesets/java/ali-exception.xml,rulesets/java/ali-flowcontrol.xml,rulesets/java/ali-naming.xml,rulesets/java/ali-oop.xml,rulesets/java/ali-orm.xml,rulesets/java/ali-other.xml,rulesets/java/ali-set.xml -f text
        $JAVA_HOME/bin/java -Dfile.encoding=$FILE_ENCODING -Dpmd.language=$PMD_LANGUAGE -cp $BASE_PATH/p3c-pmd-2.1.1-jar-with-dependencies.jar net.sourceforge.pmd.PMD -d $TEMPDIR -R rulesets/java/ali-comment.xml,rulesets/java/ali-concurrent.xml,rulesets/java/ali-constant.xml,rulesets/java/ali-exception.xml,rulesets/java/ali-flowcontrol.xml,rulesets/java/ali-naming.xml,rulesets/java/ali-oop.xml,rulesets/java/ali-other.xml,rulesets/java/ali-set.xml -f text
        RESULT=$?
        #echo $RESULT
        if [ $RESULT -gt 0 ]; then
          echo  "代码质量检测不通过,请修改后重试,请参考规范文档:https://github.com/alibaba/p3c"
          rm -rf $TEMPDIR
          exit 1
        fi

      echo 'Code analysis success!'
  else
      echo "在白名单中,临时允许通过"
  fi  
  
else
	echo 'No java code, analysis end!'
fi


rm -rf $TEMPDIR
}
####### 校验部分:注释校验&代码分析###########

####### 执行入口###########
pre_receive()
{
#commit message 校验
if [[ $CHECK_COMMIT_MESSAGE_ON == 0 ]]; then
   validate_commit_message $1 $2 $3
fi

#代码规则检查
if [[ $CHECK_CODE_RULE_ON == 0 ]]; then
   validate_code_rules $1 $2 $3
fi
}

# update hook触发会带参数执行if逻辑
# hooks脚本触发无参数执行else逻辑
if [ -n "$1" -a -n "$2" -a -n "$3" ]; then
	# Output to the terminal in command line mode - if someone wanted to
	# resend an email; they could redirect the output to sendmail
	# themselves
	pre_receive $2 $3 $1
	#echo $1'+'$2'+'$3
else
while read oldrev newrev refname
do
   pre_receive $oldrev $newrev $refname
   #echo $oldrev' '$newrev' '$refname
done
fi
####### 执行入口###########
exit 0

4、给 pre-receive 文件增加执行权限

chmod +777 pre-receive

5、客户端提交代码进行测试

最近发表
标签列表