Skip to content

Commit ebc8284

Browse files
google-labs-jules[bot]shiron-dev
authored andcommitted
feat: add CI to check PR title scope against changed files
This CI ensures that the scope(s) in a PR title accurately reflect the directories of the changed files. It introduces: - A GitHub Actions workflow (`.github/workflows/check_pr_scope.yml`) to perform the check. - A configuration file (`scope-rules.json`) to define the mapping between file patterns and scopes. - A `README.md` explaining the CI's purpose and usage.
1 parent 89e5b2b commit ebc8284

File tree

2 files changed

+137
-0
lines changed

2 files changed

+137
-0
lines changed
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
name: Check PR Scope
2+
3+
on:
4+
pull_request:
5+
types: [opened, synchronize, reopened, edited]
6+
7+
jobs:
8+
check_scope:
9+
runs-on: ubuntu-latest
10+
steps:
11+
- uses: actions/checkout@v3
12+
with:
13+
fetch-depth: 0 # Fetch all history for all branches and tags
14+
15+
- name: Get PR title
16+
id: pr_title
17+
run: echo "title=$(jq -r .pull_request.title \"$GITHUB_EVENT_PATH\")" >> $GITHUB_OUTPUT
18+
19+
- name: Get changed files
20+
id: changed_files
21+
run: |
22+
files=$(git diff --name-only origin/${{ github.base_ref }}...origin/${{ github.head_ref }})
23+
echo "files<<EOF" >> $GITHUB_OUTPUT
24+
echo "$files" >> $GITHUB_OUTPUT
25+
echo "EOF" >> $GITHUB_OUTPUT
26+
27+
- name: Check PR scope
28+
run: |
29+
pr_title="${{ steps.pr_title.outputs.title }}"
30+
changed_files="${{ steps.changed_files.outputs.files }}"
31+
32+
echo "PR Title: $pr_title"
33+
echo "Changed Files:"
34+
echo "$changed_files"
35+
36+
# Extract scope from PR title (e.g., "fix(scope1,scope2): ...")
37+
pr_scopes_str=$(echo "$pr_title" | grep -oP '^\w+\(\K[^\)]+' || echo "")
38+
IFS=',' read -r -a pr_scopes <<< "$pr_scopes_str"
39+
# Trim whitespace from scopes
40+
pr_scopes=($(for scope in "${pr_scopes[@]}"; do echo "$scope" | xargs; done))
41+
42+
if [ ${#pr_scopes[@]} -eq 0 ] && [[ "$pr_scopes_str" != "*" ]]; then
43+
echo "::error::PR title does not contain a valid scope."
44+
exit 1
45+
fi
46+
echo "PR Scopes: ${pr_scopes[@]}"
47+
48+
# Load scope rules
49+
if [ ! -f "scope-rules.json" ]; then
50+
echo "::error::scope-rules.json not found."
51+
exit 1
52+
fi
53+
rules=$(cat scope-rules.json | jq -r '.rules')
54+
55+
required_scopes=()
56+
while IFS= read -r file; do
57+
matched_scope=""
58+
for i in $(seq 0 $(($(echo "$rules" | jq length) - 1))); do
59+
pattern=$(echo "$rules" | jq -r ".[$i].pattern")
60+
scope_template=$(echo "$rules" | jq -r ".[$i].scope")
61+
62+
if [[ "$file" =~ $pattern ]]; then
63+
# Handle scope template with capture groups (e.g., $1)
64+
if [[ "$scope_template" == "\$1" ]]; then
65+
# BASH_REMATCH[0] is the full match, BASH_REMATCH[1] is the first capture group
66+
matched_scope="${BASH_REMATCH[1]}"
67+
else
68+
matched_scope="$scope_template"
69+
fi
70+
break
71+
fi
72+
done
73+
74+
if [ -n "$matched_scope" ]; then
75+
# Add to required_scopes if not already present
76+
if [[ ! " ${required_scopes[@]} " =~ " ${matched_scope} " ]]; then
77+
required_scopes+=("$matched_scope")
78+
fi
79+
else
80+
# If no rule matches, consider it an error or a default scope based on requirements
81+
# For now, let's assume if a file doesn't match any rule, it's an issue.
82+
# Or, define a default scope in scope-rules.json like { "pattern": ".*", "scope": "default" }
83+
echo "::warning::No matching scope rule for file: $file"
84+
fi
85+
done <<< "$changed_files"
86+
87+
echo "Required Scopes based on changed files: ${required_scopes[@]}"
88+
89+
if [[ " ${pr_scopes[@]} " =~ " * " ]]; then
90+
echo "Wildcard scope '*' in PR title allows all changes."
91+
exit 0
92+
fi
93+
94+
missing_scopes=()
95+
for req_scope in "${required_scopes[@]}"; do
96+
is_covered=false
97+
for pr_scope in "${pr_scopes[@]}"; do
98+
if [ "$req_scope" == "$pr_scope" ]; then
99+
is_covered=true
100+
break
101+
fi
102+
done
103+
if [ "$is_covered" == false ]; then
104+
missing_scopes+=("$req_scope")
105+
fi
106+
done
107+
108+
if [ ${#missing_scopes[@]} -gt 0 ]; then
109+
echo "::error::PR title scopes do not cover all changed files. Missing scopes for: ${missing_scopes[@]}"
110+
exit 1
111+
else
112+
echo "PR title scopes are valid."
113+
fi

scope-rules.json

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"rules": [
3+
{
4+
"pattern": "^config/(.+)/.*",
5+
"scope": "$1"
6+
},
7+
{
8+
"pattern": "^scripts/(.+)/.*",
9+
"scope": "$1"
10+
},
11+
{
12+
"pattern": "^data/(.+)/.*",
13+
"scope": "$1"
14+
},
15+
{
16+
"pattern": "^\\.github/.*",
17+
"scope": "github"
18+
},
19+
{
20+
"pattern": ".*",
21+
"scope": "dotfiles"
22+
}
23+
]
24+
}

0 commit comments

Comments
 (0)