Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

g1t.sh
251 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.

The sidebar asks billing whether it is on, not for the account, so no page waits on the card processor1import { createRequestHandler } from "react-router";
2
Merge branch 'worktree-agent-a8385d293d42c913a'3import { identityClient, isNamespaceShaped } from "@g1t/contracts";
4
Merge g1tusercontent.com: registry answers run nothing in a browser, the site's pages run only their own scripts, repository files and avatars on their own origin, raw files rate limited per address5import { addressesFor } from "../app/lib/addresses";
Merge site-audit: SEO routes, analytics with consent first in Europe, security page, founder and X account, docs header; the tour opens on the pull request and leads with Code, on the real shell, with Agents and Docs working today6import { visitorAsksFirst } from "../app/lib/analytics-consent";
Merge g1tusercontent.com: registry answers run nothing in a browser, the site's pages run only their own scripts, repository files and avatars on their own origin, raw files rate limited per address7import { hardenRegistryHeaders } from "../app/lib/content-safety";
8import { withSiteHeaders } from "../app/lib/page-headers";
The sidebar asks billing whether it is on, not for the account, so no page waits on the card processor9import { finishResponse, withRequestPerf } from "../app/lib/perf.server";
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r910import { READ_DEADLINE_MS, withDeadline } from "../app/lib/deadline";
Merge g1tusercontent.com: registry answers run nothing in a browser, the site's pages run only their own scripts, repository files and avatars on their own origin, raw files rate limited per address11import { gitLimited, pageLimited, usercontentLimited } from "../app/lib/front-door-limits";
Composer from the workspace's own repositories, and go get from g1t.sh12import { goImport } from "../app/lib/go-get";
Signed-out page cache: a repository's kept page is served only while the repository is still public, so one made private or deleted never shows from any data centre's copy13import { repositoryOfPage, stillPublic } from "../app/lib/public-cache";
Merge branch 'worktree-agent-a8385d293d42c913a'14import { registryWorkspace, servicePath } from "../app/lib/registry-paths";
Merge g1tusercontent.com: registry answers run nothing in a browser, the site's pages run only their own scripts, repository files and avatars on their own origin, raw files rate limited per address15import { usercontentPath } from "../app/lib/usercontent";
Merge main into Artifacts Phase 216import { TOKEN_CHALLENGE } from "../app/lib/website-token";
Merge g1tusercontent.com: registry answers run nothing in a browser, the site's pages run only their own scripts, repository files and avatars on their own origin, raw files rate limited per address17import { serveUsercontent } from "./usercontent";
The sidebar asks billing whether it is on, not for the account, so no page waits on the card processor18
Merge the site's CPU: code highlighted once per data centre by content, markdown parsed once per text, and the request handler built once per isolate19const loadBuild = () => import("virtual:react-router/server-build");
20
21/**
22 * Given a function for the build, React Router derives everything from it
23 * again on every request: each route wrapped for the timings in
24 * entry.server.tsx, then the route table flattened and ranked, about a
25 * millisecond and a half of CPU a page. The build never changes while an
26 * isolate lives, so outside development it is loaded once and the handler
27 * made once. Development keeps the function, so a changed file is picked up.
28 * Only the made handler is kept, never a promise of one, so no request
29 * waits on another's (a build that fails to load is loaded again next time).
30 */
31let handler: ReturnType<typeof createRequestHandler> | undefined;
32async function requestHandler(request: Request): Promise<Response> {
33 if (import.meta.env.DEV) return createRequestHandler(loadBuild, import.meta.env.MODE)(request);
34 handler ??= createRequestHandler(await loadBuild(), import.meta.env.MODE);
35 return handler(request);
36}
The sidebar asks billing whether it is on, not for the account, so no page waits on the card processor37
38const DOCS = "https://docs.g1t.sh";
39
40/** Where the documentation pages that used to live under /docs are now. */
41const MOVED_DOCS: Record<string, string> = {
42 "/docs": "/quickstart/",
43 "/docs/concepts": "/concepts/overview/",
44 "/docs/authentication": "/guides/authentication/",
45 "/docs/git": "/guides/git/",
46 "/docs/g1t-agents": "/guides/working-with-g1t/",
47 "/docs/agents": "/guides/bring-your-own-agent/",
48 "/docs/api": "/reference/api/",
49 "/docs/api/reference": "/reference/api/",
50};
51
52export default {
53 async fetch(request, env, ctx) {
Merge g1tusercontent.com: registry answers run nothing in a browser, the site's pages run only their own scripts, repository files and avatars on their own origin, raw files rate limited per address54 // Repository files and avatars, on their own origin
55 // (g1tusercontent.com): answered before anything of the site's runs,
56 // so nothing there reads or sets the session cookie.
57 const usercontent = usercontentPath(new URL(request.url), addressesFor(env).usercontent);
58 if (usercontent !== null) {
59 return (await usercontentLimited(env, request, usercontent)) ?? serveUsercontent(env, ctx, request, usercontent);
Composer from the workspace's own repositories, and go get from g1t.sh60 }
Merge g1tusercontent.com: registry answers run nothing in a browser, the site's pages run only their own scripts, repository files and avatars on their own origin, raw files rate limited per address61 // Every answer: no sniffing, a referrer of the origin alone, and no
62 // framing of pages (app/lib/page-headers.ts).
63 return withSiteHeaders(await site(request, env, ctx));
The sidebar asks billing whether it is on, not for the account, so no page waits on the card processor64 },
65} satisfies ExportedHandler<Env>;
66
Merge g1tusercontent.com: registry answers run nothing in a browser, the site's pages run only their own scripts, repository files and avatars on their own origin, raw files rate limited per address67async function site(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
68 const { pathname } = new URL(request.url);
69 // Git over HTTPS shares this hostname but belongs to the repos service.
70 // Its answer goes back to the git client as it is: a repository under a
71 // renamed workspace's old name answers with a 301, which git follows and
72 // must see, so the redirect is never followed here.
73 // The container registry (`docker login g1t.sh`) and the npm registry
74 // (`g1t.sh/-/npm/`) are the packages
75 // service's, handed over the same way.
76 // `go get g1t.sh/<workspace>/<repo>`: where its code is, from the
77 // address alone, so it costs nothing and caches.
78 const go = request.method === "GET" ? goImport(new URL(request.url)) : null;
79 if (go) {
80 return new Response(go, {
81 headers: { "content-type": "text/html; charset=utf-8", "cache-control": "public, max-age=3600" },
82 });
83 }
84 const service = servicePath(pathname);
85 if (service === "git") {
86 // Per address without credentials, per credential with them
87 // (app/lib/front-door-limits.ts): anonymous clones are not free to
88 // the repository's owner.
89 return (await gitLimited(env, request)) ?? proxyGit(env, request);
90 }
91 if (service === "packages") {
92 return proxyPackages(env, request);
93 }
94 // The documentation is its own site.
95 if (pathname === "/docs" || pathname.startsWith("/docs/")) {
96 const page = pathname.endsWith("/") ? pathname.slice(0, -1) : pathname;
97 const target = MOVED_DOCS[page] ?? "/";
98 return Response.redirect(DOCS + target, 301);
99 }
100 // Pages and data requests, limited per address signed out and per
101 // session signed in (app/lib/front-door-limits.ts).
102 const limited = await pageLimited(env, request, pathname);
103 if (limited) return limited;
104 // Every page and data request says where its time went (Server-Timing)
105 // and keeps the reader's D1 bookmarks (app/lib/perf.server.ts).
106 const render = () => withRequestPerf(request, async () => finishResponse(request, await requestHandler(request)));
107 if (anonymousPage(request, pathname)) return servePublic(env, request, ctx, render);
Merge main into Artifacts Phase 2108 const answer = await render();
109 // A token the site refused (app/lib/website-token.ts) is told so as the
110 // API tells it: React Router drops the headers of what middleware throws.
111 if (answer.status === 401 && request.headers.has("authorization")) answer.headers.set("www-authenticate", TOKEN_CHALLENGE);
112 return answer;
Merge g1tusercontent.com: registry answers run nothing in a browser, the site's pages run only their own scripts, repository files and avatars on their own origin, raw files rate limited per address113}
114
The sidebar asks billing whether it is on, not for the account, so no page waits on the card processor115/**
116 * Public pages as someone signed out sees them: the same for every such
117 * visitor, so kept in this data centre's cache. Reserved first segments
118 * (settings, sign-in, invitations and the like) and workspace pages (`-`)
The docs folder is gone, and what it held lives where people read it: how a self-hosted g1t runs and how to deploy g1t to Cloudflare are pages on docs.g1t.sh under Run g1t yourself, and speed, rate limits and operating g1t.sh are sections of CONTRIBUTING.md; code that cited a file in docs/ now points to the page or section that covers it, or says what it means itself, and applied migrations and the runner images are left as they were.119 * are never kept. A repository's kept page is served only while repos
120 * says the repository is still public: one made private or deleted is
121 * never served from any data centre's copy.
The sidebar asks billing whether it is on, not for the account, so no page waits on the card processor122 */
123const PUBLIC_TOP = /^\/(?:|_root\.data|pricing|explore|security|support|policies(?:\/[a-z-]+)?)(?:\.data)?$/;
124const PUBLIC_PROJECT =
Merge email confirmation gate: a code and a link, nothing until confirmed (identity 0036)125 /^\/(?!(?:settings|u|auth|oauth|integrations|new|invite|workspaces|device|verify|confirm-email|login|register|logout|forgot|reset|search|status|avatars|docs)\/)[^/]+\/(?!-\/|-$)[^/]+(?:\/(?:code|commits|issues|pulls|pull\/\d+|issues\/\d+|commit\/[0-9a-f]+|tree\/.+|blob\/.+))?(?:\.data)?$/;
The sidebar asks billing whether it is on, not for the account, so no page waits on the card processor126/** Fresh for this long; then served once more while a new copy is made. */
127const PUBLIC_FRESH_SECONDS = 30;
128const PUBLIC_STALE_SECONDS = 300;
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9129/** The longest a cache lookup holds up a public page. */
130const CACHE_DEADLINE_MS = 2_000;
The sidebar asks billing whether it is on, not for the account, so no page waits on the card processor131
132function anonymousPage(request: Request, pathname: string): boolean {
133 if (request.method !== "GET") return false;
134 if (/(?:^|;\s*)g1t_session=/.test(request.headers.get("cookie") ?? "")) return false;
Merge main into Artifacts Phase 2135 // A token signs the request in (app/lib/website-token.ts): never kept, never served a kept page.
136 if (request.headers.has("authorization")) return false;
The sidebar asks billing whether it is on, not for the account, so no page waits on the card processor137 return PUBLIC_TOP.test(pathname) || PUBLIC_PROJECT.test(pathname);
138}
139
Signed-out page cache: a repository's kept page is served only while the repository is still public, so one made private or deleted never shows from any data centre's copy140async function servePublic(env: Env, request: Request, ctx: ExecutionContext, render: () => Promise<Response>): Promise<Response> {
The sidebar asks billing whether it is on, not for the account, so no page waits on the card processor141 const cache = (caches as unknown as { default: Cache }).default;
Merge site-audit: SEO routes, analytics with consent first in Europe, security page, founder and X account, docs header; the tour opens on the pull request and leads with Code, on the real shell, with Agents and Docs working today142 // Visitors asked about analytics first get a page that says so, kept apart.
143 const keyUrl = new URL(request.url);
144 if (visitorAsksFirst(request)) keyUrl.searchParams.set("_g1t_consent", "1");
145 const key = new Request(keyUrl, { method: "GET" });
Signed-out page cache: a repository's kept page is served only while the repository is still public, so one made private or deleted never shows from any data centre's copy146 const repository = PUBLIC_PROJECT.test(new URL(request.url).pathname) ? repositoryOfPage(new URL(request.url).pathname) : null;
147 // Asked alongside the cache, so a hit waits for one indexed read at most.
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9148 // A cache that does not answer is a miss: the page is drawn afresh.
149 const [cached, visible] = await Promise.all([
150 withDeadline("cache", CACHE_DEADLINE_MS, () => cache.match(key)).catch(() => undefined),
151 repository ? isStillPublic(env, repository) : Promise.resolve(true),
152 ]);
Signed-out page cache: a repository's kept page is served only while the repository is still public, so one made private or deleted never shows from any data centre's copy153 if (cached && !visible) {
154 ctx.waitUntil(cache.delete(key).then(() => undefined, () => undefined));
155 return render();
156 }
The sidebar asks billing whether it is on, not for the account, so no page waits on the card processor157 const keptAt = Number(cached?.headers.get("x-g1t-kept-at") ?? 0);
158 const age = Math.round((Date.now() - keptAt) / 1000);
159 const refresh = async () => {
160 const fresh = await render();
161 // Only a plain answer for everyone: nothing that sets a cookie or says
162 // it is private.
163 const cacheable =
164 (fresh.status === 200 || fresh.status === 404) &&
165 !fresh.headers.has("set-cookie") &&
166 !/private|no-store/.test(fresh.headers.get("cache-control") ?? "");
167 if (cacheable) {
168 const copy = new Response(fresh.clone().body, fresh);
169 copy.headers.set("x-g1t-kept-at", String(Date.now()));
170 copy.headers.set("cache-control", `public, max-age=${PUBLIC_STALE_SECONDS}`);
171 ctx.waitUntil(cache.put(key, copy));
172 }
173 return fresh;
174 };
175 if (cached && keptAt > 0 && age < PUBLIC_STALE_SECONDS) {
176 if (age >= PUBLIC_FRESH_SECONDS) ctx.waitUntil(refresh().then(() => undefined, () => undefined));
177 const answer = new Response(cached.body, cached);
178 answer.headers.delete("x-g1t-kept-at");
179 answer.headers.delete("cache-control");
180 answer.headers.set("server-timing", `cache;desc="hit, ${age}s old"`);
181 return answer;
182 }
183 return refresh();
184}
185
Signed-out page cache: a repository's kept page is served only while the repository is still public, so one made private or deleted never shows from any data centre's copy186/** Whether the repository is there and public; anything else, including no answer, is no. */
187async function isStillPublic(env: Env, repository: string): Promise<boolean> {
188 try {
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9189 return await withDeadline("repos visibility", READ_DEADLINE_MS, async (signal) => {
190 const answer = await env.REPOS.fetch("https://service/rpc/visibility", {
191 method: "POST",
192 headers: { "content-type": "application/json" },
193 body: JSON.stringify({ paths: [repository] }),
194 signal,
195 });
196 return answer.ok && stillPublic(await answer.json(), repository);
Signed-out page cache: a repository's kept page is served only while the repository is still public, so one made private or deleted never shows from any data centre's copy197 });
198 } catch {
199 return false;
200 }
201}
202
The sidebar asks billing whether it is on, not for the account, so no page waits on the card processor203/**
204 * A git request, answered by the repos service. Its `Server-Timing` header
205 * gains `repos`: how long the answer took to start from here, so the time
206 * between this Worker and the repos service shows beside the steps the
207 * repos service reports.
208 */
209async function proxyGit(env: Env, request: Request): Promise<Response> {
210 const started = Date.now();
211 const answer = await env.REPOS.fetch(new Request(request, { redirect: "manual" }));
212 const response = new Response(answer.body, answer);
213 response.headers.append("server-timing", `repos;dur=${Date.now() - started}`);
214 return response;
215}
216
217/**
Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member218 * A registry request, answered by the packages service as it is: its
219 * redirects (a large blob sent to storage) go back to the client, which
Merge branch 'worktree-agent-a8385d293d42c913a'220 * follows them itself. One that found nothing under a workspace's old name
221 * or an alias staff set (`g1t` for `flagon-io`) is sent to the same path
222 * under the workspace's name: only the not-found answer pays for the lookup.
Merge g1tusercontent.com: registry answers run nothing in a browser, the site's pages run only their own scripts, repository files and avatars on their own origin, raw files rate limited per address223 * Every answer runs nothing in a browser (app/lib/content-safety.ts): what
224 * a registry serves is its publisher's, on this origin.
Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member225 */
226async function proxyPackages(env: Env, request: Request): Promise<Response> {
227 const started = Date.now();
228 const answer = await env.PACKAGES.fetch(new Request(request, { redirect: "manual" }));
Merge branch 'worktree-agent-a8385d293d42c913a'229 const moved = answer.status === 404 ? await registryMoved(env, request) : null;
230 const response = moved ?? new Response(answer.body, answer);
Merge g1tusercontent.com: registry answers run nothing in a browser, the site's pages run only their own scripts, repository files and avatars on their own origin, raw files rate limited per address231 hardenRegistryHeaders(response.headers);
Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member232 response.headers.append("server-timing", `packages;dur=${Date.now() - started}`);
233 return response;
234}
235
Merge branch 'worktree-agent-a8385d293d42c913a'236/** Where a registry request under an alias or old name goes now, or null. */
237async function registryMoved(env: Env, request: Request): Promise<Response | null> {
238 const url = new URL(request.url);
239 const named = registryWorkspace(url.pathname);
240 if (!named || !isNamespaceShaped(named.slug)) return null;
241 let current: string | null = null;
242 try {
243 current = await identityClient(env.IDENTITY).resolveSlug(named.slug);
244 } catch {
245 return null;
246 }
247 if (!current || current === named.slug) return null;
248 const get = request.method === "GET" || request.method === "HEAD";
249 // 308 keeps a publish a PUT, for the clients that follow it.
250 return new Response(null, { status: get ? 301 : 308, headers: { location: named.under(current) + url.search } });
The sidebar asks billing whether it is on, not for the account, so no page waits on the card processor251}

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