模块化 PR 检查

This commit is contained in:
Wenxuan Zhao 2018-10-23 20:37:11 -07:00
parent 6a57b4dcc9
commit a31b145df9
No known key found for this signature in database
GPG Key ID: 0B45B13F10587A57
10 changed files with 229 additions and 59 deletions

View File

@ -1,3 +1,3 @@
language: c
script:
- make -s check
- sh ./scripts/check.sh

View File

@ -1,58 +0,0 @@
DIR_PATTERN := (news|talk|tech)
NAME_PATTERN := [0-9]{8} [a-zA-Z0-9_.,() -]*\.md
RULES := rule-source-added \
rule-translation-requested \
rule-translation-completed \
rule-translation-revised \
rule-translation-published
.PHONY: check match $(RULES)
CHANGE_FILE := /tmp/changes
check: $(CHANGE_FILE)
echo 'PR #$(TRAVIS_PULL_REQUEST) Changes:'
cat $(CHANGE_FILE)
echo
echo 'Check for rules...'
make -k $(RULES) 2>/dev/null | grep '^Rule Matched: '
$(CHANGE_FILE):
git --no-pager diff $(TRAVIS_BRANCH) origin/master --no-renames --name-status > $@
rule-source-added:
echo 'Unmatched Files:'
egrep -v '^A\s*"?sources/$(DIR_PATTERN)/$(NAME_PATTERN)"?' $(CHANGE_FILE) || true
echo '[End of Unmatched Files]'
[ $(shell egrep '^A\s*"?sources/$(DIR_PATTERN)/$(NAME_PATTERN)"?' $(CHANGE_FILE) | wc -l) -ge 1 ]
[ $(shell egrep -v '^A\s*"?sources/$(DIR_PATTERN)/$(NAME_PATTERN)"?' $(CHANGE_FILE) | wc -l) = 0 ]
echo 'Rule Matched: $(@)'
rule-translation-requested:
[ $(shell egrep '^M\s*"?sources/$(DIR_PATTERN)/$(NAME_PATTERN)"?' $(CHANGE_FILE) | wc -l) = 1 ]
[ $(shell cat $(CHANGE_FILE) | wc -l) = 1 ]
echo 'Rule Matched: $(@)'
rule-translation-completed:
[ $(shell egrep '^D\s*"?sources/$(DIR_PATTERN)/$(NAME_PATTERN)"?' $(CHANGE_FILE) | wc -l) = 1 ]
[ $(shell egrep '^A\s*"?translated/$(DIR_PATTERN)/$(NAME_PATTERN)"?' $(CHANGE_FILE) | wc -l) = 1 ]
[ $(shell cat $(CHANGE_FILE) | wc -l) = 2 ]
echo 'Rule Matched: $(@)'
rule-translation-revised:
[ $(shell egrep '^M\s*"?translated/$(DIR_PATTERN)/$(NAME_PATTERN)"?' $(CHANGE_FILE) | wc -l) = 1 ]
[ $(shell cat $(CHANGE_FILE) | wc -l) = 1 ]
echo 'Rule Matched: $(@)'
rule-translation-published:
[ $(shell egrep '^D\s*"?translated/$(DIR_PATTERN)/$(NAME_PATTERN)"?' $(CHANGE_FILE) | wc -l) = 1 ]
[ $(shell egrep '^A\s*"?published/$(NAME_PATTERN)' $(CHANGE_FILE) | wc -l) = 1 ]
[ $(shell cat $(CHANGE_FILE) | wc -l) = 2 ]
echo 'Rule Matched: $(@)'
badge:
mkdir -p build/badge
./lctt-scripts/show_status.sh -s published >build/badge/published.svg
./lctt-scripts/show_status.sh -s translated >build/badge/translated.svg
./lctt-scripts/show_status.sh -s translating >build/badge/translating.svg
./lctt-scripts/show_status.sh -s sources >build/badge/sources.svg

10
scripts/check.sh Normal file
View File

@ -0,0 +1,10 @@
#!/bin/bash
# PR 检查脚本
set -e
CHECK_DIR="$(dirname "$0")/check"
# sh "${CHECK_DIR}/check.sh" # 需要依赖,暂时禁用
sh "${CHECK_DIR}/info.sh"
sh "${CHECK_DIR}/collect.sh"
sh "${CHECK_DIR}/analyze.sh"
sh "${CHECK_DIR}/identify.sh"

43
scripts/check/analyze.sh Normal file
View File

@ -0,0 +1,43 @@
#!/bin/sh
# PR 文件变更分析
set -e
# 加载公用常量和函数
# shellcheck source=common.inc.sh
. "$(dirname "$0")/common.inc.sh"
################################################################################
# 读入:
# - /tmp/changes # 文件变更列表
# 写出:
# - /tmp/stats # 文件变更统计
################################################################################
# 执行分析并将统计输出到标准输出
do_analyze() {
cat /dev/null > /tmp/stats
OTHER_REGEX='^$'
for TYPE in 'SRC' 'TSL' 'PUB'; do
for STAT in 'A' 'M' 'D'; do
# 统计每个类别的每个操作
REGEX="$(get_operation_regex "$STAT" "$TYPE")"
OTHER_REGEX="${OTHER_REGEX}|${REGEX}"
eval "${TYPE}_${STAT}=\"\$(grep -Ec '$REGEX' /tmp/changes)\"" || true
eval echo "${TYPE}_${STAT}=\$${TYPE}_${STAT}"
done
done
# 统计其他操作
OTHER="$(grep -Evc "$OTHER_REGEX" /tmp/changes)" || true
echo "OTHER=$OTHER"
# 统计变更总数
TOTAL="$(wc -l < /tmp/changes )"
echo "TOTAL=$TOTAL"
}
echo "[分析] 统计文件变更……"
do_analyze > /tmp/stats
echo "[分析] 已写入统计结果:"
cat /tmp/stats

