<script lang="ts">
import type { Did } from "@atcute/lexicons/syntax";
import { resolve } from "$app/paths";
import { page } from "$app/state";
import { untrack } from "svelte";
import {
loadDiff,
type RepoDiff,
type RepoDiffDeps,
type RepoDiffSpec
} from "#lib/api/repoDiff.ts";
import { browser } from "$app/env";
import { formatPatchUrl } from "#lib/api/knotmirror.ts";
import { errorMessage } from "#lib/api/load.ts";
import { sentence } from "#lib/format.ts";
import { getPatchStream } from "#lib/api/patchStream.ts";
import type { CommitDetail } from "#lib/api/repo.ts";
import { FULL_HASH_RE } from "#lib/api/repo.ts";
import CommitHeader from "#lib/components/repo/CommitHeader.svelte";
import DiffView from "#lib/components/repo/DiffView.svelte";
import type { CodeViewFile } from "#lib/components/repo/DiffCodeView.svelte";
import { blobHref } from "#lib/components/repo/urls.ts";
import type { DiffStyle } from "#lib/components/repo/pierre.ts";
import Card from "#lib/components/ui/Card.svelte";
import type { CommitStatuses } from "#lib/api/commitStatuses.ts";
interface Props {
ownerHandle: string;
repoName: string;
// bobbin's repo did, which is how the mirror names this repo
repoDid?: Did;
// the ref the page was loaded with. raw downloads only work for full hashes
ref: string;
commit: CommitDetail;
// hex of the first parent, empty for a root commit
parent?: string;
changeId?: string;
// the commit diff always comes from the mirror. pendingSpec: the payload was
// too big to buffer through ssr, so the browser fetches it on mount
mirror: {
diff?: RepoDiff;
error?: string;
pendingSpec?: RepoDiffSpec;
deps: RepoDiffDeps;
};
// side-by-side or stacked
diffStyle?: DiffStyle;
pipelineStatuses?: Promise<CommitStatuses>;
refNames?: string[] | Promise<string[]>;
}
let {
ownerHandle,
repoName,
repoDid,
ref,
commit,
parent = "",
changeId = "",
mirror,
diffStyle = "unified",
pipelineStatuses,
refNames
}: Props = $props();
const base = $derived(`/${ownerHandle}/${repoName}`);
const blobUrl = $derived((file: CodeViewFile) =>
file.kind === "deleted"
? undefined
: blobHref(ownerHandle, repoName, commit.hash, file.name)
);
let deferred = $state<{ spec?: RepoDiffSpec; diff?: RepoDiff; error?: string }>({});
$effect(() => {
const spec = mirror.pendingSpec;
if (!spec || deferred.spec === spec) return;
deferred = { spec };
const deps = untrack(() => mirror.deps);
loadDiff(deps, spec).then(
(diff) => {
if (deferred.spec === spec) deferred = { spec, diff };
},
(cause) => {
if (deferred.spec === spec) {
deferred = {
spec,
error: errorMessage(cause, "Could not load commit diff from the mirror")
};
}
}
);
});
const mirrorDiff = $derived(
mirror.pendingSpec && deferred.spec === mirror.pendingSpec ? deferred.diff : mirror.diff
);
const mirrorError = $derived.by(() => {
const error =
mirror.pendingSpec && deferred.spec === mirror.pendingSpec
? deferred.error
: mirror.error;
return error && sentence(error);
});
const mirrorLoading = $derived(Boolean(mirror.pendingSpec) && !mirrorDiff && !mirrorError);
// the raw commit route only accepts full sha1/sha256 hex, anything shorter 404s
const downloadUrls = $derived(
FULL_HASH_RE.test(ref)
? {
patch: resolve(`${base}/commit/${ref}.patch` as "/"),
diff: resolve(`${base}/commit/${ref}.diff` as "/")
}
: undefined
);
const patchUrl = $derived(
repoDid && parent
? formatPatchUrl(mirror.deps.git.ctx.serviceUrl, {
repo: repoDid,
from: parent,
to: ref
})
: undefined
);
// Lookup rather than effect to avoid transient undefined triggering duplicate fetch.
const patch = $derived(browser && patchUrl ? getPatchStream(patchUrl) : undefined);
</script>
<div class="flex flex-col gap-xs pb-md">
<Card>
<CommitHeader
{ownerHandle}
{repoName}
{commit}
{parent}
{changeId}
{pipelineStatuses}
{refNames}
/>
</Card>
<DiffView
deps={mirror.deps}
diff={mirrorDiff}
error={mirrorError}
loading={mirrorLoading}
{blobUrl}
{diffStyle}
styleHref={(style) => `?diff=${style}${page.url.hash}`}
{downloadUrls}
{patchUrl}
{patch}
linkLines
/>
</div>