import { recordArtifactsCall } from "./artifacts-health";
import { basicAuthHeader, repoTokenSecret } from "./artifacts";
import { parsePack } from "./pack";
import { concatBytes, pktLine } from "./pktline";
import { limitStream, PackTooLargeError } from "./ref-sync-protocol";
import { packFromResponse, remoteFor } from "./snapshot";
const DELIM_PKT = new TextEncoder().encode("0001");
const FLUSH = new TextEncoder().encode("0000");
/** Trees come too (Artifacts only supports `blob:none`), so caps stay well inside the 128 MB isolate. */
const MAX_GRAPH_BYTES = 24 * 1024 * 1024;
const MAX_GRAPH_INFLATED_BYTES = 40 * 1024 * 1024;
const MAX_GRAPH_OBJECTS = 200_000;
const MAX_GRAPH_OBJECT_BYTES = 8 * 1024 * 1024;
const GRAPH_TIMEOUT_MS = 25_000;
const PARENT_LINE = /^parent ([0-9a-f]{40})$/gm;
const decoder = new TextDecoder();
function parentsOf(content: Uint8Array): string[] {
const header = decoder.decode(content.subarray(0, Math.min(content.byteLength, 64 * 1024))).split("\n\n")[0];
return [...header.matchAll(PARENT_LINE)].map((match) => match[1]);
}
/**
* Whether `target` is an ancestor of any of `tips`, in ONE protocol v2 fetch: wants every tip, has the
* target, `filter blob:none`. The pack then holds exactly the commits reachable from the tips but not from
* the target, so the target is reachable iff one of those commits names it as a parent.
* Artifacts' `ready`/`ACK` only report that it has the object (it answers `ready` for unrelated commits),
* so they cannot be used. Returns null when the answer is unknown (no remote, error, or over budget).
*/
export async function reachableViaFetch(
artifactsName: string,
tips: string[],
target: string,
): Promise<boolean | null> {
if (tips.includes(target)) return true;
const wants = [...new Set(tips)];
if (wants.length === 0) return false;
const remote = await remoteFor(artifactsName);
if (!remote) return null;