mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
cea83bd8d5
* Bump Microsoft.Extensions.AI packages to 10.6.0 * Align transitive package versions for Microsoft.Extensions.AI 10.6.0 * Ignore external review check in Merge Gatekeeper --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Roger Barreto <19890735+rogerbarreto@users.noreply.github.com>
118 lines
4.7 KiB
YAML
118 lines
4.7 KiB
YAML
name: Merge Gatekeeper
|
|
|
|
on:
|
|
pull_request:
|
|
branches: ["main", "feature*"]
|
|
merge_group:
|
|
branches: ["main"]
|
|
|
|
concurrency:
|
|
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
jobs:
|
|
merge-gatekeeper:
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
checks: read
|
|
statuses: read
|
|
steps:
|
|
- name: Wait for required checks
|
|
if: github.event_name == 'pull_request'
|
|
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
|
env:
|
|
TIMEOUT_SECONDS: "3600"
|
|
INTERVAL_SECONDS: "30"
|
|
SELF_JOB_NAME: ${{ github.job }}
|
|
# "Cleanup artifacts", "Agent", "Prepare", and "Upload results" are check runs
|
|
# created by an org-level GitHub App (MSDO), not by any workflow in this repo.
|
|
# They are outside our control and their transient failures should not block merges.
|
|
IGNORED_NAMES: "CodeQL,CodeQL analysis (csharp),Cleanup artifacts,Agent,Prepare,Upload results,review"
|
|
with:
|
|
script: |
|
|
const timeoutSeconds = Number(process.env.TIMEOUT_SECONDS);
|
|
const intervalSeconds = Number(process.env.INTERVAL_SECONDS);
|
|
const selfName = process.env.SELF_JOB_NAME;
|
|
const ignored = new Set(
|
|
process.env.IGNORED_NAMES.split(',').map((s) => s.trim()).filter(Boolean),
|
|
);
|
|
|
|
const sha = context.payload.pull_request.head.sha;
|
|
const { owner, repo } = context.repo;
|
|
|
|
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
|
|
// Mirrors upsidr/merge-gatekeeper: merge combined-statuses and check-runs
|
|
// for the PR head SHA, with combined-statuses winning on name collision.
|
|
async function collectChecks() {
|
|
const merged = new Map();
|
|
|
|
const combined = await github.rest.repos.getCombinedStatusForRef({
|
|
owner, repo, ref: sha, per_page: 100,
|
|
});
|
|
for (const s of combined.data.statuses ?? []) {
|
|
if (!merged.has(s.context)) {
|
|
// Combined-status states: success | pending | error | failure
|
|
merged.set(s.context, { name: s.context, state: s.state });
|
|
}
|
|
}
|
|
|
|
const runs = await github.paginate(github.rest.checks.listForRef, {
|
|
owner, repo, ref: sha, per_page: 100,
|
|
});
|
|
for (const r of runs) {
|
|
if (merged.has(r.name)) continue;
|
|
let state;
|
|
if (r.status !== 'completed') {
|
|
state = 'pending';
|
|
} else if (r.conclusion === 'skipped') {
|
|
continue; // Skipped runs are dropped, matching the original action.
|
|
} else if (r.conclusion === 'success' || r.conclusion === 'neutral') {
|
|
state = 'success';
|
|
} else {
|
|
// cancelled | timed_out | action_required | stale | failure
|
|
state = 'error';
|
|
}
|
|
merged.set(r.name, { name: r.name, state });
|
|
}
|
|
|
|
return [...merged.values()];
|
|
}
|
|
|
|
function evaluate(entries) {
|
|
const failed = [];
|
|
const pending = [];
|
|
const succeeded = [];
|
|
for (const e of entries) {
|
|
if (e.name === selfName || ignored.has(e.name)) continue;
|
|
if (e.state === 'success') succeeded.push(e.name);
|
|
else if (e.state === 'error' || e.state === 'failure') failed.push(e.name);
|
|
else pending.push(e.name);
|
|
}
|
|
return { failed, pending, succeeded };
|
|
}
|
|
|
|
const deadline = Date.now() + timeoutSeconds * 1000;
|
|
for (;;) {
|
|
const entries = await collectChecks();
|
|
const { failed, pending, succeeded } = evaluate(entries);
|
|
|
|
core.info(
|
|
`succeeded=${succeeded.length} pending=${pending.length} failed=${failed.length}`,
|
|
);
|
|
if (failed.length) {
|
|
core.setFailed(`Failing checks: ${failed.join(', ')}`);
|
|
return;
|
|
}
|
|
if (pending.length === 0) {
|
|
core.info(`All required checks passed: ${succeeded.join(', ') || '(none)'}`);
|
|
return;
|
|
}
|
|
if (Date.now() > deadline) {
|
|
core.setFailed(`Timed out waiting for: ${pending.join(', ')}`);
|
|
return;
|
|
}
|
|
core.info(`Waiting on (${pending.length}): ${pending.slice(0, 10).join(', ')}${pending.length > 10 ? ', …' : ''}`);
|
|
await sleep(intervalSeconds * 1000);
|
|
}
|