From 5110bdf56cbf280d139e2fdb1e1923527420905b Mon Sep 17 00:00:00 2001 From: Sri Pasumarthi <111310667+sripasg@users.noreply.github.com> Date: Mon, 1 Jun 2026 19:05:33 -0700 Subject: [PATCH] chore(ci): add optimized PR size labeler and batch workflows (#27616) --- .../workflows/pr-size-labeler-batch-run.yml | 107 ++++++++++++++++ .github/workflows/pr-size-labeler.yml | 120 ++++++++++++++++++ docs/issue-and-pr-automation.md | 30 ++++- 3 files changed, 256 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/pr-size-labeler-batch-run.yml create mode 100644 .github/workflows/pr-size-labeler.yml diff --git a/.github/workflows/pr-size-labeler-batch-run.yml b/.github/workflows/pr-size-labeler-batch-run.yml new file mode 100644 index 0000000000..1b010dba13 --- /dev/null +++ b/.github/workflows/pr-size-labeler-batch-run.yml @@ -0,0 +1,107 @@ +name: 'PR Size Labeler (Batch)' + +on: + workflow_dispatch: + inputs: + process_all: + description: 'Process all PRs (open and closed) or open only' + required: true + default: 'false' + type: 'choice' + options: + - 'true' + - 'false' + limit: + description: 'Max number of PRs to fetch and check' + required: true + default: '100' + type: 'string' + +permissions: + pull-requests: 'write' + +jobs: + batch-label: + runs-on: 'ubuntu-latest' + steps: + - name: 'Batch label PRs' + env: + GH_TOKEN: '${{ secrets.GITHUB_TOKEN }}' + GH_REPO: '${{ github.repository }}' + run: | + # Determine the state filter + STATE="open" + if [ "${{ github.event.inputs.process_all }}" = "true" ]; then + STATE="all" + fi + + LIMIT="${{ github.event.inputs.limit }}" + echo "Batch labeling up to $LIMIT $STATE PRs..." + + # 1. Ensure standard premium size labels exist in the repository (self-healing) + gh label create "size/XS" --color "7ee081" --description "XS: <10 lines changed" 2>/dev/null || true + gh label create "size/S" --color "a6d49f" --description "S: 10-49 lines changed" 2>/dev/null || true + gh label create "size/M" --color "f7d070" --description "M: 50-249 lines changed" 2>/dev/null || true + gh label create "size/L" --color "f48c06" --description "L: 250-999 lines changed" 2>/dev/null || true + gh label create "size/XL" --color "dc2f02" --description "XL: >=1000 lines changed" 2>/dev/null || true + + # 2. Query PR list with all required fields in ONE call to prevent N+1 queries + PR_LIST=$(gh pr list --state "$STATE" --limit "$LIMIT" --json number,additions,deletions,labels) + if [ -z "$PR_LIST" ] || [ "$PR_LIST" = "[]" ]; then + echo "â„šī¸ No PRs found matching the criteria." + exit 0 + fi + + # Parse and iterate over PRs + UPDATED_COUNT=0 + SKIPPED_COUNT=0 + + echo "$PR_LIST" | jq -c '.[]' | while read -r PR_JSON; do + PR_NUMBER=$(echo "$PR_JSON" | jq '.number') + ADDITIONS=$(echo "$PR_JSON" | jq '.additions') + DELETIONS=$(echo "$PR_JSON" | jq '.deletions') + TOTAL=$((ADDITIONS + DELETIONS)) + + # Calculate target size + if [ $TOTAL -lt 10 ]; then + SIZE="size/XS" + elif [ $TOTAL -lt 50 ]; then + SIZE="size/S" + elif [ $TOTAL -lt 250 ]; then + SIZE="size/M" + elif [ $TOTAL -lt 1000 ]; then + SIZE="size/L" + else + SIZE="size/XL" + fi + + # Inspect existing labels to detect discrepancies + EXISTING_LABELS=$(echo "$PR_JSON" | jq -r '.labels[].name' 2>/dev/null || echo "") + + LABELS_TO_REMOVE=() + for L in size/XS size/S size/M size/L size/XL; do + if echo "$EXISTING_LABELS" | grep -Fq "$L" && [ "$L" != "$SIZE" ]; then + LABELS_TO_REMOVE+=("--remove-label" "$L") + fi + done + + LABEL_TO_ADD=() + if ! echo "$EXISTING_LABELS" | grep -Fq "$SIZE"; then + LABEL_TO_ADD+=("--add-label" "$SIZE") + fi + + # Update labels if there's a difference + if [ ${#LABELS_TO_REMOVE[@]} -gt 0 ] || [ ${#LABEL_TO_ADD[@]} -gt 0 ]; then + echo "🔄 PR #$PR_NUMBER (+$ADDITIONS/-$DELETIONS = $TOTAL lines): updating size to $SIZE" + gh pr edit "$PR_NUMBER" "${LABELS_TO_REMOVE[@]}" "${LABEL_TO_ADD[@]}" 2>/dev/null || true + UPDATED_COUNT=$((UPDATED_COUNT + 1)) + else + echo "✅ PR #$PR_NUMBER (+$ADDITIONS/-$DELETIONS = $TOTAL lines): already has correct label ($SIZE). Skipping." + SKIPPED_COUNT=$((SKIPPED_COUNT + 1)) + fi + done + + echo "============================================" + echo "🎉 Batch run completed!" + echo "Skipped (already correct): $SKIPPED_COUNT" + echo "Updated: $UPDATED_COUNT" diff --git a/.github/workflows/pr-size-labeler.yml b/.github/workflows/pr-size-labeler.yml new file mode 100644 index 0000000000..afcfadb20f --- /dev/null +++ b/.github/workflows/pr-size-labeler.yml @@ -0,0 +1,120 @@ +name: 'PR Size Labeler' + +on: + pull_request: + types: ['opened', 'synchronize', 'reopened'] + workflow_dispatch: + inputs: + pr_number: + description: 'PR number to label manually (for workflow_dispatch)' + required: false + type: 'string' + +permissions: + pull-requests: 'write' + issues: 'write' + +jobs: + size-label: + runs-on: 'ubuntu-latest' + steps: + - name: 'Run size labeler' + env: + GH_TOKEN: '${{ secrets.GITHUB_TOKEN }}' + GH_REPO: '${{ github.repository }}' + run: | + # Determine the target PR number + if [ -n "${{ github.event.pull_request.number }}" ]; then + PR_NUMBER="${{ github.event.pull_request.number }}" + elif [ -n "${{ github.event.inputs.pr_number }}" ]; then + PR_NUMBER="${{ github.event.inputs.pr_number }}" + else + echo "❌ Error: No PR number provided." + exit 1 + fi + + echo "Checking PR #$PR_NUMBER..." + + # 1. Ensure standard premium size labels exist in the repository (self-healing) + # size/XS: Light green (#7ee081) + # size/S: Yellow-green (#a6d49f) + # size/M: Amber/Yellow (#f7d070) + # size/L: Orange (#f48c06) + # size/XL: Red (#dc2f02) + gh label create "size/XS" --color "7ee081" --description "XS: <10 lines changed" 2>/dev/null || true + gh label create "size/S" --color "a6d49f" --description "S: 10-49 lines changed" 2>/dev/null || true + gh label create "size/M" --color "f7d070" --description "M: 50-249 lines changed" 2>/dev/null || true + gh label create "size/L" --color "f48c06" --description "L: 250-999 lines changed" 2>/dev/null || true + gh label create "size/XL" --color "dc2f02" --description "XL: >=1000 lines changed" 2>/dev/null || true + + # 2. Fetch PR details in a single efficient API call + PR_DATA=$(gh pr view "$PR_NUMBER" --json additions,deletions,changedFiles,labels) + if [ -z "$PR_DATA" ]; then + echo "❌ Error: Could not fetch PR details." + exit 1 + fi + + ADDITIONS=$(echo "$PR_DATA" | jq '.additions') + DELETIONS=$(echo "$PR_DATA" | jq '.deletions') + CHANGED_FILES=$(echo "$PR_DATA" | jq '.changedFiles') + TOTAL=$((ADDITIONS + DELETIONS)) + + echo "PR additions: $ADDITIONS, deletions: $DELETIONS, total changes: $TOTAL, files: $CHANGED_FILES" + + # 3. Calculate new size label + if [ $TOTAL -lt 10 ]; then + SIZE="size/XS" + elif [ $TOTAL -lt 50 ]; then + SIZE="size/S" + elif [ $TOTAL -lt 250 ]; then + SIZE="size/M" + elif [ $TOTAL -lt 1000 ]; then + SIZE="size/L" + else + SIZE="size/XL" + fi + + # 4. Check existing labels and update only if necessary + EXISTING_LABELS=$(echo "$PR_DATA" | jq -r '.labels[].name' 2>/dev/null || echo "") + + LABELS_TO_REMOVE=() + for L in size/XS size/S size/M size/L size/XL; do + if echo "$EXISTING_LABELS" | grep -Fq "$L" && [ "$L" != "$SIZE" ]; then + LABELS_TO_REMOVE+=("--remove-label" "$L") + fi + done + + LABEL_TO_ADD=() + if ! echo "$EXISTING_LABELS" | grep -Fq "$SIZE"; then + LABEL_TO_ADD+=("--add-label" "$SIZE") + fi + + # Perform a single, highly atomic edit call if changes are needed + if [ ${#LABELS_TO_REMOVE[@]} -gt 0 ] || [ ${#LABEL_TO_ADD[@]} -gt 0 ]; then + echo "Updating labels: removing ${LABELS_TO_REMOVE[*]}, adding $SIZE" + gh pr edit "$PR_NUMBER" "${LABELS_TO_REMOVE[@]}" "${LABEL_TO_ADD[@]}" + else + echo "✅ PR #$PR_NUMBER already has the correct size label ($SIZE)." + fi + + # 5. Premium, anti-spam comment logic (updates previous comment to keep thread clean) + COMMENT="📊 PR Size: **$SIZE** + - Lines changed: **$TOTAL** + - Additions: +$ADDITIONS + - Deletions: -$DELETIONS + - Files changed: $CHANGED_FILES" + + # Find any existing size labeler comment by the github-actions bot + echo "Searching for existing size comment..." + COMMENT_ID=$(gh api "repos/${{ github.repository }}/issues/$PR_NUMBER/comments" \ + --jq '.[] | select(.user.login == "github-actions[bot]" and (.body | startswith("📊 PR Size:"))) | .id' | head -n 1) + + if [ -n "$COMMENT_ID" ]; then + echo "Updating existing comment (ID: $COMMENT_ID)..." + gh api "repos/${{ github.repository }}/issues/comments/$COMMENT_ID" -X PATCH -f body="$COMMENT" > /dev/null + else + echo "Creating new comment..." + gh pr comment "$PR_NUMBER" --body "$COMMENT" > /dev/null + fi + + echo "🎉 PR size labeling completed successfully." diff --git a/docs/issue-and-pr-automation.md b/docs/issue-and-pr-automation.md index 3107bfcb4e..cdcfbb75c7 100644 --- a/docs/issue-and-pr-automation.md +++ b/docs/issue-and-pr-automation.md @@ -154,7 +154,35 @@ and will never be auto-unassigned. - **Unassign yourself** if you can no longer work on the issue by commenting `/unassign`, so other contributors can pick it up right away. -### 6. Release automation +### 6. Automatically label PRs by size: `PR Size Labeler` + +To help maintainers estimate review effort and keep the PR history clean, this +workflow automatically tags every pull request with a size label representing +the total volume of line changes. + +- **Workflow File**: `.github/workflows/pr-size-labeler.yml` +- **When it runs**: Immediately after a pull request is created, synchronized + (new commits pushed), or reopened. It can also be triggered manually via + `workflow_dispatch` with a PR number. +- **What it does**: + - **Calculates total changes**: Summarizes additions and deletions across all + changed files in a single consolidated API request. + - **Applies standard size labels**: + - `size/XS`: < 10 lines changed + - `size/S`: 10-49 lines changed + - `size/M`: 50-249 lines changed + - `size/L`: 250-999 lines changed + - `size/XL`: >= 1000 lines changed + - **Updates size tag atomically**: Adds the new correct size label and removes + any obsolete size labels in one atomic step. + - **Updates/Posts PR size info comment**: Instead of spamming a new comment on + every commit push, it updates the existing size labeler status comment + inline to keep the PR conversation timeline perfectly neat and clean. +- **What you should do**: + - You do not need to take any actions. The workflow runs automatically and + updates the label and comment seamlessly as you push new updates. + +### 7. Release automation This workflow handles the process of packaging and publishing new versions of Gemini CLI.