diff --git a/scripts/forgejo-pr-automerge.mjs b/scripts/forgejo-pr-automerge.mjs index b1ac0f1..ea1b9d8 100644 --- a/scripts/forgejo-pr-automerge.mjs +++ b/scripts/forgejo-pr-automerge.mjs @@ -46,6 +46,10 @@ try { console.log(`[forgejo-pr-automerge] PR #${prNumber} já Ready`) } const merged = await api.autoMerge(prNumber, { + // The workflow job that runs this script reports its own status on the + // PR head sha — ignoring it avoids the self-wait deadlock. The required + // `checks` context is enforced by waitForChecks. + ignoreContexts: ['ready-automerge'], log: (line) => console.log(`[forgejo-pr-automerge] ${line}`), }) console.log( diff --git a/scripts/lib/forgejo-api.mjs b/scripts/lib/forgejo-api.mjs index 2349382..caf1de5 100644 --- a/scripts/lib/forgejo-api.mjs +++ b/scripts/lib/forgejo-api.mjs @@ -271,22 +271,32 @@ export const createApi = ({ base, token, repository, fetchImpl } = {}) => { body: { ...payload, branch_name: branch }, }), - /** All required statuses on the PR head are success (or a failure exists). */ - waitForChecks: async (number, { timeoutMs = 30 * 60 * 1000, pollMs = 15000, log } = {}) => { + /** + * All required statuses on the PR head are success (or a failure exists). + * `ignoreContexts` excludes a job's OWN status — a workflow that merges a + * PR sees itself as `pending` on the head sha and would deadlock waiting + * for itself. `requiredContext` (default `checks`) must be success. + */ + waitForChecks: async ( + number, + { timeoutMs = 30 * 60 * 1000, pollMs = 15000, log, ignoreContexts = [], requiredContext = 'checks' } = {}, + ) => { const started = Date.now() for (;;) { const pr = await api.getPullRequest(number) if (!pr || pr.state !== 'OPEN' || pr.merged) return pr const statuses = await api.listCommitStatuses(pr.head.sha) - const failed = statuses.filter( + const relevant = statuses.filter((entry) => !ignoreContexts.some((c) => entry.context.includes(c))) + const failed = relevant.filter( (entry) => entry.state === 'failure' || entry.state === 'error', ) - const pending = statuses.filter((entry) => entry.state === 'pending') + const pending = relevant.filter((entry) => entry.state === 'pending') if (failed.length > 0) { const names = failed.map((entry) => entry.context).join(', ') throw new Error(`Checks falharam no PR #${number}: ${names}`) } - if (statuses.length > 0 && pending.length === 0) return pr + const required = relevant.find((entry) => entry.context === requiredContext) + if (required && required.state === 'success' && pending.length === 0) return pr if (Date.now() - started > timeoutMs) { throw new Error(`Timeout esperando checks do PR #${number}.`) }