mirror of
https://github.com/garrytan/gstack.git
synced 2026-08-07 23:08:36 +02:00
fix(ship): exclude current PR from queue-awareness (self-reference bug)
Version gate flagged PR #1168 as stale because the util counted the PR itself as a queued claim. The exclude filter removes that self-reference. New --exclude-pr <N> flag on bin/gstack-next-version. CI workflows pass github.event.pull_request.number / CI_MERGE_REQUEST_IID. Local /ship auto-detects via gh pr view when the flag isn't passed, with a warning recording the auto-exclusion so it's observable. Caught during the first live ship through the v1.8.0.0 gate — the kind of dogfood the whole release is designed for. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
bd39b6995f
commit
416a56a5c8
@@ -58,6 +58,7 @@ jobs:
|
|||||||
--bump "${{ steps.bump.outputs.level }}" \
|
--bump "${{ steps.bump.outputs.level }}" \
|
||||||
--current-version "${{ steps.versions.outputs.base_version }}" \
|
--current-version "${{ steps.versions.outputs.base_version }}" \
|
||||||
--workspace-root null \
|
--workspace-root null \
|
||||||
|
--exclude-pr "${{ github.event.pull_request.number }}" \
|
||||||
> next.json 2> next.err
|
> next.json 2> next.err
|
||||||
RC=$?
|
RC=$?
|
||||||
if [ "$RC" != "0" ] || [ ! -s next.json ]; then
|
if [ "$RC" != "0" ] || [ ! -s next.json ]; then
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ version-gate:
|
|||||||
--bump "$LEVEL" \
|
--bump "$LEVEL" \
|
||||||
--current-version "$BASE_VERSION" \
|
--current-version "$BASE_VERSION" \
|
||||||
--workspace-root null \
|
--workspace-root null \
|
||||||
|
--exclude-pr "$CI_MERGE_REQUEST_IID" \
|
||||||
> next.json
|
> next.json
|
||||||
RC=$?
|
RC=$?
|
||||||
if [ "$RC" != "0" ] || [ ! -s next.json ]; then
|
if [ "$RC" != "0" ] || [ ! -s next.json ]; then
|
||||||
|
|||||||
+33
-9
@@ -140,7 +140,7 @@ function readBaseVersion(base: string, warnings: string[]): string {
|
|||||||
return r.stdout.trim();
|
return r.stdout.trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchGithubClaimed(base: string, warnings: string[]): Promise<{ claimed: ClaimedPR[]; offline: boolean }> {
|
async function fetchGithubClaimed(base: string, excludePR: number | null, warnings: string[]): Promise<{ claimed: ClaimedPR[]; offline: boolean }> {
|
||||||
const list = runCommand("gh", [
|
const list = runCommand("gh", [
|
||||||
"pr",
|
"pr",
|
||||||
"list",
|
"list",
|
||||||
@@ -175,9 +175,10 @@ async function fetchGithubClaimed(base: string, warnings: string[]): Promise<{ c
|
|||||||
// otherwise return our main's VERSION as a phantom claim.
|
// otherwise return our main's VERSION as a phantom claim.
|
||||||
const viewer = runCommand("gh", ["repo", "view", "--json", "owner", "-q", ".owner.login"]);
|
const viewer = runCommand("gh", ["repo", "view", "--json", "owner", "-q", ".owner.login"]);
|
||||||
const myOwner = viewer.ok ? viewer.stdout.trim() : "";
|
const myOwner = viewer.ok ? viewer.stdout.trim() : "";
|
||||||
const sameRepoPRs = myOwner
|
const sameRepoPRs = (myOwner
|
||||||
? prs.filter((p) => (p.headRepositoryOwner?.login ?? "") === myOwner)
|
? prs.filter((p) => (p.headRepositoryOwner?.login ?? "") === myOwner)
|
||||||
: prs;
|
: prs
|
||||||
|
).filter((p) => excludePR === null || p.number !== excludePR);
|
||||||
// Fetch each PR's VERSION at its head in parallel (bounded concurrency).
|
// Fetch each PR's VERSION at its head in parallel (bounded concurrency).
|
||||||
const results: ClaimedPR[] = [];
|
const results: ClaimedPR[] = [];
|
||||||
const queue = [...sameRepoPRs];
|
const queue = [...sameRepoPRs];
|
||||||
@@ -214,7 +215,7 @@ async function fetchGithubClaimed(base: string, warnings: string[]): Promise<{ c
|
|||||||
return { claimed: results, offline: false };
|
return { claimed: results, offline: false };
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchGitlabClaimed(base: string, warnings: string[]): Promise<{ claimed: ClaimedPR[]; offline: boolean }> {
|
async function fetchGitlabClaimed(base: string, excludePR: number | null, warnings: string[]): Promise<{ claimed: ClaimedPR[]; offline: boolean }> {
|
||||||
const list = runCommand("glab", [
|
const list = runCommand("glab", [
|
||||||
"mr",
|
"mr",
|
||||||
"list",
|
"list",
|
||||||
@@ -237,6 +238,9 @@ async function fetchGitlabClaimed(base: string, warnings: string[]): Promise<{ c
|
|||||||
warnings.push(`glab mr list returned invalid JSON`);
|
warnings.push(`glab mr list returned invalid JSON`);
|
||||||
return { claimed: [], offline: true };
|
return { claimed: [], offline: true };
|
||||||
}
|
}
|
||||||
|
if (excludePR !== null) {
|
||||||
|
mrs = mrs.filter((mr) => mr.iid !== excludePR);
|
||||||
|
}
|
||||||
const results: ClaimedPR[] = [];
|
const results: ClaimedPR[] = [];
|
||||||
for (const mr of mrs) {
|
for (const mr of mrs) {
|
||||||
const content = runCommand("glab", [
|
const content = runCommand("glab", [
|
||||||
@@ -342,11 +346,12 @@ function markActiveSiblings(siblings: Sibling[], baseVersion: Version): Sibling[
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseArgs(argv: string[]): { base: string; bump: Bump; current: string; workspaceRoot?: string; help: boolean } {
|
function parseArgs(argv: string[]): { base: string; bump: Bump; current: string; workspaceRoot?: string; excludePR: number | null; help: boolean } {
|
||||||
let base = "";
|
let base = "";
|
||||||
let bump: Bump | "" = "";
|
let bump: Bump | "" = "";
|
||||||
let current = "";
|
let current = "";
|
||||||
let workspaceRoot: string | undefined;
|
let workspaceRoot: string | undefined;
|
||||||
|
let excludePR: number | null = null;
|
||||||
let help = false;
|
let help = false;
|
||||||
for (let i = 0; i < argv.length; i++) {
|
for (let i = 0; i < argv.length; i++) {
|
||||||
const a = argv[i];
|
const a = argv[i];
|
||||||
@@ -354,9 +359,13 @@ function parseArgs(argv: string[]): { base: string; bump: Bump; current: string;
|
|||||||
else if (a === "--bump") bump = (argv[++i] ?? "") as Bump;
|
else if (a === "--bump") bump = (argv[++i] ?? "") as Bump;
|
||||||
else if (a === "--current-version") current = argv[++i] ?? "";
|
else if (a === "--current-version") current = argv[++i] ?? "";
|
||||||
else if (a === "--workspace-root") workspaceRoot = argv[++i];
|
else if (a === "--workspace-root") workspaceRoot = argv[++i];
|
||||||
|
else if (a === "--exclude-pr") {
|
||||||
|
const n = Number(argv[++i]);
|
||||||
|
excludePR = Number.isFinite(n) && n > 0 ? n : null;
|
||||||
|
}
|
||||||
else if (a === "-h" || a === "--help") help = true;
|
else if (a === "-h" || a === "--help") help = true;
|
||||||
}
|
}
|
||||||
if (help) return { base: "", bump: "micro", current: "", help: true };
|
if (help) return { base: "", bump: "micro", current: "", excludePR: null, help: true };
|
||||||
if (!base) base = "main";
|
if (!base) base = "main";
|
||||||
if (!bump) {
|
if (!bump) {
|
||||||
console.error("Error: --bump is required (major|minor|patch|micro)");
|
console.error("Error: --bump is required (major|minor|patch|micro)");
|
||||||
@@ -366,7 +375,17 @@ function parseArgs(argv: string[]): { base: string; bump: Bump; current: string;
|
|||||||
console.error(`Error: --bump must be major|minor|patch|micro (got ${bump})`);
|
console.error(`Error: --bump must be major|minor|patch|micro (got ${bump})`);
|
||||||
process.exit(2);
|
process.exit(2);
|
||||||
}
|
}
|
||||||
return { base, bump: bump as Bump, current, workspaceRoot, help: false };
|
return { base, bump: bump as Bump, current, workspaceRoot, excludePR, help: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auto-detect: if --exclude-pr wasn't passed, check whether the current branch
|
||||||
|
// already has an open PR and exclude it by default. This prevents the self-
|
||||||
|
// reference bug where /ship's own PR inflates the queue on rerun.
|
||||||
|
function autoDetectExcludePR(): number | null {
|
||||||
|
const r = runCommand("gh", ["pr", "view", "--json", "number", "-q", ".number"]);
|
||||||
|
if (!r.ok) return null;
|
||||||
|
const n = Number(r.stdout.trim());
|
||||||
|
return Number.isFinite(n) && n > 0 ? n : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
@@ -386,12 +405,17 @@ async function main() {
|
|||||||
process.exit(2);
|
process.exit(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const excludePR = args.excludePR ?? autoDetectExcludePR();
|
||||||
|
if (excludePR !== null && args.excludePR === null) {
|
||||||
|
warnings.push(`auto-excluded PR #${excludePR} (current branch's own PR)`);
|
||||||
|
}
|
||||||
|
|
||||||
let claimed: ClaimedPR[] = [];
|
let claimed: ClaimedPR[] = [];
|
||||||
let offline = false;
|
let offline = false;
|
||||||
if (host === "github") {
|
if (host === "github") {
|
||||||
({ claimed, offline } = await fetchGithubClaimed(args.base, warnings));
|
({ claimed, offline } = await fetchGithubClaimed(args.base, excludePR, warnings));
|
||||||
} else if (host === "gitlab") {
|
} else if (host === "gitlab") {
|
||||||
({ claimed, offline } = await fetchGitlabClaimed(args.base, warnings));
|
({ claimed, offline } = await fetchGitlabClaimed(args.base, excludePR, warnings));
|
||||||
} else {
|
} else {
|
||||||
warnings.push("host unknown; queue-awareness unavailable");
|
warnings.push("host unknown; queue-awareness unavailable");
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user