Something went wrong. Try again.
Monorepo for Tangled forked from tangled.org/core
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204import { error, redirect } from "@sveltejs/kit";import { getRequestEvent } from "$app/server";import { branch as gitBranch, branches as gitBranches, gitTarget, hasGit, languages as gitLanguages, log as gitLog, requireGit, tags as gitTags, tree as gitTree} from "#lib/api/gitclient.ts";import { toMirrorCommitSummary } from "#lib/api/knotmirror.ts";import { orNull, parallel, stream } from "#lib/api/load.ts";import { branchSummaries, importState, sortTreeEntries, tagsByCommitHash, toTagSummary, toTreeCommitSummary, toTreeEntrySummary, type ImportState} from "#lib/api/repo.ts";import { attempt, BRANCH_LIMIT, classifyRepoAvailability, COMMIT_LIMIT, REF_LIMIT, TAG_LIMIT, toLanguageSlices, withAuthorHandles, type RepoAvailability} from "#lib/api/repoIndex.ts";import type { RepoInfo } from "#lib/components/repo/types.ts";import { blobHref } from "#lib/components/repo/urls.ts";
export interface RepoParent { publicConfig: { bobbinUrl: string }; repo: RepoInfo | Promise<RepoInfo>;}
export interface RepoLoadEvent { url: URL;}
const currentFirst = <T extends { name: string }>(branches: T[], ref: string): T[] => { const current = branches.find((branch) => branch.name === ref); return current ? [current, ...branches.filter((branch) => branch !== current)] : branches;};
const refNames = (branches: { name: string }[], tags: { name: string }[]) => ({ branches: branches.map((branch) => branch.name), tags: tags.map((tag) => tag.name)});
// the knot sends a readme with empty fields when a directory has noneconst readmeOf = (tree: { readme?: { filename: string; contents: string } } | null) => { const readme = tree?.readme; return readme?.filename ? readme : null;};
export interface RepoIndexOptions { requireRef?: boolean; importStatus?: boolean;}
// `/tree/{ref}` is this same page at another ref, so they share a loadexport const loadRepoIndex = ( parent: RepoParent, ref?: string, // a ref from the url has to resolve or a typo looks like a repo with no // files. the default branch renders whatever the knot managed to answer { requireRef = false, importStatus: withImportStatus = false }: RepoIndexOptions = {}) => { const { fetch } = getRequestEvent(); const repoP = Promise.resolve(parent.repo);
// each list falls back on its own, so half a page still renders // null content is a repo without git, nothing on this page applies to it const loaded = repoP.then(async (repo) => { if (requireRef) requireGit(repo); if (!hasGit(repo)) return null; const targetRef = ref ?? repo.git.defaultBranch; const git = gitTarget(parent.publicConfig, repo, fetch);
const results = await parallel({ tree: attempt(gitTree(git, { ref: targetRef })), log: attempt(gitLog(git, { ref: targetRef, limit: COMMIT_LIMIT })), branch: attempt(gitBranch(git, targetRef)), branches: attempt(gitBranches(git, REF_LIMIT)), tags: attempt(gitTags(git, REF_LIMIT)) });
const branches = results.branches.value ? branchSummaries(results.branches.value) : []; const tags = (results.tags.value?.tags ?? []).map(toTagSummary); const commits = await withAuthorHandles( (results.log.value?.commits ?? []).map(toMirrorCommitSummary), parent.publicConfig.bobbinUrl, fetch ); const files = sortTreeEntries((results.tree.value?.files ?? []).map(toTreeEntrySummary));
const contentAttempts = [results.tree, results.log, results.branches]; const mirror = classifyRepoAvailability(contentAttempts); const availability: RepoAvailability = mirror === "ok" && files.length === 0 && branches.length === 0 ? "empty" : mirror;
// there are refs but not this one, so it is not a real ref. an empty repo has // no refs at all and still gets a page if (requireRef && results.tree.value === null && branches.length > 0) { error(404, `${targetRef} does not exist in this repository`); }
const readme = readmeOf(results.tree.value);
return { content: { ref: targetRef, availability, files, readme, commits, tagsByCommit: tagsByCommitHash(commits, tags), totalCommits: results.branch.value?.totalCommits ?? commits.length, branches: currentFirst(branches, targetRef).slice(0, BRANCH_LIMIT), totalBranches: results.branches.value?.total ?? branches.length, tags: tags.slice(0, TAG_LIMIT), totalTags: results.tags.value?.total ?? tags.length, // the switcher needs every ref, not just the visible slice refs: refNames(branches, tags) } }; });
const content = loaded.then((resolved) => resolved?.content ?? null);
const languages = repoP.then(async (repo) => { if (!hasGit(repo)) return []; const targetRef = ref ?? repo.git.defaultBranch; if (targetRef === repo.git.defaultBranch && repo.git.languages) { return toLanguageSlices(repo.git.languages); } const git = gitTarget(parent.publicConfig, repo, fetch); const result = await attempt(gitLanguages(git, targetRef)); return toLanguageSlices(result.value?.languages ?? []); });
const importStatusP = withImportStatus ? repoP.then(async (repo): Promise<ImportState | null> => { if (!repo.importSource || !hasGit(repo)) return null; const state = await orNull(importState(repo, { fetch })); return state ? { ...state, source: repo.importSource } : null; }) : Promise.resolve(null);
return { ref: ref ?? "", content: stream(content), languages: stream(languages), importStatus: stream(importStatusP) };};
export const loadRepoTree = ( event: RepoLoadEvent & { params: { handle: string; repo: string } }, parent: RepoParent, ref: string, path: string) => { const { fetch } = getRequestEvent(); const repoP = Promise.resolve(parent.repo);
const loaded = repoP.then(async (repo) => { const git = gitTarget(parent.publicConfig, requireGit(repo), fetch); const result = await orNull(gitTree(git, { ref, path })); const files = sortTreeEntries((result?.files ?? []).map(toTreeEntrySummary)); // the mirror lists nothing for a file and git can't keep an empty dir, so empty means blob or nothing if (result === null || files.length === 0) { redirect(307, blobHref(event.params.handle, event.params.repo, ref, path)); }
// the same author resolution the blob page and commit log give their commits, so // the panel shows the account's avatar and handle rather than the raw git name const [lastCommit = null] = result.lastCommit ? await withAuthorHandles( [toTreeCommitSummary(result.lastCommit)], parent.publicConfig.bobbinUrl, fetch ) : [];
return { files, readme: readmeOf(result), lastCommit }; });
return { ref, path, tree: stream(loaded) };};