Skip to content
44 linesCodeBlameRaw
1/**
2 * Some files of a change with their lines, fetched by the diff view as it
3 * reaches files a large pull request, commit or comparison was sent
4 * without (lib/diff.ts firstScreens, components/diff-view.tsx).
5 *
6 * `base` and `head` are the commits the page compared, so the answer never
7 * changes: it is kept in the data centre's cache once the viewer is known
8 * to read the repository, and the browser keeps it too.
9 */
10import { data } from "react-router";
11
12import type { FileDiff } from "@g1t/contracts";
13
14import type { Route } from "./+types/diff-files";
15import { immutable } from "../../lib/immutable.server";
16import { repos } from "../../lib/services.server";
17import { getViewer, unwrap } from "../../lib/session.server";
18
19const COMMIT = /^[0-9a-f]{40}$/;
20/** The most files one request reads; the repos service reads no more than 50. */
21export const MAX_PATHS = 20;
22
23export async function loader({ params, context, request }: Route.LoaderArgs) {
24 const url = new URL(request.url);
25 const head = url.searchParams.get("head") ?? "";
26 const base = url.searchParams.get("base") ?? "";
27 const paths = [...new Set(url.searchParams.getAll("path"))].sort();
28 if (!COMMIT.test(head) || (base !== "" && !COMMIT.test(base)) || paths.length === 0 || paths.length > MAX_PATHS) {
29 throw data({ error: "Give head (and base) as full commit hashes and 1 to 20 paths." }, { status: 400 });
30 }
31 const viewer = getViewer(context);
32 const repo = unwrap(await repos.get({ namespace: params.owner, name: params.repo }, viewer));
33 // With no base the head is compared with its first parent, as the page
34 // that sent the file did: a commit's own page, or a change with no
35 // shared history.
36 const files = await immutable(`diff-files:${repo.id}:${base}:${head}:${paths.join("\n")}`, async () => {
37 const compared = await repos.compare(repo.id, viewer, base || null, head, null, paths);
38 return compared.ok ? compared.value.files : null;
39 });
40 if (!files) throw data({ error: "Those files could not be compared." }, { status: 404 });
41 return Response.json({ files } satisfies { files: FileDiff[] }, {
42 headers: { "cache-control": "private, max-age=31536000, immutable" },
43 });
44}