mirror of
https://github.com/comfyanonymous/ComfyUI.git
synced 2026-06-02 20:37:35 +08:00
Replace detector with thin caller of Comfy-Org/github-workflows
Shrinks this workflow from ~210 lines to ~20 by delegating the detection logic to a centralized reusable workflow in Comfy-Org/github-workflows. Future changes ship from that one repo and propagate here automatically.
This commit is contained in:
parent
e40cc8f286
commit
e3261c3e37
209
.github/workflows/detect-unreviewed-merge.yml
vendored
209
.github/workflows/detect-unreviewed-merge.yml
vendored
@ -1,5 +1,8 @@
|
|||||||
name: Detect Unreviewed Merge
|
name: Detect Unreviewed Merge
|
||||||
|
|
||||||
|
# SOC 2 compliance — reusable workflow lives in Comfy-Org/github-workflows,
|
||||||
|
# tracking issues are filed in Comfy-Org/unreviewed-merges.
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [master]
|
branches: [master]
|
||||||
@ -8,206 +11,10 @@ concurrency:
|
|||||||
group: detect-unreviewed-merge-${{ github.sha }}
|
group: detect-unreviewed-merge-${{ github.sha }}
|
||||||
cancel-in-progress: false
|
cancel-in-progress: false
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
pull-requests: read
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
detect:
|
detect:
|
||||||
runs-on: ubuntu-latest
|
uses: Comfy-Org/github-workflows/.github/workflows/detect-unreviewed-merge.yml@v1
|
||||||
steps:
|
with:
|
||||||
- name: Check for unreviewed merge
|
approval-mode: latest-per-reviewer
|
||||||
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0
|
secrets:
|
||||||
env:
|
UNREVIEWED_MERGES_TOKEN: ${{ secrets.UNREVIEWED_MERGES_TOKEN }}
|
||||||
UNREVIEWED_MERGES_TOKEN: ${{ secrets.UNREVIEWED_MERGES_TOKEN }}
|
|
||||||
with:
|
|
||||||
script: |
|
|
||||||
const sha = context.sha;
|
|
||||||
const { owner, repo } = context.repo;
|
|
||||||
const branch = context.ref.replace('refs/heads/', '');
|
|
||||||
|
|
||||||
// Find the PR associated with this merge commit
|
|
||||||
const { data: prs } = await github.rest.repos.listPullRequestsAssociatedWithCommit({
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
commit_sha: sha,
|
|
||||||
});
|
|
||||||
|
|
||||||
const pr = prs.find(p => p.merged_at && p.base.ref === branch);
|
|
||||||
if (!pr) {
|
|
||||||
core.info('No merged PR found for this commit — skipping.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
core.info(`Found PR #${pr.number}: ${pr.title}`);
|
|
||||||
|
|
||||||
// Fetch full PR to get merged_by (not in pull-request-simple schema)
|
|
||||||
const { data: fullPr } = await github.rest.pulls.get({
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
pull_number: pr.number,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Check for approving reviews using latest review per reviewer.
|
|
||||||
// DISMISSED approvals (from "dismiss stale reviews on new commits")
|
|
||||||
// are intentionally NOT counted — OSS PRs require current approval.
|
|
||||||
const reviews = await github.paginate(github.rest.pulls.listReviews, {
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
pull_number: pr.number,
|
|
||||||
});
|
|
||||||
|
|
||||||
const latestByReviewer = new Map();
|
|
||||||
for (const r of reviews) {
|
|
||||||
if (!r.user || r.state === 'COMMENTED') continue;
|
|
||||||
const prev = latestByReviewer.get(r.user.login);
|
|
||||||
if (!prev || new Date(r.submitted_at) > new Date(prev.submitted_at)) {
|
|
||||||
latestByReviewer.set(r.user.login, r);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const hasApproval = Array.from(latestByReviewer.values()).some(
|
|
||||||
r => r.state === 'APPROVED'
|
|
||||||
);
|
|
||||||
if (hasApproval) {
|
|
||||||
core.info('PR has an approving review — no action needed.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
core.info('No approving review found. Checking for inline justification...');
|
|
||||||
|
|
||||||
// Search PR body and author comments for "Justification: <reason>"
|
|
||||||
const pattern = /^justification:\s*(.+)$/im;
|
|
||||||
let justification = null;
|
|
||||||
|
|
||||||
if (pr.body) {
|
|
||||||
const match = pr.body.match(pattern);
|
|
||||||
if (match) justification = match[1].trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!justification) {
|
|
||||||
const comments = await github.paginate(github.rest.issues.listComments, {
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
issue_number: pr.number,
|
|
||||||
});
|
|
||||||
|
|
||||||
for (const c of comments) {
|
|
||||||
if (c.user && pr.user && c.user.login === pr.user.login) {
|
|
||||||
const match = c.body.match(pattern);
|
|
||||||
if (match) {
|
|
||||||
justification = match[1].trim();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const hasJustification = !!justification;
|
|
||||||
core.info(hasJustification
|
|
||||||
? `Found inline justification: ${justification}`
|
|
||||||
: 'No inline justification found.');
|
|
||||||
|
|
||||||
// Build issue body
|
|
||||||
const mergedBy = fullPr.merged_by ? fullPr.merged_by.login : 'unknown';
|
|
||||||
const table = [
|
|
||||||
'## Unreviewed Merge Detected',
|
|
||||||
'',
|
|
||||||
'| Field | Value |',
|
|
||||||
'|-------|-------|',
|
|
||||||
`| **Repository** | ${owner}/${repo} |`,
|
|
||||||
`| **PR** | ${owner}/${repo}#${pr.number} |`,
|
|
||||||
`| **Author** | @${pr.user.login} |`,
|
|
||||||
`| **Merged by** | @${mergedBy} |`,
|
|
||||||
`| **Merged at** | ${pr.merged_at} |`,
|
|
||||||
`| **Branch** | ${branch} |`,
|
|
||||||
];
|
|
||||||
|
|
||||||
const policyRef = [
|
|
||||||
'## Policy reference',
|
|
||||||
'',
|
|
||||||
'Per the Secure Development Policy (Emergency Change Procedures), changes merged',
|
|
||||||
'without prior approval must be justified and peer-reviewed within 1 business day.',
|
|
||||||
'Unresolved items older than 3 days are escalated to engineering leadership.',
|
|
||||||
];
|
|
||||||
|
|
||||||
let body;
|
|
||||||
if (hasJustification) {
|
|
||||||
body = [
|
|
||||||
...table,
|
|
||||||
'',
|
|
||||||
'## Justification (provided at merge time)',
|
|
||||||
'',
|
|
||||||
`> ${justification}`,
|
|
||||||
'',
|
|
||||||
'## Required actions',
|
|
||||||
'',
|
|
||||||
'- [x] **Justification** — Provided at merge time (see above)',
|
|
||||||
'- [ ] **Post-merge review** — A peer must review the PR and comment confirmation here (within 1 business day)',
|
|
||||||
'',
|
|
||||||
'Once both items are checked, close this issue.',
|
|
||||||
'',
|
|
||||||
...policyRef,
|
|
||||||
].join('\n');
|
|
||||||
} else {
|
|
||||||
body = [
|
|
||||||
...table,
|
|
||||||
'',
|
|
||||||
'## Required actions',
|
|
||||||
'',
|
|
||||||
'- [ ] **Justification** — Author must comment on this issue explaining why pre-merge approval was bypassed (within 1 business day)',
|
|
||||||
'- [ ] **Post-merge review** — A peer must review the PR and comment confirmation here (within 1 business day)',
|
|
||||||
'',
|
|
||||||
'Once both items are checked, close this issue.',
|
|
||||||
'',
|
|
||||||
...policyRef,
|
|
||||||
].join('\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create issue in the tracking repo with the dedicated PAT
|
|
||||||
if (!process.env.UNREVIEWED_MERGES_TOKEN) {
|
|
||||||
core.setFailed('UNREVIEWED_MERGES_TOKEN secret is not configured');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { getOctokit } = require('@actions/github');
|
|
||||||
const tracking = getOctokit(process.env.UNREVIEWED_MERGES_TOKEN);
|
|
||||||
|
|
||||||
const repoLabel = `repo:${repo.toLowerCase()}`;
|
|
||||||
const statusLabel = hasJustification ? 'needs-review' : 'needs-justification';
|
|
||||||
|
|
||||||
// Idempotency check — avoid duplicate issues on workflow re-runs
|
|
||||||
const { data: search } = await tracking.rest.search.issuesAndPullRequests({
|
|
||||||
q: `repo:Comfy-Org/unreviewed-merges in:title "PR #${pr.number}" label:${repoLabel}`,
|
|
||||||
});
|
|
||||||
if (search.total_count > 0) {
|
|
||||||
core.info(`Tracking issue already exists: ${search.items[0].html_url}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let issue;
|
|
||||||
try {
|
|
||||||
issue = await tracking.rest.issues.create({
|
|
||||||
owner: 'Comfy-Org',
|
|
||||||
repo: 'unreviewed-merges',
|
|
||||||
title: `[${repo}] PR #${pr.number} — ${pr.title}`,
|
|
||||||
body,
|
|
||||||
labels: ['unreviewed-merge', statusLabel, repoLabel],
|
|
||||||
assignees: [pr.user.login],
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
if (e.status === 422) {
|
|
||||||
core.warning(`Retrying without assignee: ${e.message}`);
|
|
||||||
issue = await tracking.rest.issues.create({
|
|
||||||
owner: 'Comfy-Org',
|
|
||||||
repo: 'unreviewed-merges',
|
|
||||||
title: `[${repo}] PR #${pr.number} — ${pr.title}`,
|
|
||||||
body,
|
|
||||||
labels: ['unreviewed-merge', statusLabel, repoLabel],
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
core.info(`Created tracking issue: ${issue.data.html_url}`);
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user