import { existsSync, readFileSync, renameSync, rmSync } from "node:fs"; import path from "node:path"; import { Elysia, t } from "elysia"; import { fileTypeFromBuffer } from "file-type"; import { sql } from "kysely"; import config from "../config.ts"; import { BINARY_DETECT_BYTES, BRANCHES_PER_PAGE, COMMITS_PER_PAGE, MAX_BRANCH_NAME_LENGTH, MAX_LABEL_NAME_LENGTH, paths, REPOS_PER_PAGE, TAGS_PER_PAGE, VALID_REPO_NAME_RE, YEAR_SECONDS, } from "../constants.ts"; import { db } from "../db/index.ts"; import { contentDisposition } from "../lib/contentDisposition.ts"; import { redirect } from "../lib/redirect.ts"; import { requireAdmin, resolveSession } from "../middleware/session.ts"; import { purgeRepoCaches } from "../services/ci.ts"; import { git, invalidateRefCache, repoPath, type TreeEntry, } from "../services/git.ts"; import { hasBinaryContent, prepareDiff, serveFile, } from "../services/highlightWorker.ts"; import { renderMarkdown } from "../services/markdown.ts"; import { ensureRepoRecord, repoDiskExists } from "../services/repoSync.ts"; import { html } from "../views/render.tsx"; import { BranchList } from "../views/repos/BranchList.tsx"; import { CommitDetail } from "../views/repos/CommitDetail.tsx"; import { CommitLog } from "../views/repos/CommitLog.tsx"; import { FileBlob } from "../views/repos/FileBlob.tsx"; import { FileEdit } from "../views/repos/FileEdit.tsx"; import { FileTree } from "../views/repos/FileTree.tsx"; import { NewFileForm } from "../views/repos/NewFileForm.tsx"; import { NewRepo } from "../views/repos/NewRepo.tsx"; import { RepoHome } from "../views/repos/RepoHome.tsx"; import { RepoList } from "../views/repos/RepoList.tsx"; import { RepoSettings } from "../views/repos/RepoSettings.tsx"; import { TagList } from "../views/repos/TagList.tsx"; async function getRepo(name: string, isAdmin: boolean) { if (!repoDiskExists(name)) return null; const repo = await ensureRepoRecord(name); if (repo.is_private && !isAdmin) return null; return repo; } /** * Read a byte range out of a streaming source without ever holding * the full content in memory. Used by the /raw endpoint to honour * HTTP Range headers against `git show`'s pipe. */ function sliceStream( source: ReadableStream, start: number, length: number, onDone: () => void, ): ReadableStream { const reader = source.getReader(); let skipped = 0; let emitted = 0; let finished = false; const finish = () => { if (finished) return; finished = true; reader.cancel().catch(() => {}); onDone(); }; return new ReadableStream({ async pull(controller) { while (emitted < length) { const { value, done } = await reader.read(); if (done) { controller.close(); finish(); return; } let chunk = value; if (skipped < start) { const drop = Math.min(start - skipped, chunk.length); skipped += drop; chunk = chunk.subarray(drop); if (chunk.length === 0) continue; } const remaining = length - emitted; if (chunk.length > remaining) chunk = chunk.subarray(0, remaining); emitted += chunk.length; controller.enqueue(chunk); if (emitted >= length) { controller.close(); finish(); } return; } controller.close(); finish(); }, cancel() { finish(); }, }); } /** Wrap a process stdout stream so the underlying process is killed on * close or cancel. Without this, a client disconnect partway through a * large blob leaves `git cat-file` running until its pipe back-pressures. */ function streamWithKill( source: ReadableStream, proc: { kill: () => void }, ): ReadableStream { const reader = source.getReader(); let killed = false; const finish = () => { if (killed) return; killed = true; reader.cancel().catch(() => {}); proc.kill(); }; return new ReadableStream({ async pull(controller) { try { const { value, done } = await reader.read(); if (done) { controller.close(); finish(); return; } controller.enqueue(value); } catch (err) { controller.error(err); finish(); } }, cancel() { finish(); }, }); } async function mimeForContent( filename: string, content: Buffer, ): Promise { const result = await fileTypeFromBuffer(content); if (result) return result.mime; const typeFromName = Bun.file(filename).type; if (typeFromName !== "application/octet-stream") { return typeFromName; } return hasBinaryContent(content.subarray(0, BINARY_DETECT_BYTES)) ? "application/octet-stream" : "text/plain; charset=utf-8"; } // A repo file opened directly via /raw is served from the forge's own origin. // HTML and SVG would render as active documents there and, despite our CSP, // could load a same-origin `