g1t/apps/web/app/routes/repo/pull-new.tsx

126 lines4,869 bytesCodeBlame

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.

Pull requests from branches1import { 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";
Pull requests from branches8import {
Merge branch 'worktree-agent-a985c01f43538af01'9 SubmitButton,
Pull requests from branches10 CopyLine,
11 EmptyState,
12 ErrorText,
13 Field,
14 Input,
15 Textarea,
16} from "../../components/ui";
17import { repos, work } from "../../lib/services.server";
18import { assertSameOrigin, requireUser, unwrap } from "../../lib/session.server";
19
Social cards for every page: og.g1t.sh20export function meta({ params, ...args }: Route.MetaArgs) {
21 return page(args, { title: `New pull request · ${params.owner}/${params.repo} · g1t` });
Pull requests from branches22}
23
24export async function loader({ request, params, context }: Route.LoaderArgs) {
25 const user = requireUser(context, request);
26 const path = { namespace: params.owner, name: params.repo };
27 const [repo, branches, open] = await Promise.all([
28 repos.get(path, user),
29 repos.branches(path, user),
30 work.listPulls(path, user, "open"),
31 ]);
32 const { defaultBranch } = unwrap(repo);
33 const all = unwrap(branches);
34 const mainHead = all.find((branch) => branch.name === defaultBranch)?.hash;
35 // A branch that already has an open pull request cannot have another.
36 const taken = new Set(open.ok ? open.value.map((pull) => pull.branch) : []);
37 const query = new URL(request.url).searchParams;
38 return {
39 defaultBranch,
40 branches: all
41 // A branch at the same commit as the default branch has nothing to merge.
42 .filter((branch) => branch.name !== defaultBranch && branch.hash !== mainHead)
43 .map((branch) => branch.name)
44 .filter((name) => !taken.has(name)),
45 selected: query.get("branch") ?? "",
46 issue: query.get("issue") ?? "",
47 };
48}
49
50export async function action({ request, params, context }: Route.ActionArgs) {
51 assertSameOrigin(request);
52 const user = requireUser(context, request);
53 const form = await request.formData();
54 const issue = Number(form.get("issue"));
55 const result = await work.openPull(
56 user,
57 { namespace: params.owner, name: params.repo },
58 {
59 branch: String(form.get("branch") ?? ""),
60 title: String(form.get("title") ?? ""),
61 body: String(form.get("body") ?? ""),
62 issue: issue > 0 ? issue : undefined,
63 agent: user.username,
64 runtime: "external",
65 },
66 );
67 if (!result.ok) return { error: result.error.message };
68 throw redirect(`/${params.owner}/${params.repo}/pull/${result.value.number}`);
69}
70
71export default function NewPull({ loaderData, actionData, params }: Route.ComponentProps) {
72 const { defaultBranch, branches, selected, issue } = loaderData;
Merge branch 'worktree-agent-aaf03bdceac799c89'73 const remote = cloneUrl(useAddresses(), `${params.owner}/${params.repo}`);
Pull requests from branches74
75 if (branches.length === 0) {
76 return (
77 <div className="max-w-2xl">
78 <EmptyState title="No branch to open a pull request from">
79 Push a branch to this repository, then come back.
80 </EmptyState>
81 <div className="mt-4 space-y-2">
82 <CopyLine prompt text="git switch -c my-change" />
83 <CopyLine prompt text={`git push ${remote} my-change`} />
84 </div>
85 <p className="mt-4 text-sm text-muted">
86 Agents do not need a branch. Open an issue and assign agents to it,
87 and each gets a pull request with its own fork.
88 </p>
89 </div>
90 );
91 }
92
93 return (
94 <Form method="post" className="max-w-2xl space-y-4">
95 <div className="flex flex-wrap items-center gap-2 rounded-xl border border-line bg-surface px-4 py-3 text-sm">
96 <GitBranch size={15} className="text-faint" />
97 <span className="text-muted">Merge</span>
Workspace names and icons, and a component kit for every control98 <Combobox
Pull requests from branches99 name="branch"
100 defaultValue={branches.includes(selected) ? selected : branches[0]}
101 aria-label="Branch to merge"
Workspace names and icons, and a component kit for every control102 searchPlaceholder="Find a branch"
103 emptyText="No branch by that name."
104 options={branches.map((name) => ({ value: name, label: name, icon: <GitBranch /> }))}
105 className="h-8 w-auto max-w-full min-w-40 font-mono"
106 />
Pull requests from branches107 <span className="text-muted">into</span>
108 <span className="font-mono">{defaultBranch}</span>
109 </div>
110 <Field label="Title">
111 <Input name="title" required autoFocus maxLength={200} />
112 </Field>
113 <Field label="Description" hint="What changed and why. Markdown works.">
114 <Textarea name="body" rows={8} />
115 </Field>
116 <Field
117 label="Issue (optional)"
118 hint="The number of the issue this resolves. Merging the pull request closes it."
119 >
120 <Input name="issue" type="number" min={1} defaultValue={issue} placeholder="12" />
121 </Field>
122 <ErrorText>{actionData?.error}</ErrorText>
Merge branch 'worktree-agent-a985c01f43538af01'123 <SubmitButton pending="Opening…">Open pull request</SubmitButton>
Pull requests from branches124 </Form>
125 );
126}

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