10
scripts/check/check.sh Normal file
View File

@ -0,0 +1,10 @@
#!/bin/bash
# 检查脚本状态
set -e
################################################################################
# 暂时仅供开发使用
################################################################################
shellcheck -e SC2034 -x mock/stats.sh "$(dirname "$0")"/*.sh \
&& echo '[检查] ShellCheck 通过'

32
scripts/check/collect.sh Normal file
View File

@ -0,0 +1,32 @@
#!/bin/bash
# PR 文件变更收集
set -e
################################################################################
# 读入:(无)
# 写出:
# - /tmp/changes # 文件变更列表
################################################################################
echo "[收集] 计算 PR 分支与目标分支的分叉点……"
TARGET_BRANCH="${TRAVIS_BRANCH:-master}"
echo "[收集] 目标分支设定为:${TARGET_BRANCH}"
MERGE_BASE="$(git merge-base "$TARGET_BRANCH" FETCH_HEAD)"
echo "[收集] 找到分叉节点:${MERGE_BASE}"
{
git log --oneline "${MERGE_BASE}..FETCH_HEAD" | grep -Eq '^绕过检查:' && {
touch /tmp/bypass
echo "[收集] 已标记为绕过检查项"
}
} || true
echo "[收集] 写出文件变更列表……"
git diff "$MERGE_BASE" FETCH_HEAD --no-renames --name-status > /tmp/changes
echo "[收集] 已写出文件变更列表:"
cat /tmp/changes
{ [ -z "$(cat /tmp/changes)" ] && echo "(无变更)"; } || true

View File

@ -0,0 +1,30 @@
#!/bin/sh
################################################################################
# 公用常量和函数
################################################################################
# 定义类别目录
export SRC_DIR='sources' # 未翻译
export TSL_DIR='translated' # 已翻译
export PUB_DIR='published' # 已发布
# 定义匹配规则
export CATE_PATTERN='(news|talk|tech)' # 类别
export FILE_PATTERN='[0-9]{8} [a-zA-Z0-9_.,() -]*\.md' # 文件名
# 用法get_operation_regex 状态 类型
#
# 状态为:
# - A添加
# - M修改
# - D删除
# 类型为:
# - SRC未翻译
# - TSL已翻译
# - PUB已发布
get_operation_regex() {
STAT="$1"
TYPE="$2"
echo "^${STAT}\\s+\"?$(eval echo "\$${TYPE}_DIR")/"
}

86
scripts/check/identify.sh Normal file
View File

@ -0,0 +1,86 @@
#!/bin/bash
# 匹配 PR 规则
set -e
################################################################################
# 读入:
# - /tmp/stats
# 写出:(无)
################################################################################
# 加载公用常量和函数
# shellcheck source=common.inc.sh
. "$(dirname "$0")/common.inc.sh"
echo "[匹配] 加载统计结果……"
# 加载统计结果
# shellcheck source=mock/stats.sh
. /tmp/stats
# 定义 PR 规则
# 绕过检查:绕过 PR 检查
rule_bypass_check() {
[ -f /tmp/bypass ] && echo "匹配规则:绕过检查"
}
# 添加原文:添加至少一篇原文
rule_source_added() {
[ "$SRC_A" -ge 1 ] \
&& [ "$TOTAL" -eq "$SRC_A" ] && echo "匹配规则:添加原文 ${SRC_A}"
}
# 申领翻译:只能申领一篇原文
rule_translation_requested() {
[ "$SRC_M" -eq 1 ] \
&& [ "$TOTAL" -eq 1 ] && echo "匹配规则:申领翻译"
}
# 提交译文:只能提交一篇译文
rule_translation_completed() {
[ "$SRC_D" -eq 1 ] && [ "$TSL_A" -eq 1 ] \
&& [ "$TOTAL" -eq 2 ] && echo "匹配规则:提交译文"
}
# 校对译文:只能校对一篇
rule_translation_revised() {
[ "$TSL_M" -eq 1 ] \
&& [ "$TOTAL" -eq 1 ] && echo "匹配规则:校对译文"
}
# 发布译文:发布多篇译文
rule_translation_published() {
[ "$TSL_D" -ge 1 ] && [ "$PUB_A" -ge 1 ] && [ "$TSL_D" -eq "$PUB_A" ] \
&& [ "$TOTAL" -eq $(("$TSL_D" + "$PUB_A")) ] \
&& echo "匹配规则:发布译文 ${PUB_A}"
}
# 定义常见错误
# 未知错误
error_undefined() {
echo "未知错误:无匹配规则,请尝试只对一篇文章进行操作"
}
# 申领多篇
error_translation_requested_multiple() {
[ "$SRC_M" -gt 1 ] \
&& echo "匹配错误:申领多篇,请一次仅申领一篇"
}
# 执行检查并输出匹配项目
do_check() {
rule_bypass_check \
|| rule_source_added \
|| rule_translation_requested \
|| rule_translation_completed \
|| rule_translation_revised \
|| rule_translation_published \
|| {
error_translation_requested_multiple \
|| error_undefined
exit 1
}
}
do_check

4
scripts/check/info.sh Normal file
View File

@ -0,0 +1,4 @@
#!/bin/bash
# 打印 PR 信息
echo ""

View File

@ -0,0 +1,13 @@
#!/bin/sh
# 给 ShellCheck 用的 Mock 统计
SRC_A=0
SRC_M=0
SRC_D=0
TSL_A=0
TSL_M=0
TSL_D=0
PUB_A=0
PUB_M=0
PUB_D=0
OTHER=0
TOTAL=0