Something went wrong. Try again.
Monorepo for Tangled tangled.org
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157import type { ResourceUri } from "@atcute/lexicons";import { isDid, type Did } from "@atcute/lexicons/syntax";import { ClientResponseError, type BobbinContext, type XrpcRequestInit } from "$lib/api/client";import { resolveEmailToDid } from "$lib/api/emailDid";import { getBlame } from "$lib/api/gitmirror";import { resolveMiniDoc } from "$lib/api/identity";import { blame as knotBlame } from "$lib/api/knot";import { didFromSignature, splitMessage } from "$lib/api/repo";import type * as GetBlame from "$lib/api/lexicons/types/sh/tangled/git/temp2/getBlame";import type * as KnotBlame from "$lib/api/lexicons/types/sh/tangled/repo/blame";import type { Blame, BlameCommit } from "$lib/components/repo/blame/types";
type RunTable = { start: number; count: number; commit: string };
// knot and mirror payloads share the run-table contract: commits must be unique// and runs must be a gap-free, sorted cover of the file starting at line 1const toBlame = (commits: readonly BlameCommit[], runs: readonly RunTable[]): Blame => { const byShort = new Map<string, BlameCommit>(); for (const commit of commits) { if (byShort.has(commit.sha)) throw new Error(`duplicate blame commit: ${commit.sha}`); byShort.set(commit.sha, commit); } const blame: Blame = []; let nextLine = 1; for (const run of runs) { if (run.start !== nextLine || run.count < 1 || !Number.isSafeInteger(run.count)) { throw new Error(`invalid blame run at line ${run.start}`); } const commit = byShort.get(run.commit); if (!commit) throw new Error(`unknown blame commit: ${run.commit}`); blame.push({ start: run.start, count: run.count, commit }); nextLine += run.count; } return blame;};
// a did in the author slot is a tangled account signing its own commitsconst toAuthor = ({ name, email }: { name: string; email: string }) => ({ name, email, did: didFromSignature(email)});
const fromKnot = (result: KnotBlame.$output): Blame => toBlame( result.commits.map((commit) => ({ sha: commit.sha, subject: commit.subject, when: commit.authoredAt, author: toAuthor(commit.author) })), result.runs );
const fromMirror = (result: GetBlame.$output): Blame => toBlame( result.commits.map((commit) => ({ sha: commit.oid, subject: splitMessage(commit.message)[0], when: commit.author.when, author: toAuthor(commit.author) })), result.runs );
// the mirror only speaks for what it has fetched: a repo it doesn't mirror is// RepoNotFound, a commit it hasn't caught up to is RefNotFound, and the knot can// still answer bothconst mirrorMiss = (cause: unknown): boolean => cause instanceof ClientResponseError && (cause.error === "RepoNotFound" || cause.error === "RefNotFound");
// the mirror keys repos by bobbin's repo did, the knot by the record's at-uri, so// blame needs both names for the same repoexport const blameFor = async ( ctx: BobbinContext, repo: { repo: ResourceUri; repoDid?: Did }, ref: string, path: string, init?: XrpcRequestInit): Promise<Blame> => { if (repo.repoDid) { try { return fromMirror(await getBlame(ctx, { repo: repo.repoDid, ref, path }, init)); } catch (cause) { if (!mirrorMiss(cause)) throw cause; } } return fromKnot(await knotBlame(ctx, { repo: repo.repo, ref, path }, init));};
const MAX_AUTHOR_HYDRATIONS = 250;const AUTHOR_HYDRATION_CONCURRENCY = 8;
const mapConcurrent = async <T, U>( items: T[], concurrency: number, fn: (item: T) => Promise<U>): Promise<U[]> => { const results = new Array<U>(items.length); let next = 0; await Promise.all( Array.from({ length: Math.min(concurrency, items.length) }, async () => { while (next < items.length) { const index = next++; results[index] = await fn(items[index]); } }) ); return results;};
export const hydrateBlameAuthors = async (blame: Blame, ctx: BobbinContext): Promise<Blame> => { const emails = [ ...new Set( blame.flatMap(({ commit }) => commit.author.did || !commit.author.email ? [] : [commit.author.email] ) ) ].slice(0, MAX_AUTHOR_HYDRATIONS); const emailDids = await resolveEmailToDid(ctx.fetch, emails); const didFor = (email: string): string | undefined => isDid(email) ? email : emailDids.get(email); const dids = [ ...new Set( blame.flatMap(({ commit }) => { const did = commit.author.did ?? didFor(commit.author.email); return did ? [did] : []; }) ) ].slice(0, MAX_AUTHOR_HYDRATIONS); const docs = await mapConcurrent( dids, AUTHOR_HYDRATION_CONCURRENCY, async (did) => [did, await resolveMiniDoc(ctx, did).catch(() => null)] as const ); const handles = new Map( docs.flatMap(([did, doc]) => doc && !doc.handle.endsWith(".invalid") ? [[did, doc.handle] as const] : [] ) ); return blame.map((run) => { const did = run.commit.author.did ?? didFor(run.commit.author.email); return { ...run, commit: { ...run.commit, author: { ...run.commit.author, did, handle: did ? handles.get(did) : undefined } } }; });};