mirror of
https://github.com/TheAlgorithms/Java.git
synced 2025-12-19 07:00:35 +08:00
* chore: add workflow to close stale PRs with failed workflows * Include workflow failures when closing stale PRs * Filter meaningful commits to only include those within the last 14 days
157 lines
6.5 KiB
YAML
157 lines
6.5 KiB
YAML
name: Close stale PRs with failed workflows
|
|
|
|
on:
|
|
schedule:
|
|
- cron: '0 3 * * *' # runs daily at 03:00 UTC
|
|
workflow_dispatch:
|
|
|
|
permissions:
|
|
contents: read
|
|
issues: write
|
|
pull-requests: write
|
|
|
|
jobs:
|
|
close-stale:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Close stale PRs
|
|
uses: actions/github-script@v8
|
|
with:
|
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
script: |
|
|
const mainBranches = ['main', 'master'];
|
|
const cutoffDays = 14;
|
|
const cutoff = new Date();
|
|
cutoff.setDate(cutoff.getDate() - cutoffDays);
|
|
|
|
console.log(`Checking PRs older than: ${cutoff.toISOString()}`);
|
|
|
|
try {
|
|
const { data: prs } = await github.rest.pulls.list({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
state: 'open',
|
|
sort: 'updated',
|
|
direction: 'asc',
|
|
per_page: 100
|
|
});
|
|
|
|
console.log(`Found ${prs.length} open PRs to check`);
|
|
|
|
for (const pr of prs) {
|
|
try {
|
|
const updated = new Date(pr.updated_at);
|
|
|
|
if (updated > cutoff) {
|
|
console.log(`⏩ Skipping PR #${pr.number} - updated recently`);
|
|
continue;
|
|
}
|
|
|
|
console.log(`🔍 Checking PR #${pr.number}: "${pr.title}"`);
|
|
|
|
// Get commits
|
|
const commits = await github.paginate(github.rest.pulls.listCommits, {
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
pull_number: pr.number,
|
|
per_page: 100
|
|
});
|
|
|
|
const meaningfulCommits = commits.filter(c => {
|
|
const msg = c.commit.message.toLowerCase();
|
|
const date = new Date(c.commit.committer.date);
|
|
const isMergeFromMain = mainBranches.some(branch =>
|
|
msg.startsWith(`merge branch '${branch}'`) ||
|
|
msg.includes(`merge remote-tracking branch '${branch}'`)
|
|
);
|
|
|
|
return !isMergeFromMain && date > cutoff;
|
|
});
|
|
|
|
// Get checks with error handling
|
|
let hasFailedChecks = false;
|
|
let allChecksCompleted = false;
|
|
let hasChecks = false;
|
|
|
|
try {
|
|
const { data: checks } = await github.rest.checks.listForRef({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
ref: pr.head.sha
|
|
});
|
|
|
|
hasChecks = checks.check_runs.length > 0;
|
|
hasFailedChecks = checks.check_runs.some(c => c.conclusion === 'failure');
|
|
allChecksCompleted = checks.check_runs.every(c =>
|
|
c.status === 'completed' || c.status === 'skipped'
|
|
);
|
|
} catch (error) {
|
|
console.log(`⚠️ Could not fetch checks for PR #${pr.number}: ${error.message}`);
|
|
}
|
|
|
|
// Get workflow runs with error handling
|
|
let hasFailedWorkflows = false;
|
|
let allWorkflowsCompleted = false;
|
|
let hasWorkflows = false;
|
|
|
|
try {
|
|
const { data: runs } = await github.rest.actions.listWorkflowRuns({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
head_sha: pr.head.sha,
|
|
per_page: 50
|
|
});
|
|
|
|
hasWorkflows = runs.workflow_runs.length > 0;
|
|
hasFailedWorkflows = runs.workflow_runs.some(r => r.conclusion === 'failure');
|
|
allWorkflowsCompleted = runs.workflow_runs.every(r =>
|
|
['completed', 'skipped', 'cancelled'].includes(r.status)
|
|
);
|
|
|
|
console.log(`PR #${pr.number}: ${runs.workflow_runs.length} workflow runs found`);
|
|
|
|
} catch (error) {
|
|
console.log(`⚠️ Could not fetch workflow runs for PR #${pr.number}: ${error.message}`);
|
|
}
|
|
|
|
console.log(`PR #${pr.number}: ${meaningfulCommits.length} meaningful commits`);
|
|
console.log(`Checks - has: ${hasChecks}, failed: ${hasFailedChecks}, completed: ${allChecksCompleted}`);
|
|
console.log(`Workflows - has: ${hasWorkflows}, failed: ${hasFailedWorkflows}, completed: ${allWorkflowsCompleted}`);
|
|
|
|
// Combine conditions - only consider if we actually have checks/workflows
|
|
const hasAnyFailure = (hasChecks && hasFailedChecks) || (hasWorkflows && hasFailedWorkflows);
|
|
const allCompleted = (!hasChecks || allChecksCompleted) && (!hasWorkflows || allWorkflowsCompleted);
|
|
|
|
if (meaningfulCommits.length === 0 && hasAnyFailure && allCompleted) {
|
|
console.log(`✅ Closing PR #${pr.number} (${pr.title})`);
|
|
|
|
await github.rest.issues.createComment({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
issue_number: pr.number,
|
|
body: `This pull request has been automatically closed because its workflows or checks failed and it has been inactive for more than ${cutoffDays} days. Please fix the workflows and reopen if you'd like to continue. Merging from main/master alone does not count as activity.`
|
|
});
|
|
|
|
await github.rest.pulls.update({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
pull_number: pr.number,
|
|
state: 'closed'
|
|
});
|
|
|
|
console.log(`✅ Successfully closed PR #${pr.number}`);
|
|
} else {
|
|
console.log(`⏩ Not closing PR #${pr.number} - conditions not met`);
|
|
}
|
|
|
|
} catch (prError) {
|
|
console.error(`❌ Error processing PR #${pr.number}: ${prError.message}`);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error(`❌ Fatal error: ${error.message}`);
|
|
throw error;
|
|
}
|