Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 107 additions & 0 deletions .github/workflows/pr-size-labeler-batch-run.yml
Original file line number Diff line number Diff line change
@@ -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"
120 changes: 120 additions & 0 deletions .github/workflows/pr-size-labeler.yml
Original file line number Diff line number Diff line change
@@ -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."
30 changes: 29 additions & 1 deletion docs/issue-and-pr-automation.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Loading