Skip to content

Commit b605a5b

Browse files
committed
[CI] Add Check PR Template
1 parent 63ef593 commit b605a5b

File tree

2 files changed

+184
-0
lines changed

2 files changed

+184
-0
lines changed
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
name: Check PR Template
2+
3+
on:
4+
pull_request:
5+
branches:
6+
- develop
7+
- 'release/*'
8+
9+
jobs:
10+
check:
11+
name: Check PR Template
12+
runs-on: ubuntu-latest
13+
14+
steps:
15+
- name: Checkout base branch
16+
uses: actions/checkout@v4
17+
with:
18+
ref: ${{ github.event.pull_request.base.ref }}
19+
fetch-depth: 100
20+
21+
- name: Setup Python 3.10
22+
uses: actions/setup-python@v5
23+
with:
24+
python-version: '3.10'
25+
cache: 'pip'
26+
27+
- name: Install Python dependencies
28+
run: |
29+
python -m pip install --upgrade pip
30+
pip install httpx
31+
32+
- name: Check PR Template
33+
env:
34+
AGILE_PULL_ID: ${{ github.event.pull_request.number }}
35+
AGILE_COMPILE_BRANCH: ${{ github.event.pull_request.base.ref }}
36+
AGILE_CHECKIN_AUTHOR: ${{ github.event.pull_request.user.login }}
37+
GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }}
38+
run: |
39+
python FastDeploy/scripts/CheckPRTemplate.py
40+
EXCODE=$?
41+
echo "EXCODE=$EXCODE"
42+
exit $EXCODE

scripts/CheckPRTemplate.py

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import os
16+
import re
17+
import sys
18+
19+
import httpx
20+
21+
# ==============================
22+
# PR Template Definition
23+
# ==============================
24+
REPO_TEMPLATE = {
25+
"FastDeploy": {
26+
"sections": [
27+
"## Motivation",
28+
"## Modifications",
29+
"## Usage or Command",
30+
"## Accuracy Tests",
31+
"## Checklist",
32+
]
33+
}
34+
}
35+
36+
37+
# ==============================
38+
# Utility Functions
39+
# ==============================
40+
def remove_comments(body):
41+
"""Remove HTML-style comments (<!-- -->) from Markdown."""
42+
if not body:
43+
return ""
44+
comment_pattern = re.compile(r"<!--.*?-->", re.DOTALL)
45+
return comment_pattern.sub("", body).strip()
46+
47+
48+
def check_section_content(body, section_titles):
49+
"""Extract content between section headers."""
50+
results = {}
51+
valid_titles = [t for t in section_titles if t]
52+
53+
for i, title in enumerate(valid_titles):
54+
next_title = valid_titles[i + 1] if i + 1 < len(valid_titles) else None
55+
56+
if next_title:
57+
pattern = r"{}(.*?)(?={}|$)".format(re.escape(title), re.escape(next_title))
58+
else:
59+
pattern = r"{}(.*)".format(re.escape(title)) # Match until the end
60+
61+
match = re.search(pattern, body, re.DOTALL | re.IGNORECASE)
62+
content = match.group(1).strip() if match else ""
63+
results[title] = content
64+
65+
return results
66+
67+
68+
def check_pr_template(repo, body):
69+
"""Check whether a PR description follows the expected template."""
70+
body = remove_comments(body)
71+
template_info = REPO_TEMPLATE.get(repo)
72+
73+
if not template_info:
74+
print("[INFO] Repo '{}' not in REPO_TEMPLATE. Skipping check.".format(repo))
75+
return True, ""
76+
77+
section_titles = template_info["sections"]
78+
results = check_section_content(body, section_titles)
79+
80+
missing = [sec for sec, content in results.items() if not content]
81+
if missing:
82+
if len(missing) == 1:
83+
message = "❌ Missing section: {}. Please complete it.".format(missing[0])
84+
else:
85+
message = "❌ Missing sections: {}. Please complete them.".format(", ".join(missing))
86+
87+
message += (
88+
"\n\n💡 **Tips for fixing:**\n"
89+
"1. Each PR must follow the standard FastDeploy PR template.\n"
90+
"2. Ensure every section (Motivation, Modifications, Usage, Accuracy Tests, Checklist) "
91+
"is clearly filled with relevant details.\n"
92+
"3. You can refer to the official PR example: "
93+
"https://github.com/PaddlePaddle/FastDeploy/blob/develop/.github/pull_request_template.md\n"
94+
"4. For missing parts, please describe briefly what was changed or verified.\n\n"
95+
"📩 If you have any questions, please contact **@yubaoku(EmmonsCurse)**"
96+
)
97+
return False, message
98+
99+
return True, "✅ PR description template check passed."
100+
101+
102+
def get_pull_request(org, repo, pull_id, token):
103+
"""Fetch PR information from GitHub API."""
104+
url = "https://api.github.com/repos/{}/{}/pulls/{}".format(org, repo, pull_id)
105+
headers = {
106+
"Authorization": "token {}".format(token),
107+
"Accept": "application/vnd.github+json",
108+
}
109+
110+
response = httpx.get(url, headers=headers, follow_redirects=True, timeout=30)
111+
response.raise_for_status()
112+
return response.json()
113+
114+
115+
# ==============================
116+
# Main Entry
117+
# ==============================
118+
def main():
119+
org = os.getenv("AGILE_ORG", "PaddlePaddle")
120+
repo = os.getenv("AGILE_REPO", "FastDeploy")
121+
pull_id = os.getenv("AGILE_PULL_ID")
122+
token = os.getenv("GITHUB_API_TOKEN")
123+
124+
if not pull_id or not token:
125+
print("❌ Environment variables AGILE_PULL_ID and GITHUB_API_TOKEN are required.")
126+
sys.exit(1)
127+
128+
pr_info = get_pull_request(org, repo, pull_id, token)
129+
body = pr_info.get("body", "")
130+
title = pr_info.get("title", "")
131+
user = pr_info.get("user", {}).get("login", "unknown")
132+
133+
print("🔍 Checking PR #{} by {}: {}".format(pull_id, user, title))
134+
135+
ok, message = check_pr_template(repo, body)
136+
print(message)
137+
138+
sys.exit(0 if ok else 7)
139+
140+
141+
if __name__ == "__main__":
142+
main()

0 commit comments

Comments
 (0)