76d991c447
Auto Update PR / update-prs (push) Has been cancelled
CI / format-check (push) Has been cancelled
CI / test (3.10) (push) Has been cancelled
CI / test (3.11) (push) Has been cancelled
CI / test (3.12) (push) Has been cancelled
CI / live-api-tests (push) Has been cancelled
CI / plugin-integration-test (push) Has been cancelled
CI / ollama-integration-test (push) Has been cancelled
CI / test-fork-pr (push) Has been cancelled
167 lines
7.3 KiB
YAML
167 lines
7.3 KiB
YAML
# Copyright 2025 Google LLC.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
name: Auto Update PR
|
|
|
|
on:
|
|
push:
|
|
branches: [main]
|
|
schedule:
|
|
# Run daily at 2 AM UTC to catch stale PRs
|
|
- cron: '0 2 * * *'
|
|
workflow_dispatch:
|
|
inputs:
|
|
pr_number:
|
|
description: 'PR number to update (optional, updates all if not specified)'
|
|
required: false
|
|
type: string
|
|
|
|
permissions:
|
|
contents: write # Required for updateBranch API
|
|
pull-requests: write
|
|
issues: write
|
|
|
|
jobs:
|
|
update-prs:
|
|
runs-on: ubuntu-latest
|
|
concurrency:
|
|
group: auto-update-pr-${{ github.event_name }}
|
|
cancel-in-progress: true
|
|
steps:
|
|
- name: Update PRs that are behind main
|
|
uses: actions/github-script@v9
|
|
with:
|
|
script: |
|
|
const prNumber = context.payload.inputs?.pr_number;
|
|
|
|
// Get list of open PRs
|
|
const prs = prNumber
|
|
? [(await github.rest.pulls.get({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
pull_number: parseInt(prNumber)
|
|
})).data]
|
|
: await github.paginate(github.rest.pulls.list, {
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
state: 'open',
|
|
sort: 'updated',
|
|
direction: 'desc'
|
|
});
|
|
|
|
console.log(`Found ${prs.length} open PRs to check`);
|
|
|
|
// Constants for comment flood control
|
|
const UPDATE_COMMENT_COOLDOWN_DAYS = 7;
|
|
const COOLDOWN_MS = UPDATE_COMMENT_COOLDOWN_DAYS * 24 * 60 * 60 * 1000;
|
|
|
|
for (const pr of prs) {
|
|
// Skip bot PRs and drafts
|
|
if (pr.user.login.includes('[bot]')) {
|
|
console.log(`Skipping bot PR #${pr.number} from ${pr.user.login}`);
|
|
continue;
|
|
}
|
|
if (pr.draft) {
|
|
console.log(`Skipping draft PR #${pr.number}`);
|
|
continue;
|
|
}
|
|
|
|
try {
|
|
// Check if PR is behind main (base...head comparison)
|
|
const { data: comparison } = await github.rest.repos.compareCommits({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
base: pr.base.ref, // main branch
|
|
head: `${pr.head.repo.owner.login}:${pr.head.ref}` // Fully qualified ref for forks
|
|
});
|
|
|
|
if (comparison.behind_by > 0) {
|
|
console.log(`PR #${pr.number} is ${comparison.behind_by} commits behind ${pr.base.ref}`);
|
|
|
|
// Check if the PR allows maintainer edits
|
|
if (pr.maintainer_can_modify) {
|
|
// Try to update the branch
|
|
try {
|
|
await github.rest.pulls.updateBranch({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
pull_number: pr.number
|
|
});
|
|
|
|
console.log(`✅ Updated PR #${pr.number}`);
|
|
|
|
// Add a comment
|
|
await github.rest.issues.createComment({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
issue_number: pr.number,
|
|
body: `🔄 **Branch Updated**\n\nYour branch was ${comparison.behind_by} commits behind \`${pr.base.ref}\` and has been automatically updated. CI checks will re-run shortly.`
|
|
});
|
|
} catch (updateError) {
|
|
console.log(`Could not auto-update PR #${pr.number}: ${updateError.message}`);
|
|
|
|
// Determine the reason for failure
|
|
let failureReason = '';
|
|
if (updateError.status === 409 || updateError.message.includes('merge conflict')) {
|
|
failureReason = '\n\n**Note:** Automatic update failed due to merge conflicts. Please resolve them manually.';
|
|
} else if (updateError.status === 422) {
|
|
failureReason = '\n\n**Note:** Cannot push to fork. Please update manually.';
|
|
}
|
|
|
|
// Notify the contributor to update manually
|
|
await github.rest.issues.createComment({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
issue_number: pr.number,
|
|
body: `⚠️ **Branch Update Required**\n\nYour branch is ${comparison.behind_by} commits behind \`${pr.base.ref}\`.${failureReason}\n\nPlease update your branch:\n\n\`\`\`bash\ngit fetch origin ${pr.base.ref}\ngit merge origin/${pr.base.ref}\ngit push\n\`\`\`\n\nOr use GitHub's "Update branch" button if available.`
|
|
});
|
|
}
|
|
} else {
|
|
// Can't modify, just notify
|
|
console.log(`PR #${pr.number} doesn't allow maintainer edits`);
|
|
|
|
// Check if we already commented recently (within last 7 days)
|
|
const { data: comments } = await github.rest.issues.listComments({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
issue_number: pr.number,
|
|
since: new Date(Date.now() - COOLDOWN_MS).toISOString()
|
|
});
|
|
|
|
const hasRecentUpdateComment = comments.some(c =>
|
|
c.body?.includes('Branch Update Required') &&
|
|
c.user?.login === 'github-actions[bot]'
|
|
);
|
|
|
|
if (!hasRecentUpdateComment) {
|
|
await github.rest.issues.createComment({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
issue_number: pr.number,
|
|
body: `⚠️ **Branch Update Required**\n\nYour branch is ${comparison.behind_by} commits behind \`${pr.base.ref}\`. Please update your branch to ensure CI checks run with the latest code:\n\n\`\`\`bash\ngit fetch origin ${pr.base.ref}\ngit merge origin/${pr.base.ref}\ngit push\n\`\`\`\n\nNote: Enable "Allow edits by maintainers" to allow automatic updates.`
|
|
});
|
|
}
|
|
}
|
|
} else {
|
|
console.log(`PR #${pr.number} is up to date`);
|
|
}
|
|
} catch (error) {
|
|
console.error(`Error processing PR #${pr.number}:`, error.message);
|
|
}
|
|
}
|
|
|
|
// Log rate limit status
|
|
const { data: rateLimit } = await github.rest.rateLimit.get();
|
|
console.log(`API rate limit remaining: ${rateLimit.rate.remaining}/${rateLimit.rate.limit}`);
|