Skip to content

Commit ca405ae

Browse files
sripasgrfcclub
authored andcommitted
chore(ci): add optimized PR size labeler and batch workflows (google-gemini#27616)
1 parent 7037190 commit ca405ae

3 files changed

Lines changed: 256 additions & 1 deletion

File tree

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
name: 'PR Size Labeler (Batch)'
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
process_all:
7+
description: 'Process all PRs (open and closed) or open only'
8+
required: true
9+
default: 'false'
10+
type: 'choice'
11+
options:
12+
- 'true'
13+
- 'false'
14+
limit:
15+
description: 'Max number of PRs to fetch and check'
16+
required: true
17+
default: '100'
18+
type: 'string'
19+
20+
permissions:
21+
pull-requests: 'write'
22+
23+
jobs:
24+
batch-label:
25+
runs-on: 'ubuntu-latest'
26+
steps:
27+
- name: 'Batch label PRs'
28+
env:
29+
GH_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
30+
GH_REPO: '${{ github.repository }}'
31+
run: |
32+
# Determine the state filter
33+
STATE="open"
34+
if [ "${{ github.event.inputs.process_all }}" = "true" ]; then
35+
STATE="all"
36+
fi
37+
38+
LIMIT="${{ github.event.inputs.limit }}"
39+
echo "Batch labeling up to $LIMIT $STATE PRs..."
40+
41+
# 1. Ensure standard premium size labels exist in the repository (self-healing)
42+
gh label create "size/XS" --color "7ee081" --description "XS: <10 lines changed" 2>/dev/null || true
43+
gh label create "size/S" --color "a6d49f" --description "S: 10-49 lines changed" 2>/dev/null || true
44+
gh label create "size/M" --color "f7d070" --description "M: 50-249 lines changed" 2>/dev/null || true
45+
gh label create "size/L" --color "f48c06" --description "L: 250-999 lines changed" 2>/dev/null || true
46+
gh label create "size/XL" --color "dc2f02" --description "XL: >=1000 lines changed" 2>/dev/null || true
47+
48+
# 2. Query PR list with all required fields in ONE call to prevent N+1 queries
49+
PR_LIST=$(gh pr list --state "$STATE" --limit "$LIMIT" --json number,additions,deletions,labels)
50+
if [ -z "$PR_LIST" ] || [ "$PR_LIST" = "[]" ]; then
51+
echo "ℹ️ No PRs found matching the criteria."
52+
exit 0
53+
fi
54+
55+
# Parse and iterate over PRs
56+
UPDATED_COUNT=0
57+
SKIPPED_COUNT=0
58+
59+
echo "$PR_LIST" | jq -c '.[]' | while read -r PR_JSON; do
60+
PR_NUMBER=$(echo "$PR_JSON" | jq '.number')
61+
ADDITIONS=$(echo "$PR_JSON" | jq '.additions')
62+
DELETIONS=$(echo "$PR_JSON" | jq '.deletions')
63+
TOTAL=$((ADDITIONS + DELETIONS))
64+
65+
# Calculate target size
66+
if [ $TOTAL -lt 10 ]; then
67+
SIZE="size/XS"
68+
elif [ $TOTAL -lt 50 ]; then
69+
SIZE="size/S"
70+
elif [ $TOTAL -lt 250 ]; then
71+
SIZE="size/M"
72+
elif [ $TOTAL -lt 1000 ]; then
73+
SIZE="size/L"
74+
else
75+
SIZE="size/XL"
76+
fi
77+
78+
# Inspect existing labels to detect discrepancies
79+
EXISTING_LABELS=$(echo "$PR_JSON" | jq -r '.labels[].name' 2>/dev/null || echo "")
80+
81+
LABELS_TO_REMOVE=()
82+
for L in size/XS size/S size/M size/L size/XL; do
83+
if echo "$EXISTING_LABELS" | grep -Fq "$L" && [ "$L" != "$SIZE" ]; then
84+
LABELS_TO_REMOVE+=("--remove-label" "$L")
85+
fi
86+
done
87+
88+
LABEL_TO_ADD=()
89+
if ! echo "$EXISTING_LABELS" | grep -Fq "$SIZE"; then
90+
LABEL_TO_ADD+=("--add-label" "$SIZE")
91+
fi
92+
93+
# Update labels if there's a difference
94+
if [ ${#LABELS_TO_REMOVE[@]} -gt 0 ] || [ ${#LABEL_TO_ADD[@]} -gt 0 ]; then
95+
echo "🔄 PR #$PR_NUMBER (+$ADDITIONS/-$DELETIONS = $TOTAL lines): updating size to $SIZE"
96+
gh pr edit "$PR_NUMBER" "${LABELS_TO_REMOVE[@]}" "${LABEL_TO_ADD[@]}" 2>/dev/null || true
97+
UPDATED_COUNT=$((UPDATED_COUNT + 1))
98+
else
99+
echo "✅ PR #$PR_NUMBER (+$ADDITIONS/-$DELETIONS = $TOTAL lines): already has correct label ($SIZE). Skipping."
100+
SKIPPED_COUNT=$((SKIPPED_COUNT + 1))
101+
fi
102+
done
103+
104+
echo "============================================"
105+
echo "🎉 Batch run completed!"
106+
echo "Skipped (already correct): $SKIPPED_COUNT"
107+
echo "Updated: $UPDATED_COUNT"
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
name: 'PR Size Labeler'
2+
3+
on:
4+
pull_request:
5+
types: ['opened', 'synchronize', 'reopened']
6+
workflow_dispatch:
7+
inputs:
8+
pr_number:
9+
description: 'PR number to label manually (for workflow_dispatch)'
10+
required: false
11+
type: 'string'
12+
13+
permissions:
14+
pull-requests: 'write'
15+
issues: 'write'
16+
17+
jobs:
18+
size-label:
19+
runs-on: 'ubuntu-latest'
20+
steps:
21+
- name: 'Run size labeler'
22+
env:
23+
GH_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
24+
GH_REPO: '${{ github.repository }}'
25+
run: |
26+
# Determine the target PR number
27+
if [ -n "${{ github.event.pull_request.number }}" ]; then
28+
PR_NUMBER="${{ github.event.pull_request.number }}"
29+
elif [ -n "${{ github.event.inputs.pr_number }}" ]; then
30+
PR_NUMBER="${{ github.event.inputs.pr_number }}"
31+
else
32+
echo "❌ Error: No PR number provided."
33+
exit 1
34+
fi
35+
36+
echo "Checking PR #$PR_NUMBER..."
37+
38+
# 1. Ensure standard premium size labels exist in the repository (self-healing)
39+
# size/XS: Light green (#7ee081)
40+
# size/S: Yellow-green (#a6d49f)
41+
# size/M: Amber/Yellow (#f7d070)
42+
# size/L: Orange (#f48c06)
43+
# size/XL: Red (#dc2f02)
44+
gh label create "size/XS" --color "7ee081" --description "XS: <10 lines changed" 2>/dev/null || true
45+
gh label create "size/S" --color "a6d49f" --description "S: 10-49 lines changed" 2>/dev/null || true
46+
gh label create "size/M" --color "f7d070" --description "M: 50-249 lines changed" 2>/dev/null || true
47+
gh label create "size/L" --color "f48c06" --description "L: 250-999 lines changed" 2>/dev/null || true
48+
gh label create "size/XL" --color "dc2f02" --description "XL: >=1000 lines changed" 2>/dev/null || true
49+
50+
# 2. Fetch PR details in a single efficient API call
51+
PR_DATA=$(gh pr view "$PR_NUMBER" --json additions,deletions,changedFiles,labels)
52+
if [ -z "$PR_DATA" ]; then
53+
echo "❌ Error: Could not fetch PR details."
54+
exit 1
55+
fi
56+
57+
ADDITIONS=$(echo "$PR_DATA" | jq '.additions')
58+
DELETIONS=$(echo "$PR_DATA" | jq '.deletions')
59+
CHANGED_FILES=$(echo "$PR_DATA" | jq '.changedFiles')
60+
TOTAL=$((ADDITIONS + DELETIONS))
61+
62+
echo "PR additions: $ADDITIONS, deletions: $DELETIONS, total changes: $TOTAL, files: $CHANGED_FILES"
63+
64+
# 3. Calculate new size label
65+
if [ $TOTAL -lt 10 ]; then
66+
SIZE="size/XS"
67+
elif [ $TOTAL -lt 50 ]; then
68+
SIZE="size/S"
69+
elif [ $TOTAL -lt 250 ]; then
70+
SIZE="size/M"
71+
elif [ $TOTAL -lt 1000 ]; then
72+
SIZE="size/L"
73+
else
74+
SIZE="size/XL"
75+
fi
76+
77+
# 4. Check existing labels and update only if necessary
78+
EXISTING_LABELS=$(echo "$PR_DATA" | jq -r '.labels[].name' 2>/dev/null || echo "")
79+
80+
LABELS_TO_REMOVE=()
81+
for L in size/XS size/S size/M size/L size/XL; do
82+
if echo "$EXISTING_LABELS" | grep -Fq "$L" && [ "$L" != "$SIZE" ]; then
83+
LABELS_TO_REMOVE+=("--remove-label" "$L")
84+
fi
85+
done
86+
87+
LABEL_TO_ADD=()
88+
if ! echo "$EXISTING_LABELS" | grep -Fq "$SIZE"; then
89+
LABEL_TO_ADD+=("--add-label" "$SIZE")
90+
fi
91+
92+
# Perform a single, highly atomic edit call if changes are needed
93+
if [ ${#LABELS_TO_REMOVE[@]} -gt 0 ] || [ ${#LABEL_TO_ADD[@]} -gt 0 ]; then
94+
echo "Updating labels: removing ${LABELS_TO_REMOVE[*]}, adding $SIZE"
95+
gh pr edit "$PR_NUMBER" "${LABELS_TO_REMOVE[@]}" "${LABEL_TO_ADD[@]}"
96+
else
97+
echo "✅ PR #$PR_NUMBER already has the correct size label ($SIZE)."
98+
fi
99+
100+
# 5. Premium, anti-spam comment logic (updates previous comment to keep thread clean)
101+
COMMENT="📊 PR Size: **$SIZE**
102+
- Lines changed: **$TOTAL**
103+
- Additions: +$ADDITIONS
104+
- Deletions: -$DELETIONS
105+
- Files changed: $CHANGED_FILES"
106+
107+
# Find any existing size labeler comment by the github-actions bot
108+
echo "Searching for existing size comment..."
109+
COMMENT_ID=$(gh api "repos/${{ github.repository }}/issues/$PR_NUMBER/comments" \
110+
--jq '.[] | select(.user.login == "github-actions[bot]" and (.body | startswith("📊 PR Size:"))) | .id' | head -n 1)
111+
112+
if [ -n "$COMMENT_ID" ]; then
113+
echo "Updating existing comment (ID: $COMMENT_ID)..."
114+
gh api "repos/${{ github.repository }}/issues/comments/$COMMENT_ID" -X PATCH -f body="$COMMENT" > /dev/null
115+
else
116+
echo "Creating new comment..."
117+
gh pr comment "$PR_NUMBER" --body "$COMMENT" > /dev/null
118+
fi
119+
120+
echo "🎉 PR size labeling completed successfully."

‎docs/issue-and-pr-automation.md‎

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,35 @@ and will never be auto-unassigned.
154154
- **Unassign yourself** if you can no longer work on the issue by commenting
155155
`/unassign`, so other contributors can pick it up right away.
156156

157-
### 6. Release automation
157+
### 6. Automatically label PRs by size: `PR Size Labeler`
158+
159+
To help maintainers estimate review effort and keep the PR history clean, this
160+
workflow automatically tags every pull request with a size label representing
161+
the total volume of line changes.
162+
163+
- **Workflow File**: `.github/workflows/pr-size-labeler.yml`
164+
- **When it runs**: Immediately after a pull request is created, synchronized
165+
(new commits pushed), or reopened. It can also be triggered manually via
166+
`workflow_dispatch` with a PR number.
167+
- **What it does**:
168+
- **Calculates total changes**: Summarizes additions and deletions across all
169+
changed files in a single consolidated API request.
170+
- **Applies standard size labels**:
171+
- `size/XS`: < 10 lines changed
172+
- `size/S`: 10-49 lines changed
173+
- `size/M`: 50-249 lines changed
174+
- `size/L`: 250-999 lines changed
175+
- `size/XL`: >= 1000 lines changed
176+
- **Updates size tag atomically**: Adds the new correct size label and removes
177+
any obsolete size labels in one atomic step.
178+
- **Updates/Posts PR size info comment**: Instead of spamming a new comment on
179+
every commit push, it updates the existing size labeler status comment
180+
inline to keep the PR conversation timeline perfectly neat and clean.
181+
- **What you should do**:
182+
- You do not need to take any actions. The workflow runs automatically and
183+
updates the label and comment seamlessly as you push new updates.
184+
185+
### 7. Release automation
158186

159187
This workflow handles the process of packaging and publishing new versions of
160188
Gemini CLI.

0 commit comments

Comments
 (0)