gh-stack 0.19.7 → 0.19.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/package.json +1 -1
- package/src/commands/merge.ts +24 -2
package/README.md
CHANGED
|
@@ -289,7 +289,9 @@ merge --base [--dry-run] [-d|--delete-branch] [--no-push|--push]
|
|
|
289
289
|
via GitHub (PR3 → PR2 → PR1), then enables auto-merge for the base PR
|
|
290
290
|
into main. The whole stack lands on main as a single squash commit.
|
|
291
291
|
All merges happen on GitHub so PRs show as "Merged", Linear tickets
|
|
292
|
-
close automatically, and GitHub Actions fire normally.
|
|
292
|
+
close automatically, and GitHub Actions fire normally. Squash commit
|
|
293
|
+
subjects leave out the ` (N/M)` stack position that `submit` adds to
|
|
294
|
+
PR titles. Skips already-
|
|
293
295
|
merged PRs (safe to re-run). Waits for GitHub between merges, and
|
|
294
296
|
self-heals its spurious "Head branch is out of date" (a stale PR
|
|
295
297
|
head pointer after a child squash) by re-syncing and retrying. Before
|
package/package.json
CHANGED
package/src/commands/merge.ts
CHANGED
|
@@ -25,6 +25,7 @@ import type { Stack } from "../types.ts";
|
|
|
25
25
|
import { confirmAction, isAutoYes } from "../lib/ui.ts";
|
|
26
26
|
import { guardRemoteContent } from "../lib/remote-guard.ts";
|
|
27
27
|
import { ensureNoPendingRun } from "./restack.ts";
|
|
28
|
+
import { stripSeqSuffix } from "./update-prs.ts";
|
|
28
29
|
import {
|
|
29
30
|
planMerges,
|
|
30
31
|
advancePastMerged,
|
|
@@ -62,6 +63,24 @@ export function isTransientMergeError(stderr: string): boolean {
|
|
|
62
63
|
);
|
|
63
64
|
}
|
|
64
65
|
|
|
66
|
+
/**
|
|
67
|
+
* The squash commit subject for a PR titled `title`, without the ` (N/M)`
|
|
68
|
+
* stack position `submit` adds to PR titles, which GitHub would otherwise put
|
|
69
|
+
* on the trunk's commit. Null when the title has no position, so GitHub's own
|
|
70
|
+
* "<title> (#N)" stands.
|
|
71
|
+
*/
|
|
72
|
+
export function squashSubject(title: string, prNumber: number): string | null {
|
|
73
|
+
const base = stripSeqSuffix(title);
|
|
74
|
+
return base === title.trimEnd() ? null : `${base} (#${prNumber})`;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** `gh pr merge` args setting the squash subject (see squashSubject); empty to keep GitHub's. */
|
|
78
|
+
async function squashSubjectArgs(prNumber: number): Promise<string[]> {
|
|
79
|
+
const title = (await getPrInfo(prNumber))?.title;
|
|
80
|
+
const subject = title ? squashSubject(title, prNumber) : null;
|
|
81
|
+
return subject ? ["--subject", subject] : [];
|
|
82
|
+
}
|
|
83
|
+
|
|
65
84
|
/**
|
|
66
85
|
* Squash-merge a PR, healing the two timing artifacts a top-down cascade
|
|
67
86
|
* provokes — neither of which is a real conflict:
|
|
@@ -87,8 +106,9 @@ async function squashMergeWithRetry(
|
|
|
87
106
|
s: ReturnType<typeof p.spinner>,
|
|
88
107
|
): Promise<{ ok: true } | { ok: false; stderr: string; genuine: boolean }> {
|
|
89
108
|
const maxAttempts = MERGE_RETRY_BACKOFF_MS.length + 1;
|
|
109
|
+
const subject = await squashSubjectArgs(prNumber);
|
|
90
110
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
91
|
-
const ghArgs = ["pr", "merge", String(prNumber), "--squash"];
|
|
111
|
+
const ghArgs = ["pr", "merge", String(prNumber), "--squash", ...subject];
|
|
92
112
|
if (deleteFlag) ghArgs.push("--delete-branch");
|
|
93
113
|
|
|
94
114
|
const proc = Bun.spawn(["gh", ...ghArgs], { stdout: "pipe", stderr: "pipe" });
|
|
@@ -423,7 +443,8 @@ OPTIONS
|
|
|
423
443
|
: "Single branch stack — enabling auto-merge on GitHub.",
|
|
424
444
|
);
|
|
425
445
|
if (!dryRun) {
|
|
426
|
-
const
|
|
446
|
+
const subject = await squashSubjectArgs(basePr);
|
|
447
|
+
const auto = await $`gh pr merge ${basePr} --squash --auto ${subject}`.nothrow().quiet();
|
|
427
448
|
if (auto.exitCode === 0) {
|
|
428
449
|
p.log.success(`Auto-merge enabled for PR #${basePr}`);
|
|
429
450
|
} else {
|
|
@@ -712,6 +733,7 @@ OPTIONS
|
|
|
712
733
|
|
|
713
734
|
const enableAutoMerge = async (): Promise<{ ok: boolean; stderr: string }> => {
|
|
714
735
|
const ghArgs = ["pr", "merge", String(basePr), "--squash", "--auto"];
|
|
736
|
+
if (basePr != null) ghArgs.push(...(await squashSubjectArgs(basePr)));
|
|
715
737
|
if (deleteFlag) ghArgs.push("--delete-branch");
|
|
716
738
|
const proc = Bun.spawn(["gh", ...ghArgs], { stdout: "pipe", stderr: "pipe" });
|
|
717
739
|
const stderr = await new Response(proc.stderr).text();
|