Skip to content
161 linesCodeBlameRaw

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

Get started on mission control1import { GitBranch } from "lucide-react";
2import { Form, redirect } from "react-router";
3
4import type { Route } from "./+types/pull-new";
Social cards for every page: og.g1t.sh5import { page } from "../../lib/meta";
Merge branch 'worktree-agent-aaf03bdceac799c89'6import { cloneUrl, useAddresses } from "../../lib/addresses";
Workspace names and icons, and a component kit for every control7import { Combobox } from "../../components/ui/combobox";
Merge branch 'mirroring' into artifacts-mode8import { Hint } from "../../components/ui/hint";
9import { useMirrorReason } from "../../components/mirror";
Get started on mission control10import {
Merge branch 'worktree-agent-a985c01f43538af01'11 SubmitButton,
Get started on mission control12 CopyLine,
13 EmptyState,
14 ErrorText,
15 Field,
16 Input,
17 Textarea,
18} from "../../components/ui";
19import { repos, work } from "../../lib/services.server";
20import { assertSameOrigin, requireUser, unwrap } from "../../lib/session.server";
21
Social cards for every page: og.g1t.sh22export function meta({ params, ...args }: Route.MetaArgs) {
23 return page(args, { title: `New pull request · ${params.owner}/${params.repo} · g1t` });
Get started on mission control24}
25
26export async function loader({ request, params, context }: Route.LoaderArgs) {
27 const user = requireUser(context, request);
28 const path = { namespace: params.owner, name: params.repo };
29 const [repo, branches, open] = await Promise.all([
30 repos.get(path, user),
31 repos.branches(path, user),
32 work.listPulls(path, user, "open"),
33 ]);
34 const { defaultBranch } = unwrap(repo);
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar35 const all = unwrap(branches).filter((branch) => !branch.name.startsWith("g1t-queue/"));
Get started on mission control36 const query = new URL(request.url).searchParams;
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar37 // Into the default branch unless another is asked for (Compare's base).
38 const asked = query.get("base") ?? "";
39 const base = all.some((branch) => branch.name === asked) ? asked : defaultBranch;
40 const baseHead = all.find((branch) => branch.name === base)?.hash;
41 // A branch that already has an open pull request into this base cannot
42 // have another.
43 const taken = new Set(open.ok ? open.value.filter((pull) => (pull.base ?? defaultBranch) === base).map((pull) => pull.branch) : []);
Get started on mission control44 return {
45 defaultBranch,
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar46 base,
47 bases: all.map((branch) => branch.name),
Get started on mission control48 branches: all
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar49 // A branch at the same commit as the base has nothing to merge.
50 .filter((branch) => branch.name !== base && branch.hash !== baseHead)
Get started on mission control51 .map((branch) => branch.name)
52 .filter((name) => !taken.has(name)),
53 selected: query.get("branch") ?? "",
54 issue: query.get("issue") ?? "",
55 };
56}
57
58export async function action({ request, params, context }: Route.ActionArgs) {
59 assertSameOrigin(request);
60 const user = requireUser(context, request);
61 const form = await request.formData();
62 const issue = Number(form.get("issue"));
63 const result = await work.openPull(
64 user,
65 { namespace: params.owner, name: params.repo },
66 {
67 branch: String(form.get("branch") ?? ""),
68 title: String(form.get("title") ?? ""),
69 body: String(form.get("body") ?? ""),
70 issue: issue > 0 ? issue : undefined,
71 agent: user.username,
72 runtime: "external",
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar73 base: String(form.get("base") ?? "") || undefined,
Open a pull request as a draft: it can't merge, and agents' review routines wait, until it is marked ready74 draft: form.get("intent") === "draft",
Get started on mission control75 },
76 );
77 if (!result.ok) return { error: result.error.message };
78 throw redirect(`/${params.owner}/${params.repo}/pull/${result.value.number}`);
79}
80
81export default function NewPull({ loaderData, actionData, params }: Route.ComponentProps) {
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar82 const { defaultBranch, base, bases, branches, selected, issue } = loaderData;
Merge branch 'worktree-agent-aaf03bdceac799c89'83 const remote = cloneUrl(useAddresses(), `${params.owner}/${params.repo}`);
Merge branch 'mirroring' into artifacts-mode84 // A mirror takes no pull requests until someone takes over.
85 const mirrorBlocked = useMirrorReason();
Get started on mission control86
87 if (branches.length === 0) {
88 return (
89 <div className="max-w-2xl">
90 <EmptyState title="No branch to open a pull request from">
91 Push a branch to this repository, then come back.
92 </EmptyState>
93 <div className="mt-4 space-y-2">
94 <CopyLine prompt text="git switch -c my-change" />
95 <CopyLine prompt text={`git push ${remote} my-change`} />
96 </div>
97 <p className="mt-4 text-sm text-muted">
98 Agents do not need a branch. Open an issue and assign agents to it,
99 and each gets a pull request with its own fork.
100 </p>
101 </div>
102 );
103 }
104
105 return (
106 <Form method="post" className="max-w-2xl space-y-4">
107 <div className="flex flex-wrap items-center gap-2 rounded-xl border border-line bg-surface px-4 py-3 text-sm">
108 <GitBranch size={15} className="text-faint" />
109 <span className="text-muted">Merge</span>
Workspace names and icons, and a component kit for every control110 <Combobox
Get started on mission control111 name="branch"
112 defaultValue={branches.includes(selected) ? selected : branches[0]}
113 aria-label="Branch to merge"
Workspace names and icons, and a component kit for every control114 searchPlaceholder="Find a branch"
115 emptyText="No branch by that name."
116 options={branches.map((name) => ({ value: name, label: name, icon: <GitBranch /> }))}
117 className="h-8 w-auto max-w-full min-w-40 font-mono"
118 />
Get started on mission control119 <span className="text-muted">into</span>
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar120 <Combobox
121 name="base"
122 defaultValue={base}
123 aria-label="Branch to merge into"
124 searchPlaceholder="Find a branch"
125 emptyText="No branch by that name."
126 options={bases.map((name) => ({
127 value: name,
128 label: name,
129 icon: <GitBranch />,
130 description: name === defaultBranch ? "default" : undefined,
131 }))}
132 className="h-8 w-auto max-w-full min-w-32 font-mono"
133 />
Get started on mission control134 </div>
135 <Field label="Title">
136 <Input name="title" required autoFocus maxLength={200} />
137 </Field>
138 <Field label="Description" hint="What changed and why. Markdown works.">
139 <Textarea name="body" rows={8} />
140 </Field>
141 <Field
142 label="Issue (optional)"
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar143 hint={`The number of the issue this resolves. Merging the pull request into ${defaultBranch} closes it.`}
Get started on mission control144 >
145 <Input name="issue" type="number" min={1} defaultValue={issue} placeholder="12" />
146 </Field>
147 <ErrorText>{actionData?.error}</ErrorText>
Open a pull request as a draft: it can't merge, and agents' review routines wait, until it is marked ready148 <div className="flex flex-wrap items-center gap-2">
Merge branch 'mirroring' into artifacts-mode149 <Hint label={mirrorBlocked} disabled={mirrorBlocked != null}>
150 <SubmitButton name="intent" value="open" match={{ intent: "open" }} pending="Opening…" disabled={mirrorBlocked != null}>
151 Open pull request
152 </SubmitButton>
153 </Hint>
154 <SubmitButton name="intent" value="draft" match={{ intent: "draft" }} variant="quiet" pending="Opening…" disabled={mirrorBlocked != null}>
Open a pull request as a draft: it can't merge, and agents' review routines wait, until it is marked ready155 Open as draft
156 </SubmitButton>
157 <span className="text-xs text-faint">A draft can't merge, and agents' review routines wait, until you mark it ready.</span>
158 </div>
Get started on mission control159 </Form>
160 );
161}

This file's history is long; its oldest lines are credited to the oldest commit read.