Refactor PR size limit workflow for clarity and efficiency

This commit is contained in:
Meng Meng
2026-08-15 22:29:45 +08:00
committed by GitHub
parent 091d12cd30
commit 70bdb88e4c
+178 -51
View File
@@ -2,7 +2,13 @@ name: Pull request size limit
on:
pull_request_target:
types: [opened, synchronize, reopened, ready_for_review]
branches:
- master
types:
- opened
- synchronize
- reopened
- ready_for_review
permissions:
contents: read
@@ -15,6 +21,7 @@ concurrency:
jobs:
enforce-size-limit:
# 保持这个名字不变,这样你 Ruleset 里的 Required Check 不需要修改
name: Enforce 5,000-line limit
runs-on: ubuntu-latest
timeout-minutes: 5
@@ -32,7 +39,7 @@ jobs:
fetch-depth: 0
persist-credentials: false
- name: Calculate real pull request size
- name: Check conflicts and pull request size
shell: bash
run: |
set -euo pipefail
@@ -40,40 +47,164 @@ jobs:
echo "Checking PR #${PR_NUMBER}"
echo "Base branch: ${BASE_REF}"
# Fetch the latest target branch and the PR head.
# The PR code is fetched only for diff inspection; it is never executed.
############################################################
# Helper: comment on and close rejected PR
############################################################
reject_pr() {
local message="$1"
echo "::error::${message}"
COMMENT_PAYLOAD="$(
jq -nc \
--arg body "${message}" \
'{body: $body}'
)"
echo "Posting rejection comment..."
curl \
--fail-with-body \
--silent \
--show-error \
--request POST \
--header "Accept: application/vnd.github+json" \
--header "Authorization: Bearer ${GH_TOKEN}" \
--header "X-GitHub-Api-Version: 2022-11-28" \
"${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/issues/${PR_NUMBER}/comments" \
--data "${COMMENT_PAYLOAD}" \
>/dev/null
echo "Closing PR #${PR_NUMBER}..."
curl \
--fail-with-body \
--silent \
--show-error \
--request PATCH \
--header "Accept: application/vnd.github+json" \
--header "Authorization: Bearer ${GH_TOKEN}" \
--header "X-GitHub-Api-Version: 2022-11-28" \
"${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/pulls/${PR_NUMBER}" \
--data '{"state":"closed"}' \
>/dev/null
exit 1
}
############################################################
# Fetch target branch and PR HEAD
############################################################
echo "Fetching base branch and PR head..."
git fetch --no-tags --force origin \
"+refs/heads/${BASE_REF}:refs/remotes/origin/base-size-check" \
"+refs/heads/${BASE_REF}:refs/remotes/origin/base-pr-check" \
"+refs/pull/${PR_NUMBER}/head:refs/remotes/origin/pr-${PR_NUMBER}"
BASE_COMMIT="$(git rev-parse refs/remotes/origin/base-size-check)"
PR_COMMIT="$(git rev-parse refs/remotes/origin/pr-${PR_NUMBER})"
BASE_COMMIT="$(
git rev-parse refs/remotes/origin/base-pr-check
)"
PR_COMMIT="$(
git rev-parse refs/remotes/origin/pr-${PR_NUMBER}
)"
echo "Base commit: ${BASE_COMMIT}"
echo "PR commit: ${PR_COMMIT}"
if ! MERGE_BASE="$(git merge-base "${BASE_COMMIT}" "${PR_COMMIT}")"; then
echo "::error::Unable to determine merge base."
############################################################
# STEP 1: Reject PRs with merge conflicts
############################################################
echo
echo "Checking for merge conflicts..."
set +e
git merge-tree \
--write-tree \
--quiet \
"${BASE_COMMIT}" \
"${PR_COMMIT}"
MERGE_STATUS=$?
set -e
if [[ "${MERGE_STATUS}" -eq 1 ]]; then
{
echo "### Pull request policy"
echo
echo "- Merge conflicts: ❌ Detected"
echo "- Result: Rejected"
} >> "${GITHUB_STEP_SUMMARY}"
reject_pr "This pull request has merge conflicts with the current master branch and cannot be accepted. Please update your branch with the latest master, resolve all merge conflicts locally, and submit a conflict-free pull request."
elif [[ "${MERGE_STATUS}" -ne 0 ]]; then
echo "::error::Unable to determine whether the pull request can be merged."
echo "git merge-tree returned status ${MERGE_STATUS}."
{
echo "### Pull request policy"
echo
echo "- Merge conflict check: ⚠️ Error"
echo "- Result: Check failed"
} >> "${GITHUB_STEP_SUMMARY}"
exit 1
fi
echo "Merge base: ${MERGE_BASE}"
echo "No merge conflicts detected."
############################################################
# STEP 2: Determine merge base
############################################################
if ! MERGE_BASE="$(
git merge-base "${BASE_COMMIT}" "${PR_COMMIT}"
)"; then
echo "::error::Unable to determine merge base."
{
echo "### Pull request policy"
echo
echo "- Merge conflicts: ✅ None"
echo "- Diff calculation: ⚠️ Failed"
} >> "${GITHUB_STEP_SUMMARY}"
exit 1
fi
echo "Merge base: ${MERGE_BASE}"
############################################################
# STEP 3: Calculate actual PR changed lines
############################################################
NUMSTAT_FILE="$(mktemp)"
# Do not allow external diff/textconv helpers.
git diff \
--no-ext-diff \
--no-textconv \
--numstat \
"${MERGE_BASE}" \
"${PR_COMMIT}" > "${NUMSTAT_FILE}"
"${PR_COMMIT}" \
> "${NUMSTAT_FILE}"
ADDITIONS="$(
awk '
$1 ~ /^[0-9]+$/ {
total += $1
}
END {
print total + 0
}
@@ -85,6 +216,7 @@ jobs:
$2 ~ /^[0-9]+$/ {
total += $2
}
END {
print total + 0
}
@@ -92,64 +224,59 @@ jobs:
)"
CHANGED_FILES="$(
awk 'END { print NR + 0 }' "${NUMSTAT_FILE}"
awk '
END {
print NR + 0
}
' "${NUMSTAT_FILE}"
)"
CHANGED_LINES=$((ADDITIONS + DELETIONS))
############################################################
# Action summary
############################################################
{
echo "### Pull request size"
echo "### Pull request policy"
echo
echo "- Merge conflicts: ✅ None"
echo "- Changed files: ${CHANGED_FILES}"
echo "- Additions: ${ADDITIONS}"
echo "- Deletions: ${DELETIONS}"
echo "- Total changed lines: ${CHANGED_LINES}"
echo "- Limit: ${MAX_CHANGED_LINES}"
echo "- Maximum allowed: ${MAX_CHANGED_LINES}"
} >> "${GITHUB_STEP_SUMMARY}"
echo
echo "Changed files: ${CHANGED_FILES}"
echo "Additions: ${ADDITIONS}"
echo "Deletions: ${DELETIONS}"
echo "Total changed lines: ${CHANGED_LINES}"
echo "Limit: ${MAX_CHANGED_LINES}"
if (( CHANGED_LINES <= MAX_CHANGED_LINES )); then
echo "Pull request is within the ${MAX_CHANGED_LINES}-line limit."
exit 0
############################################################
# STEP 4: Reject oversized PRs
############################################################
if (( CHANGED_LINES > MAX_CHANGED_LINES )); then
reject_pr "This pull request changes ${CHANGED_LINES} lines (${ADDITIONS} additions + ${DELETIONS} deletions) across ${CHANGED_FILES} files, exceeding the repository limit of ${MAX_CHANGED_LINES} changed lines. It has been closed automatically. Please split the changes into smaller pull requests."
fi
echo "::error::Pull request changes ${CHANGED_LINES} lines; maximum allowed is ${MAX_CHANGED_LINES}."
############################################################
# PASS
############################################################
MESSAGE="This pull request changes ${CHANGED_LINES} lines (${ADDITIONS} additions + ${DELETIONS} deletions) across ${CHANGED_FILES} files, exceeding the repository limit of ${MAX_CHANGED_LINES} changed lines. It has been closed automatically. Please split the changes into smaller pull requests."
echo
echo "Pull request passed all policy checks."
echo "No merge conflicts."
echo "Changed lines: ${CHANGED_LINES}/${MAX_CHANGED_LINES}."
COMMENT_PAYLOAD="$(
jq -nc \
--arg body "${MESSAGE}" \
'{body: $body}'
)"
# Leave a comment explaining why the PR was rejected.
curl \
--fail-with-body \
--silent \
--show-error \
--request POST \
--header "Accept: application/vnd.github+json" \
--header "Authorization: Bearer ${GH_TOKEN}" \
--header "X-GitHub-Api-Version: 2022-11-28" \
"${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/issues/${PR_NUMBER}/comments" \
--data "${COMMENT_PAYLOAD}" >/dev/null
# Close the oversized PR.
curl \
--fail-with-body \
--silent \
--show-error \
--request PATCH \
--header "Accept: application/vnd.github+json" \
--header "Authorization: Bearer ${GH_TOKEN}" \
--header "X-GitHub-Api-Version: 2022-11-28" \
"${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/pulls/${PR_NUMBER}" \
--data '{"state":"closed"}' >/dev/null
exit 1
{
echo
echo "### Result"
echo
echo "✅ Pull request passed."
} >> "${GITHUB_STEP_SUMMARY}"