Diffinery

codician/diffinerypublic

Open-source, agent-first GitHub and GitHub Actions alternative that runs on your own Cloudflare account: git hosting, pull requests, code review and drop-in GitHub Actions YAML on Workers, Durable Objects, R2 and Containers.

CLI: pr view shows auto-merge and what is left before it lands (#208)

* CLI: pr view shows auto-merge and what is left before it lands
test-bot authored 1 parent 5e6977ecommit 1eb39ea80dbcfd482caf2cda078c561be7ea6a53Browse files
2 files changed+23−0
modifiedapps/cli/src/commands/pr.ts+10−0View file
@@ -37,6 +37,9 @@
3737 approvals: number;
3838 required_approvals: number;
3939 checks: Array<{ name: string; state: string }>;
40+ auto_merge?: { merge_method: string } | null;
41+ /** Only for agents and workflow tokens: what stops Diffinery landing it through auto-merge. */
42+ auto_merge_blockers?: string[];
4043 }
4144
4245 export interface CommentJson {
@@ -188,6 +191,13 @@
188191 if (pr.state === "open") {
189192 ctx.out(`${c.dim("mergeable:")} ${statusText(c, pr.mergeable_state)} · approvals ${pr.approvals}/${pr.required_approvals}`);
190193 for (const b of pr.merge_blockers ?? []) ctx.out(` ${c.red("•")} ${b}`);
194+ if (pr.auto_merge) {
195+ // For an agent, merge_blockers can name an approval its auto-merge doesn't need; say what is really left.
196+ const left = pr.auto_merge_blockers;
197+ const when = !left ? "it merges by itself once every requirement is met" : left.length ? "it merges by itself once these are done:" : "nothing is left, it merges now";
198+ ctx.out(`${c.dim("auto-merge:")} on (${pr.auto_merge.merge_method}), ${when}`);
199+ for (const b of left ?? []) ctx.out(` ${c.yellow("•")} ${b}`);
200+ }
191201 }
192202 if (pr.checks?.length) ctx.out(`${c.dim("checks:")} ${pr.checks.map((k) => `${checkSymbol(ctx, k.state)} ${k.name}`).join(" ")}`);
193203 if (pr.body?.trim()) ctx.out(`\n${pr.body.trim()}\n`);
modifiedapps/cli/test/pr.test.ts+13−0View file
@@ -205,6 +205,19 @@
205205 expect(srv.requests.length).toBe(before);
206206 });
207207
208+ it("pr view shows auto-merge and, for an agent, what is left before it lands", async () => {
209+ const detail = { mergeable: false, mergeable_state: "blocked", merge_blockers: ["needs 1 more approving review(s)", "required check 'ci' is in_progress"], approvals: 0, required_approvals: 1, checks: [] };
210+ srv.on("GET", "/api/v3/repos/o/r/pulls/3", { body: { ...pull(3), ...detail, auto_merge: { merge_method: "squash" }, auto_merge_blockers: ["required check 'ci' is in_progress"] } });
211+ srv.on("GET", "/api/v3/repos/o/r/pulls/4", { body: { ...pull(4), ...detail, auto_merge: { merge_method: "merge" }, auto_merge_blockers: [] } });
212+ srv.on("GET", "/api/v3/repos/o/r/pulls/5", { body: { ...pull(5), ...detail, auto_merge: { merge_method: "rebase" } } });
213+ srv.on("GET", "/api/v3/repos/o/r/pulls/6", { body: { ...pull(6), ...detail, auto_merge: null } });
214+ const agent = (await run(["pr", "view", "3", "-R", "o/r"], { env })).stdout;
215+ expect(agent).toContain("auto-merge: on (squash), it merges by itself once these are done:\n • required check 'ci' is in_progress\n");
216+ expect((await run(["pr", "view", "4", "-R", "o/r"], { env })).stdout).toContain("auto-merge: on (merge), nothing is left, it merges now");
217+ expect((await run(["pr", "view", "5", "-R", "o/r"], { env })).stdout).toContain("auto-merge: on (rebase), it merges by itself once every requirement is met");
218+ expect((await run(["pr", "view", "6", "-R", "o/r"], { env })).stdout).not.toContain("auto-merge");
219+ });
220+
208221 it("without a number, fails clearly when there is no current branch or no open pull request for it", async () => {
209222 srv.on("GET", "/api/v3/repos/acme/widgets/pulls", { body: [] });
210223 const cwd = gitRepo(env, `${srv.url}/acme/widgets.git`, "lonely");