Skip to content

g1t/apps/web/app/routes/repo/settings-branches.tsx

212 lines10,674 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.

Merge rulesets: branch and tag rules, agent-first, enforced on push and merge1import { ChevronRight, Scale, ShieldCheck } from "lucide-react";
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar2import { Suspense } from "react";
3import { Await, Form, Link } from "react-router";
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look4
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge5import { ruleInfo } from "../../lib/rules";
6
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look7import type { Route } from "./+types/settings-branches";
8import { page } from "../../lib/meta";
Fast pages, required checks on the branch, self-hosted runners, honest incidents9import { AddCiPrompt } from "../../components/add-ci";
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar10import { CodeownersReportPanel, CodeownersReportSkeleton } from "../../components/codeowners";
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look11import { RepoSettingsHeading } from "../../components/repo-settings-heading";
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge12import { EnforcementBadge } from "../../components/rules";
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look13import { SettingChoice as Choice, SettingsSection as Section, SettingToggle as Toggle } from "../../components/settings-section";
Merge branch 'worktree-agent-a985c01f43538af01'14import { ErrorText, SubmitButton, TimeAgo } from "../../components/ui";
Fast pages, required checks on the branch, self-hosted runners, honest incidents15import { actions, repos, work } from "../../lib/services.server";
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look16import { assertSameOrigin, getViewer, requireUser, unwrap } from "../../lib/session.server";
17import { requireCapability, requireInsider } from "../../lib/access.server";
18
19export function meta({ params, ...args }: Route.MetaArgs) {
20 return page(args, { title: `Branches and merging · ${params.owner}/${params.repo} · g1t` });
21}
22
23export async function loader({ params, context }: Route.LoaderArgs) {
24 const viewer = getViewer(context);
25 // Maintain and up; to anyone without a role here the page does not exist.
Fast pages, required checks on the branch, self-hosted runners, honest incidents26 const { access } = await requireInsider(context, params, "manage_protection");
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look27 const path = { namespace: params.owner, name: params.repo };
Fast pages, required checks on the branch, self-hosted runners, honest incidents28 const [repo, settings, seen, workflows] = await Promise.all([
29 repos.get(path, viewer),
30 work.getSettings(path, viewer),
31 work.seenChecks(path, viewer),
32 actions.workflows(path, viewer),
33 ]);
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge34 const found = unwrap(repo);
35 // What holds for the default branch: shown here, changed in Rules.
36 const effective = await work.effectiveRules(path, found.defaultBranch, viewer).catch(() => null);
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar37 // Streamed: the CODEOWNERS file is read and checked on its own time.
38 const codeowners = work
39 .codeownersErrors(path, viewer)
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge40 .then((report) => (report.ok ? report.value : null))
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar41 .catch(() => null);
Fast pages, required checks on the branch, self-hosted runners, honest incidents42 return {
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar43 codeowners,
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge44 repo: found,
Fast pages, required checks on the branch, self-hosted runners, honest incidents45 settings: unwrap(settings),
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge46 effective: effective?.ok ? effective.value : null,
Fast pages, required checks on the branch, self-hosted runners, honest incidents47 // Nothing to require until something runs: the page offers to add CI.
48 noChecks: workflows.ok && workflows.value.length === 0 && seen.ok && seen.value.length === 0,
49 canPush: access.can.push,
50 };
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look51}
52
53/** A whole number from a form field, kept within `min` and `max`. */
54function count(value: FormDataEntryValue | null, min: number, max: number): number {
55 const number = Math.trunc(Number(value));
56 return Number.isFinite(number) ? Math.min(Math.max(number, min), max) : min;
57}
58
59export async function action({ request, params, context }: Route.ActionArgs) {
60 assertSameOrigin(request);
61 const user = requireUser(context, request);
62 await requireCapability(context, params, "manage_protection");
63 const form = await request.formData();
64 const path = { namespace: params.owner, name: params.repo };
65 const on = (name: string) => form.get(name) === "on";
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge66 // Branch protection is the repository's rulesets' (Settings → Rules):
67 // what it holds is sent back as it is, so only how g1t's agents work
68 // changes here.
69 const current = await work.getSettings(path, user);
70 if (!current.ok) return { saved: false, error: current.error.message };
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look71 const settings = await work.updateSettings(user, path, {
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge72 ...current.value,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look73 autoMerge: on("autoMerge"),
74 agentReview: on("agentReview"),
75 maxRevisions: count(form.get("maxRevisions"), 0, 5),
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step76 holdLowConfidence: on("holdLowConfidence"),
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look77 });
78 return settings.ok ? { saved: true, error: null } : { saved: false, error: settings.error.message };
79}
80
81export default function BranchSettings({ loaderData, actionData }: Route.ComponentProps) {
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge82 const { repo, settings, effective, noChecks, canPush, codeowners } = loaderData;
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look83 const branch = repo.defaultBranch;
84 const base = `/${repo.namespace}/${repo.name}`;
85 const archived = Boolean(repo.archivedAt);
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge86 const active = effective?.rules.filter((rule) => rule.enforcement === "active") ?? [];
87 const labels = [...new Set(active.map((rule) => ruleInfo(rule.type)?.label ?? rule.type))];
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look88 return (
89 <>
90 <RepoSettingsHeading base={base} />
Fast pages, required checks on the branch, self-hosted runners, honest incidents91 {/* Its own form, so outside the settings one. */}
92 {noChecks && (
93 <div className="mb-8 max-w-4xl">
94 <AddCiPrompt owner={repo.namespace} repo={repo.name} canAdd={canPush && !archived} />
95 </div>
96 )}
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge97 <div className="max-w-4xl space-y-8">
98 <Section
99 title="Branch protection"
100 about={`What holds for ${branch} and every other branch is set by rulesets: for people and agents alike, and across the workspace.`}
101 >
102 <Link
103 to={`${base}/settings/rules`}
104 className="group flex items-start gap-3 rounded-xl border border-line p-4 transition-colors hover:border-line-strong hover:bg-surface"
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar105 >
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge106 <Scale size={16} className="mt-0.5 shrink-0 text-accent" />
107 <span className="min-w-0 grow">
108 <span className="block text-sm font-medium">Rules for {branch}</span>
109 <span className="mt-1 block text-sm text-muted">
110 {labels.length > 0 ? labels.join(" · ") : `No active rules hold for ${branch}: anyone who may push can change it.`}
111 </span>
112 {effective && effective.rulesets.length > 0 && (
113 <span className="mt-2 flex flex-wrap gap-2">
114 {effective.rulesets.map((ruleset) => (
115 <span key={ruleset.id} className="inline-flex items-center gap-1.5 text-xs text-muted">
116 {ruleset.name} <EnforcementBadge enforcement={ruleset.enforcement} />
117 </span>
118 ))}
119 </span>
120 )}
121 </span>
122 <ChevronRight size={16} className="mt-0.5 shrink-0 text-faint transition-transform group-hover:translate-x-0.5" />
123 </Link>
124 </Section>
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar125
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge126 <Section
127 id="codeowners"
128 title="CODEOWNERS"
129 about={`Who owns which files, read from ${branch}. Owners are asked to review changes to their files.`}
130 >
131 <Suspense fallback={<CodeownersReportSkeleton />}>
132 <Await resolve={codeowners}>
133 {(report) => <CodeownersReportPanel report={report} base={base} branch={branch} />}
134 </Await>
135 </Suspense>
136 </Section>
137
138 <Form method="post">
139 <fieldset disabled={archived} className="min-w-0 space-y-8">
140 <Section
141 title="g1t"
142 about="What happens to a pull request g1t makes, from the moment it is ready. Its checks are the same workflows, and the branch's rules hold."
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look143 >
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge144 <Toggle name="agentReview" on={settings.agentReview} title="Review by a second agent">
145 A different agent reads each change and posts comments on lines, a summary and a verdict. If it asks for
146 changes, the author is sent back to make them. With this off, review is left to people.
147 </Toggle>
148 <Choice
149 name="maxRevisions"
150 value={settings.maxRevisions}
151 title="Revisions before asking you"
152 options={[
153 [0, "None"],
154 [1, "1"],
155 [2, "2"],
156 [3, "3"],
157 [5, "5"],
158 ]}
159 >
160 How many times an agent is sent back to fix a failed check, with what its jobs printed, or to address a
161 review, before g1t stops and the pull request says it needs you. After that, only a required check that
162 still fails holds it.
163 </Choice>
164 <Toggle name="autoMerge" on={settings.autoMerge} title="Merge automatically when ready">
165 A pull request g1t made lands without anyone pressing merge once every rule of its branch is met, its
166 required checks included. A ruleset can turn this off for some branches, or ask for a confidence first
167 (Agent auto-merge). With this off, it waits for a member. Pull requests from people and from other agents
168 always wait.
169 </Toggle>
170 <Toggle
171 name="holdLowConfidence"
172 on={settings.holdLowConfidence}
173 title="Ask a person before merging low-confidence changes"
174 >
175 g1t rates how sure it is of each change an agent finishes, from its required checks, revisions, review,
176 tests, size and guardrails. One it rates low waits for a member to approve it, instead of merging by itself
177 or joining the queue, and shows on Mission control as needing you. A Confidence threshold rule asks for
178 more, branch by branch.
179 </Toggle>
180 <Link
181 to={`${base}/settings/guardrails`}
182 className="group flex items-center gap-3 rounded-xl border border-line p-4 transition-colors hover:border-line-strong hover:bg-surface"
183 >
184 <ShieldCheck size={16} className="shrink-0 text-accent" />
185 <span className="min-w-0 grow">
186 <span className="block text-sm font-medium">Guardrails</span>
187 <span className="mt-0.5 block text-sm text-muted">
188 What agents may reach, run and spend while they work on this repository.
189 </span>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look190 </span>
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge191 <ChevronRight size={16} className="shrink-0 text-faint transition-transform group-hover:translate-x-0.5" />
192 </Link>
193 </Section>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look194
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge195 <div className="sticky bottom-0 -mx-4 flex flex-wrap items-center gap-4 border-t border-line bg-bg/90 px-4 py-4 backdrop-blur">
196 <SubmitButton pending="Saving…" disabled={archived}>
197 Save settings
198 </SubmitButton>
199 {actionData?.saved && <span className="text-sm text-muted">Saved.</span>}
200 <ErrorText>{actionData?.error}</ErrorText>
201 {settings.updatedBy && settings.updatedAt && !actionData && (
202 <span className="text-xs text-faint">
203 Last changed by <span className="font-mono">{settings.updatedBy}</span> <TimeAgo at={settings.updatedAt} />
204 </span>
205 )}
206 </div>
207 </fieldset>
208 </Form>
209 </div>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look210 </>
211 );
212}

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