Skip to content

Commit ef7b4d3

Browse files
committed
ci(companion): aggregate companions across bundled feature PRs on prod releases
Mirror of copilot: on staging->main release PRs, aggregate Companion declarations from every squashed feature PR in the release (deduped). dev->staging stays single-PR.
1 parent 4a4205d commit ef7b4d3

1 file changed

Lines changed: 39 additions & 3 deletions

File tree

.github/workflows/companion-pr-check.yml

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,43 @@ jobs:
102102
}
103103
}
104104
105-
async function checkPR(pr) {
105+
// staging PRs are a single feature → just this PR's body ("the one").
106+
// main (prod) release PRs bundle MANY feature PRs → aggregate the
107+
// companions declared on each squashed feature PR too, so "does any
108+
// commit in this release have a companion?" is answered.
109+
async function collectCompanions(pr) {
106110
const companions = parseCompanions(pr.body);
111+
const seen = new Set(companions.map((c) => c.ref));
112+
if (pr.base.ref === 'main') {
113+
let commits = [];
114+
try {
115+
commits = await github.paginate(github.rest.pulls.listCommits, {
116+
owner, repo, pull_number: pr.number, per_page: 100,
117+
});
118+
} catch {}
119+
const featurePRs = new Set();
120+
const SQUASH = /\(#(\d+)\)/g; // squash-merge refs like "...(#306)"
121+
for (const c of commits) {
122+
const msg = (c.commit && c.commit.message) || '';
123+
let m;
124+
SQUASH.lastIndex = 0;
125+
while ((m = SQUASH.exec(msg)) !== null) featurePRs.add(Number(m[1]));
126+
}
127+
for (const n of featurePRs) {
128+
if (n === pr.number) continue;
129+
try {
130+
const { data: fpr } = await github.rest.pulls.get({ owner, repo, pull_number: n });
131+
for (const c of parseCompanions(fpr.body)) {
132+
if (!seen.has(c.ref)) { seen.add(c.ref); companions.push(c); }
133+
}
134+
} catch {}
135+
}
136+
}
137+
return companions;
138+
}
139+
140+
async function checkPR(pr) {
141+
const companions = await collectCompanions(pr);
107142
if (companions.length === 0) { await clear(pr.number); return; }
108143
await ensureLabel();
109144
try { await github.rest.issues.addLabels({ owner, repo, issue_number: pr.number, labels: ['has-companion'] }); } catch {}
@@ -134,9 +169,10 @@ jobs:
134169
}
135170
}
136171
const heading = warn ? '## ⚠️ Cross-repo companion check' : '## ✅ Cross-repo companion check';
172+
const scope = base === 'main' ? ' (aggregated across the feature PRs in this release)' : '';
137173
const note = warn
138-
? `One or more companion PRs aren't merged into \`${base}\` yet. Merging this without them will leave copilot and sim out of sync — merge them in lockstep.`
139-
: `All declared companion PRs are merged into \`${base}\`.`;
174+
? `One or more companion PRs aren't merged into \`${base}\` yet${scope}. Merging this without them will leave copilot and sim out of sync — merge them in lockstep.`
175+
: `All declared companion PRs are merged into \`${base}\`${scope}.`;
140176
await upsert(pr.number, `${STICKY}\n${heading}\n\n${note}\n\n${lines.join('\n')}`);
141177
}
142178

0 commit comments

Comments
 (0)