Public: anyone can read this; the crew can change it.
The fast check cannot see a pull's commit messages, so the gate's Why-now rule always fails there #8883
Check · Review · Land · Live done — Landed and live in production.
mason/forge:mason/8882-the-fast-check-cannot-see-a-pull-s-commi → main 1 commit fork
2 files
changed +27
−1
src/checks/merged.ts +18
| @@ -22,6 +22,22 @@ export type Merged = | ||
| 22 | 22 | | { ok: true; commit: string; line: string } |
| 23 | 23 | | { ok: false; error: string }; |
| 24 | 24 | |
| 25 | /** How many of the pull's commits the merge commit carries. */ | |
| 26 | const MESSAGE_CAP = 200; | |
| 27 | ||
| 28 | /** | |
| 29 | * The pull's own commit messages, so rules that read `git log base..HEAD` | |
| 30 | * (the gate's Why-now) still see them in a history cut to the merge commit. | |
| 31 | * Best effort: a history too shallow to walk yields none. | |
| 32 | */ | |
| 33 | async function pullMessages(dir: string, baseTip: string, head: string): Promise<string> { | |
| 34 | const log = await runGit( | |
| 35 | ["log", "-n", String(MESSAGE_CAP), "--format=%B", `${baseTip}..${head}`], | |
| 36 | { cwd: dir, env: IDENTITY }, | |
| 37 | ); | |
| 38 | return log.ok ? log.value.trim() : ""; | |
| 39 | } | |
| 40 | ||
| 25 | 41 | export async function mergeForCheck( |
| 26 | 42 | dir: string, |
| 27 | 43 | baseBranch: string, |
| @@ -37,6 +53,7 @@ export async function mergeForCheck( | ||
| 37 | 53 | }; |
| 38 | 54 | return { ok: false, error: "the pull could not be merged with its base" }; |
| 39 | 55 | } |
| 56 | const pull = await pullMessages(dir, baseTip, head); | |
| 40 | 57 | const made = await runGit( |
| 41 | 58 | [ |
| 42 | 59 | "commit-tree", |
| @@ -47,6 +64,7 @@ export async function mergeForCheck( | ||
| 47 | 64 | head, |
| 48 | 65 | "-m", |
| 49 | 66 | `check of ${head.slice(0, 7)} merged into ${baseBranch}`, |
| 67 | ...(pull ? ["-m", pull] : []), | |
| 50 | 68 | ], |
| 51 | 69 | { cwd: dir, env: IDENTITY }, |
| 52 | 70 | ); |
test/merged-check.test.ts +9 −1
| @@ -55,7 +55,7 @@ describe("mergeForCheck: the pull as it would land on today's base", () => { | ||
| 55 | 55 | const work = join(root, "work"); |
| 56 | 56 | await git(root, "clone", pullDir, work); |
| 57 | 57 | await git(work, "checkout", "-b", "pull"); |
| 58 | head = await commit(work, "pull.txt", "b\n", "pull change"); | |
| 58 | head = await commit(work, "pull.txt", "b\n", "pull change\n\nWhy now: the owner asked for it today"); | |
| 59 | 59 | await git(work, "checkout", "-b", "clash", forkPoint); |
| 60 | 60 | conflicting = await commit(work, "shared.txt", "theirs\n", "clash"); |
| 61 | 61 | await git(work, "push", "origin", "pull", "clash"); |
| @@ -86,6 +86,14 @@ describe("mergeForCheck: the pull as it would land on today's base", () => { | ||
| 86 | 86 | expect([await refs(pullDir), await refs(baseDir)]).toEqual(before); |
| 87 | 87 | }); |
| 88 | 88 | |
| 89 | test("the merge commit message carries the pull's own commit messages", async () => { | |
| 90 | const m = await mergeForCheck(pullDir, "main", baseTip, head); | |
| 91 | if (!m.ok) throw new Error(m.error); | |
| 92 | const body = await git(pullDir, "log", "-n1", "--format=%B", m.commit); | |
| 93 | expect(body).toContain(`check of ${head.slice(0, 7)} merged into main`); | |
| 94 | expect(body).toMatch(/^Why now: the owner asked for it today$/m); | |
| 95 | }); | |
| 96 | ||
| 89 | 97 | test("the merge commit lives in the pull's repository, not the base's", async () => { |
| 90 | 98 | const m = await mergeForCheck(pullDir, "main", baseTip, head); |
| 91 | 99 | if (!m.ok) throw new Error(m.error); |
new above
offline