mirror of
https://github.com/garrytan/gstack.git
synced 2026-05-02 03:35:09 +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:
@@ -58,6 +58,7 @@ jobs:
|
||||
--bump "${{ steps.bump.outputs.level }}" \
|
||||
--current-version "${{ steps.versions.outputs.base_version }}" \
|
||||
--workspace-root null \
|
||||
--exclude-pr "${{ github.event.pull_request.number }}" \
|
||||
> next.json 2> next.err
|
||||
RC=$?
|
||||
if [ "$RC" != "0" ] || [ ! -s next.json ]; then
|
||||
|
||||
@@ -35,6 +35,7 @@ version-gate:
|
||||
--bump "$LEVEL" \
|
||||
--current-version "$BASE_VERSION" \
|
||||
--workspace-root null \
|
||||
--exclude-pr "$CI_MERGE_REQUEST_IID" \
|
||||
> next.json
|
||||
RC=$?
|
||||
if [ "$RC" != "0" ] || [ ! -s next.json ]; then
|
||||
|
||||
+33
-9
@@ -140,7 +140,7 @@ function readBaseVersion(base: string, warnings: string[]): string {
|
||||
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", [
|
||||
"pr",
|
||||
"list",
|
||||
@@ -175,9 +175,10 @@ async function fetchGithubClaimed(base: string, warnings: string[]): Promise<{ c
|
||||
// otherwise return our main's VERSION as a phantom claim.
|
||||
const viewer = runCommand("gh", ["repo", "view", "--json", "owner", "-q", ".owner.login"]);
|
||||
const myOwner = viewer.ok ? viewer.stdout.trim() : "";
|
||||
const sameRepoPRs = myOwner
|
||||
const sameRepoPRs = (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).
|
||||
const results: ClaimedPR[] = [];
|
||||
const queue = [...sameRepoPRs];
|
||||
@@ -214,7 +215,7 @@ async function fetchGithubClaimed(base: string, warnings: string[]): Promise<{ c
|
||||
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", [
|
||||
"mr",
|
||||
"list",
|
||||
@@ -237,6 +238,9 @@ async function fetchGitlabClaimed(base: string, warnings: string[]): Promise<{ c
|
||||
warnings.push(`glab mr list returned invalid JSON`);
|
||||
return { claimed: [], offline: true };
|
||||
}
|
||||
if (excludePR !== null) {
|
||||
mrs = mrs.filter((mr) => mr.iid !== excludePR);
|
||||
}
|
||||
const results: ClaimedPR[] = [];
|
||||
for (const mr of mrs) {
|
||||
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 bump: Bump | "" = "";
|
||||
let current = "";
|
||||
let workspaceRoot: string | undefined;
|
||||
let excludePR: number | null = null;
|
||||
let help = false;
|
||||
for (let i = 0; i < argv.length; 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 === "--current-version") current = 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;
|
||||
}
|
||||
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 (!bump) {
|
||||
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})`);
|
||||
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() {
|
||||
@@ -386,12 +405,17 @@ async function main() {
|
||||
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 offline = false;
|
||||
if (host === "github") {
|
||||
({ claimed, offline } = await fetchGithubClaimed(args.base, warnings));
|
||||
({ claimed, offline } = await fetchGithubClaimed(args.base, excludePR, warnings));
|
||||
} else if (host === "gitlab") {
|
||||
({ claimed, offline } = await fetchGitlabClaimed(args.base, warnings));
|
||||
({ claimed, offline } = await fetchGitlabClaimed(args.base, excludePR, warnings));
|
||||
} else {
|
||||
warnings.push("host unknown; queue-awareness unavailable");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user