git-fs-s3 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -102,7 +102,7 @@ async function listBranches(repo) {
102
102
  throw err;
103
103
  }
104
104
  }
105
- async function createBranchFrom(repo, name, startPoint = "main") {
105
+ async function createBranchFrom(repo, name, startPoint) {
106
106
  assertSafeBranchName(name);
107
107
  assertSafeBranchName(startPoint);
108
108
  const object = await resolveLooseRefFast(repo, `refs/heads/${startPoint}`);
@@ -126,4 +126,4 @@ export {
126
126
  deleteBranchByName,
127
127
  assertBranchExists
128
128
  };
129
- //# sourceMappingURL=chunk-2JP4MS6H.js.map
129
+ //# sourceMappingURL=chunk-XNSZTA4Y.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/ops/branch.ts"],"sourcesContent":["import git from \"isomorphic-git\";\nimport { GitInvalidRequestError } from \"../git-errors.js\";\nimport { isSafeBranchName } from \"../refs.js\";\nimport type { Repo } from \"./types.js\";\n\nexport interface Branch {\n\tname: string;\n\tcommit: string;\n\tisDefault: boolean;\n}\n\n/**\n * Defense in depth for every branch-name argument below: `git.deleteBranch`\n * and raw resolveRef/writeRef reads don't validate ref names internally the\n * way `git.branch` does (see refs.ts) — guard at the point the primitives are\n * actually called, not just at an API boundary far above.\n */\nexport function assertSafeBranchName(name: string): void {\n\tif (!isSafeBranchName(name)) {\n\t\tthrow new GitInvalidRequestError(`Invalid branch name: ${name}`);\n\t}\n}\n\nconst FULL_SHA_RE = /^[0-9a-f]{40}$/i;\n\ntype RecursiveFileLister = {\n\tlistFilesRecursively(dirpath: string): Promise<string[]>;\n};\n\nfunction hasRecursiveFileLister(\n\tfs: Repo[\"fs\"],\n): fs is Repo[\"fs\"] & RecursiveFileLister {\n\treturn (\n\t\ttypeof fs === \"object\" &&\n\t\tfs !== null &&\n\t\t\"listFilesRecursively\" in fs &&\n\t\ttypeof (fs as Partial<RecursiveFileLister>).listFilesRecursively ===\n\t\t\t\"function\"\n\t);\n}\n\ntype PromisesFs = {\n\treadFile(path: string, encoding: \"utf8\"): Promise<string | Uint8Array>;\n};\n\n// Repo.fs is typed as isomorphic-git's own fs union (promise- or\n// callback-based) since that's what git.readTree's signature allows, but\n// every fs this package actually constructs (git-fs.ts) is promise-based —\n// this just keeps the callback-style branch honest instead of crashing on\n// it. Shared by every loose-ref fast path below instead of each repeating\n// the same cast + presence check.\nfunction getPromisesFs(fs: Repo[\"fs\"]): PromisesFs | null {\n\tconst promises = (fs as { promises?: PromisesFs }).promises;\n\treturn promises ?? null;\n}\n\nfunction decodeFileContent(content: string | Uint8Array): string {\n\treturn typeof content === \"string\"\n\t\t? content\n\t\t: new TextDecoder().decode(content);\n}\n\n/**\n * Lists loose branch names without isomorphic-git's recursive readdir/stat\n * traversal. Object stores already return only leaf keys in a recursive LIST,\n * so this is one request regardless of branch-name nesting. Ordinary\n * filesystems retain isomorphic-git's implementation and packed refs remain\n * supported there.\n */\nasync function listLooseBranches(repo: Repo): Promise<string[] | null> {\n\treturn listLooseRefNames(repo, \"refs/heads\");\n}\n\n/**\n * Loose ref names under `dir` (e.g. `refs/tags`) in one recursive listing,\n * or `null` when the fs can't list recursively (use isomorphic-git's walk).\n */\nexport async function listLooseRefNames(\n\trepo: Repo,\n\tdir: \"refs/heads\" | \"refs/tags\",\n): Promise<string[] | null> {\n\tif (!hasRecursiveFileLister(repo.fs)) return null;\n\tconst names = await repo.fs.listFilesRecursively(`${repo.gitdir}/${dir}`);\n\treturn names.filter(isSafeBranchName);\n}\n\n/** Read packed branch refs so the object-store fast path preserves Git semantics. */\nasync function readPackedBranches(repo: Repo): Promise<Map<string, string>> {\n\tif (!hasRecursiveFileLister(repo.fs)) return new Map();\n\tconst promisesFs = getPromisesFs(repo.fs);\n\tif (!promisesFs) return new Map();\n\ttry {\n\t\tconst content = await promisesFs.readFile(\n\t\t\t`${repo.gitdir}/packed-refs`,\n\t\t\t\"utf8\",\n\t\t);\n\t\tconst refs = new Map<string, string>();\n\t\tfor (const line of decodeFileContent(content).split(\"\\n\")) {\n\t\t\tconst match = /^([0-9a-f]{40}) refs\\/heads\\/(.+)$/i.exec(line);\n\t\t\tif (match?.[1] && match[2] && isSafeBranchName(match[2])) {\n\t\t\t\trefs.set(match[2], match[1]);\n\t\t\t}\n\t\t}\n\t\treturn refs;\n\t} catch {\n\t\treturn new Map();\n\t}\n}\n\n/** Read the symbolic HEAD directly when the object-store fs is available. */\nasync function getCurrentLooseBranch(repo: Repo): Promise<string | null> {\n\tif (!hasRecursiveFileLister(repo.fs)) return null;\n\tconst promisesFs = getPromisesFs(repo.fs);\n\tif (!promisesFs) return null;\n\ttry {\n\t\tconst content = await promisesFs.readFile(`${repo.gitdir}/HEAD`, \"utf8\");\n\t\tconst head = decodeFileContent(content).trim();\n\t\tconst match = /^ref: refs\\/heads\\/(.+)$/.exec(head);\n\t\treturn match?.[1] && isSafeBranchName(match[1]) ? match[1] : null;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\n/**\n * Resolve a fully-qualified ref's oid with one read instead of isomorphic-\n * git's own `resolveRef`, which does a stat *then* a read for a loose ref\n * (two round trips against object storage) — wasteful specifically here,\n * where the caller already knows the ref exists (it came from a directory\n * listing moments earlier). Reads the loose file directly and falls back to\n * `git.resolveRef` only when that doesn't pan out (packed-refs, or anything\n * else the plain-loose-file assumption doesn't cover), so correctness for\n * those cases is unchanged — this only removes the redundant round trip on\n * the common path.\n */\nexport async function resolveLooseRefFast(\n\trepo: Repo,\n\tref: string,\n): Promise<string> {\n\tconst promisesFs = getPromisesFs(repo.fs);\n\tif (promisesFs) {\n\t\ttry {\n\t\t\tconst content = await promisesFs.readFile(\n\t\t\t\t`${repo.gitdir}/${ref}`,\n\t\t\t\t\"utf8\",\n\t\t\t);\n\t\t\tconst oid = decodeFileContent(content).trim();\n\t\t\tif (FULL_SHA_RE.test(oid)) return oid;\n\t\t} catch {\n\t\t\t// Not a loose file (packed-refs, or genuinely absent) — fall through.\n\t\t}\n\t}\n\treturn git.resolveRef({ ...repo, ref });\n}\n\n/** All branches with their tip commits; [] for an empty repository. */\nexport async function listBranches(repo: Repo): Promise<Branch[]> {\n\ttry {\n\t\tconst [looseBranches, looseCurrentBranch, packedBranches] =\n\t\t\tawait Promise.all([\n\t\t\t\tlistLooseBranches(repo),\n\t\t\t\tgetCurrentLooseBranch(repo),\n\t\t\t\treadPackedBranches(repo),\n\t\t\t]);\n\t\tconst branches =\n\t\t\tlooseBranches === null\n\t\t\t\t? await git.listBranches(repo)\n\t\t\t\t: [...new Set([...looseBranches, ...packedBranches.keys()])].sort();\n\t\tconst currentBranch =\n\t\t\tlooseBranches === null\n\t\t\t\t? await git\n\t\t\t\t\t\t.currentBranch({ ...repo, fullname: false })\n\t\t\t\t\t\t.catch(() => null)\n\t\t\t\t: looseCurrentBranch;\n\n\t\treturn Promise.all(\n\t\t\tbranches.map(async (branch) => {\n\t\t\t\tconst packedCommit = packedBranches.get(branch);\n\t\t\t\treturn {\n\t\t\t\t\tname: branch,\n\t\t\t\t\tcommit:\n\t\t\t\t\t\tpackedCommit && !looseBranches?.includes(branch)\n\t\t\t\t\t\t\t? packedCommit\n\t\t\t\t\t\t\t: await resolveLooseRefFast(repo, `refs/heads/${branch}`),\n\t\t\t\t\tisDefault: branch === currentBranch,\n\t\t\t\t};\n\t\t\t}),\n\t\t);\n\t} catch (err: unknown) {\n\t\tif ((err as { code?: string }).code === \"NotFoundError\") return [];\n\t\tthrow err;\n\t}\n}\n\n/** Create `name` pointing at the tip of `startPoint` (no checkout). */\nexport async function createBranchFrom(\n\trepo: Repo,\n\tname: string,\n\tstartPoint = \"main\",\n): Promise<void> {\n\tassertSafeBranchName(name);\n\tassertSafeBranchName(startPoint);\n\tconst object = await resolveLooseRefFast(repo, `refs/heads/${startPoint}`);\n\tawait git.branch({ ...repo, ref: name, checkout: false, object });\n}\n\n/** Delete a branch ref (validated — deleteBranch has no internal ref check). */\nexport async function deleteBranchByName(\n\trepo: Repo,\n\tname: string,\n): Promise<void> {\n\tassertSafeBranchName(name);\n\tawait git.deleteBranch({ ...repo, ref: name });\n}\n\n/** Throws (NotFoundError) unless the branch resolves. */\nexport async function assertBranchExists(\n\trepo: Repo,\n\tname: string,\n): Promise<void> {\n\tassertSafeBranchName(name);\n\tawait resolveLooseRefFast(repo, `refs/heads/${name}`);\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/ops/branch.ts"],"sourcesContent":["import git from \"isomorphic-git\";\nimport { GitInvalidRequestError } from \"../git-errors.js\";\nimport { isSafeBranchName } from \"../refs.js\";\nimport type { Repo } from \"./types.js\";\n\nexport interface Branch {\n\tname: string;\n\tcommit: string;\n\tisDefault: boolean;\n}\n\n/**\n * Defense in depth for every branch-name argument below: `git.deleteBranch`\n * and raw resolveRef/writeRef reads don't validate ref names internally the\n * way `git.branch` does (see refs.ts) — guard at the point the primitives are\n * actually called, not just at an API boundary far above.\n */\nexport function assertSafeBranchName(name: string): void {\n\tif (!isSafeBranchName(name)) {\n\t\tthrow new GitInvalidRequestError(`Invalid branch name: ${name}`);\n\t}\n}\n\nconst FULL_SHA_RE = /^[0-9a-f]{40}$/i;\n\ntype RecursiveFileLister = {\n\tlistFilesRecursively(dirpath: string): Promise<string[]>;\n};\n\nfunction hasRecursiveFileLister(\n\tfs: Repo[\"fs\"],\n): fs is Repo[\"fs\"] & RecursiveFileLister {\n\treturn (\n\t\ttypeof fs === \"object\" &&\n\t\tfs !== null &&\n\t\t\"listFilesRecursively\" in fs &&\n\t\ttypeof (fs as Partial<RecursiveFileLister>).listFilesRecursively ===\n\t\t\t\"function\"\n\t);\n}\n\ntype PromisesFs = {\n\treadFile(path: string, encoding: \"utf8\"): Promise<string | Uint8Array>;\n};\n\n// Repo.fs is typed as isomorphic-git's own fs union (promise- or\n// callback-based) since that's what git.readTree's signature allows, but\n// every fs this package actually constructs (git-fs.ts) is promise-based —\n// this just keeps the callback-style branch honest instead of crashing on\n// it. Shared by every loose-ref fast path below instead of each repeating\n// the same cast + presence check.\nfunction getPromisesFs(fs: Repo[\"fs\"]): PromisesFs | null {\n\tconst promises = (fs as { promises?: PromisesFs }).promises;\n\treturn promises ?? null;\n}\n\nfunction decodeFileContent(content: string | Uint8Array): string {\n\treturn typeof content === \"string\"\n\t\t? content\n\t\t: new TextDecoder().decode(content);\n}\n\n/**\n * Lists loose branch names without isomorphic-git's recursive readdir/stat\n * traversal. Object stores already return only leaf keys in a recursive LIST,\n * so this is one request regardless of branch-name nesting. Ordinary\n * filesystems retain isomorphic-git's implementation and packed refs remain\n * supported there.\n */\nasync function listLooseBranches(repo: Repo): Promise<string[] | null> {\n\treturn listLooseRefNames(repo, \"refs/heads\");\n}\n\n/**\n * Loose ref names under `dir` (e.g. `refs/tags`) in one recursive listing,\n * or `null` when the fs can't list recursively (use isomorphic-git's walk).\n */\nexport async function listLooseRefNames(\n\trepo: Repo,\n\tdir: \"refs/heads\" | \"refs/tags\",\n): Promise<string[] | null> {\n\tif (!hasRecursiveFileLister(repo.fs)) return null;\n\tconst names = await repo.fs.listFilesRecursively(`${repo.gitdir}/${dir}`);\n\treturn names.filter(isSafeBranchName);\n}\n\n/** Read packed branch refs so the object-store fast path preserves Git semantics. */\nasync function readPackedBranches(repo: Repo): Promise<Map<string, string>> {\n\tif (!hasRecursiveFileLister(repo.fs)) return new Map();\n\tconst promisesFs = getPromisesFs(repo.fs);\n\tif (!promisesFs) return new Map();\n\ttry {\n\t\tconst content = await promisesFs.readFile(\n\t\t\t`${repo.gitdir}/packed-refs`,\n\t\t\t\"utf8\",\n\t\t);\n\t\tconst refs = new Map<string, string>();\n\t\tfor (const line of decodeFileContent(content).split(\"\\n\")) {\n\t\t\tconst match = /^([0-9a-f]{40}) refs\\/heads\\/(.+)$/i.exec(line);\n\t\t\tif (match?.[1] && match[2] && isSafeBranchName(match[2])) {\n\t\t\t\trefs.set(match[2], match[1]);\n\t\t\t}\n\t\t}\n\t\treturn refs;\n\t} catch {\n\t\treturn new Map();\n\t}\n}\n\n/** Read the symbolic HEAD directly when the object-store fs is available. */\nasync function getCurrentLooseBranch(repo: Repo): Promise<string | null> {\n\tif (!hasRecursiveFileLister(repo.fs)) return null;\n\tconst promisesFs = getPromisesFs(repo.fs);\n\tif (!promisesFs) return null;\n\ttry {\n\t\tconst content = await promisesFs.readFile(`${repo.gitdir}/HEAD`, \"utf8\");\n\t\tconst head = decodeFileContent(content).trim();\n\t\tconst match = /^ref: refs\\/heads\\/(.+)$/.exec(head);\n\t\treturn match?.[1] && isSafeBranchName(match[1]) ? match[1] : null;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\n/**\n * Resolve a fully-qualified ref's oid with one read instead of isomorphic-\n * git's own `resolveRef`, which does a stat *then* a read for a loose ref\n * (two round trips against object storage) — wasteful specifically here,\n * where the caller already knows the ref exists (it came from a directory\n * listing moments earlier). Reads the loose file directly and falls back to\n * `git.resolveRef` only when that doesn't pan out (packed-refs, or anything\n * else the plain-loose-file assumption doesn't cover), so correctness for\n * those cases is unchanged — this only removes the redundant round trip on\n * the common path.\n */\nexport async function resolveLooseRefFast(\n\trepo: Repo,\n\tref: string,\n): Promise<string> {\n\tconst promisesFs = getPromisesFs(repo.fs);\n\tif (promisesFs) {\n\t\ttry {\n\t\t\tconst content = await promisesFs.readFile(\n\t\t\t\t`${repo.gitdir}/${ref}`,\n\t\t\t\t\"utf8\",\n\t\t\t);\n\t\t\tconst oid = decodeFileContent(content).trim();\n\t\t\tif (FULL_SHA_RE.test(oid)) return oid;\n\t\t} catch {\n\t\t\t// Not a loose file (packed-refs, or genuinely absent) — fall through.\n\t\t}\n\t}\n\treturn git.resolveRef({ ...repo, ref });\n}\n\n/** All branches with their tip commits; [] for an empty repository. */\nexport async function listBranches(repo: Repo): Promise<Branch[]> {\n\ttry {\n\t\tconst [looseBranches, looseCurrentBranch, packedBranches] =\n\t\t\tawait Promise.all([\n\t\t\t\tlistLooseBranches(repo),\n\t\t\t\tgetCurrentLooseBranch(repo),\n\t\t\t\treadPackedBranches(repo),\n\t\t\t]);\n\t\tconst branches =\n\t\t\tlooseBranches === null\n\t\t\t\t? await git.listBranches(repo)\n\t\t\t\t: [...new Set([...looseBranches, ...packedBranches.keys()])].sort();\n\t\tconst currentBranch =\n\t\t\tlooseBranches === null\n\t\t\t\t? await git\n\t\t\t\t\t\t.currentBranch({ ...repo, fullname: false })\n\t\t\t\t\t\t.catch(() => null)\n\t\t\t\t: looseCurrentBranch;\n\n\t\treturn Promise.all(\n\t\t\tbranches.map(async (branch) => {\n\t\t\t\tconst packedCommit = packedBranches.get(branch);\n\t\t\t\treturn {\n\t\t\t\t\tname: branch,\n\t\t\t\t\tcommit:\n\t\t\t\t\t\tpackedCommit && !looseBranches?.includes(branch)\n\t\t\t\t\t\t\t? packedCommit\n\t\t\t\t\t\t\t: await resolveLooseRefFast(repo, `refs/heads/${branch}`),\n\t\t\t\t\tisDefault: branch === currentBranch,\n\t\t\t\t};\n\t\t\t}),\n\t\t);\n\t} catch (err: unknown) {\n\t\tif ((err as { code?: string }).code === \"NotFoundError\") return [];\n\t\tthrow err;\n\t}\n}\n\n/** Create `name` pointing at the tip of `startPoint` (no checkout). */\nexport async function createBranchFrom(\n\trepo: Repo,\n\tname: string,\n\tstartPoint: string,\n): Promise<void> {\n\tassertSafeBranchName(name);\n\tassertSafeBranchName(startPoint);\n\tconst object = await resolveLooseRefFast(repo, `refs/heads/${startPoint}`);\n\tawait git.branch({ ...repo, ref: name, checkout: false, object });\n}\n\n/** Delete a branch ref (validated — deleteBranch has no internal ref check). */\nexport async function deleteBranchByName(\n\trepo: Repo,\n\tname: string,\n): Promise<void> {\n\tassertSafeBranchName(name);\n\tawait git.deleteBranch({ ...repo, ref: name });\n}\n\n/** Throws (NotFoundError) unless the branch resolves. */\nexport async function assertBranchExists(\n\trepo: Repo,\n\tname: string,\n): Promise<void> {\n\tassertSafeBranchName(name);\n\tawait resolveLooseRefFast(repo, `refs/heads/${name}`);\n}\n"],"mappings":"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","names":[]}
package/dist/http.js CHANGED
@@ -6,7 +6,7 @@ import {
6
6
  listBranches,
7
7
  listLooseRefNames,
8
8
  resolveLooseRefFast
9
- } from "./chunk-2JP4MS6H.js";
9
+ } from "./chunk-XNSZTA4Y.js";
10
10
  import {
11
11
  concat,
12
12
  decodeAscii,
@@ -79,7 +79,7 @@ function rawFs(repo) {
79
79
  }
80
80
 
81
81
  // src/http/info-refs.ts
82
- async function listAllRefs(repo, defaultBranch = "main") {
82
+ async function listAllRefs(repo, defaultBranch) {
83
83
  const [branches, tagNames] = await Promise.all([
84
84
  listBranches(repo),
85
85
  listLooseRefNames(repo, "refs/tags").then(
@@ -111,7 +111,7 @@ async function handleInfoRefs(repo, options, hooks) {
111
111
  const { refs, headSymref } = await runStep(
112
112
  hooks,
113
113
  "listAllRefs",
114
- () => listAllRefs(repo, options.defaultBranch ?? "main")
114
+ () => listAllRefs(repo, options.defaultBranch)
115
115
  );
116
116
  const isUpload = service === "git-upload-pack";
117
117
  const caps = isUpload ? `no-progress side-band-64k symref=HEAD:${headSymref} allow-tip-sha1-in-want allow-reachable-sha1-in-want agent=${agent}` : `delete-refs report-status no-done agent=${agent}`;
@@ -552,7 +552,7 @@ function parseReceivePackBody(body) {
552
552
  }
553
553
  return { refUpdates, packData: body.subarray(pos) };
554
554
  }
555
- async function ensureRepoInitialized(repo, defaultBranch = "main") {
555
+ async function ensureRepoInitialized(repo, defaultBranch) {
556
556
  const fsp = rawFs(repo);
557
557
  try {
558
558
  await fsp.stat(`${repo.gitdir}/HEAD`);
@@ -662,7 +662,7 @@ function receivePackResponse(results) {
662
662
  };
663
663
  }
664
664
  async function applyReceivePack(repo, parsed, options, hooks) {
665
- await ensureRepoInitialized(repo, options?.defaultBranch ?? "main");
665
+ await ensureRepoInitialized(repo, options.defaultBranch);
666
666
  const preflight = await validateRefUpdates(repo, parsed.refUpdates);
667
667
  const acceptedUpdates = parsed.refUpdates.filter(
668
668
  (_, index) => preflight[index]?.ok
@@ -705,7 +705,7 @@ async function applyReceivePack(repo, parsed, options, hooks) {
705
705
  (entry) => entry.refName === parsed.refUpdates[index]?.refName
706
706
  ) ?? result : result
707
707
  );
708
- const repackOptions = options?.repack;
708
+ const repackOptions = options.repack;
709
709
  const stalePackPaths = repackOptions === false || !results.some((result) => result.ok) ? [] : await runStep(
710
710
  hooks,
711
711
  "repack",
package/dist/http.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/http/info-refs.ts","../src/http/pkt-line.ts","../src/http/types.ts","../src/http/reachability.ts","../src/http/receive-pack.ts","../src/http/repack.ts","../src/http/pack-reuse.ts","../src/http/upload-pack.ts"],"sourcesContent":["import git from \"isomorphic-git\";\nimport { concat } from \"../edge-utils.js\";\nimport {\n\tlistBranches,\n\tlistLooseRefNames,\n\tresolveLooseRefFast,\n} from \"../ops/branch.js\";\nimport type { Repo } from \"../ops/types.js\";\nimport { FLUSH, pktLine } from \"./pkt-line.js\";\nimport { type GitHttpResult, type HttpHooks, runStep } from \"./types.js\";\n\nexport type GitService = \"git-upload-pack\" | \"git-receive-pack\";\n\n/** All refs with resolved oids, plus the symref HEAD should advertise. */\nexport async function listAllRefs(repo: Repo, defaultBranch = \"main\") {\n\tconst [branches, tagNames] = await Promise.all([\n\t\tlistBranches(repo),\n\t\tlistLooseRefNames(repo, \"refs/tags\").then(\n\t\t\t(names) => names ?? git.listTags(repo),\n\t\t),\n\t]);\n\tconst tagRefs = await Promise.all(\n\t\ttagNames.map(async (tag) => {\n\t\t\tconst name = `refs/tags/${tag}`;\n\t\t\treturn resolveLooseRefFast(repo, name)\n\t\t\t\t.then((oid) => ({ name, oid }))\n\t\t\t\t.catch(() => null);\n\t\t}),\n\t);\n\tconst branchRefs = branches.map((b) => ({\n\t\tname: `refs/heads/${b.name}`,\n\t\toid: b.commit,\n\t}));\n\tconst defaultRef = branches.find((b) => b.isDefault);\n\tconst headSymref = defaultRef\n\t\t? `refs/heads/${defaultRef.name}`\n\t\t: await Promise.resolve(git.currentBranch({ ...repo, fullname: true }))\n\t\t\t\t.then((cb) => cb ?? `refs/heads/${defaultBranch}`)\n\t\t\t\t.catch(() => `refs/heads/${defaultBranch}`);\n\n\t// HEAD in a bare repo is almost always a symref to a branch whose oid\n\t// branchRefs already resolved above with one direct GET each — reuse it\n\t// instead of a second, separate resolveRef(\"HEAD\") call. isomorphic-git's\n\t// ref resolution runs its full multi-candidate expansion (refs/%s,\n\t// refs/tags/%s, refs/heads/%s, refs/remotes/%s, refs/remotes/%s/HEAD)\n\t// even when the symref target is already fully qualified, which used to\n\t// cost several guaranteed-404 R2 reads on every clone/fetch. Falls back\n\t// to the slow-but-correct path only when that lookup misses: detached\n\t// HEAD (points at a raw sha, not a symref) or a symref to a branch that\n\t// doesn't exist yet (freshly initialized, still-empty repo).\n\tconst headOid =\n\t\tbranchRefs.find((r) => r?.name === headSymref)?.oid ??\n\t\t(await git.resolveRef({ ...repo, ref: \"HEAD\" }).catch(() => null));\n\n\tconst refs: Array<{ name: string; oid: string }> = [];\n\tif (headOid) refs.push({ name: \"HEAD\", oid: headOid });\n\tfor (const r of branchRefs) if (r) refs.push(r);\n\tfor (const r of tagRefs) if (r) refs.push(r);\n\n\treturn { refs, headSymref };\n}\n\nexport interface InfoRefsOptions {\n\tservice: GitService;\n\tdefaultBranch?: string;\n\t/** Advertised in the agent capability. Default \"git-fs-s3\". */\n\tagent?: string;\n}\n\n/**\n * `GET …/info/refs?service=…` — the ref advertisement. Authentication and\n * authorization are the caller's job before invoking this.\n *\n * Capabilities advertised match what the sibling handlers implement:\n * side-band-64k (honored by handleUploadPack's response framing — clients\n * like isomorphic-git unconditionally expect it), tip/reachable sha1 wants,\n * delete-refs and report-status for pushes.\n */\nexport async function handleInfoRefs(\n\trepo: Repo,\n\toptions: InfoRefsOptions,\n\thooks?: HttpHooks,\n): Promise<GitHttpResult> {\n\tconst { service } = options;\n\tconst agent = options.agent ?? \"git-fs-s3\";\n\tconst { refs, headSymref } = await runStep(hooks, \"listAllRefs\", () =>\n\t\tlistAllRefs(repo, options.defaultBranch ?? \"main\"),\n\t);\n\n\tconst isUpload = service === \"git-upload-pack\";\n\tconst caps = isUpload\n\t\t? `no-progress side-band-64k symref=HEAD:${headSymref} allow-tip-sha1-in-want allow-reachable-sha1-in-want agent=${agent}`\n\t\t: `delete-refs report-status no-done agent=${agent}`;\n\n\tconst parts: Uint8Array[] = [pktLine(`# service=${service}\\n`), FLUSH];\n\n\tif (refs.length === 0) {\n\t\t// Empty repo: git needs this exact sentinel\n\t\tparts.push(\n\t\t\tpktLine(\n\t\t\t\t`0000000000000000000000000000000000000000 capabilities^{}\\0${caps}\\n`,\n\t\t\t),\n\t\t);\n\t} else {\n\t\tlet first = true;\n\t\tfor (const { name, oid } of refs) {\n\t\t\tparts.push(\n\t\t\t\tpktLine(first ? `${oid} ${name}\\0${caps}\\n` : `${oid} ${name}\\n`),\n\t\t\t);\n\t\t\tfirst = false;\n\t\t}\n\t}\n\tparts.push(FLUSH);\n\n\treturn {\n\t\tstatus: 200,\n\t\theaders: {\n\t\t\t\"Content-Type\": `application/x-${service}-advertisement`,\n\t\t\t\"Cache-Control\": \"no-cache\",\n\t\t},\n\t\tbody: concat(...parts),\n\t};\n}\n","/**\n * Git pkt-line framing (smart HTTP protocol wire format).\n *\n * Edge-compatible: uses Uint8Array instead of Buffer.\n */\n\nimport { concat, decodeAscii, decodeUtf8, encodeUtf8 } from \"../edge-utils.js\";\n\n/** Flush packet: exactly four zero bytes. */\nexport const FLUSH: Uint8Array<ArrayBuffer> = new Uint8Array([\n\t0x30, 0x30, 0x30, 0x30,\n]); // \"0000\"\n\n/** Frame a UTF-8 string as one pkt-line. */\nexport function pktLine(data: string): Uint8Array<ArrayBuffer> {\n\tconst body = encodeUtf8(data);\n\tconst len = (body.length + 4).toString(16).padStart(4, \"0\");\n\treturn concat(encodeUtf8(len), body);\n}\n\n/** Frame raw bytes as one pkt-line. */\nexport function pktLineBuffer(body: Uint8Array): Uint8Array<ArrayBuffer> {\n\tconst len = (body.length + 4).toString(16).padStart(4, \"0\");\n\treturn concat(encodeUtf8(len), body);\n}\n\n/** Decode pkt-lines to strings; `null` marks a flush-pkt. Stops on garbage. */\nexport function parsePktLines(buf: Uint8Array): Array<string | null> {\n\tconst lines: Array<string | null> = [];\n\tlet pos = 0;\n\twhile (pos + 4 <= buf.length) {\n\t\tconst len = Number.parseInt(decodeAscii(buf.subarray(pos, pos + 4)), 16);\n\t\tif (len === 0) {\n\t\t\tlines.push(null);\n\t\t\tpos += 4;\n\t\t} else if (len >= 4) {\n\t\t\tlines.push(decodeUtf8(buf.subarray(pos + 4, pos + len)));\n\t\t\tpos += len;\n\t\t} else {\n\t\t\tbreak;\n\t\t}\n\t}\n\treturn lines;\n}\n\n/**\n * Per the git protocol, once side-band-64k has been negotiated, packfile\n * bytes in the upload-pack response must be chunked into pkt-lines each\n * prefixed with a control byte (0x01 = packfile data), terminated by a\n * flush-pkt. Without this, clients that don't special-case \"no side-band\" —\n * e.g. isomorphic-git's GitSideBand.demux, which always treats the response\n * as side-band-framed — misparse the raw packfile bytes as bogus pkt-line\n * length headers and spin forever. (Native `git` tolerates a raw unframed\n * stream when side-band isn't negotiated, so this only surfaces with\n * isomorphic-git as the HTTP client.)\n */\nconst SIDE_BAND_MAX_CHUNK = 65515;\n\nexport function sideBandPackfile(\n\tpackData: Uint8Array,\n): Uint8Array<ArrayBuffer> {\n\tconst parts: Uint8Array[] = [];\n\tfor (\n\t\tlet offset = 0;\n\t\toffset < packData.length;\n\t\toffset += SIDE_BAND_MAX_CHUNK\n\t) {\n\t\tconst chunk = packData.subarray(offset, offset + SIDE_BAND_MAX_CHUNK);\n\t\tparts.push(pktLineBuffer(concat(new Uint8Array([1]), chunk)));\n\t}\n\tparts.push(FLUSH);\n\treturn concat(...parts);\n}\n","import type { Repo } from \"../ops/types.js\";\n\n/**\n * Transport-agnostic HTTP response the handlers produce.\n *\n * `body` is pinned to `Uint8Array<ArrayBuffer>` (not the bare `Uint8Array`,\n * whose default type argument isn't consistent across TypeScript versions)\n * so it's always assignable to Fetch API `BodyInit` regardless of which\n * TypeScript/lib version a consumer compiles against — every producer here\n * (`concat`, `pktLine`, `TextEncoder.encode`) is ArrayBuffer-backed already.\n */\nexport interface GitHttpResult {\n\tstatus: number;\n\theaders: Record<string, string>;\n\tbody: Uint8Array<ArrayBuffer>;\n}\n\n/** Optional instrumentation hooks shared by the /http handlers. */\nexport interface HttpHooks {\n\t/** Wrap a timed sub-step. Default: run directly. */\n\tstep?: <T>(label: string, fn: () => Promise<T>) => Promise<T>;\n\t/** Non-fatal problem sink (missing objects, failed repacks). */\n\tonWarn?: (message: string, error?: unknown) => void;\n}\n\nexport const runStep = <T>(\n\thooks: HttpHooks | undefined,\n\tlabel: string,\n\tfn: () => Promise<T>,\n): Promise<T> => (hooks?.step ? hooks.step(label, fn) : fn());\n\n/**\n * The raw promise-fs surface some /http paths need beyond isomorphic-git's\n * plumbing (writing an incoming pack file, listing/deleting pack files).\n * Both `node:fs` and this package's `GitFs` satisfy it via `.promises`.\n */\nexport interface RawFsPromises {\n\treadFile(path: string): Promise<Uint8Array | string>;\n\twriteFile(path: string, data: Uint8Array | string): Promise<void>;\n\tunlink(path: string): Promise<void>;\n\treaddir(path: string): Promise<string[]>;\n\tmkdir(path: string, options?: { recursive?: boolean }): Promise<unknown>;\n\tstat(path: string): Promise<unknown>;\n}\n\n/** Duck-type the `.promises` surface off a repo's fs. */\nexport function rawFs(repo: Repo): RawFsPromises {\n\tconst fs = repo.fs as { promises?: RawFsPromises };\n\tif (!fs?.promises) {\n\t\tthrow new TypeError(\n\t\t\t\"This operation needs a promise fs (`fs.promises`) — node:fs and createGitFs both provide one\",\n\t\t);\n\t}\n\treturn fs.promises;\n}\n","import git from \"isomorphic-git\";\nimport type { Repo } from \"../ops/types.js\";\nimport type { HttpHooks } from \"./types.js\";\n\nexport interface ReachabilityResult {\n\toids: string[];\n\t/**\n\t * False if any object in the graph couldn't be read — repack uses this\n\t * (not a raw object-count comparison) to decide whether it's safe to\n\t * delete old packs: counts alone can't distinguish \"everything read fine\"\n\t * from \"some objects silently failed\".\n\t */\n\tcomplete: boolean;\n}\n\nconst delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));\n\n/**\n * A read landing in the gap between a repack's new consolidated pack being\n * uploaded and the stale-pack cleanup finishing can transiently miss an\n * object that is not actually lost — it exists in the new pack the whole\n * time; the reader's cached pack listing was just taken mid-transition. One\n * retry after a short delay is enough to observe the consistent listing.\n */\nconst MISSING_OBJECT_RETRY_DELAY_MS = 200;\n\n/**\n * Walk the object graph from `startOids`, returning every reachable oid.\n * Concurrent traversal paths are deduplicated promise-per-oid; a missing\n * object is retried once, then reported through `hooks.onWarn` and\n * reflected in `complete: false`.\n *\n * `haveOids` bounds the walk: any oid in this set is assumed already\n * verified (it was reachable from a ref that existed before this call) and\n * is recorded in `seen` without being read or descended into. Without a\n * boundary, every call re-walks the *entire* history from scratch — for\n * receive-pack validation (see receive-pack.ts) that means every push pays\n * for reading every object in the repo, not just the ones it introduced,\n * and one transient read failure anywhere in that whole history rejects an\n * otherwise-valid push.\n *\n * Commits, trees and tags are read once each (parsed). Blobs have no\n * children, so `verifyBlobs: false` records a blob from its tree entry\n * without reading (and inflating) it — for callers that read every blob\n * themselves afterwards anyway, like upload-pack's `packObjects`. Leave it\n * true wherever the walk *is* the existence check (receive-pack, repack).\n */\nexport async function collectReachableOids(\n\trepo: Repo,\n\tstartOids: string[],\n\thooks?: HttpHooks,\n\thaveOids?: ReadonlySet<string> | readonly string[],\n\toptions?: { verifyBlobs?: boolean },\n): Promise<ReachabilityResult> {\n\tconst verifyBlobs = options?.verifyBlobs ?? true;\n\tconst have = haveOids instanceof Set ? haveOids : new Set(haveOids ?? []);\n\tconst seen = new Set<string>();\n\tlet complete = true;\n\tconst promises = new Map<string, Promise<void>>();\n\n\tasync function readAndVisitChildren(oid: string): Promise<void> {\n\t\tconst obj = await git.readObject({ ...repo, oid });\n\t\tseen.add(oid);\n\t\tconst children: [string, string | undefined][] = [];\n\t\tif (obj.type === \"commit\") {\n\t\t\tconst commit = obj.object as { tree: string; parent: string[] };\n\t\t\tchildren.push([commit.tree, \"tree\"]);\n\t\t\tfor (const parent of commit.parent) children.push([parent, \"commit\"]);\n\t\t} else if (obj.type === \"tree\") {\n\t\t\tconst tree = obj.object as { oid: string; type: string }[];\n\t\t\tfor (const entry of tree) {\n\t\t\t\t// Gitlinks (submodules, mode 160000) point at commits in another\n\t\t\t\t// repository; git never stores them here, so following one would\n\t\t\t\t// report a \"missing\" object and fail every walk over that history.\n\t\t\t\tif (entry.type === \"commit\") continue;\n\t\t\t\tchildren.push([entry.oid, entry.type]);\n\t\t\t}\n\t\t} else if (obj.type === \"tag\") {\n\t\t\tchildren.push([(obj.object as { object: string }).object, undefined]);\n\t\t}\n\t\tawait Promise.all(children.map(([child, type]) => visit(child, type)));\n\t}\n\n\tfunction visit(oid: string, type?: string): Promise<void> {\n\t\tconst existing = promises.get(oid);\n\t\tif (existing) return existing;\n\n\t\tif (have.has(oid) || (type === \"blob\" && !verifyBlobs)) {\n\t\t\tseen.add(oid);\n\t\t\tconst p = Promise.resolve();\n\t\t\tpromises.set(oid, p);\n\t\t\treturn p;\n\t\t}\n\n\t\tconst p = (async () => {\n\t\t\ttry {\n\t\t\t\tawait readAndVisitChildren(oid);\n\t\t\t} catch {\n\t\t\t\ttry {\n\t\t\t\t\tawait delay(MISSING_OBJECT_RETRY_DELAY_MS);\n\t\t\t\t\tawait readAndVisitChildren(oid);\n\t\t\t\t} catch (err) {\n\t\t\t\t\tcomplete = false;\n\t\t\t\t\thooks?.onWarn?.(`missing object ${oid}`, err);\n\t\t\t\t}\n\t\t\t}\n\t\t})();\n\n\t\tpromises.set(oid, p);\n\t\treturn p;\n\t}\n\n\tawait Promise.all(startOids.map((oid) => visit(oid)));\n\treturn { oids: Array.from(seen), complete };\n}\n","import git from \"isomorphic-git\";\nimport { concat, decodeAscii, decodeUtf8 } from \"../edge-utils.js\";\nimport type { Repo } from \"../ops/types.js\";\nimport { isSafeFullRefName } from \"../refs.js\";\nimport { FLUSH, pktLine } from \"./pkt-line.js\";\nimport { collectReachableOids } from \"./reachability.js\";\nimport { type RepackOptions, repackRepository } from \"./repack.js\";\nimport { type GitHttpResult, type HttpHooks, rawFs, runStep } from \"./types.js\";\n\nconst ZERO_OID = \"0\".repeat(40);\n\nexport interface RefUpdateCommand {\n\toldOid: string;\n\tnewOid: string;\n\trefName: string;\n}\n\nexport interface RefUpdateResult {\n\trefName: string;\n\tok: boolean;\n\treason?: string;\n}\n\n/** Split a receive-pack body: pkt-line ref commands, flush, then raw PACK. */\nexport function parseReceivePackBody(body: Uint8Array): {\n\trefUpdates: RefUpdateCommand[];\n\tpackData: Uint8Array;\n} {\n\tconst refUpdates: RefUpdateCommand[] = [];\n\tlet pos = 0;\n\twhile (pos + 4 <= body.length) {\n\t\tconst len = Number.parseInt(decodeAscii(body.subarray(pos, pos + 4)), 16);\n\t\tif (len === 0) {\n\t\t\tpos += 4;\n\t\t\tbreak; // flush = end of ref update commands\n\t\t}\n\t\tif (len < 4) break;\n\t\t// Strip NUL-separated capabilities from the first command line\n\t\tconst line = decodeUtf8(body.subarray(pos + 4, pos + len))\n\t\t\t.replace(/\\n$/, \"\")\n\t\t\t.split(\"\\0\")[0];\n\t\tpos += len;\n\t\tconst parts = (line ?? \"\").split(\" \");\n\t\tconst [oldOid, newOid, refName] = parts;\n\t\tif (oldOid && newOid && refName) {\n\t\t\trefUpdates.push({ oldOid, newOid, refName });\n\t\t}\n\t}\n\treturn { refUpdates, packData: body.subarray(pos) };\n}\n\n/** Initialize the repo when its HEAD doesn't exist yet (first push). */\nexport async function ensureRepoInitialized(\n\trepo: Repo,\n\tdefaultBranch = \"main\",\n): Promise<void> {\n\tconst fsp = rawFs(repo);\n\ttry {\n\t\tawait fsp.stat(`${repo.gitdir}/HEAD`);\n\t} catch {\n\t\tawait fsp.mkdir(repo.gitdir, { recursive: true }).catch(() => {});\n\t\tawait git.init({ ...repo, dir: repo.gitdir, defaultBranch, bare: true });\n\t}\n}\n\n/** Write the incoming PACK into objects/pack/ and index it. */\nexport async function indexIncomingPack(\n\trepo: Repo,\n\tpackData: Uint8Array,\n\thooks?: HttpHooks,\n): Promise<string[]> {\n\tif (packData.length < 4) return [];\n\tconst packName = await runStep(\n\t\thooks,\n\t\t\"write + indexPack incoming pack\",\n\t\tasync () => {\n\t\t\tconst fsp = rawFs(repo);\n\t\t\tconst packDir = `${repo.gitdir}/objects/pack`;\n\t\t\tawait fsp.mkdir(packDir, { recursive: true });\n\n\t\t\tconst packName = `recv-${Date.now()}`;\n\t\t\tawait fsp.writeFile(`${packDir}/${packName}.pack`, packData);\n\n\t\t\tawait git.indexPack({\n\t\t\t\t...repo,\n\t\t\t\tdir: packDir,\n\t\t\t\tfilepath: `${packName}.pack`,\n\t\t\t});\n\t\t\treturn packName;\n\t\t},\n\t);\n\treturn [\n\t\t`${repo.gitdir}/objects/pack/${packName}.pack`,\n\t\t`${repo.gitdir}/objects/pack/${packName}.idx`,\n\t];\n}\n\n/**\n * Validate a ref name and compare-and-swap it against its claimed current\n * oid. Returns the failure result to report if either check fails, or\n * `null` when both pass — shared by the preflight check (`validateRefUpdates`,\n * run before the possibly-slow pack indexing/graph-walk work, purely to\n * avoid doing that work for an update already doomed to fail) and the real\n * atomicity guarantee (`applyRefUpdates`, run again right before writing,\n * since a concurrent push could have moved the ref in the time that work\n * took).\n */\nasync function checkRefCas(\n\trepo: Repo,\n\trefName: string,\n\toldOid: string,\n): Promise<RefUpdateResult | null> {\n\tif (!isSafeFullRefName(refName)) {\n\t\treturn { refName, ok: false, reason: \"invalid ref name\" };\n\t}\n\tconst currentOid = await git\n\t\t.resolveRef({ ...repo, ref: refName })\n\t\t.catch(() => ZERO_OID);\n\treturn currentOid === oldOid\n\t\t? null\n\t\t: {\n\t\t\t\trefName,\n\t\t\t\tok: false,\n\t\t\t\treason: \"non-fast-forward, ref updated by another push\",\n\t\t\t};\n}\n\n/** Every branch/tag tip currently in the repo, as a validation boundary — see\n * its call site in {@link applyReceivePack}. */\nasync function collectExistingRefTips(repo: Repo): Promise<Set<string>> {\n\tconst [branches, tags] = await Promise.all([\n\t\tgit.listBranches(repo),\n\t\tgit.listTags(repo),\n\t]);\n\tconst refs = [\n\t\t...branches.map((b) => `refs/heads/${b}`),\n\t\t...tags.map((t) => `refs/tags/${t}`),\n\t];\n\tconst oids = await Promise.all(\n\t\trefs.map((ref) => git.resolveRef({ ...repo, ref }).catch(() => null)),\n\t);\n\treturn new Set(oids.filter((oid): oid is string => oid !== null));\n}\n\nasync function validateRefUpdates(\n\trepo: Repo,\n\trefUpdates: RefUpdateCommand[],\n): Promise<RefUpdateResult[]> {\n\treturn Promise.all(\n\t\trefUpdates.map(\n\t\t\tasync ({ oldOid, refName }) =>\n\t\t\t\t(await checkRefCas(repo, refName, oldOid)) ?? { refName, ok: true },\n\t\t),\n\t);\n}\n\n/**\n * Apply ref updates, enforcing compare-and-swap against each command's\n * claimed oldOid. Every client-supplied refName is validated before any\n * filesystem call reads or writes through it.\n */\nexport async function applyRefUpdates(\n\trepo: Repo,\n\trefUpdates: RefUpdateCommand[],\n\thooks?: HttpHooks,\n): Promise<RefUpdateResult[]> {\n\treturn runStep(hooks, \"apply ref updates\", () =>\n\t\tPromise.all(\n\t\t\trefUpdates.map(async ({ oldOid, newOid, refName }) => {\n\t\t\t\tconst failure = await checkRefCas(repo, refName, oldOid);\n\t\t\t\tif (failure) return failure;\n\n\t\t\t\tif (newOid === ZERO_OID) {\n\t\t\t\t\tawait git.deleteRef({ ...repo, ref: refName }).catch(() => {});\n\t\t\t\t} else {\n\t\t\t\t\tawait git.writeRef({\n\t\t\t\t\t\t...repo,\n\t\t\t\t\t\tref: refName,\n\t\t\t\t\t\tvalue: newOid,\n\t\t\t\t\t\tforce: true,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\treturn { refName, ok: true };\n\t\t\t}),\n\t\t),\n\t);\n}\n\n/** The report-status response body for a set of ref-update results. */\nexport function receivePackResponse(results: RefUpdateResult[]): GitHttpResult {\n\tconst responseBody = concat(\n\t\tpktLine(\"unpack ok\\n\"),\n\t\t...results.map(({ refName, ok, reason }) =>\n\t\t\tpktLine(ok ? `ok ${refName}\\n` : `ng ${refName} ${reason}\\n`),\n\t\t),\n\t\tFLUSH,\n\t);\n\n\treturn {\n\t\tstatus: 200,\n\t\theaders: {\n\t\t\t\"Content-Type\": \"application/x-git-receive-pack-result\",\n\t\t\t\"Cache-Control\": \"no-cache\",\n\t\t},\n\t\tbody: responseBody,\n\t};\n}\n\nexport interface ApplyReceivePackOptions {\n\tdefaultBranch?: string;\n\t/**\n\t * Repack tuning, or `false` to skip repacking entirely. The returned\n\t * `stalePackPaths` are gitdir-relative pack files the repack deleted from\n\t * this repo — after syncing to any secondary storage, delete them there\n\t * too.\n\t */\n\trepack?: RepackOptions | false;\n}\n\n/**\n * The storage half of a push against `repo`: initialize if needed, index the\n * incoming pack, CAS-apply ref updates, then repack when enough packs have\n * accumulated. Serialize concurrent pushes to the same repo externally (a\n * per-repo lock); build the HTTP response afterwards with\n * {@link receivePackResponse}.\n */\nexport async function applyReceivePack(\n\trepo: Repo,\n\tparsed: { refUpdates: RefUpdateCommand[]; packData: Uint8Array },\n\toptions?: ApplyReceivePackOptions,\n\thooks?: HttpHooks,\n): Promise<{ results: RefUpdateResult[]; stalePackPaths: string[] }> {\n\tawait ensureRepoInitialized(repo, options?.defaultBranch ?? \"main\");\n\tconst preflight = await validateRefUpdates(repo, parsed.refUpdates);\n\tconst acceptedUpdates = parsed.refUpdates.filter(\n\t\t(_, index) => preflight[index]?.ok,\n\t);\n\tconst incomingPackPaths = await indexIncomingPack(\n\t\trepo,\n\t\tacceptedUpdates.some((update) => update.newOid !== ZERO_OID)\n\t\t\t? parsed.packData\n\t\t\t: new Uint8Array(),\n\t\thooks,\n\t);\n\n\tconst newTips = acceptedUpdates\n\t\t.filter((update) => update.newOid !== ZERO_OID)\n\t\t.map((update) => update.newOid);\n\tif (newTips.length > 0) {\n\t\t// Every oid reachable from an existing ref was already accepted by an\n\t\t// earlier push — not just the ref(s) this push happens to touch. A\n\t\t// brand-new branch (oldOid === ZERO_OID, so it has no pre-image of its\n\t\t// own) is the common case this matters for: creating a branch off an\n\t\t// existing one is still just one new commit's worth of new objects,\n\t\t// and shouldn't cost a walk of the entire repo history to validate.\n\t\tconst haveOids = await collectExistingRefTips(repo);\n\t\tfor (const update of acceptedUpdates) {\n\t\t\tif (update.oldOid !== ZERO_OID) haveOids.add(update.oldOid);\n\t\t}\n\t\tconst { complete } = await collectReachableOids(\n\t\t\trepo,\n\t\t\tnewTips,\n\t\t\thooks,\n\t\t\thaveOids,\n\t\t);\n\t\tif (!complete) {\n\t\t\tawait Promise.all(\n\t\t\t\tincomingPackPaths.map((filepath) =>\n\t\t\t\t\trawFs(repo)\n\t\t\t\t\t\t.unlink(filepath)\n\t\t\t\t\t\t.catch(() => {}),\n\t\t\t\t),\n\t\t\t);\n\t\t\treturn {\n\t\t\t\tresults: preflight.map((result) =>\n\t\t\t\t\tresult.ok\n\t\t\t\t\t\t? { ...result, ok: false, reason: \"incomplete object graph\" }\n\t\t\t\t\t\t: result,\n\t\t\t\t),\n\t\t\t\tstalePackPaths: [],\n\t\t\t};\n\t\t}\n\t}\n\n\tconst applied = await applyRefUpdates(repo, acceptedUpdates, hooks);\n\tconst results = preflight.map((result, index) =>\n\t\tresult.ok\n\t\t\t? (applied.find(\n\t\t\t\t\t(entry) => entry.refName === parsed.refUpdates[index]?.refName,\n\t\t\t\t) ?? result)\n\t\t\t: result,\n\t);\n\tconst repackOptions = options?.repack;\n\tconst stalePackPaths =\n\t\trepackOptions === false || !results.some((result) => result.ok)\n\t\t\t? []\n\t\t\t: await runStep(hooks, \"repack\", () =>\n\t\t\t\t\trepackRepository(repo, repackOptions, hooks),\n\t\t\t\t);\n\treturn { results, stalePackPaths };\n}\n","/**\n * Pack consolidation: merge all packs into one, reusing stored (possibly\n * deltified) entries when every object is packed.\n *\n * Edge-compatible: uses Web Crypto (SHA-1), CompressionStream (deflate),\n * and Uint8Array throughout — no node:crypto, node:zlib, or Buffer.\n */\n\nimport git from \"isomorphic-git\";\nimport { concat, deflate, encodeUtf8, fromHex, sha1 } from \"../edge-utils.js\";\nimport type { Repo } from \"../ops/types.js\";\nimport {\n\ttype PackFileName,\n\tpackFileName,\n\tparsePackDirectory,\n} from \"../pack-files.js\";\nimport { buildPackFromExistingPacks } from \"./pack-reuse.js\";\nimport { collectReachableOids } from \"./reachability.js\";\nimport { type HttpHooks, rawFs } from \"./types.js\";\n\ntype PackObjectType = \"commit\" | \"tree\" | \"blob\" | \"tag\";\n\n// Git pack object type bits (bits 6-4 of the header's first byte) — same\n// constants real git and isomorphic-git's own (de)serializers use.\nconst PACK_OBJECT_TYPE_BITS: Record<PackObjectType, number> = {\n\tcommit: 0b0010000,\n\ttree: 0b0100000,\n\tblob: 0b0110000,\n\ttag: 0b1000000,\n};\n\n// Git's pack object header: first byte packs (continuation bit | 3-bit type |\n// low 4 bits of length); any remaining length is emitted 7 bits at a time,\n// each with its own continuation bit, little-endian.\nfunction encodePackObjectHeader(\n\ttype: PackObjectType,\n\tlength: number,\n): Uint8Array {\n\tconst bytes: number[] = [];\n\tlet more = length > 0b1111;\n\tbytes.push(\n\t\t(more ? 0b10000000 : 0) | PACK_OBJECT_TYPE_BITS[type] | (length & 0b1111),\n\t);\n\tlength >>>= 4;\n\twhile (more) {\n\t\tmore = length > 0b01111111;\n\t\tbytes.push((more ? 0b10000000 : 0) | (length & 0b01111111));\n\t\tlength >>>= 7;\n\t}\n\treturn new Uint8Array(bytes);\n}\n\n// Git's object hash: sha1(\"<type> <byte length>\\0<content>\") — matches\n// isomorphic-git's internal GitObject.wrap + shasum, computed independently\n// here rather than trusted from isomorphic-git's own read path.\nasync function hashGitObject(\n\ttype: string,\n\tcontent: Uint8Array,\n): Promise<string> {\n\tconst prefix = encodeUtf8(`${type} ${content.length}\\0`);\n\treturn sha1(concat(prefix, content));\n}\n\ntype VerifiedObject = {\n\ttype: PackObjectType;\n\tcontent: Uint8Array<ArrayBuffer>;\n};\n\n/**\n * Read every reachable object and independently re-derive its oid from the\n * bytes isomorphic-git handed back, instead of trusting the oid it was asked\n * for. isomorphic-git's *packed*-object read path never verifies the\n * resolved content's SHA-1 against the requested oid — only the\n * loose-object branch does.\n */\nasync function readAndVerifyObjects(\n\trepo: Repo,\n\toids: string[],\n): Promise<Map<string, VerifiedObject>> {\n\tconst objects = new Map<string, VerifiedObject>();\n\tconst BATCH_SIZE = 100;\n\tfor (let i = 0; i < oids.length; i += BATCH_SIZE) {\n\t\tconst batch = oids.slice(i, i + BATCH_SIZE);\n\t\tconst entries = await Promise.all(\n\t\t\tbatch.map(async (oid) => {\n\t\t\t\tconst { type, object } = await git.readObject({\n\t\t\t\t\t...repo,\n\t\t\t\t\toid,\n\t\t\t\t\tformat: \"content\",\n\t\t\t\t});\n\t\t\t\t// Own ArrayBuffer-backed copy: Web APIs (Blob, digest) reject\n\t\t\t\t// views that might sit on a SharedArrayBuffer.\n\t\t\t\tconst content = new Uint8Array(object as Uint8Array);\n\t\t\t\tconst actualOid = await hashGitObject(type, content);\n\t\t\t\tif (actualOid !== oid) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`repack: object ${oid} failed independent SHA-1 verification ` +\n\t\t\t\t\t\t\t`(recomputed ${actualOid}) — refusing to trust this read, aborting repack`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\treturn { oid, type: type as PackObjectType, content };\n\t\t\t}),\n\t\t);\n\t\tfor (const { oid, type, content } of entries) {\n\t\t\tobjects.set(oid, { type, content });\n\t\t}\n\t}\n\treturn objects;\n}\n\n/**\n * Serialize verified objects into a pack containing only full (never\n * deltified) entries, in the given oid order.\n */\nasync function buildVerifiedPack(\n\toids: string[],\n\tobjects: Map<string, VerifiedObject>,\n): Promise<Uint8Array> {\n\t// PACK header: \"PACK\" + version(2) + count, all big-endian uint32\n\tconst header = new Uint8Array(12);\n\tconst view = new DataView(header.buffer);\n\theader.set(encodeUtf8(\"PACK\"), 0);\n\tview.setUint32(4, 2, false); // version 2\n\tview.setUint32(8, oids.length, false); // object count\n\n\tconst chunks: Uint8Array[] = [header];\n\n\tconst BATCH_SIZE = 100;\n\tfor (let i = 0; i < oids.length; i += BATCH_SIZE) {\n\t\tconst batch = oids.slice(i, i + BATCH_SIZE);\n\t\tconst encoded = await Promise.all(\n\t\t\tbatch.map(async (oid) => {\n\t\t\t\tconst entry = objects.get(oid);\n\t\t\t\tif (!entry) {\n\t\t\t\t\tthrow new Error(`repack: missing verified object for ${oid}`);\n\t\t\t\t}\n\t\t\t\tconst objHeader = encodePackObjectHeader(\n\t\t\t\t\tentry.type,\n\t\t\t\t\tentry.content.length,\n\t\t\t\t);\n\t\t\t\tconst compressed = await deflate(entry.content);\n\t\t\t\treturn concat(objHeader, compressed);\n\t\t\t}),\n\t\t);\n\t\tchunks.push(...encoded);\n\t}\n\n\t// Git's pack trailer is the SHA-1 of every preceding byte (header +\n\t// every object entry) as a single digest — NOT an incremental/chained\n\t// hash. Web Crypto's subtle.digest has no streaming mode, so unlike\n\t// Node's crypto.createHash this can't be folded into the loop above;\n\t// everything is already buffered in `chunks`, so hash it in one pass.\n\tconst packBody = concat(...chunks);\n\tconst trailer = fromHex(await sha1(packBody));\n\treturn concat(packBody, trailer);\n}\n\n/**\n * Default repack threshold. Consolidating is O(total repo object count) —\n * so paying it on every push makes push latency grow with repo size forever.\n */\nexport const REPACK_PACK_COUNT_THRESHOLD = 4;\n\nasync function readPackDirectory(repo: Repo): Promise<PackFileName[]> {\n\tconst entries = await rawFs(repo)\n\t\t.readdir(`${repo.gitdir}/objects/pack`)\n\t\t.catch(() => [] as string[]);\n\treturn parsePackDirectory(entries);\n}\n\nasync function countPacks(repo: Repo): Promise<number> {\n\tconst files = await readPackDirectory(repo);\n\treturn files.filter((file) => file.kind === \"pack\").length;\n}\n\nexport interface RepackOptions {\n\t/** Skip repacking below this many accumulated packs. Default 4. */\n\tthreshold?: number;\n}\n\n/**\n * Consolidate all pack files into one, returning the gitdir-relative paths\n * of the old .pack/.idx files it removed — after syncing the new pack to any\n * secondary storage, delete those same paths there too.\n */\nexport async function repackRepository(\n\trepo: Repo,\n\toptions?: RepackOptions,\n\thooks?: HttpHooks,\n): Promise<string[]> {\n\tconst threshold = options?.threshold ?? REPACK_PACK_COUNT_THRESHOLD;\n\tconst fsp = rawFs(repo);\n\ttry {\n\t\tif ((await countPacks(repo)) < threshold) {\n\t\t\treturn [];\n\t\t}\n\n\t\tconst [branches, tags] = await Promise.all([\n\t\t\tgit.listBranches(repo),\n\t\t\tgit.listTags(repo),\n\t\t]);\n\t\tconst refNames = [\n\t\t\t...branches.map((b) => `refs/heads/${b}`),\n\t\t\t...tags.map((t) => `refs/tags/${t}`),\n\t\t];\n\t\tconst tipOids = (\n\t\t\tawait Promise.all(\n\t\t\t\trefNames.map((ref) =>\n\t\t\t\t\tgit.resolveRef({ ...repo, ref }).catch(() => null),\n\t\t\t\t),\n\t\t\t)\n\t\t).filter((oid): oid is string => oid !== null);\n\t\tif (tipOids.length === 0) return [];\n\n\t\tconst { oids: reachable, complete } = await collectReachableOids(\n\t\t\trepo,\n\t\t\ttipOids,\n\t\t\thooks,\n\t\t);\n\t\tif (!complete || reachable.length === 0) return [];\n\n\t\tconst packDir = `${repo.gitdir}/objects/pack`;\n\t\t// Copy stored entries when everything is packed: keeps deltas, so\n\t\t// the consolidated pack is as compact as its sources. Loose objects\n\t\t// fall back to a verified, non-deltified rebuild.\n\t\tconst packBuffer =\n\t\t\t(await buildPackFromExistingPacks(repo, reachable)) ??\n\t\t\t(await buildVerifiedPack(\n\t\t\t\treachable,\n\t\t\t\tawait readAndVerifyObjects(repo, reachable),\n\t\t\t));\n\n\t\tawait fsp.mkdir(packDir, { recursive: true });\n\t\tconst newBase = `pack-${Date.now()}`;\n\t\tconst newPackFile = packFileName(newBase, \"pack\");\n\t\tawait fsp.writeFile(`${packDir}/${newPackFile}`, packBuffer);\n\n\t\tconst { oids: indexedOids } = await git.indexPack({\n\t\t\t...repo,\n\t\t\tdir: packDir,\n\t\t\tfilepath: newPackFile,\n\t\t});\n\n\t\t// indexPack re-hashes every object it indexes, so this also verifies\n\t\t// the content. Reused packs may carry extra delta bases: superset is fine.\n\t\tconst indexed = new Set(indexedOids);\n\t\tif (reachable.some((oid) => !indexed.has(oid))) {\n\t\t\tawait Promise.all(\n\t\t\t\t([\"pack\", \"idx\"] as const).map((kind) =>\n\t\t\t\t\tfsp\n\t\t\t\t\t\t.unlink(`${packDir}/${packFileName(newBase, kind)}`)\n\t\t\t\t\t\t.catch(() => {}),\n\t\t\t\t),\n\t\t\t);\n\t\t\tthrow new Error(\n\t\t\t\t\"repack: indexed pack is missing reachable objects — aborting\",\n\t\t\t);\n\t\t}\n\n\t\tconst stale = (await readPackDirectory(repo)).filter(\n\t\t\t(file) => file.base !== newBase,\n\t\t);\n\t\tawait Promise.all(\n\t\t\tstale.map((file) =>\n\t\t\t\tfsp.unlink(`${packDir}/${file.name}`).catch(() => {}),\n\t\t\t),\n\t\t);\n\t\treturn stale.map((file) => `objects/pack/${file.name}`);\n\t} catch (err) {\n\t\thooks?.onWarn?.(\"repack failed (non-fatal)\", err);\n\t\treturn [];\n\t}\n}\n","/**\n * Build a packfile by copying already-compressed entries out of existing\n * packs (\"pack reuse\"), instead of inflating and re-deflating every object.\n *\n * Entries keep their stored form, deltas included, so the output is as\n * compact as the packs it came from. `OFS_DELTA` entries are rewritten as\n * `REF_DELTA` (base named by oid), since offsets don't survive the move into\n * a new pack; delta bases are always included and written before the deltas\n * that use them. Formats: gitformat-pack(5).\n *\n * Edge-compatible: Web Crypto SHA-1 and Uint8Array only.\n */\n\nimport { concat, encodeUtf8, fromHex, sha1, toHex } from \"../edge-utils.js\";\nimport type { Repo } from \"../ops/types.js\";\nimport { packFileName, parsePackDirectory } from \"../pack-files.js\";\nimport { rawFs } from \"./types.js\";\n\nconst OBJ_OFS_DELTA = 6;\nconst OBJ_REF_DELTA = 7;\nconst OID_BYTES = 20;\nconst PACK_TRAILER_BYTES = 20;\n\nconst IDX_V2 = {\n\tmagic: 0xff744f63,\n\tversion: 2,\n\theaderBytes: 8,\n\tfanoutEntries: 256,\n\t/** Offsets with this bit set index the 64-bit large-offset table. */\n\tlargeOffsetFlag: 0x80000000,\n} as const;\n\n/** One object's stored entry inside a pack. */\ninterface PackEntry {\n\treadonly pack: Uint8Array;\n\treadonly start: number;\n\treadonly end: number;\n}\n\n/** Every object stored in a pack, by oid, plus its offset → oid inverse. */\ninterface IndexedPack {\n\treadonly entries: Map<string, PackEntry>;\n\treadonly oidAtOffset: Map<number, string>;\n}\n\n/** An object's position in a pack, as listed by its `.idx`. */\nexport interface PackIndexEntry {\n\treadonly oid: string;\n\treadonly offset: number;\n}\n\n/** Entries of a version-2 `.idx`; `null` for any other format. */\nexport function readPackIndex(idx: Uint8Array): PackIndexEntry[] | null {\n\tconst view = new DataView(idx.buffer, idx.byteOffset, idx.byteLength);\n\tconst namesStart = IDX_V2.headerBytes + IDX_V2.fanoutEntries * 4;\n\tif (\n\t\tidx.byteLength < namesStart ||\n\t\tview.getUint32(0) !== IDX_V2.magic ||\n\t\tview.getUint32(4) !== IDX_V2.version\n\t) {\n\t\treturn null;\n\t}\n\t// The last fanout slot counts every object in the pack.\n\tconst count = view.getUint32(namesStart - 4);\n\tconst crcStart = namesStart + count * OID_BYTES;\n\tconst offsetsStart = crcStart + count * 4;\n\tif (idx.byteLength < offsetsStart + count * 4) return null;\n\n\tconst entries: PackIndexEntry[] = [];\n\tfor (let i = 0; i < count; i++) {\n\t\tconst offset = view.getUint32(offsetsStart + i * 4);\n\t\t// Packs over 2 GiB never reach this in-memory path; let the caller\n\t\t// fall back instead of half-supporting the large-offset table.\n\t\tif (offset & IDX_V2.largeOffsetFlag) return null;\n\t\tconst at = namesStart + i * OID_BYTES;\n\t\tentries.push({ oid: toHex(idx.subarray(at, at + OID_BYTES)), offset });\n\t}\n\treturn entries;\n}\n\n/** Locate every entry of `pack` using its `.idx`. */\nfunction indexPack(idx: Uint8Array, pack: Uint8Array): IndexedPack | null {\n\tconst located = readPackIndex(idx);\n\tif (!located) return null;\n\n\t// An entry runs until the next entry's offset (or the pack trailer).\n\tlocated.sort((a, b) => a.offset - b.offset);\n\tconst entries = new Map<string, PackEntry>();\n\tconst oidAtOffset = new Map<number, string>();\n\tconst packEnd = pack.byteLength - PACK_TRAILER_BYTES;\n\tfor (let i = 0; i < located.length; i++) {\n\t\tconst { oid, offset } = located[i] as PackIndexEntry;\n\t\tconst end = located[i + 1]?.offset ?? packEnd;\n\t\tentries.set(oid, { pack, start: offset, end });\n\t\toidAtOffset.set(offset, oid);\n\t}\n\treturn { entries, oidAtOffset };\n}\n\n/** The variable-length type/size header that starts every pack entry. */\ninterface EntryHeader {\n\treadonly type: number;\n\treadonly size: number;\n\t/** Bytes the header occupies. */\n\treadonly length: number;\n}\n\nfunction readEntryHeader(bytes: Uint8Array, start: number): EntryHeader {\n\tlet byte = bytes[start] as number;\n\tconst type = (byte >> 4) & 0b111;\n\tlet size = byte & 0b1111;\n\tlet shift = 4;\n\tlet at = start + 1;\n\twhile (byte & 0x80) {\n\t\tbyte = bytes[at++] as number;\n\t\tsize += (byte & 0x7f) * 2 ** shift;\n\t\tshift += 7;\n\t}\n\treturn { type, size, length: at - start };\n}\n\nfunction encodeEntryHeader(type: number, size: number): Uint8Array {\n\tconst bytes: number[] = [];\n\tlet rest = Math.floor(size / 16);\n\tbytes.push((rest > 0 ? 0x80 : 0) | (type << 4) | (size % 16));\n\twhile (rest > 0) {\n\t\tconst low = rest % 128;\n\t\trest = Math.floor(rest / 128);\n\t\tbytes.push((rest > 0 ? 0x80 : 0) | low);\n\t}\n\treturn new Uint8Array(bytes);\n}\n\n/** `OFS_DELTA`'s negative base offset: big-endian base-128 with +1 carries. */\nfunction readOfsDeltaOffset(\n\tbytes: Uint8Array,\n\tstart: number,\n): { distance: number; length: number } {\n\tlet at = start;\n\tlet byte = bytes[at++] as number;\n\tlet distance = byte & 0x7f;\n\twhile (byte & 0x80) {\n\t\tbyte = bytes[at++] as number;\n\t\tdistance = (distance + 1) * 128 + (byte & 0x7f);\n\t}\n\treturn { distance, length: at - start };\n}\n\n/** A pack entry ready to copy, with the oid of its delta base if any. */\ninterface ReusableEntry {\n\treadonly bytes: Uint8Array;\n\treadonly baseOid: string | null;\n}\n\n/** Copy one entry, rewriting `OFS_DELTA` to `REF_DELTA`. */\nfunction reusableEntry(\n\tentry: PackEntry,\n\tindexed: IndexedPack,\n): ReusableEntry | null {\n\tconst { pack, start, end } = entry;\n\tconst header = readEntryHeader(pack, start);\n\tconst bodyStart = start + header.length;\n\tif (header.type === OBJ_REF_DELTA) {\n\t\treturn {\n\t\t\tbytes: pack.subarray(start, end),\n\t\t\tbaseOid: toHex(pack.subarray(bodyStart, bodyStart + OID_BYTES)),\n\t\t};\n\t}\n\tif (header.type !== OBJ_OFS_DELTA) {\n\t\treturn { bytes: pack.subarray(start, end), baseOid: null };\n\t}\n\tconst { distance, length } = readOfsDeltaOffset(pack, bodyStart);\n\tconst baseOid = indexed.oidAtOffset.get(start - distance);\n\tif (baseOid === undefined) return null;\n\treturn {\n\t\tbytes: concat(\n\t\t\tencodeEntryHeader(OBJ_REF_DELTA, header.size),\n\t\t\tfromHex(baseOid),\n\t\t\tpack.subarray(bodyStart + length, end),\n\t\t),\n\t\tbaseOid,\n\t};\n}\n\n/**\n * A pack holding at least `oids`, built from stored entries, or `null` when\n * any of them (or a delta base they need) isn't in a readable pack — e.g. a\n * loose object — so the caller should build the pack another way.\n */\nexport async function buildPackFromExistingPacks(\n\trepo: Repo,\n\toids: readonly string[],\n): Promise<Uint8Array | null> {\n\tconst fsp = rawFs(repo);\n\tconst packDir = `${repo.gitdir}/objects/pack`;\n\tconst packs = parsePackDirectory(\n\t\tawait fsp.readdir(packDir).catch(() => [] as string[]),\n\t).filter((file) => file.kind === \"pack\");\n\n\tconst indexes: IndexedPack[] = [];\n\tfor (const { base } of packs) {\n\t\t// A pack without a readable index can't be reused.\n\t\tconst [pack, idx] = await Promise.all([\n\t\t\tfsp.readFile(`${packDir}/${packFileName(base, \"pack\")}`),\n\t\t\tfsp.readFile(`${packDir}/${packFileName(base, \"idx\")}`),\n\t\t]).catch(() => [null, null]);\n\t\tif (!(pack instanceof Uint8Array) || !(idx instanceof Uint8Array)) {\n\t\t\treturn null;\n\t\t}\n\t\tconst indexed = indexPack(idx, pack);\n\t\tif (!indexed) return null;\n\t\tindexes.push(indexed);\n\t}\n\n\tconst locate = (oid: string): ReusableEntry | null => {\n\t\tfor (const indexed of indexes) {\n\t\t\tconst entry = indexed.entries.get(oid);\n\t\t\tif (entry) return reusableEntry(entry, indexed);\n\t\t}\n\t\treturn null;\n\t};\n\n\t// Depth-first so every base is written before the deltas that use it.\n\tconst ordered: Uint8Array[] = [];\n\tconst done = new Set<string>();\n\tconst pending = new Set<string>();\n\tconst visit = (oid: string): boolean => {\n\t\tif (done.has(oid)) return true;\n\t\tif (pending.has(oid)) return false; // a delta cycle means corruption\n\t\tconst entry = locate(oid);\n\t\tif (!entry) return false;\n\t\tpending.add(oid);\n\t\tif (entry.baseOid !== null && !visit(entry.baseOid)) return false;\n\t\tpending.delete(oid);\n\t\tdone.add(oid);\n\t\tordered.push(entry.bytes);\n\t\treturn true;\n\t};\n\tfor (const oid of oids) {\n\t\tif (!visit(oid)) return null;\n\t}\n\n\tconst header = new Uint8Array(12);\n\theader.set(encodeUtf8(\"PACK\"), 0);\n\tconst view = new DataView(header.buffer);\n\tview.setUint32(4, 2);\n\tview.setUint32(8, ordered.length);\n\tconst body = concat(header, ...ordered);\n\treturn concat(body, fromHex(await sha1(body)));\n}\n","import git from \"isomorphic-git\";\nimport { concat } from \"../edge-utils.js\";\nimport type { Repo } from \"../ops/types.js\";\nimport { parsePackDirectory } from \"../pack-files.js\";\nimport { buildPackFromExistingPacks } from \"./pack-reuse.js\";\nimport { parsePktLines, pktLine, sideBandPackfile } from \"./pkt-line.js\";\nimport { collectReachableOids } from \"./reachability.js\";\nimport { type GitHttpResult, type HttpHooks, rawFs, runStep } from \"./types.js\";\n\nconst ZERO_OID = \"0\".repeat(40);\n\nexport interface UploadPackOptions {\n\t/**\n\t * Called once before the reachability walk — wire loose-object detection\n\t * (`GitFs.detectLooseObjects`) here so a fully packed repo's walk doesn't\n\t * pay a doomed loose-object probe per object.\n\t */\n\tbeforeWalk?: () => Promise<void>;\n}\n\n/**\n * `POST …/git-upload-pack` — serve a clone/fetch. Authorization is the\n * caller's job. Negotiation is client-driven (no multi_ack advertised):\n * \"have\" batches without \"done\" get a bare NAK; the final batch gets the\n * packfile, side-band-64k framed.\n *\n * Fast path: a fresh clone (no haves) of a repo consolidated down to a\n * single pack serves that pack's bytes directly — skipping the O(objects)\n * traversal + repack entirely.\n */\nexport async function handleUploadPack(\n\trepo: Repo,\n\tbody: Uint8Array,\n\toptions?: UploadPackOptions,\n\thooks?: HttpHooks,\n): Promise<GitHttpResult> {\n\tconst lines = parsePktLines(body);\n\n\tconst wants: string[] = [];\n\tconst haves: string[] = [];\n\tlet done = false;\n\tfor (const line of lines) {\n\t\tif (!line) continue;\n\t\tif (line.startsWith(\"want \")) {\n\t\t\twants.push(line.slice(5, 45));\n\t\t}\n\t\tif (line.startsWith(\"have \")) {\n\t\t\tconst sha = line.slice(5, 45);\n\t\t\tif (sha !== ZERO_OID) {\n\t\t\t\thaves.push(sha);\n\t\t\t}\n\t\t}\n\t\tif (line.startsWith(\"done\")) {\n\t\t\tdone = true;\n\t\t}\n\t}\n\n\tif (wants.length === 0) {\n\t\treturn {\n\t\t\tstatus: 200,\n\t\t\theaders: { \"Content-Type\": \"application/x-git-upload-pack-result\" },\n\t\t\tbody: concat(pktLine(\"NAK\\n\")),\n\t\t};\n\t}\n\n\tif (haves.length > 0 && !done) {\n\t\treturn {\n\t\t\tstatus: 200,\n\t\t\theaders: { \"Content-Type\": \"application/x-git-upload-pack-result\" },\n\t\t\tbody: concat(pktLine(\"NAK\\n\")),\n\t\t};\n\t}\n\n\t// Fresh clone = no haves, so all objects are needed. When the repo is down\n\t// to a single pack, serve it directly and skip the traversal + pack build.\n\tif (haves.length === 0) {\n\t\tconst packDirPath = `${repo.gitdir}/objects/pack`;\n\t\tconst entries = await runStep(hooks, \"readdir objects/pack\", () =>\n\t\t\trawFs(repo)\n\t\t\t\t.readdir(packDirPath)\n\t\t\t\t.catch(() => [] as string[]),\n\t\t);\n\t\tconst packNames = parsePackDirectory(entries)\n\t\t\t.filter((file) => file.kind === \"pack\")\n\t\t\t.map((file) => file.name);\n\t\tconst packName = packNames[0];\n\t\tif (packNames.length === 1 && packName !== undefined) {\n\t\t\tconst packData = await runStep(\n\t\t\t\thooks,\n\t\t\t\t\"read consolidated pack (fast path)\",\n\t\t\t\t() => rawFs(repo).readFile(`${packDirPath}/${packName}`),\n\t\t\t);\n\t\t\tconst bytes =\n\t\t\t\tpackData instanceof Uint8Array\n\t\t\t\t\t? packData\n\t\t\t\t\t: new TextEncoder().encode(packData as string);\n\t\t\treturn {\n\t\t\t\tstatus: 200,\n\t\t\t\theaders: {\n\t\t\t\t\t\"Content-Type\": \"application/x-git-upload-pack-result\",\n\t\t\t\t\t\"Cache-Control\": \"no-cache\",\n\t\t\t\t},\n\t\t\t\tbody: concat(pktLine(\"NAK\\n\"), sideBandPackfile(bytes)),\n\t\t\t};\n\t\t}\n\t}\n\n\tif (options?.beforeWalk) {\n\t\tawait runStep(hooks, \"beforeWalk\", options.beforeWalk);\n\t}\n\n\t// Walk haves first so the wants walk can stop at anything the client\n\t// already has, instead of re-walking all shared history a second time.\n\tlet haveSet: ReadonlySet<string> | undefined;\n\tif (haves.length > 0) {\n\t\tconst { oids: haveOids } = await runStep(\n\t\t\thooks,\n\t\t\t\"collectReachableOids(haves)\",\n\t\t\t() =>\n\t\t\t\tcollectReachableOids(repo, haves, hooks, undefined, {\n\t\t\t\t\tverifyBlobs: false,\n\t\t\t\t}),\n\t\t);\n\t\thaveSet = new Set(haveOids);\n\t}\n\n\tconst { oids: wantOids } = await runStep(\n\t\thooks,\n\t\t\"collectReachableOids(wants)\",\n\t\t() =>\n\t\t\tcollectReachableOids(repo, wants, hooks, haveSet, {\n\t\t\t\tverifyBlobs: false,\n\t\t\t}),\n\t);\n\t// Boundary oids are recorded as seen by the walk; drop them from the pack.\n\tconst oids = haveSet ? wantOids.filter((oid) => !haveSet.has(oid)) : wantOids;\n\n\t// Copy stored (already compressed, possibly deltified) entries when every\n\t// object is packed; build from scratch only when some aren't.\n\tconst reused = await runStep(\n\t\thooks,\n\t\t`reuse pack entries (${oids.length} oids)`,\n\t\t() => buildPackFromExistingPacks(repo, oids),\n\t);\n\tconst packBytes =\n\t\treused ??\n\t\t(await runStep(hooks, `packObjects (${oids.length} oids)`, async () => {\n\t\t\tconst { packfile } = await git.packObjects({ ...repo, oids });\n\t\t\treturn packfile instanceof Uint8Array\n\t\t\t\t? packfile\n\t\t\t\t: new Uint8Array(packfile ?? []);\n\t\t}));\n\n\treturn {\n\t\tstatus: 200,\n\t\theaders: {\n\t\t\t\"Content-Type\": \"application/x-git-upload-pack-result\",\n\t\t\t\"Cache-Control\": \"no-cache\",\n\t\t},\n\t\tbody: concat(pktLine(\"NAK\\n\"), sideBandPackfile(packBytes)),\n\t};\n}\n"],"mappings":"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","names":["git","p","git","git","git","git","packName","git","ZERO_OID","git"]}
1
+ {"version":3,"sources":["../src/http/info-refs.ts","../src/http/pkt-line.ts","../src/http/types.ts","../src/http/reachability.ts","../src/http/receive-pack.ts","../src/http/repack.ts","../src/http/pack-reuse.ts","../src/http/upload-pack.ts"],"sourcesContent":["import git from \"isomorphic-git\";\nimport { concat } from \"../edge-utils.js\";\nimport {\n\tlistBranches,\n\tlistLooseRefNames,\n\tresolveLooseRefFast,\n} from \"../ops/branch.js\";\nimport type { Repo } from \"../ops/types.js\";\nimport { FLUSH, pktLine } from \"./pkt-line.js\";\nimport { type GitHttpResult, type HttpHooks, runStep } from \"./types.js\";\n\nexport type GitService = \"git-upload-pack\" | \"git-receive-pack\";\n\n/** All refs with resolved oids, plus the symref HEAD should advertise. */\nexport async function listAllRefs(repo: Repo, defaultBranch: string) {\n\tconst [branches, tagNames] = await Promise.all([\n\t\tlistBranches(repo),\n\t\tlistLooseRefNames(repo, \"refs/tags\").then(\n\t\t\t(names) => names ?? git.listTags(repo),\n\t\t),\n\t]);\n\tconst tagRefs = await Promise.all(\n\t\ttagNames.map(async (tag) => {\n\t\t\tconst name = `refs/tags/${tag}`;\n\t\t\treturn resolveLooseRefFast(repo, name)\n\t\t\t\t.then((oid) => ({ name, oid }))\n\t\t\t\t.catch(() => null);\n\t\t}),\n\t);\n\tconst branchRefs = branches.map((b) => ({\n\t\tname: `refs/heads/${b.name}`,\n\t\toid: b.commit,\n\t}));\n\tconst defaultRef = branches.find((b) => b.isDefault);\n\tconst headSymref = defaultRef\n\t\t? `refs/heads/${defaultRef.name}`\n\t\t: await Promise.resolve(git.currentBranch({ ...repo, fullname: true }))\n\t\t\t\t.then((cb) => cb ?? `refs/heads/${defaultBranch}`)\n\t\t\t\t.catch(() => `refs/heads/${defaultBranch}`);\n\n\t// HEAD in a bare repo is almost always a symref to a branch whose oid\n\t// branchRefs already resolved above with one direct GET each — reuse it\n\t// instead of a second, separate resolveRef(\"HEAD\") call. isomorphic-git's\n\t// ref resolution runs its full multi-candidate expansion (refs/%s,\n\t// refs/tags/%s, refs/heads/%s, refs/remotes/%s, refs/remotes/%s/HEAD)\n\t// even when the symref target is already fully qualified, which used to\n\t// cost several guaranteed-404 R2 reads on every clone/fetch. Falls back\n\t// to the slow-but-correct path only when that lookup misses: detached\n\t// HEAD (points at a raw sha, not a symref) or a symref to a branch that\n\t// doesn't exist yet (freshly initialized, still-empty repo).\n\tconst headOid =\n\t\tbranchRefs.find((r) => r?.name === headSymref)?.oid ??\n\t\t(await git.resolveRef({ ...repo, ref: \"HEAD\" }).catch(() => null));\n\n\tconst refs: Array<{ name: string; oid: string }> = [];\n\tif (headOid) refs.push({ name: \"HEAD\", oid: headOid });\n\tfor (const r of branchRefs) if (r) refs.push(r);\n\tfor (const r of tagRefs) if (r) refs.push(r);\n\n\treturn { refs, headSymref };\n}\n\nexport interface InfoRefsOptions {\n\tservice: GitService;\n\t/** Advertised as HEAD when HEAD itself doesn't name an existing branch. */\n\tdefaultBranch: string;\n\t/** Advertised in the agent capability. Default \"git-fs-s3\". */\n\tagent?: string;\n}\n\n/**\n * `GET …/info/refs?service=…` — the ref advertisement. Authentication and\n * authorization are the caller's job before invoking this.\n *\n * Capabilities advertised match what the sibling handlers implement:\n * side-band-64k (honored by handleUploadPack's response framing — clients\n * like isomorphic-git unconditionally expect it), tip/reachable sha1 wants,\n * delete-refs and report-status for pushes.\n */\nexport async function handleInfoRefs(\n\trepo: Repo,\n\toptions: InfoRefsOptions,\n\thooks?: HttpHooks,\n): Promise<GitHttpResult> {\n\tconst { service } = options;\n\tconst agent = options.agent ?? \"git-fs-s3\";\n\tconst { refs, headSymref } = await runStep(hooks, \"listAllRefs\", () =>\n\t\tlistAllRefs(repo, options.defaultBranch),\n\t);\n\n\tconst isUpload = service === \"git-upload-pack\";\n\tconst caps = isUpload\n\t\t? `no-progress side-band-64k symref=HEAD:${headSymref} allow-tip-sha1-in-want allow-reachable-sha1-in-want agent=${agent}`\n\t\t: `delete-refs report-status no-done agent=${agent}`;\n\n\tconst parts: Uint8Array[] = [pktLine(`# service=${service}\\n`), FLUSH];\n\n\tif (refs.length === 0) {\n\t\t// Empty repo: git needs this exact sentinel\n\t\tparts.push(\n\t\t\tpktLine(\n\t\t\t\t`0000000000000000000000000000000000000000 capabilities^{}\\0${caps}\\n`,\n\t\t\t),\n\t\t);\n\t} else {\n\t\tlet first = true;\n\t\tfor (const { name, oid } of refs) {\n\t\t\tparts.push(\n\t\t\t\tpktLine(first ? `${oid} ${name}\\0${caps}\\n` : `${oid} ${name}\\n`),\n\t\t\t);\n\t\t\tfirst = false;\n\t\t}\n\t}\n\tparts.push(FLUSH);\n\n\treturn {\n\t\tstatus: 200,\n\t\theaders: {\n\t\t\t\"Content-Type\": `application/x-${service}-advertisement`,\n\t\t\t\"Cache-Control\": \"no-cache\",\n\t\t},\n\t\tbody: concat(...parts),\n\t};\n}\n","/**\n * Git pkt-line framing (smart HTTP protocol wire format).\n *\n * Edge-compatible: uses Uint8Array instead of Buffer.\n */\n\nimport { concat, decodeAscii, decodeUtf8, encodeUtf8 } from \"../edge-utils.js\";\n\n/** Flush packet: exactly four zero bytes. */\nexport const FLUSH: Uint8Array<ArrayBuffer> = new Uint8Array([\n\t0x30, 0x30, 0x30, 0x30,\n]); // \"0000\"\n\n/** Frame a UTF-8 string as one pkt-line. */\nexport function pktLine(data: string): Uint8Array<ArrayBuffer> {\n\tconst body = encodeUtf8(data);\n\tconst len = (body.length + 4).toString(16).padStart(4, \"0\");\n\treturn concat(encodeUtf8(len), body);\n}\n\n/** Frame raw bytes as one pkt-line. */\nexport function pktLineBuffer(body: Uint8Array): Uint8Array<ArrayBuffer> {\n\tconst len = (body.length + 4).toString(16).padStart(4, \"0\");\n\treturn concat(encodeUtf8(len), body);\n}\n\n/** Decode pkt-lines to strings; `null` marks a flush-pkt. Stops on garbage. */\nexport function parsePktLines(buf: Uint8Array): Array<string | null> {\n\tconst lines: Array<string | null> = [];\n\tlet pos = 0;\n\twhile (pos + 4 <= buf.length) {\n\t\tconst len = Number.parseInt(decodeAscii(buf.subarray(pos, pos + 4)), 16);\n\t\tif (len === 0) {\n\t\t\tlines.push(null);\n\t\t\tpos += 4;\n\t\t} else if (len >= 4) {\n\t\t\tlines.push(decodeUtf8(buf.subarray(pos + 4, pos + len)));\n\t\t\tpos += len;\n\t\t} else {\n\t\t\tbreak;\n\t\t}\n\t}\n\treturn lines;\n}\n\n/**\n * Per the git protocol, once side-band-64k has been negotiated, packfile\n * bytes in the upload-pack response must be chunked into pkt-lines each\n * prefixed with a control byte (0x01 = packfile data), terminated by a\n * flush-pkt. Without this, clients that don't special-case \"no side-band\" —\n * e.g. isomorphic-git's GitSideBand.demux, which always treats the response\n * as side-band-framed — misparse the raw packfile bytes as bogus pkt-line\n * length headers and spin forever. (Native `git` tolerates a raw unframed\n * stream when side-band isn't negotiated, so this only surfaces with\n * isomorphic-git as the HTTP client.)\n */\nconst SIDE_BAND_MAX_CHUNK = 65515;\n\nexport function sideBandPackfile(\n\tpackData: Uint8Array,\n): Uint8Array<ArrayBuffer> {\n\tconst parts: Uint8Array[] = [];\n\tfor (\n\t\tlet offset = 0;\n\t\toffset < packData.length;\n\t\toffset += SIDE_BAND_MAX_CHUNK\n\t) {\n\t\tconst chunk = packData.subarray(offset, offset + SIDE_BAND_MAX_CHUNK);\n\t\tparts.push(pktLineBuffer(concat(new Uint8Array([1]), chunk)));\n\t}\n\tparts.push(FLUSH);\n\treturn concat(...parts);\n}\n","import type { Repo } from \"../ops/types.js\";\n\n/**\n * Transport-agnostic HTTP response the handlers produce.\n *\n * `body` is pinned to `Uint8Array<ArrayBuffer>` (not the bare `Uint8Array`,\n * whose default type argument isn't consistent across TypeScript versions)\n * so it's always assignable to Fetch API `BodyInit` regardless of which\n * TypeScript/lib version a consumer compiles against — every producer here\n * (`concat`, `pktLine`, `TextEncoder.encode`) is ArrayBuffer-backed already.\n */\nexport interface GitHttpResult {\n\tstatus: number;\n\theaders: Record<string, string>;\n\tbody: Uint8Array<ArrayBuffer>;\n}\n\n/** Optional instrumentation hooks shared by the /http handlers. */\nexport interface HttpHooks {\n\t/** Wrap a timed sub-step. Default: run directly. */\n\tstep?: <T>(label: string, fn: () => Promise<T>) => Promise<T>;\n\t/** Non-fatal problem sink (missing objects, failed repacks). */\n\tonWarn?: (message: string, error?: unknown) => void;\n}\n\nexport const runStep = <T>(\n\thooks: HttpHooks | undefined,\n\tlabel: string,\n\tfn: () => Promise<T>,\n): Promise<T> => (hooks?.step ? hooks.step(label, fn) : fn());\n\n/**\n * The raw promise-fs surface some /http paths need beyond isomorphic-git's\n * plumbing (writing an incoming pack file, listing/deleting pack files).\n * Both `node:fs` and this package's `GitFs` satisfy it via `.promises`.\n */\nexport interface RawFsPromises {\n\treadFile(path: string): Promise<Uint8Array | string>;\n\twriteFile(path: string, data: Uint8Array | string): Promise<void>;\n\tunlink(path: string): Promise<void>;\n\treaddir(path: string): Promise<string[]>;\n\tmkdir(path: string, options?: { recursive?: boolean }): Promise<unknown>;\n\tstat(path: string): Promise<unknown>;\n}\n\n/** Duck-type the `.promises` surface off a repo's fs. */\nexport function rawFs(repo: Repo): RawFsPromises {\n\tconst fs = repo.fs as { promises?: RawFsPromises };\n\tif (!fs?.promises) {\n\t\tthrow new TypeError(\n\t\t\t\"This operation needs a promise fs (`fs.promises`) — node:fs and createGitFs both provide one\",\n\t\t);\n\t}\n\treturn fs.promises;\n}\n","import git from \"isomorphic-git\";\nimport type { Repo } from \"../ops/types.js\";\nimport type { HttpHooks } from \"./types.js\";\n\nexport interface ReachabilityResult {\n\toids: string[];\n\t/**\n\t * False if any object in the graph couldn't be read — repack uses this\n\t * (not a raw object-count comparison) to decide whether it's safe to\n\t * delete old packs: counts alone can't distinguish \"everything read fine\"\n\t * from \"some objects silently failed\".\n\t */\n\tcomplete: boolean;\n}\n\nconst delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));\n\n/**\n * A read landing in the gap between a repack's new consolidated pack being\n * uploaded and the stale-pack cleanup finishing can transiently miss an\n * object that is not actually lost — it exists in the new pack the whole\n * time; the reader's cached pack listing was just taken mid-transition. One\n * retry after a short delay is enough to observe the consistent listing.\n */\nconst MISSING_OBJECT_RETRY_DELAY_MS = 200;\n\n/**\n * Walk the object graph from `startOids`, returning every reachable oid.\n * Concurrent traversal paths are deduplicated promise-per-oid; a missing\n * object is retried once, then reported through `hooks.onWarn` and\n * reflected in `complete: false`.\n *\n * `haveOids` bounds the walk: any oid in this set is assumed already\n * verified (it was reachable from a ref that existed before this call) and\n * is recorded in `seen` without being read or descended into. Without a\n * boundary, every call re-walks the *entire* history from scratch — for\n * receive-pack validation (see receive-pack.ts) that means every push pays\n * for reading every object in the repo, not just the ones it introduced,\n * and one transient read failure anywhere in that whole history rejects an\n * otherwise-valid push.\n *\n * Commits, trees and tags are read once each (parsed). Blobs have no\n * children, so `verifyBlobs: false` records a blob from its tree entry\n * without reading (and inflating) it — for callers that read every blob\n * themselves afterwards anyway, like upload-pack's `packObjects`. Leave it\n * true wherever the walk *is* the existence check (receive-pack, repack).\n */\nexport async function collectReachableOids(\n\trepo: Repo,\n\tstartOids: string[],\n\thooks?: HttpHooks,\n\thaveOids?: ReadonlySet<string> | readonly string[],\n\toptions?: { verifyBlobs?: boolean },\n): Promise<ReachabilityResult> {\n\tconst verifyBlobs = options?.verifyBlobs ?? true;\n\tconst have = haveOids instanceof Set ? haveOids : new Set(haveOids ?? []);\n\tconst seen = new Set<string>();\n\tlet complete = true;\n\tconst promises = new Map<string, Promise<void>>();\n\n\tasync function readAndVisitChildren(oid: string): Promise<void> {\n\t\tconst obj = await git.readObject({ ...repo, oid });\n\t\tseen.add(oid);\n\t\tconst children: [string, string | undefined][] = [];\n\t\tif (obj.type === \"commit\") {\n\t\t\tconst commit = obj.object as { tree: string; parent: string[] };\n\t\t\tchildren.push([commit.tree, \"tree\"]);\n\t\t\tfor (const parent of commit.parent) children.push([parent, \"commit\"]);\n\t\t} else if (obj.type === \"tree\") {\n\t\t\tconst tree = obj.object as { oid: string; type: string }[];\n\t\t\tfor (const entry of tree) {\n\t\t\t\t// Gitlinks (submodules, mode 160000) point at commits in another\n\t\t\t\t// repository; git never stores them here, so following one would\n\t\t\t\t// report a \"missing\" object and fail every walk over that history.\n\t\t\t\tif (entry.type === \"commit\") continue;\n\t\t\t\tchildren.push([entry.oid, entry.type]);\n\t\t\t}\n\t\t} else if (obj.type === \"tag\") {\n\t\t\tchildren.push([(obj.object as { object: string }).object, undefined]);\n\t\t}\n\t\tawait Promise.all(children.map(([child, type]) => visit(child, type)));\n\t}\n\n\tfunction visit(oid: string, type?: string): Promise<void> {\n\t\tconst existing = promises.get(oid);\n\t\tif (existing) return existing;\n\n\t\tif (have.has(oid) || (type === \"blob\" && !verifyBlobs)) {\n\t\t\tseen.add(oid);\n\t\t\tconst p = Promise.resolve();\n\t\t\tpromises.set(oid, p);\n\t\t\treturn p;\n\t\t}\n\n\t\tconst p = (async () => {\n\t\t\ttry {\n\t\t\t\tawait readAndVisitChildren(oid);\n\t\t\t} catch {\n\t\t\t\ttry {\n\t\t\t\t\tawait delay(MISSING_OBJECT_RETRY_DELAY_MS);\n\t\t\t\t\tawait readAndVisitChildren(oid);\n\t\t\t\t} catch (err) {\n\t\t\t\t\tcomplete = false;\n\t\t\t\t\thooks?.onWarn?.(`missing object ${oid}`, err);\n\t\t\t\t}\n\t\t\t}\n\t\t})();\n\n\t\tpromises.set(oid, p);\n\t\treturn p;\n\t}\n\n\tawait Promise.all(startOids.map((oid) => visit(oid)));\n\treturn { oids: Array.from(seen), complete };\n}\n","import git from \"isomorphic-git\";\nimport { concat, decodeAscii, decodeUtf8 } from \"../edge-utils.js\";\nimport type { Repo } from \"../ops/types.js\";\nimport { isSafeFullRefName } from \"../refs.js\";\nimport { FLUSH, pktLine } from \"./pkt-line.js\";\nimport { collectReachableOids } from \"./reachability.js\";\nimport { type RepackOptions, repackRepository } from \"./repack.js\";\nimport { type GitHttpResult, type HttpHooks, rawFs, runStep } from \"./types.js\";\n\nconst ZERO_OID = \"0\".repeat(40);\n\nexport interface RefUpdateCommand {\n\toldOid: string;\n\tnewOid: string;\n\trefName: string;\n}\n\nexport interface RefUpdateResult {\n\trefName: string;\n\tok: boolean;\n\treason?: string;\n}\n\n/** Split a receive-pack body: pkt-line ref commands, flush, then raw PACK. */\nexport function parseReceivePackBody(body: Uint8Array): {\n\trefUpdates: RefUpdateCommand[];\n\tpackData: Uint8Array;\n} {\n\tconst refUpdates: RefUpdateCommand[] = [];\n\tlet pos = 0;\n\twhile (pos + 4 <= body.length) {\n\t\tconst len = Number.parseInt(decodeAscii(body.subarray(pos, pos + 4)), 16);\n\t\tif (len === 0) {\n\t\t\tpos += 4;\n\t\t\tbreak; // flush = end of ref update commands\n\t\t}\n\t\tif (len < 4) break;\n\t\t// Strip NUL-separated capabilities from the first command line\n\t\tconst line = decodeUtf8(body.subarray(pos + 4, pos + len))\n\t\t\t.replace(/\\n$/, \"\")\n\t\t\t.split(\"\\0\")[0];\n\t\tpos += len;\n\t\tconst parts = (line ?? \"\").split(\" \");\n\t\tconst [oldOid, newOid, refName] = parts;\n\t\tif (oldOid && newOid && refName) {\n\t\t\trefUpdates.push({ oldOid, newOid, refName });\n\t\t}\n\t}\n\treturn { refUpdates, packData: body.subarray(pos) };\n}\n\n/** Initialize the repo when its HEAD doesn't exist yet (first push). */\nexport async function ensureRepoInitialized(\n\trepo: Repo,\n\tdefaultBranch: string,\n): Promise<void> {\n\tconst fsp = rawFs(repo);\n\ttry {\n\t\tawait fsp.stat(`${repo.gitdir}/HEAD`);\n\t} catch {\n\t\tawait fsp.mkdir(repo.gitdir, { recursive: true }).catch(() => {});\n\t\tawait git.init({ ...repo, dir: repo.gitdir, defaultBranch, bare: true });\n\t}\n}\n\n/** Write the incoming PACK into objects/pack/ and index it. */\nexport async function indexIncomingPack(\n\trepo: Repo,\n\tpackData: Uint8Array,\n\thooks?: HttpHooks,\n): Promise<string[]> {\n\tif (packData.length < 4) return [];\n\tconst packName = await runStep(\n\t\thooks,\n\t\t\"write + indexPack incoming pack\",\n\t\tasync () => {\n\t\t\tconst fsp = rawFs(repo);\n\t\t\tconst packDir = `${repo.gitdir}/objects/pack`;\n\t\t\tawait fsp.mkdir(packDir, { recursive: true });\n\n\t\t\tconst packName = `recv-${Date.now()}`;\n\t\t\tawait fsp.writeFile(`${packDir}/${packName}.pack`, packData);\n\n\t\t\tawait git.indexPack({\n\t\t\t\t...repo,\n\t\t\t\tdir: packDir,\n\t\t\t\tfilepath: `${packName}.pack`,\n\t\t\t});\n\t\t\treturn packName;\n\t\t},\n\t);\n\treturn [\n\t\t`${repo.gitdir}/objects/pack/${packName}.pack`,\n\t\t`${repo.gitdir}/objects/pack/${packName}.idx`,\n\t];\n}\n\n/**\n * Validate a ref name and compare-and-swap it against its claimed current\n * oid. Returns the failure result to report if either check fails, or\n * `null` when both pass — shared by the preflight check (`validateRefUpdates`,\n * run before the possibly-slow pack indexing/graph-walk work, purely to\n * avoid doing that work for an update already doomed to fail) and the real\n * atomicity guarantee (`applyRefUpdates`, run again right before writing,\n * since a concurrent push could have moved the ref in the time that work\n * took).\n */\nasync function checkRefCas(\n\trepo: Repo,\n\trefName: string,\n\toldOid: string,\n): Promise<RefUpdateResult | null> {\n\tif (!isSafeFullRefName(refName)) {\n\t\treturn { refName, ok: false, reason: \"invalid ref name\" };\n\t}\n\tconst currentOid = await git\n\t\t.resolveRef({ ...repo, ref: refName })\n\t\t.catch(() => ZERO_OID);\n\treturn currentOid === oldOid\n\t\t? null\n\t\t: {\n\t\t\t\trefName,\n\t\t\t\tok: false,\n\t\t\t\treason: \"non-fast-forward, ref updated by another push\",\n\t\t\t};\n}\n\n/** Every branch/tag tip currently in the repo, as a validation boundary — see\n * its call site in {@link applyReceivePack}. */\nasync function collectExistingRefTips(repo: Repo): Promise<Set<string>> {\n\tconst [branches, tags] = await Promise.all([\n\t\tgit.listBranches(repo),\n\t\tgit.listTags(repo),\n\t]);\n\tconst refs = [\n\t\t...branches.map((b) => `refs/heads/${b}`),\n\t\t...tags.map((t) => `refs/tags/${t}`),\n\t];\n\tconst oids = await Promise.all(\n\t\trefs.map((ref) => git.resolveRef({ ...repo, ref }).catch(() => null)),\n\t);\n\treturn new Set(oids.filter((oid): oid is string => oid !== null));\n}\n\nasync function validateRefUpdates(\n\trepo: Repo,\n\trefUpdates: RefUpdateCommand[],\n): Promise<RefUpdateResult[]> {\n\treturn Promise.all(\n\t\trefUpdates.map(\n\t\t\tasync ({ oldOid, refName }) =>\n\t\t\t\t(await checkRefCas(repo, refName, oldOid)) ?? { refName, ok: true },\n\t\t),\n\t);\n}\n\n/**\n * Apply ref updates, enforcing compare-and-swap against each command's\n * claimed oldOid. Every client-supplied refName is validated before any\n * filesystem call reads or writes through it.\n */\nexport async function applyRefUpdates(\n\trepo: Repo,\n\trefUpdates: RefUpdateCommand[],\n\thooks?: HttpHooks,\n): Promise<RefUpdateResult[]> {\n\treturn runStep(hooks, \"apply ref updates\", () =>\n\t\tPromise.all(\n\t\t\trefUpdates.map(async ({ oldOid, newOid, refName }) => {\n\t\t\t\tconst failure = await checkRefCas(repo, refName, oldOid);\n\t\t\t\tif (failure) return failure;\n\n\t\t\t\tif (newOid === ZERO_OID) {\n\t\t\t\t\tawait git.deleteRef({ ...repo, ref: refName }).catch(() => {});\n\t\t\t\t} else {\n\t\t\t\t\tawait git.writeRef({\n\t\t\t\t\t\t...repo,\n\t\t\t\t\t\tref: refName,\n\t\t\t\t\t\tvalue: newOid,\n\t\t\t\t\t\tforce: true,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\treturn { refName, ok: true };\n\t\t\t}),\n\t\t),\n\t);\n}\n\n/** The report-status response body for a set of ref-update results. */\nexport function receivePackResponse(results: RefUpdateResult[]): GitHttpResult {\n\tconst responseBody = concat(\n\t\tpktLine(\"unpack ok\\n\"),\n\t\t...results.map(({ refName, ok, reason }) =>\n\t\t\tpktLine(ok ? `ok ${refName}\\n` : `ng ${refName} ${reason}\\n`),\n\t\t),\n\t\tFLUSH,\n\t);\n\n\treturn {\n\t\tstatus: 200,\n\t\theaders: {\n\t\t\t\"Content-Type\": \"application/x-git-receive-pack-result\",\n\t\t\t\"Cache-Control\": \"no-cache\",\n\t\t},\n\t\tbody: responseBody,\n\t};\n}\n\nexport interface ApplyReceivePackOptions {\n\t/** HEAD's branch when a push initializes an empty repository. */\n\tdefaultBranch: string;\n\t/**\n\t * Repack tuning, or `false` to skip repacking entirely. The returned\n\t * `stalePackPaths` are gitdir-relative pack files the repack deleted from\n\t * this repo — after syncing to any secondary storage, delete them there\n\t * too.\n\t */\n\trepack?: RepackOptions | false;\n}\n\n/**\n * The storage half of a push against `repo`: initialize if needed, index the\n * incoming pack, CAS-apply ref updates, then repack when enough packs have\n * accumulated. Serialize concurrent pushes to the same repo externally (a\n * per-repo lock); build the HTTP response afterwards with\n * {@link receivePackResponse}.\n */\nexport async function applyReceivePack(\n\trepo: Repo,\n\tparsed: { refUpdates: RefUpdateCommand[]; packData: Uint8Array },\n\toptions: ApplyReceivePackOptions,\n\thooks?: HttpHooks,\n): Promise<{ results: RefUpdateResult[]; stalePackPaths: string[] }> {\n\tawait ensureRepoInitialized(repo, options.defaultBranch);\n\tconst preflight = await validateRefUpdates(repo, parsed.refUpdates);\n\tconst acceptedUpdates = parsed.refUpdates.filter(\n\t\t(_, index) => preflight[index]?.ok,\n\t);\n\tconst incomingPackPaths = await indexIncomingPack(\n\t\trepo,\n\t\tacceptedUpdates.some((update) => update.newOid !== ZERO_OID)\n\t\t\t? parsed.packData\n\t\t\t: new Uint8Array(),\n\t\thooks,\n\t);\n\n\tconst newTips = acceptedUpdates\n\t\t.filter((update) => update.newOid !== ZERO_OID)\n\t\t.map((update) => update.newOid);\n\tif (newTips.length > 0) {\n\t\t// Every oid reachable from an existing ref was already accepted by an\n\t\t// earlier push — not just the ref(s) this push happens to touch. A\n\t\t// brand-new branch (oldOid === ZERO_OID, so it has no pre-image of its\n\t\t// own) is the common case this matters for: creating a branch off an\n\t\t// existing one is still just one new commit's worth of new objects,\n\t\t// and shouldn't cost a walk of the entire repo history to validate.\n\t\tconst haveOids = await collectExistingRefTips(repo);\n\t\tfor (const update of acceptedUpdates) {\n\t\t\tif (update.oldOid !== ZERO_OID) haveOids.add(update.oldOid);\n\t\t}\n\t\tconst { complete } = await collectReachableOids(\n\t\t\trepo,\n\t\t\tnewTips,\n\t\t\thooks,\n\t\t\thaveOids,\n\t\t);\n\t\tif (!complete) {\n\t\t\tawait Promise.all(\n\t\t\t\tincomingPackPaths.map((filepath) =>\n\t\t\t\t\trawFs(repo)\n\t\t\t\t\t\t.unlink(filepath)\n\t\t\t\t\t\t.catch(() => {}),\n\t\t\t\t),\n\t\t\t);\n\t\t\treturn {\n\t\t\t\tresults: preflight.map((result) =>\n\t\t\t\t\tresult.ok\n\t\t\t\t\t\t? { ...result, ok: false, reason: \"incomplete object graph\" }\n\t\t\t\t\t\t: result,\n\t\t\t\t),\n\t\t\t\tstalePackPaths: [],\n\t\t\t};\n\t\t}\n\t}\n\n\tconst applied = await applyRefUpdates(repo, acceptedUpdates, hooks);\n\tconst results = preflight.map((result, index) =>\n\t\tresult.ok\n\t\t\t? (applied.find(\n\t\t\t\t\t(entry) => entry.refName === parsed.refUpdates[index]?.refName,\n\t\t\t\t) ?? result)\n\t\t\t: result,\n\t);\n\tconst repackOptions = options.repack;\n\tconst stalePackPaths =\n\t\trepackOptions === false || !results.some((result) => result.ok)\n\t\t\t? []\n\t\t\t: await runStep(hooks, \"repack\", () =>\n\t\t\t\t\trepackRepository(repo, repackOptions, hooks),\n\t\t\t\t);\n\treturn { results, stalePackPaths };\n}\n","/**\n * Pack consolidation: merge all packs into one, reusing stored (possibly\n * deltified) entries when every object is packed.\n *\n * Edge-compatible: uses Web Crypto (SHA-1), CompressionStream (deflate),\n * and Uint8Array throughout — no node:crypto, node:zlib, or Buffer.\n */\n\nimport git from \"isomorphic-git\";\nimport { concat, deflate, encodeUtf8, fromHex, sha1 } from \"../edge-utils.js\";\nimport type { Repo } from \"../ops/types.js\";\nimport {\n\ttype PackFileName,\n\tpackFileName,\n\tparsePackDirectory,\n} from \"../pack-files.js\";\nimport { buildPackFromExistingPacks } from \"./pack-reuse.js\";\nimport { collectReachableOids } from \"./reachability.js\";\nimport { type HttpHooks, rawFs } from \"./types.js\";\n\ntype PackObjectType = \"commit\" | \"tree\" | \"blob\" | \"tag\";\n\n// Git pack object type bits (bits 6-4 of the header's first byte) — same\n// constants real git and isomorphic-git's own (de)serializers use.\nconst PACK_OBJECT_TYPE_BITS: Record<PackObjectType, number> = {\n\tcommit: 0b0010000,\n\ttree: 0b0100000,\n\tblob: 0b0110000,\n\ttag: 0b1000000,\n};\n\n// Git's pack object header: first byte packs (continuation bit | 3-bit type |\n// low 4 bits of length); any remaining length is emitted 7 bits at a time,\n// each with its own continuation bit, little-endian.\nfunction encodePackObjectHeader(\n\ttype: PackObjectType,\n\tlength: number,\n): Uint8Array {\n\tconst bytes: number[] = [];\n\tlet more = length > 0b1111;\n\tbytes.push(\n\t\t(more ? 0b10000000 : 0) | PACK_OBJECT_TYPE_BITS[type] | (length & 0b1111),\n\t);\n\tlength >>>= 4;\n\twhile (more) {\n\t\tmore = length > 0b01111111;\n\t\tbytes.push((more ? 0b10000000 : 0) | (length & 0b01111111));\n\t\tlength >>>= 7;\n\t}\n\treturn new Uint8Array(bytes);\n}\n\n// Git's object hash: sha1(\"<type> <byte length>\\0<content>\") — matches\n// isomorphic-git's internal GitObject.wrap + shasum, computed independently\n// here rather than trusted from isomorphic-git's own read path.\nasync function hashGitObject(\n\ttype: string,\n\tcontent: Uint8Array,\n): Promise<string> {\n\tconst prefix = encodeUtf8(`${type} ${content.length}\\0`);\n\treturn sha1(concat(prefix, content));\n}\n\ntype VerifiedObject = {\n\ttype: PackObjectType;\n\tcontent: Uint8Array<ArrayBuffer>;\n};\n\n/**\n * Read every reachable object and independently re-derive its oid from the\n * bytes isomorphic-git handed back, instead of trusting the oid it was asked\n * for. isomorphic-git's *packed*-object read path never verifies the\n * resolved content's SHA-1 against the requested oid — only the\n * loose-object branch does.\n */\nasync function readAndVerifyObjects(\n\trepo: Repo,\n\toids: string[],\n): Promise<Map<string, VerifiedObject>> {\n\tconst objects = new Map<string, VerifiedObject>();\n\tconst BATCH_SIZE = 100;\n\tfor (let i = 0; i < oids.length; i += BATCH_SIZE) {\n\t\tconst batch = oids.slice(i, i + BATCH_SIZE);\n\t\tconst entries = await Promise.all(\n\t\t\tbatch.map(async (oid) => {\n\t\t\t\tconst { type, object } = await git.readObject({\n\t\t\t\t\t...repo,\n\t\t\t\t\toid,\n\t\t\t\t\tformat: \"content\",\n\t\t\t\t});\n\t\t\t\t// Own ArrayBuffer-backed copy: Web APIs (Blob, digest) reject\n\t\t\t\t// views that might sit on a SharedArrayBuffer.\n\t\t\t\tconst content = new Uint8Array(object as Uint8Array);\n\t\t\t\tconst actualOid = await hashGitObject(type, content);\n\t\t\t\tif (actualOid !== oid) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`repack: object ${oid} failed independent SHA-1 verification ` +\n\t\t\t\t\t\t\t`(recomputed ${actualOid}) — refusing to trust this read, aborting repack`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\treturn { oid, type: type as PackObjectType, content };\n\t\t\t}),\n\t\t);\n\t\tfor (const { oid, type, content } of entries) {\n\t\t\tobjects.set(oid, { type, content });\n\t\t}\n\t}\n\treturn objects;\n}\n\n/**\n * Serialize verified objects into a pack containing only full (never\n * deltified) entries, in the given oid order.\n */\nasync function buildVerifiedPack(\n\toids: string[],\n\tobjects: Map<string, VerifiedObject>,\n): Promise<Uint8Array> {\n\t// PACK header: \"PACK\" + version(2) + count, all big-endian uint32\n\tconst header = new Uint8Array(12);\n\tconst view = new DataView(header.buffer);\n\theader.set(encodeUtf8(\"PACK\"), 0);\n\tview.setUint32(4, 2, false); // version 2\n\tview.setUint32(8, oids.length, false); // object count\n\n\tconst chunks: Uint8Array[] = [header];\n\n\tconst BATCH_SIZE = 100;\n\tfor (let i = 0; i < oids.length; i += BATCH_SIZE) {\n\t\tconst batch = oids.slice(i, i + BATCH_SIZE);\n\t\tconst encoded = await Promise.all(\n\t\t\tbatch.map(async (oid) => {\n\t\t\t\tconst entry = objects.get(oid);\n\t\t\t\tif (!entry) {\n\t\t\t\t\tthrow new Error(`repack: missing verified object for ${oid}`);\n\t\t\t\t}\n\t\t\t\tconst objHeader = encodePackObjectHeader(\n\t\t\t\t\tentry.type,\n\t\t\t\t\tentry.content.length,\n\t\t\t\t);\n\t\t\t\tconst compressed = await deflate(entry.content);\n\t\t\t\treturn concat(objHeader, compressed);\n\t\t\t}),\n\t\t);\n\t\tchunks.push(...encoded);\n\t}\n\n\t// Git's pack trailer is the SHA-1 of every preceding byte (header +\n\t// every object entry) as a single digest — NOT an incremental/chained\n\t// hash. Web Crypto's subtle.digest has no streaming mode, so unlike\n\t// Node's crypto.createHash this can't be folded into the loop above;\n\t// everything is already buffered in `chunks`, so hash it in one pass.\n\tconst packBody = concat(...chunks);\n\tconst trailer = fromHex(await sha1(packBody));\n\treturn concat(packBody, trailer);\n}\n\n/**\n * Default repack threshold. Consolidating is O(total repo object count) —\n * so paying it on every push makes push latency grow with repo size forever.\n */\nexport const REPACK_PACK_COUNT_THRESHOLD = 4;\n\nasync function readPackDirectory(repo: Repo): Promise<PackFileName[]> {\n\tconst entries = await rawFs(repo)\n\t\t.readdir(`${repo.gitdir}/objects/pack`)\n\t\t.catch(() => [] as string[]);\n\treturn parsePackDirectory(entries);\n}\n\nasync function countPacks(repo: Repo): Promise<number> {\n\tconst files = await readPackDirectory(repo);\n\treturn files.filter((file) => file.kind === \"pack\").length;\n}\n\nexport interface RepackOptions {\n\t/** Skip repacking below this many accumulated packs. Default 4. */\n\tthreshold?: number;\n}\n\n/**\n * Consolidate all pack files into one, returning the gitdir-relative paths\n * of the old .pack/.idx files it removed — after syncing the new pack to any\n * secondary storage, delete those same paths there too.\n */\nexport async function repackRepository(\n\trepo: Repo,\n\toptions?: RepackOptions,\n\thooks?: HttpHooks,\n): Promise<string[]> {\n\tconst threshold = options?.threshold ?? REPACK_PACK_COUNT_THRESHOLD;\n\tconst fsp = rawFs(repo);\n\ttry {\n\t\tif ((await countPacks(repo)) < threshold) {\n\t\t\treturn [];\n\t\t}\n\n\t\tconst [branches, tags] = await Promise.all([\n\t\t\tgit.listBranches(repo),\n\t\t\tgit.listTags(repo),\n\t\t]);\n\t\tconst refNames = [\n\t\t\t...branches.map((b) => `refs/heads/${b}`),\n\t\t\t...tags.map((t) => `refs/tags/${t}`),\n\t\t];\n\t\tconst tipOids = (\n\t\t\tawait Promise.all(\n\t\t\t\trefNames.map((ref) =>\n\t\t\t\t\tgit.resolveRef({ ...repo, ref }).catch(() => null),\n\t\t\t\t),\n\t\t\t)\n\t\t).filter((oid): oid is string => oid !== null);\n\t\tif (tipOids.length === 0) return [];\n\n\t\tconst { oids: reachable, complete } = await collectReachableOids(\n\t\t\trepo,\n\t\t\ttipOids,\n\t\t\thooks,\n\t\t);\n\t\tif (!complete || reachable.length === 0) return [];\n\n\t\tconst packDir = `${repo.gitdir}/objects/pack`;\n\t\t// Copy stored entries when everything is packed: keeps deltas, so\n\t\t// the consolidated pack is as compact as its sources. Loose objects\n\t\t// fall back to a verified, non-deltified rebuild.\n\t\tconst packBuffer =\n\t\t\t(await buildPackFromExistingPacks(repo, reachable)) ??\n\t\t\t(await buildVerifiedPack(\n\t\t\t\treachable,\n\t\t\t\tawait readAndVerifyObjects(repo, reachable),\n\t\t\t));\n\n\t\tawait fsp.mkdir(packDir, { recursive: true });\n\t\tconst newBase = `pack-${Date.now()}`;\n\t\tconst newPackFile = packFileName(newBase, \"pack\");\n\t\tawait fsp.writeFile(`${packDir}/${newPackFile}`, packBuffer);\n\n\t\tconst { oids: indexedOids } = await git.indexPack({\n\t\t\t...repo,\n\t\t\tdir: packDir,\n\t\t\tfilepath: newPackFile,\n\t\t});\n\n\t\t// indexPack re-hashes every object it indexes, so this also verifies\n\t\t// the content. Reused packs may carry extra delta bases: superset is fine.\n\t\tconst indexed = new Set(indexedOids);\n\t\tif (reachable.some((oid) => !indexed.has(oid))) {\n\t\t\tawait Promise.all(\n\t\t\t\t([\"pack\", \"idx\"] as const).map((kind) =>\n\t\t\t\t\tfsp\n\t\t\t\t\t\t.unlink(`${packDir}/${packFileName(newBase, kind)}`)\n\t\t\t\t\t\t.catch(() => {}),\n\t\t\t\t),\n\t\t\t);\n\t\t\tthrow new Error(\n\t\t\t\t\"repack: indexed pack is missing reachable objects — aborting\",\n\t\t\t);\n\t\t}\n\n\t\tconst stale = (await readPackDirectory(repo)).filter(\n\t\t\t(file) => file.base !== newBase,\n\t\t);\n\t\tawait Promise.all(\n\t\t\tstale.map((file) =>\n\t\t\t\tfsp.unlink(`${packDir}/${file.name}`).catch(() => {}),\n\t\t\t),\n\t\t);\n\t\treturn stale.map((file) => `objects/pack/${file.name}`);\n\t} catch (err) {\n\t\thooks?.onWarn?.(\"repack failed (non-fatal)\", err);\n\t\treturn [];\n\t}\n}\n","/**\n * Build a packfile by copying already-compressed entries out of existing\n * packs (\"pack reuse\"), instead of inflating and re-deflating every object.\n *\n * Entries keep their stored form, deltas included, so the output is as\n * compact as the packs it came from. `OFS_DELTA` entries are rewritten as\n * `REF_DELTA` (base named by oid), since offsets don't survive the move into\n * a new pack; delta bases are always included and written before the deltas\n * that use them. Formats: gitformat-pack(5).\n *\n * Edge-compatible: Web Crypto SHA-1 and Uint8Array only.\n */\n\nimport { concat, encodeUtf8, fromHex, sha1, toHex } from \"../edge-utils.js\";\nimport type { Repo } from \"../ops/types.js\";\nimport { packFileName, parsePackDirectory } from \"../pack-files.js\";\nimport { rawFs } from \"./types.js\";\n\nconst OBJ_OFS_DELTA = 6;\nconst OBJ_REF_DELTA = 7;\nconst OID_BYTES = 20;\nconst PACK_TRAILER_BYTES = 20;\n\nconst IDX_V2 = {\n\tmagic: 0xff744f63,\n\tversion: 2,\n\theaderBytes: 8,\n\tfanoutEntries: 256,\n\t/** Offsets with this bit set index the 64-bit large-offset table. */\n\tlargeOffsetFlag: 0x80000000,\n} as const;\n\n/** One object's stored entry inside a pack. */\ninterface PackEntry {\n\treadonly pack: Uint8Array;\n\treadonly start: number;\n\treadonly end: number;\n}\n\n/** Every object stored in a pack, by oid, plus its offset → oid inverse. */\ninterface IndexedPack {\n\treadonly entries: Map<string, PackEntry>;\n\treadonly oidAtOffset: Map<number, string>;\n}\n\n/** An object's position in a pack, as listed by its `.idx`. */\nexport interface PackIndexEntry {\n\treadonly oid: string;\n\treadonly offset: number;\n}\n\n/** Entries of a version-2 `.idx`; `null` for any other format. */\nexport function readPackIndex(idx: Uint8Array): PackIndexEntry[] | null {\n\tconst view = new DataView(idx.buffer, idx.byteOffset, idx.byteLength);\n\tconst namesStart = IDX_V2.headerBytes + IDX_V2.fanoutEntries * 4;\n\tif (\n\t\tidx.byteLength < namesStart ||\n\t\tview.getUint32(0) !== IDX_V2.magic ||\n\t\tview.getUint32(4) !== IDX_V2.version\n\t) {\n\t\treturn null;\n\t}\n\t// The last fanout slot counts every object in the pack.\n\tconst count = view.getUint32(namesStart - 4);\n\tconst crcStart = namesStart + count * OID_BYTES;\n\tconst offsetsStart = crcStart + count * 4;\n\tif (idx.byteLength < offsetsStart + count * 4) return null;\n\n\tconst entries: PackIndexEntry[] = [];\n\tfor (let i = 0; i < count; i++) {\n\t\tconst offset = view.getUint32(offsetsStart + i * 4);\n\t\t// Packs over 2 GiB never reach this in-memory path; let the caller\n\t\t// fall back instead of half-supporting the large-offset table.\n\t\tif (offset & IDX_V2.largeOffsetFlag) return null;\n\t\tconst at = namesStart + i * OID_BYTES;\n\t\tentries.push({ oid: toHex(idx.subarray(at, at + OID_BYTES)), offset });\n\t}\n\treturn entries;\n}\n\n/** Locate every entry of `pack` using its `.idx`. */\nfunction indexPack(idx: Uint8Array, pack: Uint8Array): IndexedPack | null {\n\tconst located = readPackIndex(idx);\n\tif (!located) return null;\n\n\t// An entry runs until the next entry's offset (or the pack trailer).\n\tlocated.sort((a, b) => a.offset - b.offset);\n\tconst entries = new Map<string, PackEntry>();\n\tconst oidAtOffset = new Map<number, string>();\n\tconst packEnd = pack.byteLength - PACK_TRAILER_BYTES;\n\tfor (let i = 0; i < located.length; i++) {\n\t\tconst { oid, offset } = located[i] as PackIndexEntry;\n\t\tconst end = located[i + 1]?.offset ?? packEnd;\n\t\tentries.set(oid, { pack, start: offset, end });\n\t\toidAtOffset.set(offset, oid);\n\t}\n\treturn { entries, oidAtOffset };\n}\n\n/** The variable-length type/size header that starts every pack entry. */\ninterface EntryHeader {\n\treadonly type: number;\n\treadonly size: number;\n\t/** Bytes the header occupies. */\n\treadonly length: number;\n}\n\nfunction readEntryHeader(bytes: Uint8Array, start: number): EntryHeader {\n\tlet byte = bytes[start] as number;\n\tconst type = (byte >> 4) & 0b111;\n\tlet size = byte & 0b1111;\n\tlet shift = 4;\n\tlet at = start + 1;\n\twhile (byte & 0x80) {\n\t\tbyte = bytes[at++] as number;\n\t\tsize += (byte & 0x7f) * 2 ** shift;\n\t\tshift += 7;\n\t}\n\treturn { type, size, length: at - start };\n}\n\nfunction encodeEntryHeader(type: number, size: number): Uint8Array {\n\tconst bytes: number[] = [];\n\tlet rest = Math.floor(size / 16);\n\tbytes.push((rest > 0 ? 0x80 : 0) | (type << 4) | (size % 16));\n\twhile (rest > 0) {\n\t\tconst low = rest % 128;\n\t\trest = Math.floor(rest / 128);\n\t\tbytes.push((rest > 0 ? 0x80 : 0) | low);\n\t}\n\treturn new Uint8Array(bytes);\n}\n\n/** `OFS_DELTA`'s negative base offset: big-endian base-128 with +1 carries. */\nfunction readOfsDeltaOffset(\n\tbytes: Uint8Array,\n\tstart: number,\n): { distance: number; length: number } {\n\tlet at = start;\n\tlet byte = bytes[at++] as number;\n\tlet distance = byte & 0x7f;\n\twhile (byte & 0x80) {\n\t\tbyte = bytes[at++] as number;\n\t\tdistance = (distance + 1) * 128 + (byte & 0x7f);\n\t}\n\treturn { distance, length: at - start };\n}\n\n/** A pack entry ready to copy, with the oid of its delta base if any. */\ninterface ReusableEntry {\n\treadonly bytes: Uint8Array;\n\treadonly baseOid: string | null;\n}\n\n/** Copy one entry, rewriting `OFS_DELTA` to `REF_DELTA`. */\nfunction reusableEntry(\n\tentry: PackEntry,\n\tindexed: IndexedPack,\n): ReusableEntry | null {\n\tconst { pack, start, end } = entry;\n\tconst header = readEntryHeader(pack, start);\n\tconst bodyStart = start + header.length;\n\tif (header.type === OBJ_REF_DELTA) {\n\t\treturn {\n\t\t\tbytes: pack.subarray(start, end),\n\t\t\tbaseOid: toHex(pack.subarray(bodyStart, bodyStart + OID_BYTES)),\n\t\t};\n\t}\n\tif (header.type !== OBJ_OFS_DELTA) {\n\t\treturn { bytes: pack.subarray(start, end), baseOid: null };\n\t}\n\tconst { distance, length } = readOfsDeltaOffset(pack, bodyStart);\n\tconst baseOid = indexed.oidAtOffset.get(start - distance);\n\tif (baseOid === undefined) return null;\n\treturn {\n\t\tbytes: concat(\n\t\t\tencodeEntryHeader(OBJ_REF_DELTA, header.size),\n\t\t\tfromHex(baseOid),\n\t\t\tpack.subarray(bodyStart + length, end),\n\t\t),\n\t\tbaseOid,\n\t};\n}\n\n/**\n * A pack holding at least `oids`, built from stored entries, or `null` when\n * any of them (or a delta base they need) isn't in a readable pack — e.g. a\n * loose object — so the caller should build the pack another way.\n */\nexport async function buildPackFromExistingPacks(\n\trepo: Repo,\n\toids: readonly string[],\n): Promise<Uint8Array | null> {\n\tconst fsp = rawFs(repo);\n\tconst packDir = `${repo.gitdir}/objects/pack`;\n\tconst packs = parsePackDirectory(\n\t\tawait fsp.readdir(packDir).catch(() => [] as string[]),\n\t).filter((file) => file.kind === \"pack\");\n\n\tconst indexes: IndexedPack[] = [];\n\tfor (const { base } of packs) {\n\t\t// A pack without a readable index can't be reused.\n\t\tconst [pack, idx] = await Promise.all([\n\t\t\tfsp.readFile(`${packDir}/${packFileName(base, \"pack\")}`),\n\t\t\tfsp.readFile(`${packDir}/${packFileName(base, \"idx\")}`),\n\t\t]).catch(() => [null, null]);\n\t\tif (!(pack instanceof Uint8Array) || !(idx instanceof Uint8Array)) {\n\t\t\treturn null;\n\t\t}\n\t\tconst indexed = indexPack(idx, pack);\n\t\tif (!indexed) return null;\n\t\tindexes.push(indexed);\n\t}\n\n\tconst locate = (oid: string): ReusableEntry | null => {\n\t\tfor (const indexed of indexes) {\n\t\t\tconst entry = indexed.entries.get(oid);\n\t\t\tif (entry) return reusableEntry(entry, indexed);\n\t\t}\n\t\treturn null;\n\t};\n\n\t// Depth-first so every base is written before the deltas that use it.\n\tconst ordered: Uint8Array[] = [];\n\tconst done = new Set<string>();\n\tconst pending = new Set<string>();\n\tconst visit = (oid: string): boolean => {\n\t\tif (done.has(oid)) return true;\n\t\tif (pending.has(oid)) return false; // a delta cycle means corruption\n\t\tconst entry = locate(oid);\n\t\tif (!entry) return false;\n\t\tpending.add(oid);\n\t\tif (entry.baseOid !== null && !visit(entry.baseOid)) return false;\n\t\tpending.delete(oid);\n\t\tdone.add(oid);\n\t\tordered.push(entry.bytes);\n\t\treturn true;\n\t};\n\tfor (const oid of oids) {\n\t\tif (!visit(oid)) return null;\n\t}\n\n\tconst header = new Uint8Array(12);\n\theader.set(encodeUtf8(\"PACK\"), 0);\n\tconst view = new DataView(header.buffer);\n\tview.setUint32(4, 2);\n\tview.setUint32(8, ordered.length);\n\tconst body = concat(header, ...ordered);\n\treturn concat(body, fromHex(await sha1(body)));\n}\n","import git from \"isomorphic-git\";\nimport { concat } from \"../edge-utils.js\";\nimport type { Repo } from \"../ops/types.js\";\nimport { parsePackDirectory } from \"../pack-files.js\";\nimport { buildPackFromExistingPacks } from \"./pack-reuse.js\";\nimport { parsePktLines, pktLine, sideBandPackfile } from \"./pkt-line.js\";\nimport { collectReachableOids } from \"./reachability.js\";\nimport { type GitHttpResult, type HttpHooks, rawFs, runStep } from \"./types.js\";\n\nconst ZERO_OID = \"0\".repeat(40);\n\nexport interface UploadPackOptions {\n\t/**\n\t * Called once before the reachability walk — wire loose-object detection\n\t * (`GitFs.detectLooseObjects`) here so a fully packed repo's walk doesn't\n\t * pay a doomed loose-object probe per object.\n\t */\n\tbeforeWalk?: () => Promise<void>;\n}\n\n/**\n * `POST …/git-upload-pack` — serve a clone/fetch. Authorization is the\n * caller's job. Negotiation is client-driven (no multi_ack advertised):\n * \"have\" batches without \"done\" get a bare NAK; the final batch gets the\n * packfile, side-band-64k framed.\n *\n * Fast path: a fresh clone (no haves) of a repo consolidated down to a\n * single pack serves that pack's bytes directly — skipping the O(objects)\n * traversal + repack entirely.\n */\nexport async function handleUploadPack(\n\trepo: Repo,\n\tbody: Uint8Array,\n\toptions?: UploadPackOptions,\n\thooks?: HttpHooks,\n): Promise<GitHttpResult> {\n\tconst lines = parsePktLines(body);\n\n\tconst wants: string[] = [];\n\tconst haves: string[] = [];\n\tlet done = false;\n\tfor (const line of lines) {\n\t\tif (!line) continue;\n\t\tif (line.startsWith(\"want \")) {\n\t\t\twants.push(line.slice(5, 45));\n\t\t}\n\t\tif (line.startsWith(\"have \")) {\n\t\t\tconst sha = line.slice(5, 45);\n\t\t\tif (sha !== ZERO_OID) {\n\t\t\t\thaves.push(sha);\n\t\t\t}\n\t\t}\n\t\tif (line.startsWith(\"done\")) {\n\t\t\tdone = true;\n\t\t}\n\t}\n\n\tif (wants.length === 0) {\n\t\treturn {\n\t\t\tstatus: 200,\n\t\t\theaders: { \"Content-Type\": \"application/x-git-upload-pack-result\" },\n\t\t\tbody: concat(pktLine(\"NAK\\n\")),\n\t\t};\n\t}\n\n\tif (haves.length > 0 && !done) {\n\t\treturn {\n\t\t\tstatus: 200,\n\t\t\theaders: { \"Content-Type\": \"application/x-git-upload-pack-result\" },\n\t\t\tbody: concat(pktLine(\"NAK\\n\")),\n\t\t};\n\t}\n\n\t// Fresh clone = no haves, so all objects are needed. When the repo is down\n\t// to a single pack, serve it directly and skip the traversal + pack build.\n\tif (haves.length === 0) {\n\t\tconst packDirPath = `${repo.gitdir}/objects/pack`;\n\t\tconst entries = await runStep(hooks, \"readdir objects/pack\", () =>\n\t\t\trawFs(repo)\n\t\t\t\t.readdir(packDirPath)\n\t\t\t\t.catch(() => [] as string[]),\n\t\t);\n\t\tconst packNames = parsePackDirectory(entries)\n\t\t\t.filter((file) => file.kind === \"pack\")\n\t\t\t.map((file) => file.name);\n\t\tconst packName = packNames[0];\n\t\tif (packNames.length === 1 && packName !== undefined) {\n\t\t\tconst packData = await runStep(\n\t\t\t\thooks,\n\t\t\t\t\"read consolidated pack (fast path)\",\n\t\t\t\t() => rawFs(repo).readFile(`${packDirPath}/${packName}`),\n\t\t\t);\n\t\t\tconst bytes =\n\t\t\t\tpackData instanceof Uint8Array\n\t\t\t\t\t? packData\n\t\t\t\t\t: new TextEncoder().encode(packData as string);\n\t\t\treturn {\n\t\t\t\tstatus: 200,\n\t\t\t\theaders: {\n\t\t\t\t\t\"Content-Type\": \"application/x-git-upload-pack-result\",\n\t\t\t\t\t\"Cache-Control\": \"no-cache\",\n\t\t\t\t},\n\t\t\t\tbody: concat(pktLine(\"NAK\\n\"), sideBandPackfile(bytes)),\n\t\t\t};\n\t\t}\n\t}\n\n\tif (options?.beforeWalk) {\n\t\tawait runStep(hooks, \"beforeWalk\", options.beforeWalk);\n\t}\n\n\t// Walk haves first so the wants walk can stop at anything the client\n\t// already has, instead of re-walking all shared history a second time.\n\tlet haveSet: ReadonlySet<string> | undefined;\n\tif (haves.length > 0) {\n\t\tconst { oids: haveOids } = await runStep(\n\t\t\thooks,\n\t\t\t\"collectReachableOids(haves)\",\n\t\t\t() =>\n\t\t\t\tcollectReachableOids(repo, haves, hooks, undefined, {\n\t\t\t\t\tverifyBlobs: false,\n\t\t\t\t}),\n\t\t);\n\t\thaveSet = new Set(haveOids);\n\t}\n\n\tconst { oids: wantOids } = await runStep(\n\t\thooks,\n\t\t\"collectReachableOids(wants)\",\n\t\t() =>\n\t\t\tcollectReachableOids(repo, wants, hooks, haveSet, {\n\t\t\t\tverifyBlobs: false,\n\t\t\t}),\n\t);\n\t// Boundary oids are recorded as seen by the walk; drop them from the pack.\n\tconst oids = haveSet ? wantOids.filter((oid) => !haveSet.has(oid)) : wantOids;\n\n\t// Copy stored (already compressed, possibly deltified) entries when every\n\t// object is packed; build from scratch only when some aren't.\n\tconst reused = await runStep(\n\t\thooks,\n\t\t`reuse pack entries (${oids.length} oids)`,\n\t\t() => buildPackFromExistingPacks(repo, oids),\n\t);\n\tconst packBytes =\n\t\treused ??\n\t\t(await runStep(hooks, `packObjects (${oids.length} oids)`, async () => {\n\t\t\tconst { packfile } = await git.packObjects({ ...repo, oids });\n\t\t\treturn packfile instanceof Uint8Array\n\t\t\t\t? packfile\n\t\t\t\t: new Uint8Array(packfile ?? []);\n\t\t}));\n\n\treturn {\n\t\tstatus: 200,\n\t\theaders: {\n\t\t\t\"Content-Type\": \"application/x-git-upload-pack-result\",\n\t\t\t\"Cache-Control\": \"no-cache\",\n\t\t},\n\t\tbody: concat(pktLine(\"NAK\\n\"), sideBandPackfile(packBytes)),\n\t};\n}\n"],"mappings":"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","names":["git","p","git","git","git","git","packName","git","ZERO_OID","git"]}
package/dist/ops.js CHANGED
@@ -5,10 +5,11 @@ import {
5
5
  deleteBranchByName,
6
6
  listBranches,
7
7
  resolveLooseRefFast
8
- } from "./chunk-2JP4MS6H.js";
8
+ } from "./chunk-XNSZTA4Y.js";
9
9
  import {
10
10
  GitObjectNotFoundError,
11
11
  GitPathNotFoundError,
12
+ GitRefNotFoundError,
12
13
  decodeUtf8,
13
14
  hasNullByte,
14
15
  qualifyBranchRef,
@@ -219,7 +220,12 @@ function wrapMissingObject(promise, context) {
219
220
  }
220
221
  var isNotFound = (err) => err?.code === "NotFoundError";
221
222
  async function resolveCommit(repo, ref) {
222
- const oid = await git3.resolveRef({ ...repo, ref: qualifyBranchRef(ref) });
223
+ const oid = await git3.resolveRef({ ...repo, ref: qualifyBranchRef(ref) }).catch((err) => {
224
+ if (isNotFound(err)) {
225
+ throw new GitRefNotFoundError(`Ref ${ref} does not exist`);
226
+ }
227
+ throw err;
228
+ });
223
229
  const result = await wrapMissingObject(
224
230
  git3.readCommit({ ...repo, oid }),
225
231
  `${repo.gitdir} commit ${oid}`
@@ -233,7 +239,7 @@ async function getBlob(repo, sha) {
233
239
  );
234
240
  return blob;
235
241
  }
236
- async function getFileContent(repo, filePath, ref = "main", hooks) {
242
+ async function getFileContent(repo, filePath, ref, hooks) {
237
243
  if (hooks?.prefetch) {
238
244
  await runStep(hooks, "prefetch", hooks.prefetch);
239
245
  }
@@ -263,8 +269,8 @@ function isFullyWalked(commits) {
263
269
  const last = commits[commits.length - 1];
264
270
  return !!last && last.commit.parent.length === 0;
265
271
  }
266
- async function getCommitLog(repo, options = {}, hooks) {
267
- const ref = options.ref ?? "main";
272
+ async function getCommitLog(repo, options, hooks) {
273
+ const { ref } = options;
268
274
  const depth = options.depth ?? 50;
269
275
  let headSha;
270
276
  if (options.knownHeadSha) {
@@ -318,19 +324,10 @@ async function getFileFromRef(repo, filePath, ref, hooks) {
318
324
  isBinary
319
325
  };
320
326
  }
321
- async function getTreeFromRef(repo, options = {}, hooks) {
322
- const ref = options.ref ?? "main";
327
+ async function getTreeFromRef(repo, options, hooks) {
328
+ const { ref } = options;
323
329
  const treePath = options.treePath ?? "";
324
- let commit;
325
- let headSha;
326
- try {
327
- const resolved = await resolveCommit(repo, ref);
328
- commit = resolved.commit;
329
- headSha = resolved.oid;
330
- } catch (err) {
331
- if (isNotFound(err)) return [];
332
- throw err;
333
- }
330
+ const { commit, oid: headSha } = await resolveCommit(repo, ref);
334
331
  const cacheKey = `tree:${repo.gitdir}:${headSha}:${treePath}`;
335
332
  const cached = hooks?.resultCache?.get(cacheKey);
336
333
  if (cached) {
package/dist/ops.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/ops/commit.ts","../src/ops/tree.ts","../src/ops/diff.ts","../src/ops/history.ts","../src/ops/types.ts","../src/ops/file-history.ts","../src/ops/last-commit.ts","../src/ops/merge.ts"],"sourcesContent":["import git from \"isomorphic-git\";\nimport { assertSafeBranchName } from \"./branch.js\";\nimport { deleteFromTree, upsertTree } from \"./tree.js\";\nimport type { Repo } from \"./types.js\";\n\nexport interface CommitAuthor {\n\tname: string;\n\temail: string;\n\ttimestamp: number;\n\ttimezoneOffset: number;\n}\n\n/** An author stamped with the current time. */\nexport function authorNow(name: string, email: string): CommitAuthor {\n\treturn {\n\t\tname,\n\t\temail,\n\t\ttimestamp: Math.floor(Date.now() / 1000),\n\t\ttimezoneOffset: 0,\n\t};\n}\n\n/**\n * Write a commit directly to a bare repository — no worktree, no checkout.\n * `buildTree` receives the parent commit's tree oid (undefined on an empty\n * repo / unborn branch) and returns the new root tree oid; this function\n * writes the commit object and force-updates `refs/heads/<branch>`.\n *\n * Serialize concurrent writers externally (a per-repo lock): the\n * resolve-ref → write-ref sequence is not atomic on object storage.\n */\nexport async function writeCommitToBare(\n\trepo: Repo,\n\toptions: {\n\t\tbranch: string;\n\t\tmessage: string;\n\t\tauthor: CommitAuthor;\n\t\tbuildTree: (parentTreeOid: string | undefined) => Promise<string>;\n\t},\n): Promise<string> {\n\tassertSafeBranchName(options.branch);\n\tlet parentOid: string | undefined;\n\tlet parentTreeOid: string | undefined;\n\ttry {\n\t\tparentOid = await git.resolveRef({\n\t\t\t...repo,\n\t\t\tref: `refs/heads/${options.branch}`,\n\t\t});\n\t\tconst { commit } = await git.readCommit({ ...repo, oid: parentOid });\n\t\tparentTreeOid = commit.tree;\n\t} catch (err) {\n\t\tif ((err as { code?: string })?.code !== \"NotFoundError\") {\n\t\t\tthrow err;\n\t\t}\n\t\t// empty repo — first commit\n\t}\n\tconst treeOid = await options.buildTree(parentTreeOid);\n\tconst commitOid = await git.writeCommit({\n\t\t...repo,\n\t\tcommit: {\n\t\t\tmessage: options.message,\n\t\t\ttree: treeOid,\n\t\t\tparent: parentOid ? [parentOid] : [],\n\t\t\tauthor: options.author,\n\t\t\tcommitter: options.author,\n\t\t},\n\t});\n\tawait git.writeRef({\n\t\t...repo,\n\t\tref: `refs/heads/${options.branch}`,\n\t\tvalue: commitOid,\n\t\tforce: true,\n\t});\n\treturn commitOid;\n}\n\n/**\n * Commit a set of files onto a branch, straight to the bare repo. Each blob\n * is written to its own content-addressed key — no shared state between\n * files, so they're written in parallel.\n */\nexport function commitFilesToBare(\n\trepo: Repo,\n\toptions: {\n\t\tbranch: string;\n\t\tmessage: string;\n\t\tauthor: CommitAuthor;\n\t\tfiles: Array<{ path: string; content: string | Uint8Array }>;\n\t},\n): Promise<string> {\n\treturn writeCommitToBare(repo, {\n\t\tbranch: options.branch,\n\t\tmessage: options.message,\n\t\tauthor: options.author,\n\t\tbuildTree: async (parentTreeOid) => {\n\t\t\tconst blobs = new Map<string, string>();\n\t\t\tawait Promise.all(\n\t\t\t\toptions.files.map(async (file) => {\n\t\t\t\t\tconst content =\n\t\t\t\t\t\ttypeof file.content === \"string\"\n\t\t\t\t\t\t\t? new TextEncoder().encode(file.content)\n\t\t\t\t\t\t\t: file.content;\n\t\t\t\t\tconst oid = await git.writeBlob({ ...repo, blob: content });\n\t\t\t\t\tblobs.set(file.path, oid);\n\t\t\t\t}),\n\t\t\t);\n\t\t\treturn upsertTree(repo, parentTreeOid, blobs);\n\t\t},\n\t});\n}\n\n/** Commit the removal of one file from a branch, straight to the bare repo. */\nexport function deleteFileFromBare(\n\trepo: Repo,\n\toptions: {\n\t\tbranch: string;\n\t\tfilePath: string;\n\t\tmessage: string;\n\t\tauthor: CommitAuthor;\n\t},\n): Promise<string> {\n\treturn writeCommitToBare(repo, {\n\t\tbranch: options.branch,\n\t\tmessage: options.message,\n\t\tauthor: options.author,\n\t\tbuildTree: async (parentTreeOid) => {\n\t\t\tif (!parentTreeOid) {\n\t\t\t\tthrow new Error(`Branch ${options.branch} is empty`);\n\t\t\t}\n\t\t\treturn deleteFromTree(repo, parentTreeOid, options.filePath);\n\t\t},\n\t});\n}\n","import git from \"isomorphic-git\";\nimport type { Repo } from \"./types.js\";\n\nexport interface TreeEntry {\n\tpath: string;\n\tmode: string;\n\ttype: \"blob\" | \"tree\";\n\toid: string;\n\tsize?: number;\n}\n\nconst joinPath = (prefix: string, name: string) =>\n\tprefix ? `${prefix.replace(/\\/+$/, \"\")}/${name}` : name;\n\n/**\n * Build/update a git tree by overlaying new blobs onto an existing tree,\n * returning the new root tree oid. `entries` maps relative paths to blob oids.\n */\nexport async function upsertTree(\n\trepo: Repo,\n\ttreeOid: string | undefined,\n\tentries: Map<string, string>,\n): Promise<string> {\n\tconst existing = treeOid\n\t\t? (await git.readTree({ ...repo, oid: treeOid })).tree\n\t\t: [];\n\tconst byName = new Map(existing.map((e) => [e.path, e]));\n\tconst direct = new Map<string, string>();\n\tconst nested = new Map<string, Map<string, string>>();\n\tfor (const [filePath, blobOid] of entries) {\n\t\tconst slash = filePath.indexOf(\"/\");\n\t\tif (slash === -1) {\n\t\t\tdirect.set(filePath, blobOid);\n\t\t} else {\n\t\t\tconst dir = filePath.slice(0, slash);\n\t\t\tconst rest = filePath.slice(slash + 1);\n\t\t\tif (!nested.has(dir)) nested.set(dir, new Map());\n\t\t\tnested.get(dir)?.set(rest, blobOid);\n\t\t}\n\t}\n\tfor (const [name, blobOid] of direct) {\n\t\tbyName.set(name, {\n\t\t\tmode: \"100644\",\n\t\t\tpath: name,\n\t\t\toid: blobOid,\n\t\t\ttype: \"blob\",\n\t\t});\n\t}\n\t// Sibling subdirectories are independent subtree writes — no reason to\n\t// serialize them for multi-file commits touching several directories.\n\tconst nestedResults = await Promise.all(\n\t\tArray.from(nested, async ([dir, subEntries]) => {\n\t\t\tconst entry = byName.get(dir);\n\t\t\tconst subtreeOid = entry?.type === \"tree\" ? entry.oid : undefined;\n\t\t\tconst newOid = await upsertTree(repo, subtreeOid, subEntries);\n\t\t\treturn [dir, newOid] as const;\n\t\t}),\n\t);\n\tfor (const [dir, newOid] of nestedResults) {\n\t\tbyName.set(dir, { mode: \"040000\", path: dir, oid: newOid, type: \"tree\" });\n\t}\n\treturn git.writeTree({ ...repo, tree: Array.from(byName.values()) });\n}\n\n/** Remove a file path from a tree, returning the new root tree oid. */\nexport async function deleteFromTree(\n\trepo: Repo,\n\ttreeOid: string,\n\tfilePath: string,\n): Promise<string> {\n\tconst existing = (await git.readTree({ ...repo, oid: treeOid })).tree;\n\tconst byName = new Map(existing.map((e) => [e.path, e]));\n\tconst slash = filePath.indexOf(\"/\");\n\tif (slash === -1) {\n\t\tbyName.delete(filePath);\n\t} else {\n\t\tconst dir = filePath.slice(0, slash);\n\t\tconst rest = filePath.slice(slash + 1);\n\t\tconst entry = byName.get(dir);\n\t\tif (entry?.type === \"tree\") {\n\t\t\tconst newOid = await deleteFromTree(repo, entry.oid, rest);\n\t\t\tbyName.set(dir, { ...entry, oid: newOid });\n\t\t}\n\t}\n\treturn git.writeTree({ ...repo, tree: Array.from(byName.values()) });\n}\n\n/** Resolve a path inside a tree to its entry, or null when absent. */\nexport async function findTreeEntry(\n\trepo: Repo,\n\trootTreeOid: string,\n\ttreePath: string,\n): Promise<TreeEntry | null> {\n\tif (!treePath) {\n\t\treturn { path: \"\", mode: \"040000\", type: \"tree\", oid: rootTreeOid };\n\t}\n\n\tconst parts = treePath.split(\"/\").filter(Boolean);\n\tlet currentTreeOid = rootTreeOid;\n\tlet currentPath = \"\";\n\n\tfor (const [index, part] of parts.entries()) {\n\t\tconst tree = await git.readTree({ ...repo, oid: currentTreeOid });\n\t\tconst entry = tree.tree.find((candidate) => candidate.path === part);\n\n\t\tif (!entry) return null;\n\n\t\tcurrentPath = currentPath ? joinPath(currentPath, entry.path) : entry.path;\n\n\t\tif (index === parts.length - 1) {\n\t\t\treturn {\n\t\t\t\tpath: currentPath,\n\t\t\t\tmode: entry.mode,\n\t\t\t\ttype: entry.type as \"blob\" | \"tree\",\n\t\t\t\toid: entry.oid,\n\t\t\t};\n\t\t}\n\n\t\tif (entry.type !== \"tree\") return null;\n\n\t\tcurrentTreeOid = entry.oid;\n\t}\n\n\treturn null;\n}\n\n/** List a tree's direct entries, with paths prefixed by `prefix`. */\nexport async function listTreeEntries(\n\trepo: Repo,\n\ttreeOid: string,\n\tprefix = \"\",\n): Promise<TreeEntry[]> {\n\tconst tree = await git.readTree({ ...repo, oid: treeOid });\n\n\treturn tree.tree.map((entry) => ({\n\t\tpath: prefix ? joinPath(prefix, entry.path) : entry.path,\n\t\tmode: entry.mode,\n\t\ttype: entry.type as \"blob\" | \"tree\",\n\t\toid: entry.oid,\n\t}));\n}\n","import { createTwoFilesPatch } from \"diff\";\nimport git from \"isomorphic-git\";\nimport { readBlobContent, toBase64 } from \"../edge-utils.js\";\nimport { qualifyBranchRef } from \"../refs.js\";\nimport { getCommit } from \"./history.js\";\nimport type { Repo } from \"./types.js\";\n\nexport interface DiffFile {\n\tpath: string;\n\tstatus: \"added\" | \"modified\" | \"deleted\" | \"renamed\";\n\tadditions: number;\n\tdeletions: number;\n\tpatch: string;\n\toldPath?: string;\n\tisBinary?: boolean;\n\toldContent?: string;\n\tnewContent?: string;\n\toldSize?: number;\n\tnewSize?: number;\n}\n\nexport interface DiffResult {\n\tfiles: DiffFile[];\n\ttotalAdditions: number;\n\ttotalDeletions: number;\n\ttotalFiles: number;\n}\n\n/** Binary detection via null-byte heuristic. */\nfunction detectBlobContent(blob: Uint8Array): {\n\tisBinary: boolean;\n\ttext: string;\n\tbytes: Uint8Array;\n} {\n\treturn readBlobContent(blob);\n}\n\nfunction countContentLines(content: string): number {\n\tif (content.length === 0) return 0;\n\tconst lines = content.split(\"\\n\");\n\tif (lines[lines.length - 1] === \"\") lines.pop();\n\treturn lines.length;\n}\n\nfunction createUnifiedPatch(params: {\n\tpath: string;\n\tbefore: string;\n\tafter: string;\n\toldPath?: string;\n\tnewPath?: string;\n}): string {\n\tconst oldPath = params.oldPath ?? `a/${params.path}`;\n\tconst newPath = params.newPath ?? `b/${params.path}`;\n\tconst patchBody = createTwoFilesPatch(\n\t\toldPath,\n\t\tnewPath,\n\t\tparams.before,\n\t\tparams.after,\n\t\t\"\",\n\t\t\"\",\n\t\t{ context: 3 },\n\t).replace(/^=+\\n/, \"\");\n\n\treturn `diff --git a/${params.path} b/${params.path}\\n${patchBody}`;\n}\n\nfunction summarizeDiff(files: DiffFile[]): DiffResult {\n\treturn {\n\t\tfiles,\n\t\ttotalAdditions: files.reduce((sum, f) => sum + f.additions, 0),\n\t\ttotalDeletions: files.reduce((sum, f) => sum + f.deletions, 0),\n\t\ttotalFiles: files.length,\n\t};\n}\n\ntype BlobContent = { isBinary: boolean; text: string; bytes: Uint8Array };\n\n/**\n * Build one `DiffFile` from a path's before/after content — `before: null`\n * for an added file, `after: null` for a deleted one, both present for a\n * modification. The shared core of walkTreeDiff's three branches and\n * getCommitDiff's root-commit case (every file is an addition there, with no\n * two-tree walk to drive it), which each used to duplicate this field\n * construction independently.\n */\nfunction buildDiffFile(\n\tpath: string,\n\tstatus: DiffFile[\"status\"],\n\tbefore: BlobContent | null,\n\tafter: BlobContent | null,\n): DiffFile {\n\tconst isBinary = !!(before?.isBinary || after?.isBinary);\n\treturn {\n\t\tpath,\n\t\tstatus,\n\t\tadditions: !isBinary && after ? countContentLines(after.text) : 0,\n\t\tdeletions: !isBinary && before ? countContentLines(before.text) : 0,\n\t\tpatch: isBinary\n\t\t\t? \"\"\n\t\t\t: createUnifiedPatch({\n\t\t\t\t\tpath,\n\t\t\t\t\tbefore: before?.text ?? \"\",\n\t\t\t\t\tafter: after?.text ?? \"\",\n\t\t\t\t\toldPath: before ? undefined : \"/dev/null\",\n\t\t\t\t\tnewPath: after ? undefined : \"/dev/null\",\n\t\t\t\t}),\n\t\tisBinary,\n\t\toldContent: before && isBinary ? toBase64(before.bytes) : undefined,\n\t\tnewContent: after && isBinary ? toBase64(after.bytes) : undefined,\n\t\toldSize: before?.bytes.length,\n\t\tnewSize: after?.bytes.length,\n\t};\n}\n\n/**\n * Walk two trees (oldOid -> newOid) and return one DiffFile per changed path\n * — the shared core of both {@link getCommitDiff} (parent -> commit) and\n * {@link getDiffBetweenRefs} (base -> compare).\n */\nasync function walkTreeDiff(\n\trepo: Repo,\n\toldOid: string,\n\tnewOid: string,\n): Promise<DiffFile[]> {\n\tconst changes = await git.walk({\n\t\t...repo,\n\t\ttrees: [git.TREE({ ref: oldOid }), git.TREE({ ref: newOid })],\n\t\tmap: async (filepath, [A, B]) => {\n\t\t\tconst [typeA, typeB] = await Promise.all([A?.type(), B?.type()]);\n\n\t\t\tif (typeA === \"tree\" || typeB === \"tree\") return;\n\n\t\t\tif (typeA && !typeB) {\n\t\t\t\tconst oidA = A ? await A.oid() : \"\";\n\t\t\t\tconst { blob } = await git.readBlob({ ...repo, oid: oidA });\n\t\t\t\tconst before = detectBlobContent(blob);\n\t\t\t\treturn buildDiffFile(filepath, \"deleted\", before, null);\n\t\t\t}\n\n\t\t\tif (!typeA && typeB) {\n\t\t\t\tconst oidB = B ? await B.oid() : \"\";\n\t\t\t\tconst { blob } = await git.readBlob({ ...repo, oid: oidB });\n\t\t\t\tconst after = detectBlobContent(blob);\n\t\t\t\treturn buildDiffFile(filepath, \"added\", null, after);\n\t\t\t}\n\n\t\t\tconst [oidA, oidB] = await Promise.all([\n\t\t\t\tA ? A.oid() : Promise.resolve(\"\"),\n\t\t\t\tB ? B.oid() : Promise.resolve(\"\"),\n\t\t\t]);\n\n\t\t\tif (oidA !== oidB) {\n\t\t\t\tconst [{ blob: blobA }, { blob: blobB }] = await Promise.all([\n\t\t\t\t\tgit.readBlob({ ...repo, oid: oidA }),\n\t\t\t\t\tgit.readBlob({ ...repo, oid: oidB }),\n\t\t\t\t]);\n\t\t\t\tconst before = detectBlobContent(blobA);\n\t\t\t\tconst after = detectBlobContent(blobB);\n\t\t\t\treturn buildDiffFile(filepath, \"modified\", before, after);\n\t\t\t}\n\n\t\t\treturn null;\n\t\t},\n\t});\n\n\treturn (changes ?? []).filter(\n\t\t(c: DiffFile | null | undefined): c is DiffFile =>\n\t\t\tc !== null && c !== undefined,\n\t);\n}\n\n/** The diff a single commit introduced (against its first parent). */\nexport async function getCommitDiff(\n\trepo: Repo,\n\tcommitSha: string,\n): Promise<DiffResult> {\n\ttry {\n\t\tconst commit = await getCommit(repo, commitSha);\n\t\tconst parent = commit.commit.parent[0];\n\n\t\tif (!parent) {\n\t\t\tconst entries: { path: string; oid: string }[] = [];\n\t\t\tconst stack: { treeOid: string; prefix: string }[] = [\n\t\t\t\t{ treeOid: commit.commit.tree, prefix: \"\" },\n\t\t\t];\n\n\t\t\twhile (stack.length) {\n\t\t\t\tconst { treeOid, prefix } = stack.pop() as {\n\t\t\t\t\ttreeOid: string;\n\t\t\t\t\tprefix: string;\n\t\t\t\t};\n\t\t\t\tconst { tree } = await git.readTree({ ...repo, oid: treeOid });\n\t\t\t\tfor (const entry of tree) {\n\t\t\t\t\tconst full = prefix ? `${prefix}/${entry.path}` : entry.path;\n\t\t\t\t\tif (entry.type === \"tree\") {\n\t\t\t\t\t\tstack.push({ treeOid: entry.oid, prefix: full });\n\t\t\t\t\t} else if (entry.type === \"blob\") {\n\t\t\t\t\t\tentries.push({ path: full, oid: entry.oid });\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst files: DiffFile[] = await Promise.all(\n\t\t\t\tentries.map(async ({ path, oid }) => {\n\t\t\t\t\tconst { blob } = await git.readBlob({ ...repo, oid });\n\t\t\t\t\tconst after = detectBlobContent(blob);\n\t\t\t\t\treturn buildDiffFile(path, \"added\", null, after);\n\t\t\t\t}),\n\t\t\t);\n\n\t\t\treturn summarizeDiff(files);\n\t\t}\n\n\t\tconst files = await walkTreeDiff(repo, parent, commitSha);\n\t\treturn summarizeDiff(files);\n\t} catch (error) {\n\t\tthrow new Error(`Failed to get commit diff: ${error}`);\n\t}\n}\n\n/** The diff between two refs (base -> compare). */\nexport async function getDiffBetweenRefs(\n\trepo: Repo,\n\tbaseRef: string,\n\tcompareRef: string,\n): Promise<DiffResult> {\n\tconst [baseOid, compareOid] = await Promise.all([\n\t\tgit.resolveRef({ ...repo, ref: qualifyBranchRef(baseRef) }),\n\t\tgit.resolveRef({ ...repo, ref: qualifyBranchRef(compareRef) }),\n\t]);\n\n\tconst files = await walkTreeDiff(repo, baseOid, compareOid);\n\treturn summarizeDiff(files);\n}\n","import git from \"isomorphic-git\";\nimport { decodeUtf8, hasNullByte, toBase64 } from \"../edge-utils.js\";\nimport { GitObjectNotFoundError, GitPathNotFoundError } from \"../git-errors.js\";\nimport { qualifyBranchRef } from \"../refs.js\";\nimport { findTreeEntry, listTreeEntries, type TreeEntry } from \"./tree.js\";\nimport { type OpsHooks, type Repo, runStep } from \"./types.js\";\n\n/**\n * A resolved ref (branch/commit) not existing is a normal, expected condition\n * (empty repo, unborn branch) and is handled by each caller. An object that\n * fails to resolve *underneath* an already-resolved ref (a tree/blob the\n * stored pack doesn't actually contain) means the repo's storage is\n * inconsistent — surface that distinctly so callers don't render it as\n * \"empty\" and clients don't see a raw isomorphic-git NotFoundError.\n */\nfunction wrapMissingObject<T>(\n\tpromise: Promise<T>,\n\tcontext: string,\n): Promise<T> {\n\treturn promise.catch((err: unknown) => {\n\t\tif ((err as { code?: string })?.code === \"NotFoundError\") {\n\t\t\tthrow new GitObjectNotFoundError(\n\t\t\t\t`Git data for ${context} is missing from storage. The repository may need to be re-pushed to repair it.`,\n\t\t\t);\n\t\t}\n\t\tthrow err;\n\t});\n}\n\nconst isNotFound = (err: unknown) =>\n\t(err as { code?: string })?.code === \"NotFoundError\";\n\nexport interface CommitInfo {\n\toid: string;\n\tcommit: {\n\t\tmessage: string;\n\t\ttree: string;\n\t\tparent: string[];\n\t\tauthor: {\n\t\t\tname: string;\n\t\t\temail: string;\n\t\t\ttimestamp: number;\n\t\t\ttimezoneOffset: number;\n\t\t};\n\t\tcommitter: {\n\t\t\tname: string;\n\t\t\temail: string;\n\t\t\ttimestamp: number;\n\t\t\ttimezoneOffset: number;\n\t\t};\n\t};\n\tpayload: string;\n}\n\n/**\n * Resolve a branch name / full ref / sha to its commit. The ref not\n * resolving (unborn branch, genuinely empty repo) and the ref resolving but\n * its commit object being unreadable (storage inconsistency — see\n * `wrapMissingObject` above) are different failures with different meanings,\n * so only the first is left as a raw isomorphic-git NotFoundError for\n * callers to treat as \"empty\"; the second is wrapped into\n * GitObjectNotFoundError specifically so it can't be mistaken for the first\n * by an `isNotFound`-style check downstream (see getTreeFromRef/getCommitLog).\n */\nexport async function resolveCommit(repo: Repo, ref: string) {\n\tconst oid = await git.resolveRef({ ...repo, ref: qualifyBranchRef(ref) });\n\tconst result = await wrapMissingObject(\n\t\tgit.readCommit({ ...repo, oid }),\n\t\t`${repo.gitdir} commit ${oid}`,\n\t);\n\treturn { oid, commit: result.commit };\n}\n\n/** Read a blob's bytes by oid. */\nexport async function getBlob(repo: Repo, sha: string): Promise<Uint8Array> {\n\tconst { blob } = await wrapMissingObject(\n\t\tgit.readBlob({ ...repo, oid: sha }),\n\t\t`${repo.gitdir} blob ${sha}`,\n\t);\n\treturn blob;\n}\n\n/** Read a file's bytes at a ref. Throws GitPathNotFoundError when absent. */\nexport async function getFileContent(\n\trepo: Repo,\n\tfilePath: string,\n\tref = \"main\",\n\thooks?: OpsHooks,\n): Promise<Uint8Array> {\n\t// A blob page needs a commit, one or more tree objects, and the blob. On a\n\t// multi-pack object store, letting isomorphic-git discover those packs one\n\t// at a time turns that otherwise short read into a serial network walk.\n\t// Warm every pack before resolving the ref so every later object lookup can\n\t// use the local parse cache. This is the same structural optimization used\n\t// for tree and history reads, now applied to file reads as well.\n\tif (hooks?.prefetch) {\n\t\tawait runStep(hooks, \"prefetch\", hooks.prefetch);\n\t}\n\tconst { commit } = await resolveCommit(repo, ref);\n\tconst context = `${repo.gitdir}@${ref}:${filePath}`;\n\tconst entry = await wrapMissingObject(\n\t\tfindTreeEntry(repo, commit.tree, filePath),\n\t\tcontext,\n\t);\n\n\tif (entry?.type !== \"blob\") {\n\t\tthrow new GitPathNotFoundError(`File not found: ${filePath}`);\n\t}\n\n\tconst { blob } = await wrapMissingObject(\n\t\tgit.readBlob({ ...repo, oid: entry.oid }),\n\t\tcontext,\n\t);\n\treturn blob;\n}\n\n/** Read one commit by sha. */\nexport async function getCommit(repo: Repo, sha: string): Promise<CommitInfo> {\n\tconst result = await wrapMissingObject(\n\t\tgit.readCommit({ ...repo, oid: sha }),\n\t\t`${repo.gitdir} commit ${sha}`,\n\t);\n\treturn { oid: result.oid, commit: result.commit, payload: result.payload };\n}\n\nfunction isFullyWalked(commits: CommitInfo[]): boolean {\n\tconst last = commits[commits.length - 1];\n\treturn !!last && last.commit.parent.length === 0;\n}\n\nexport interface CommitLogOptions {\n\tref?: string;\n\tdepth?: number;\n\t/**\n\t * Pass the head sha when the caller already resolved `ref` — resolveRef\n\t * tries several candidate paths in sequence and misses the first few every\n\t * time for a normal branch name, which is pure waste when the sha is known.\n\t */\n\tknownHeadSha?: string;\n}\n\n/**\n * The commit chain from a ref, newest first. Walking is inherently sequential\n * (each commit's oid is only discoverable by reading its child first) and\n * network-round-trip-bound against object storage, so the deepest walk seen\n * per head is memoized in `hooks.resultCache` and sliced for shallower or\n * repeated requests — don't bypass this by calling `git.log` directly.\n */\nexport async function getCommitLog(\n\trepo: Repo,\n\toptions: CommitLogOptions = {},\n\thooks?: OpsHooks,\n): Promise<CommitInfo[]> {\n\tconst ref = options.ref ?? \"main\";\n\tconst depth = options.depth ?? 50;\n\n\tlet headSha: string;\n\tif (options.knownHeadSha) {\n\t\theadSha = options.knownHeadSha;\n\t} else {\n\t\ttry {\n\t\t\theadSha = await git.resolveRef({ ...repo, ref: qualifyBranchRef(ref) });\n\t\t} catch (err: unknown) {\n\t\t\tif (isNotFound(err)) return [];\n\t\t\tthrow err;\n\t\t}\n\t}\n\n\tconst cacheKey = `commitlog:${repo.gitdir}:${headSha}`;\n\tconst cached = hooks?.resultCache?.get<CommitInfo[]>(cacheKey);\n\tif (cached && (cached.length >= depth || isFullyWalked(cached))) {\n\t\thooks?.onNote?.(\n\t\t\t`getCommitLog: result-cache HIT for ${cacheKey} (depth=${depth})`,\n\t\t);\n\t\treturn cached.slice(0, depth);\n\t}\n\thooks?.onNote?.(\n\t\t`getCommitLog: result-cache MISS for ${cacheKey} (depth=${depth})`,\n\t);\n\n\tif (hooks?.prefetch && depth >= (hooks.prefetchMinDepth ?? 5)) {\n\t\tawait runStep(hooks, \"prefetch\", hooks.prefetch);\n\t}\n\n\t// headSha is already known-good here (resolveRef above succeeded, or the\n\t// caller passed knownHeadSha) — a NotFoundError from the walk itself means\n\t// a commit/tree/blob it needs is missing from storage, not that the ref\n\t// doesn't exist. Unlike the resolveRef catch above, that's not \"empty\",\n\t// it's storage inconsistency, so wrapMissingObject turns it into a\n\t// GitObjectNotFoundError instead of silently returning [] — an empty\n\t// result here would otherwise get treated as \"this branch has no\n\t// history\" by every caller (and, worse, potentially cached as such).\n\tconst commits = await wrapMissingObject(\n\t\trunStep(hooks, `git.log ${ref} depth=${depth}`, () =>\n\t\t\tgit.log({ ...repo, ref: headSha, depth }),\n\t\t),\n\t\t`${repo.gitdir}@${ref} history`,\n\t);\n\tconst result = commits.map((commit) => ({\n\t\toid: commit.oid,\n\t\tcommit: commit.commit,\n\t\tpayload: commit.payload || \"\",\n\t}));\n\tif (!cached || result.length > cached.length) {\n\t\thooks?.resultCache?.set(cacheKey, result);\n\t}\n\treturn result;\n}\n\n/** A file at a ref, decoded for display: utf8 text or base64 when binary. */\nexport async function getFileFromRef(\n\trepo: Repo,\n\tfilePath: string,\n\tref: string,\n\thooks?: OpsHooks,\n): Promise<{ content: string; size: number; isBinary: boolean }> {\n\tconst bytes = await getFileContent(repo, filePath, ref, hooks);\n\tconst isBinary = hasNullByte(bytes);\n\n\treturn {\n\t\tcontent: isBinary ? toBase64(bytes) : decodeUtf8(bytes),\n\t\tsize: bytes.length,\n\t\tisBinary,\n\t};\n}\n\n/**\n * List a directory at the tip of a ref. Returns [] for an empty repo/unborn\n * branch; throws GitPathNotFoundError when `treePath` doesn't exist. Results\n * are memoized per head sha (auto-invalidates on push).\n */\nexport async function getTreeFromRef(\n\trepo: Repo,\n\toptions: { ref?: string; treePath?: string } = {},\n\thooks?: OpsHooks,\n): Promise<TreeEntry[]> {\n\tconst ref = options.ref ?? \"main\";\n\tconst treePath = options.treePath ?? \"\";\n\n\tlet commit: Awaited<ReturnType<typeof resolveCommit>>[\"commit\"];\n\tlet headSha: string;\n\ttry {\n\t\tconst resolved = await resolveCommit(repo, ref);\n\t\tcommit = resolved.commit;\n\t\theadSha = resolved.oid;\n\t} catch (err: unknown) {\n\t\tif (isNotFound(err)) return [];\n\t\tthrow err;\n\t}\n\n\tconst cacheKey = `tree:${repo.gitdir}:${headSha}:${treePath}`;\n\tconst cached = hooks?.resultCache?.get<TreeEntry[]>(cacheKey);\n\tif (cached) {\n\t\thooks?.onNote?.(`getTreeFromRef: result-cache HIT for ${cacheKey}`);\n\t\treturn cached;\n\t}\n\thooks?.onNote?.(`getTreeFromRef: result-cache MISS for ${cacheKey}`);\n\n\t// Unlike getCommitLog, a tree read has no \"depth\" to gate on — it always\n\t// needs at least the head commit's tree object, and (for a non-root path)\n\t// one object per path segment on top of that, so there's no shallow case\n\t// where prefetching every pack is wasted bandwidth the way a depth=1\n\t// commit-log walk can be. Without this, a cache-miss tree read falls\n\t// through to isomorphic-git's own pack resolution, which probes indexed\n\t// packs one at a time instead of warming them all in parallel up front —\n\t// this was previously the slowest of a tree page's parallel queries in\n\t// production for exactly that reason.\n\tif (hooks?.prefetch) {\n\t\tawait runStep(hooks, \"prefetch\", hooks.prefetch);\n\t}\n\n\tconst context = `${repo.gitdir}@${ref}:${treePath || \"/\"}`;\n\tlet result: TreeEntry[];\n\tif (!treePath) {\n\t\tresult = await wrapMissingObject(\n\t\t\trunStep(hooks, \"listTreeEntries (root)\", () =>\n\t\t\t\tlistTreeEntries(repo, commit.tree),\n\t\t\t),\n\t\t\tcontext,\n\t\t);\n\t} else {\n\t\tconst entry = await wrapMissingObject(\n\t\t\trunStep(hooks, `findTreeEntry ${treePath}`, () =>\n\t\t\t\tfindTreeEntry(repo, commit.tree, treePath),\n\t\t\t),\n\t\t\tcontext,\n\t\t);\n\t\tif (!entry) {\n\t\t\tthrow new GitPathNotFoundError(\n\t\t\t\t`Path \"${treePath}\" does not exist at ${ref}`,\n\t\t\t);\n\t\t}\n\t\tresult =\n\t\t\tentry.type !== \"tree\"\n\t\t\t\t? []\n\t\t\t\t: await wrapMissingObject(\n\t\t\t\t\t\trunStep(hooks, `listTreeEntries ${treePath}`, () =>\n\t\t\t\t\t\t\tlistTreeEntries(repo, entry.oid, entry.path),\n\t\t\t\t\t\t),\n\t\t\t\t\t\tcontext,\n\t\t\t\t\t);\n\t}\n\n\thooks?.resultCache?.set(cacheKey, result);\n\treturn result;\n}\n\n/**\n * A page of commit history from a branch tip. Memoized per head sha; builds\n * on {@link getCommitLog}'s walk cache for the underlying chain.\n */\nexport async function getCommitHistory(\n\trepo: Repo,\n\toptions: { ref: string; limit?: number; skip?: number },\n\thooks?: OpsHooks,\n): Promise<CommitInfo[]> {\n\tconst limit = options.limit ?? 50;\n\tconst skip = options.skip ?? 0;\n\tconst headSha = await git\n\t\t.resolveRef({ ...repo, ref: qualifyBranchRef(options.ref) })\n\t\t.catch(() => null);\n\n\tconst cacheKey = headSha\n\t\t? `commits:${repo.gitdir}:${headSha}:${limit}:${skip}`\n\t\t: null;\n\tif (cacheKey) {\n\t\tconst cached = hooks?.resultCache?.get<CommitInfo[]>(cacheKey);\n\t\tif (cached) {\n\t\t\thooks?.onNote?.(`getCommitHistory: result-cache HIT for ${cacheKey}`);\n\t\t\treturn cached;\n\t\t}\n\t}\n\thooks?.onNote?.(\n\t\t`getCommitHistory: result-cache MISS for ${cacheKey ?? \"(no head)\"}`,\n\t);\n\tif (!headSha) return [];\n\n\tconst all = await getCommitLog(\n\t\trepo,\n\t\t{ ref: options.ref, depth: limit + skip, knownHeadSha: headSha },\n\t\thooks,\n\t);\n\tconst result = all.slice(skip, skip + limit);\n\n\tif (cacheKey) hooks?.resultCache?.set(cacheKey, result);\n\treturn result;\n}\n","import type git from \"isomorphic-git\";\n\n/** The fs shape isomorphic-git accepts, derived from its own signatures. */\nexport type IsoGitFs = Parameters<typeof git.readTree>[0][\"fs\"];\n\n/**\n * A repository handle: everything isomorphic-git needs to address one bare\n * repo. Create it once per request (sharing `cache` across calls is what lets\n * isomorphic-git reuse parsed pack indexes) and pass it to every op.\n */\nexport interface Repo {\n\tfs: IsoGitFs;\n\tgitdir: string;\n\t/** isomorphic-git's shared parse cache — strongly recommended per repo. */\n\tcache?: object;\n}\n\n/**\n * Key/value store for memoizing expensive walk results (commit logs, tree\n * listings, per-file history). Keys are namespaced `kind:gitdir:headSha:…`,\n * so entries self-invalidate on push (new head, new key) — but evict entries\n * under {@link resultKeyPrefixes} after rewriting a repo's storage out of\n * band, or stale walks leak until your store's own eviction.\n */\nexport interface ResultCache {\n\tget<T>(key: string): T | null | undefined;\n\tset(key: string, value: unknown): void;\n}\n\n/** Optional instrumentation and tuning hooks accepted by every op. */\nexport interface OpsHooks {\n\tresultCache?: ResultCache;\n\t/** Wrap a timed sub-step (network walk, tree listing). Default: run directly. */\n\tstep?: <T>(label: string, fn: () => Promise<T>) => Promise<T>;\n\t/** Diagnostic sink for cache hit/miss and walk summaries. */\n\tonNote?: (message: string) => void;\n\t/**\n\t * Wire pack prefetching (e.g. `GitFs.prefetchPacks`) here so a sequential\n\t * walk doesn't pay one network round trip per commit. Called once before\n\t * history walks of depth >= `prefetchMinDepth` (see below), and\n\t * unconditionally on every cache-miss tree read ({@link getTreeFromRef}) —\n\t * a tree read has no shallow case, it always needs at least the head\n\t * commit's tree object.\n\t */\n\tprefetch?: () => Promise<void>;\n\t/** Minimum walk depth before `prefetch` fires for history walks. Default 5. */\n\tprefetchMinDepth?: number;\n}\n\nexport const runStep = <T>(\n\thooks: OpsHooks | undefined,\n\tlabel: string,\n\tfn: () => Promise<T>,\n): Promise<T> => (hooks?.step ? hooks.step(label, fn) : fn());\n\n/**\n * The result-cache key prefixes holding entries for `gitdir` — evict these\n * from your {@link ResultCache} when the repo's storage was rewritten outside\n * a normal push (bulk sync, rename, repack cleanup).\n */\nexport function resultKeyPrefixes(gitdir: string): string[] {\n\treturn [\n\t\t`commitlog:${gitdir}:`,\n\t\t`tree:${gitdir}:`,\n\t\t`commits:${gitdir}:`,\n\t\t`last-commits:${gitdir}:`,\n\t\t`file-history:${gitdir}:`,\n\t];\n}\n","import { type CommitInfo, getCommitLog } from \"./history.js\";\nimport { findTreeEntry } from \"./tree.js\";\nimport type { OpsHooks, Repo } from \"./types.js\";\nimport { runStep } from \"./types.js\";\n\nexport interface FileHistoryEntry {\n\tsha: string;\n\tmessage: string;\n\tauthorName: string;\n\tauthorEmail: string;\n\tcreatedAt: string;\n}\n\nexport interface FileHistoryResult {\n\tentries: FileHistoryEntry[];\n\t/**\n\t * True when the walk hit its depth budget (or the requested `limit`)\n\t * before exhausting the branch's full commit chain — there may be older\n\t * commits touching this file that a deeper walk would surface.\n\t */\n\ttruncated: boolean;\n}\n\n/**\n * Default walk bound for a caller that actually wants deep history (a file's\n * \"History\" tab). Walking the full chain is round-trip-bound on object\n * storage, so cap how far back a single request will look.\n */\nexport const HISTORY_WALK_DEPTH = 400;\n\n/**\n * Much shallower default for a \"latest commit touching this file\" banner that\n * only displays `entries[0]` — trades \"always finds the true last-touching\n * commit\" for \"finds it if it's reasonably recent\", the right call for a\n * banner with a full History view a click away.\n */\nexport const BANNER_WALK_DEPTH = 60;\n\n/** Tree reads are prefetched in parallel windows; see getLastCommitsForTree. */\nconst PREFETCH_WINDOW = 24;\n\nfunction toEntry(commit: CommitInfo): FileHistoryEntry {\n\treturn {\n\t\tsha: commit.oid,\n\t\tmessage: commit.commit.message.trim(),\n\t\tauthorName: commit.commit.author.name,\n\t\tauthorEmail: commit.commit.author.email,\n\t\tcreatedAt: new Date(commit.commit.author.timestamp * 1000).toISOString(),\n\t};\n}\n\n/**\n * All commits (newest first) that changed a single file's blob oid, walking\n * the first-parent chain — same approach as getLastCommitsForTree but for one\n * path and collecting every match instead of stopping at the first.\n */\nexport async function getFileHistory(\n\trepo: Repo,\n\toptions: {\n\t\tref: string;\n\t\tfilePath: string;\n\t\tlimit?: number;\n\t\tmaxDepth?: number;\n\t},\n\thooks?: OpsHooks,\n): Promise<FileHistoryResult> {\n\tconst limit = options.limit ?? 30;\n\tconst maxDepth = options.maxDepth ?? HISTORY_WALK_DEPTH;\n\tconst walkDepth = Math.max(maxDepth, limit);\n\tconst commits = await runStep(hooks, `getCommitLog depth=${walkDepth}`, () =>\n\t\tgetCommitLog(repo, { ref: options.ref, depth: walkDepth }, hooks),\n\t);\n\tconst head = commits[0];\n\tif (!head) return { entries: [], truncated: false };\n\n\tconst cacheKey = `file-history:${repo.gitdir}:${head.oid}:${options.filePath}:${limit}:${maxDepth}`;\n\tconst cached = hooks?.resultCache?.get<FileHistoryResult>(cacheKey);\n\tif (cached) {\n\t\thooks?.onNote?.(\"getFileHistory: result-cache HIT, skipping history walk\");\n\t\treturn cached;\n\t}\n\thooks?.onNote?.(\"getFileHistory: result-cache MISS, walking history\");\n\n\tconst byOid = new Map(commits.map((commit) => [commit.oid, commit]));\n\n\tconst oidByCommitTree = new Map<string, string | null>();\n\tasync function resolveOid(commitTreeOid: string): Promise<string | null> {\n\t\tconst cachedOid = oidByCommitTree.get(commitTreeOid);\n\t\tif (cachedOid !== undefined) return cachedOid;\n\t\tconst entry = await findTreeEntry(repo, commitTreeOid, options.filePath);\n\t\tconst oid = entry?.type === \"blob\" ? entry.oid : null;\n\t\toidByCommitTree.set(commitTreeOid, oid);\n\t\treturn oid;\n\t}\n\n\tconst entries: FileHistoryEntry[] = [];\n\tlet truncated = false;\n\n\touter: for (\n\t\tlet windowStart = 0;\n\t\twindowStart < commits.length;\n\t\twindowStart += PREFETCH_WINDOW\n\t) {\n\t\tconst windowEnd = Math.min(windowStart + PREFETCH_WINDOW, commits.length);\n\t\t// +1 lookahead so the last entry's parent tree is already warm too.\n\t\tconst prefetchEnd = Math.min(windowEnd + 1, commits.length);\n\n\t\tawait Promise.all(\n\t\t\tcommits\n\t\t\t\t.slice(windowStart, prefetchEnd)\n\t\t\t\t.map((commit) => resolveOid(commit.commit.tree)),\n\t\t);\n\n\t\tfor (let i = windowStart; i < windowEnd; i++) {\n\t\t\tconst commit = commits[i];\n\t\t\tif (!commit) break outer;\n\n\t\t\tconst parentSha = commit.commit.parent[0];\n\t\t\tconst parentCommit = parentSha ? byOid.get(parentSha) : undefined;\n\t\t\tif (parentSha && !parentCommit) {\n\t\t\t\t// Walked past the depth cap without reaching this commit's parent —\n\t\t\t\t// can't tell whether it changed the file; stop and report truncated.\n\t\t\t\ttruncated = true;\n\t\t\t\tbreak outer;\n\t\t\t}\n\n\t\t\tconst [oid, parentOid] = await Promise.all([\n\t\t\t\tresolveOid(commit.commit.tree),\n\t\t\t\tparentCommit\n\t\t\t\t\t? resolveOid(parentCommit.commit.tree)\n\t\t\t\t\t: Promise.resolve(null),\n\t\t\t]);\n\n\t\t\tif (oid !== parentOid) {\n\t\t\t\tentries.push(toEntry(commit));\n\t\t\t\tif (entries.length >= limit) {\n\t\t\t\t\ttruncated = i < commits.length - 1;\n\t\t\t\t\tbreak outer;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tconst result = { entries, truncated };\n\thooks?.resultCache?.set(cacheKey, result);\n\treturn result;\n}\n","import { type CommitInfo, getCommitLog } from \"./history.js\";\nimport { findTreeEntry, listTreeEntries } from \"./tree.js\";\nimport type { OpsHooks, Repo } from \"./types.js\";\nimport { runStep } from \"./types.js\";\n\nexport interface LastCommitInfo {\n\tsha: string;\n\tmessage: string;\n\tauthorName: string;\n\tauthorEmail: string;\n\tcreatedAt: string;\n}\n\n/**\n * Bounds how far back the history walk looks to resolve \"last commit touching\n * this path\" for a directory listing. Entries whose last change is older\n * simply show no last-commit info rather than paying an unbounded scan.\n */\nconst HISTORY_WALK_DEPTH = 400;\n\n/**\n * The walk itself is inherently sequential (each step needs to know what's\n * still \"remaining\" from the step before), but the tree-object reads that\n * back it are not — each commit's tree oid is already known upfront from the\n * commit log. Prefetching a window of tree reads in parallel turns ~1 network\n * round trip per commit (serialized) into ~1 round trip per window, which is\n * where nearly all of this function's wall-clock time goes on object storage.\n */\nconst PREFETCH_WINDOW = 24;\n\nfunction toLastCommitInfo(commit: CommitInfo): LastCommitInfo {\n\treturn {\n\t\tsha: commit.oid,\n\t\tmessage: commit.commit.message.trim(),\n\t\tauthorName: commit.commit.author.name,\n\t\tauthorEmail: commit.commit.author.email,\n\t\tcreatedAt: new Date(commit.commit.author.timestamp * 1000).toISOString(),\n\t};\n}\n\n/**\n * For each direct child of `treePath` (at the tip of `ref`), find the most\n * recent commit that changed it — the tree view's \"last commit\" column. Walks\n * history newest-to-oldest, comparing the directory's tree oid\n * commit-to-commit and only descending one level to diff child oids when\n * something under the directory actually changed. Preserve the two-phase\n * structure (parallel prefetch, then sequential resolve) when touching this —\n * the \"which entries are still unresolved\" state must advance\n * commit-by-commit.\n */\nexport async function getLastCommitsForTree(\n\trepo: Repo,\n\toptions: { ref: string; treePath?: string; depth?: number },\n\thooks?: OpsHooks,\n): Promise<Record<string, LastCommitInfo>> {\n\tconst treePath = options.treePath ?? \"\";\n\tconst depth = options.depth ?? HISTORY_WALK_DEPTH;\n\tconst commits = await runStep(hooks, `getCommitLog depth=${depth}`, () =>\n\t\tgetCommitLog(repo, { ref: options.ref, depth }, hooks),\n\t);\n\thooks?.onNote?.(`getLastCommitsForTree: ${commits.length} commits in log`);\n\tconst head = commits[0];\n\tif (!head) return {};\n\n\tconst cacheKey = `last-commits:${repo.gitdir}:${head.oid}:${treePath}`;\n\tconst cachedResult =\n\t\thooks?.resultCache?.get<Record<string, LastCommitInfo>>(cacheKey);\n\tif (cachedResult) {\n\t\thooks?.onNote?.(\n\t\t\t\"getLastCommitsForTree: result-cache HIT, skipping history walk\",\n\t\t);\n\t\treturn cachedResult;\n\t}\n\thooks?.onNote?.(\"getLastCommitsForTree: result-cache MISS, walking history\");\n\n\tconst byOid = new Map(commits.map((commit) => [commit.oid, commit]));\n\n\t// In a linear history, the \"parent tree\" resolved at commit[i] is the same\n\t// tree already resolved as the \"current tree\" at commit[i-1] — memoize by\n\t// commit-tree oid (and by resolved dir oid) so each distinct tree is only\n\t// walked/listed once across the whole scan instead of twice per commit.\n\tconst dirOidByCommitTree = new Map<string, string | null>();\n\tconst childrenByDirOid = new Map<\n\t\tstring,\n\t\tAwaited<ReturnType<typeof listTreeEntries>>\n\t>();\n\n\tasync function resolveDirOid(commitTreeOid: string): Promise<string | null> {\n\t\tconst cached = dirOidByCommitTree.get(commitTreeOid);\n\t\tif (cached !== undefined) return cached;\n\t\tconst entry = await findTreeEntry(repo, commitTreeOid, treePath);\n\t\tconst dirOid = entry?.type === \"tree\" ? entry.oid : null;\n\t\tdirOidByCommitTree.set(commitTreeOid, dirOid);\n\t\treturn dirOid;\n\t}\n\n\tasync function resolveChildren(dirOid: string | null) {\n\t\tif (dirOid === null) return [];\n\t\tconst cached = childrenByDirOid.get(dirOid);\n\t\tif (cached) return cached;\n\t\tconst children = await listTreeEntries(repo, dirOid, treePath);\n\t\tchildrenByDirOid.set(dirOid, children);\n\t\treturn children;\n\t}\n\n\tconst headDirOid = await resolveDirOid(head.commit.tree);\n\tif (headDirOid === null) return {};\n\n\t// listTreeEntries is prefixed with treePath so result keys match the full\n\t// paths that callers key their file listing by.\n\tconst headChildren = await resolveChildren(headDirOid);\n\tconst remaining = new Set(headChildren.map((entry) => entry.path));\n\tconst result: Record<string, LastCommitInfo> = {};\n\n\tlet commitsWalked = 0;\n\tlet prefetchWindows = 0;\n\tconst walkStart = performance.now();\n\n\touter: for (\n\t\tlet windowStart = 0;\n\t\twindowStart < commits.length && remaining.size > 0;\n\t\twindowStart += PREFETCH_WINDOW\n\t) {\n\t\tconst windowEnd = Math.min(windowStart + PREFETCH_WINDOW, commits.length);\n\t\t// +1 lookahead commit so the last entry's parent tree is already warm too.\n\t\tconst prefetchEnd = Math.min(windowEnd + 1, commits.length);\n\t\tprefetchWindows++;\n\n\t\t// Phase A: resolve this window's directory oid for every commit's tree in\n\t\t// parallel (a no-op await when treePath is \"\" — the root tree oid IS the\n\t\t// commit tree, no lookup needed).\n\t\tconst dirOids = await Promise.all(\n\t\t\tcommits\n\t\t\t\t.slice(windowStart, prefetchEnd)\n\t\t\t\t.map((commit) => resolveDirOid(commit.commit.tree)),\n\t\t);\n\n\t\t// Phase B: resolve children for every distinct directory oid the window\n\t\t// touched, in parallel — the actual tree-object read for the common case.\n\t\tawait Promise.all(\n\t\t\t[...new Set(dirOids)].map((dirOid) => resolveChildren(dirOid)),\n\t\t);\n\n\t\tfor (let i = windowStart; i < windowEnd; i++) {\n\t\t\tif (remaining.size === 0) break outer;\n\t\t\tconst commit = commits[i];\n\t\t\tif (!commit) break outer;\n\t\t\tcommitsWalked++;\n\n\t\t\tconst parentSha = commit.commit.parent[0];\n\t\t\tconst parentCommit = parentSha ? byOid.get(parentSha) : undefined;\n\t\t\tif (parentSha && !parentCommit) break outer; // walked past the depth cap\n\n\t\t\t// Already resolved above (or memoized from an earlier window/commit) —\n\t\t\t// these awaits resolve immediately from cache.\n\t\t\tconst [dirOid, parentDirOid] = await Promise.all([\n\t\t\t\tresolveDirOid(commit.commit.tree),\n\t\t\t\tparentCommit\n\t\t\t\t\t? resolveDirOid(parentCommit.commit.tree)\n\t\t\t\t\t: Promise.resolve(null),\n\t\t\t]);\n\n\t\t\tif (dirOid === parentDirOid) continue;\n\n\t\t\tconst [children, parentChildren] = await Promise.all([\n\t\t\t\tresolveChildren(dirOid),\n\t\t\t\tresolveChildren(parentDirOid),\n\t\t\t]);\n\t\t\tconst childByName = new Map(\n\t\t\t\tchildren.map((entry) => [entry.path, entry.oid]),\n\t\t\t);\n\t\t\tconst parentChildByName = new Map(\n\t\t\t\tparentChildren.map((entry) => [entry.path, entry.oid]),\n\t\t\t);\n\n\t\t\tfor (const name of remaining) {\n\t\t\t\tif (childByName.get(name) !== parentChildByName.get(name)) {\n\t\t\t\t\tresult[name] = toLastCommitInfo(commit);\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor (const name of Object.keys(result)) {\n\t\t\t\tremaining.delete(name);\n\t\t\t}\n\t\t}\n\t}\n\n\thooks?.onNote?.(\n\t\t`getLastCommitsForTree: walked ${commitsWalked}/${commits.length} commits across ${prefetchWindows} prefetch windows in ${(performance.now() - walkStart).toFixed(1)}ms, ${dirOidByCommitTree.size} unique tree lookups, ${remaining.size} entries never resolved`,\n\t);\n\n\thooks?.resultCache?.set(cacheKey, result);\n\treturn result;\n}\n","import git from \"isomorphic-git\";\nimport { qualifyBranchRef } from \"../refs.js\";\nimport { assertSafeBranchName } from \"./branch.js\";\nimport type { Repo } from \"./types.js\";\n\nexport interface MergeAnalysis {\n\tcanMerge: boolean;\n\thasConflicts: boolean;\n\tconflictingFiles: string[];\n\tfastForward: boolean;\n}\n\n/**\n * Cheap pre-merge check: do both branches exist, and is this a fast-forward?\n * `canMerge`/`fastForward` are the only fields this actually determines —\n * `hasConflicts`/`conflictingFiles` are NOT a real content-conflict check\n * (isomorphic-git's `git.merge` doesn't expose a dry-run), they only ever\n * reflect \"one of the branches couldn't be resolved\" (`canMerge: false`).\n * Real merge conflicts are only discoverable by actually attempting the\n * merge.\n */\nexport async function analyzeMerge(\n\trepo: Repo,\n\tsourceBranch: string,\n\ttargetBranch: string,\n): Promise<MergeAnalysis> {\n\tassertSafeBranchName(sourceBranch);\n\tassertSafeBranchName(targetBranch);\n\n\ttry {\n\t\tconst [sourceOid, targetOid] = await Promise.all([\n\t\t\tgit.resolveRef({ ...repo, ref: qualifyBranchRef(sourceBranch) }),\n\t\t\tgit.resolveRef({ ...repo, ref: qualifyBranchRef(targetBranch) }),\n\t\t]);\n\n\t\tconst isDescendant = await git.isDescendent({\n\t\t\t...repo,\n\t\t\toid: sourceOid,\n\t\t\tancestor: targetOid,\n\t\t});\n\n\t\treturn {\n\t\t\tcanMerge: true,\n\t\t\thasConflicts: false,\n\t\t\tconflictingFiles: [],\n\t\t\tfastForward: isDescendant,\n\t\t};\n\t} catch (err) {\n\t\tif ((err as { code?: string })?.code !== \"NotFoundError\") {\n\t\t\tthrow err;\n\t\t}\n\t\t// A branch ref failed to resolve — not a content conflict, just \"can't\n\t\t// merge because one side doesn't exist.\" hasConflicts here is a\n\t\t// misnomer kept for MergeAnalysis's existing shape; canMerge is the\n\t\t// field that actually matters to callers.\n\t\treturn {\n\t\t\tcanMerge: false,\n\t\t\thasConflicts: true,\n\t\t\tconflictingFiles: [],\n\t\t\tfastForward: false,\n\t\t};\n\t}\n}\n\n/**\n * Attempt a fast-forward merge directly against the bare repo: when source is\n * a descendant of target, just move the target ref — no worktree, no new\n * commit. Returns null when the merge is not a fast-forward (callers fall\n * back to a real three-way merge, which needs a worktree). Serialize with a\n * per-repo lock: resolve → writeRef is not atomic.\n */\nexport async function fastForwardMerge(\n\trepo: Repo,\n\tsourceBranch: string,\n\ttargetBranch: string,\n): Promise<{ success: true; commitSha: string } | null> {\n\tassertSafeBranchName(sourceBranch);\n\tassertSafeBranchName(targetBranch);\n\tconst [sourceOid, targetOid] = await Promise.all([\n\t\tgit.resolveRef({ ...repo, ref: `refs/heads/${sourceBranch}` }),\n\t\tgit.resolveRef({ ...repo, ref: `refs/heads/${targetBranch}` }),\n\t]);\n\tconst isFF = await git.isDescendent({\n\t\t...repo,\n\t\toid: sourceOid,\n\t\tancestor: targetOid,\n\t});\n\tif (!isFF) return null;\n\tawait git.writeRef({\n\t\t...repo,\n\t\tref: `refs/heads/${targetBranch}`,\n\t\tvalue: sourceOid,\n\t\tforce: true,\n\t});\n\treturn { success: true, commitSha: sourceOid };\n}\n"],"mappings":"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","names":["git","git","git","git","git","git","oidA","oidB","files","HISTORY_WALK_DEPTH","PREFETCH_WINDOW","git","git"]}
1
+ {"version":3,"sources":["../src/ops/commit.ts","../src/ops/tree.ts","../src/ops/diff.ts","../src/ops/history.ts","../src/ops/types.ts","../src/ops/file-history.ts","../src/ops/last-commit.ts","../src/ops/merge.ts"],"sourcesContent":["import git from \"isomorphic-git\";\nimport { assertSafeBranchName } from \"./branch.js\";\nimport { deleteFromTree, upsertTree } from \"./tree.js\";\nimport type { Repo } from \"./types.js\";\n\nexport interface CommitAuthor {\n\tname: string;\n\temail: string;\n\ttimestamp: number;\n\ttimezoneOffset: number;\n}\n\n/** An author stamped with the current time. */\nexport function authorNow(name: string, email: string): CommitAuthor {\n\treturn {\n\t\tname,\n\t\temail,\n\t\ttimestamp: Math.floor(Date.now() / 1000),\n\t\ttimezoneOffset: 0,\n\t};\n}\n\n/**\n * Write a commit directly to a bare repository — no worktree, no checkout.\n * `buildTree` receives the parent commit's tree oid (undefined on an empty\n * repo / unborn branch) and returns the new root tree oid; this function\n * writes the commit object and force-updates `refs/heads/<branch>`.\n *\n * Serialize concurrent writers externally (a per-repo lock): the\n * resolve-ref → write-ref sequence is not atomic on object storage.\n */\nexport async function writeCommitToBare(\n\trepo: Repo,\n\toptions: {\n\t\tbranch: string;\n\t\tmessage: string;\n\t\tauthor: CommitAuthor;\n\t\tbuildTree: (parentTreeOid: string | undefined) => Promise<string>;\n\t},\n): Promise<string> {\n\tassertSafeBranchName(options.branch);\n\tlet parentOid: string | undefined;\n\tlet parentTreeOid: string | undefined;\n\ttry {\n\t\tparentOid = await git.resolveRef({\n\t\t\t...repo,\n\t\t\tref: `refs/heads/${options.branch}`,\n\t\t});\n\t\tconst { commit } = await git.readCommit({ ...repo, oid: parentOid });\n\t\tparentTreeOid = commit.tree;\n\t} catch (err) {\n\t\tif ((err as { code?: string })?.code !== \"NotFoundError\") {\n\t\t\tthrow err;\n\t\t}\n\t\t// empty repo — first commit\n\t}\n\tconst treeOid = await options.buildTree(parentTreeOid);\n\tconst commitOid = await git.writeCommit({\n\t\t...repo,\n\t\tcommit: {\n\t\t\tmessage: options.message,\n\t\t\ttree: treeOid,\n\t\t\tparent: parentOid ? [parentOid] : [],\n\t\t\tauthor: options.author,\n\t\t\tcommitter: options.author,\n\t\t},\n\t});\n\tawait git.writeRef({\n\t\t...repo,\n\t\tref: `refs/heads/${options.branch}`,\n\t\tvalue: commitOid,\n\t\tforce: true,\n\t});\n\treturn commitOid;\n}\n\n/**\n * Commit a set of files onto a branch, straight to the bare repo. Each blob\n * is written to its own content-addressed key — no shared state between\n * files, so they're written in parallel.\n */\nexport function commitFilesToBare(\n\trepo: Repo,\n\toptions: {\n\t\tbranch: string;\n\t\tmessage: string;\n\t\tauthor: CommitAuthor;\n\t\tfiles: Array<{ path: string; content: string | Uint8Array }>;\n\t},\n): Promise<string> {\n\treturn writeCommitToBare(repo, {\n\t\tbranch: options.branch,\n\t\tmessage: options.message,\n\t\tauthor: options.author,\n\t\tbuildTree: async (parentTreeOid) => {\n\t\t\tconst blobs = new Map<string, string>();\n\t\t\tawait Promise.all(\n\t\t\t\toptions.files.map(async (file) => {\n\t\t\t\t\tconst content =\n\t\t\t\t\t\ttypeof file.content === \"string\"\n\t\t\t\t\t\t\t? new TextEncoder().encode(file.content)\n\t\t\t\t\t\t\t: file.content;\n\t\t\t\t\tconst oid = await git.writeBlob({ ...repo, blob: content });\n\t\t\t\t\tblobs.set(file.path, oid);\n\t\t\t\t}),\n\t\t\t);\n\t\t\treturn upsertTree(repo, parentTreeOid, blobs);\n\t\t},\n\t});\n}\n\n/** Commit the removal of one file from a branch, straight to the bare repo. */\nexport function deleteFileFromBare(\n\trepo: Repo,\n\toptions: {\n\t\tbranch: string;\n\t\tfilePath: string;\n\t\tmessage: string;\n\t\tauthor: CommitAuthor;\n\t},\n): Promise<string> {\n\treturn writeCommitToBare(repo, {\n\t\tbranch: options.branch,\n\t\tmessage: options.message,\n\t\tauthor: options.author,\n\t\tbuildTree: async (parentTreeOid) => {\n\t\t\tif (!parentTreeOid) {\n\t\t\t\tthrow new Error(`Branch ${options.branch} is empty`);\n\t\t\t}\n\t\t\treturn deleteFromTree(repo, parentTreeOid, options.filePath);\n\t\t},\n\t});\n}\n","import git from \"isomorphic-git\";\nimport type { Repo } from \"./types.js\";\n\nexport interface TreeEntry {\n\tpath: string;\n\tmode: string;\n\ttype: \"blob\" | \"tree\";\n\toid: string;\n\tsize?: number;\n}\n\nconst joinPath = (prefix: string, name: string) =>\n\tprefix ? `${prefix.replace(/\\/+$/, \"\")}/${name}` : name;\n\n/**\n * Build/update a git tree by overlaying new blobs onto an existing tree,\n * returning the new root tree oid. `entries` maps relative paths to blob oids.\n */\nexport async function upsertTree(\n\trepo: Repo,\n\ttreeOid: string | undefined,\n\tentries: Map<string, string>,\n): Promise<string> {\n\tconst existing = treeOid\n\t\t? (await git.readTree({ ...repo, oid: treeOid })).tree\n\t\t: [];\n\tconst byName = new Map(existing.map((e) => [e.path, e]));\n\tconst direct = new Map<string, string>();\n\tconst nested = new Map<string, Map<string, string>>();\n\tfor (const [filePath, blobOid] of entries) {\n\t\tconst slash = filePath.indexOf(\"/\");\n\t\tif (slash === -1) {\n\t\t\tdirect.set(filePath, blobOid);\n\t\t} else {\n\t\t\tconst dir = filePath.slice(0, slash);\n\t\t\tconst rest = filePath.slice(slash + 1);\n\t\t\tif (!nested.has(dir)) nested.set(dir, new Map());\n\t\t\tnested.get(dir)?.set(rest, blobOid);\n\t\t}\n\t}\n\tfor (const [name, blobOid] of direct) {\n\t\tbyName.set(name, {\n\t\t\tmode: \"100644\",\n\t\t\tpath: name,\n\t\t\toid: blobOid,\n\t\t\ttype: \"blob\",\n\t\t});\n\t}\n\t// Sibling subdirectories are independent subtree writes — no reason to\n\t// serialize them for multi-file commits touching several directories.\n\tconst nestedResults = await Promise.all(\n\t\tArray.from(nested, async ([dir, subEntries]) => {\n\t\t\tconst entry = byName.get(dir);\n\t\t\tconst subtreeOid = entry?.type === \"tree\" ? entry.oid : undefined;\n\t\t\tconst newOid = await upsertTree(repo, subtreeOid, subEntries);\n\t\t\treturn [dir, newOid] as const;\n\t\t}),\n\t);\n\tfor (const [dir, newOid] of nestedResults) {\n\t\tbyName.set(dir, { mode: \"040000\", path: dir, oid: newOid, type: \"tree\" });\n\t}\n\treturn git.writeTree({ ...repo, tree: Array.from(byName.values()) });\n}\n\n/** Remove a file path from a tree, returning the new root tree oid. */\nexport async function deleteFromTree(\n\trepo: Repo,\n\ttreeOid: string,\n\tfilePath: string,\n): Promise<string> {\n\tconst existing = (await git.readTree({ ...repo, oid: treeOid })).tree;\n\tconst byName = new Map(existing.map((e) => [e.path, e]));\n\tconst slash = filePath.indexOf(\"/\");\n\tif (slash === -1) {\n\t\tbyName.delete(filePath);\n\t} else {\n\t\tconst dir = filePath.slice(0, slash);\n\t\tconst rest = filePath.slice(slash + 1);\n\t\tconst entry = byName.get(dir);\n\t\tif (entry?.type === \"tree\") {\n\t\t\tconst newOid = await deleteFromTree(repo, entry.oid, rest);\n\t\t\tbyName.set(dir, { ...entry, oid: newOid });\n\t\t}\n\t}\n\treturn git.writeTree({ ...repo, tree: Array.from(byName.values()) });\n}\n\n/** Resolve a path inside a tree to its entry, or null when absent. */\nexport async function findTreeEntry(\n\trepo: Repo,\n\trootTreeOid: string,\n\ttreePath: string,\n): Promise<TreeEntry | null> {\n\tif (!treePath) {\n\t\treturn { path: \"\", mode: \"040000\", type: \"tree\", oid: rootTreeOid };\n\t}\n\n\tconst parts = treePath.split(\"/\").filter(Boolean);\n\tlet currentTreeOid = rootTreeOid;\n\tlet currentPath = \"\";\n\n\tfor (const [index, part] of parts.entries()) {\n\t\tconst tree = await git.readTree({ ...repo, oid: currentTreeOid });\n\t\tconst entry = tree.tree.find((candidate) => candidate.path === part);\n\n\t\tif (!entry) return null;\n\n\t\tcurrentPath = currentPath ? joinPath(currentPath, entry.path) : entry.path;\n\n\t\tif (index === parts.length - 1) {\n\t\t\treturn {\n\t\t\t\tpath: currentPath,\n\t\t\t\tmode: entry.mode,\n\t\t\t\ttype: entry.type as \"blob\" | \"tree\",\n\t\t\t\toid: entry.oid,\n\t\t\t};\n\t\t}\n\n\t\tif (entry.type !== \"tree\") return null;\n\n\t\tcurrentTreeOid = entry.oid;\n\t}\n\n\treturn null;\n}\n\n/** List a tree's direct entries, with paths prefixed by `prefix`. */\nexport async function listTreeEntries(\n\trepo: Repo,\n\ttreeOid: string,\n\tprefix = \"\",\n): Promise<TreeEntry[]> {\n\tconst tree = await git.readTree({ ...repo, oid: treeOid });\n\n\treturn tree.tree.map((entry) => ({\n\t\tpath: prefix ? joinPath(prefix, entry.path) : entry.path,\n\t\tmode: entry.mode,\n\t\ttype: entry.type as \"blob\" | \"tree\",\n\t\toid: entry.oid,\n\t}));\n}\n","import { createTwoFilesPatch } from \"diff\";\nimport git from \"isomorphic-git\";\nimport { readBlobContent, toBase64 } from \"../edge-utils.js\";\nimport { qualifyBranchRef } from \"../refs.js\";\nimport { getCommit } from \"./history.js\";\nimport type { Repo } from \"./types.js\";\n\nexport interface DiffFile {\n\tpath: string;\n\tstatus: \"added\" | \"modified\" | \"deleted\" | \"renamed\";\n\tadditions: number;\n\tdeletions: number;\n\tpatch: string;\n\toldPath?: string;\n\tisBinary?: boolean;\n\toldContent?: string;\n\tnewContent?: string;\n\toldSize?: number;\n\tnewSize?: number;\n}\n\nexport interface DiffResult {\n\tfiles: DiffFile[];\n\ttotalAdditions: number;\n\ttotalDeletions: number;\n\ttotalFiles: number;\n}\n\n/** Binary detection via null-byte heuristic. */\nfunction detectBlobContent(blob: Uint8Array): {\n\tisBinary: boolean;\n\ttext: string;\n\tbytes: Uint8Array;\n} {\n\treturn readBlobContent(blob);\n}\n\nfunction countContentLines(content: string): number {\n\tif (content.length === 0) return 0;\n\tconst lines = content.split(\"\\n\");\n\tif (lines[lines.length - 1] === \"\") lines.pop();\n\treturn lines.length;\n}\n\nfunction createUnifiedPatch(params: {\n\tpath: string;\n\tbefore: string;\n\tafter: string;\n\toldPath?: string;\n\tnewPath?: string;\n}): string {\n\tconst oldPath = params.oldPath ?? `a/${params.path}`;\n\tconst newPath = params.newPath ?? `b/${params.path}`;\n\tconst patchBody = createTwoFilesPatch(\n\t\toldPath,\n\t\tnewPath,\n\t\tparams.before,\n\t\tparams.after,\n\t\t\"\",\n\t\t\"\",\n\t\t{ context: 3 },\n\t).replace(/^=+\\n/, \"\");\n\n\treturn `diff --git a/${params.path} b/${params.path}\\n${patchBody}`;\n}\n\nfunction summarizeDiff(files: DiffFile[]): DiffResult {\n\treturn {\n\t\tfiles,\n\t\ttotalAdditions: files.reduce((sum, f) => sum + f.additions, 0),\n\t\ttotalDeletions: files.reduce((sum, f) => sum + f.deletions, 0),\n\t\ttotalFiles: files.length,\n\t};\n}\n\ntype BlobContent = { isBinary: boolean; text: string; bytes: Uint8Array };\n\n/**\n * Build one `DiffFile` from a path's before/after content — `before: null`\n * for an added file, `after: null` for a deleted one, both present for a\n * modification. The shared core of walkTreeDiff's three branches and\n * getCommitDiff's root-commit case (every file is an addition there, with no\n * two-tree walk to drive it), which each used to duplicate this field\n * construction independently.\n */\nfunction buildDiffFile(\n\tpath: string,\n\tstatus: DiffFile[\"status\"],\n\tbefore: BlobContent | null,\n\tafter: BlobContent | null,\n): DiffFile {\n\tconst isBinary = !!(before?.isBinary || after?.isBinary);\n\treturn {\n\t\tpath,\n\t\tstatus,\n\t\tadditions: !isBinary && after ? countContentLines(after.text) : 0,\n\t\tdeletions: !isBinary && before ? countContentLines(before.text) : 0,\n\t\tpatch: isBinary\n\t\t\t? \"\"\n\t\t\t: createUnifiedPatch({\n\t\t\t\t\tpath,\n\t\t\t\t\tbefore: before?.text ?? \"\",\n\t\t\t\t\tafter: after?.text ?? \"\",\n\t\t\t\t\toldPath: before ? undefined : \"/dev/null\",\n\t\t\t\t\tnewPath: after ? undefined : \"/dev/null\",\n\t\t\t\t}),\n\t\tisBinary,\n\t\toldContent: before && isBinary ? toBase64(before.bytes) : undefined,\n\t\tnewContent: after && isBinary ? toBase64(after.bytes) : undefined,\n\t\toldSize: before?.bytes.length,\n\t\tnewSize: after?.bytes.length,\n\t};\n}\n\n/**\n * Walk two trees (oldOid -> newOid) and return one DiffFile per changed path\n * — the shared core of both {@link getCommitDiff} (parent -> commit) and\n * {@link getDiffBetweenRefs} (base -> compare).\n */\nasync function walkTreeDiff(\n\trepo: Repo,\n\toldOid: string,\n\tnewOid: string,\n): Promise<DiffFile[]> {\n\tconst changes = await git.walk({\n\t\t...repo,\n\t\ttrees: [git.TREE({ ref: oldOid }), git.TREE({ ref: newOid })],\n\t\tmap: async (filepath, [A, B]) => {\n\t\t\tconst [typeA, typeB] = await Promise.all([A?.type(), B?.type()]);\n\n\t\t\tif (typeA === \"tree\" || typeB === \"tree\") return;\n\n\t\t\tif (typeA && !typeB) {\n\t\t\t\tconst oidA = A ? await A.oid() : \"\";\n\t\t\t\tconst { blob } = await git.readBlob({ ...repo, oid: oidA });\n\t\t\t\tconst before = detectBlobContent(blob);\n\t\t\t\treturn buildDiffFile(filepath, \"deleted\", before, null);\n\t\t\t}\n\n\t\t\tif (!typeA && typeB) {\n\t\t\t\tconst oidB = B ? await B.oid() : \"\";\n\t\t\t\tconst { blob } = await git.readBlob({ ...repo, oid: oidB });\n\t\t\t\tconst after = detectBlobContent(blob);\n\t\t\t\treturn buildDiffFile(filepath, \"added\", null, after);\n\t\t\t}\n\n\t\t\tconst [oidA, oidB] = await Promise.all([\n\t\t\t\tA ? A.oid() : Promise.resolve(\"\"),\n\t\t\t\tB ? B.oid() : Promise.resolve(\"\"),\n\t\t\t]);\n\n\t\t\tif (oidA !== oidB) {\n\t\t\t\tconst [{ blob: blobA }, { blob: blobB }] = await Promise.all([\n\t\t\t\t\tgit.readBlob({ ...repo, oid: oidA }),\n\t\t\t\t\tgit.readBlob({ ...repo, oid: oidB }),\n\t\t\t\t]);\n\t\t\t\tconst before = detectBlobContent(blobA);\n\t\t\t\tconst after = detectBlobContent(blobB);\n\t\t\t\treturn buildDiffFile(filepath, \"modified\", before, after);\n\t\t\t}\n\n\t\t\treturn null;\n\t\t},\n\t});\n\n\treturn (changes ?? []).filter(\n\t\t(c: DiffFile | null | undefined): c is DiffFile =>\n\t\t\tc !== null && c !== undefined,\n\t);\n}\n\n/** The diff a single commit introduced (against its first parent). */\nexport async function getCommitDiff(\n\trepo: Repo,\n\tcommitSha: string,\n): Promise<DiffResult> {\n\ttry {\n\t\tconst commit = await getCommit(repo, commitSha);\n\t\tconst parent = commit.commit.parent[0];\n\n\t\tif (!parent) {\n\t\t\tconst entries: { path: string; oid: string }[] = [];\n\t\t\tconst stack: { treeOid: string; prefix: string }[] = [\n\t\t\t\t{ treeOid: commit.commit.tree, prefix: \"\" },\n\t\t\t];\n\n\t\t\twhile (stack.length) {\n\t\t\t\tconst { treeOid, prefix } = stack.pop() as {\n\t\t\t\t\ttreeOid: string;\n\t\t\t\t\tprefix: string;\n\t\t\t\t};\n\t\t\t\tconst { tree } = await git.readTree({ ...repo, oid: treeOid });\n\t\t\t\tfor (const entry of tree) {\n\t\t\t\t\tconst full = prefix ? `${prefix}/${entry.path}` : entry.path;\n\t\t\t\t\tif (entry.type === \"tree\") {\n\t\t\t\t\t\tstack.push({ treeOid: entry.oid, prefix: full });\n\t\t\t\t\t} else if (entry.type === \"blob\") {\n\t\t\t\t\t\tentries.push({ path: full, oid: entry.oid });\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst files: DiffFile[] = await Promise.all(\n\t\t\t\tentries.map(async ({ path, oid }) => {\n\t\t\t\t\tconst { blob } = await git.readBlob({ ...repo, oid });\n\t\t\t\t\tconst after = detectBlobContent(blob);\n\t\t\t\t\treturn buildDiffFile(path, \"added\", null, after);\n\t\t\t\t}),\n\t\t\t);\n\n\t\t\treturn summarizeDiff(files);\n\t\t}\n\n\t\tconst files = await walkTreeDiff(repo, parent, commitSha);\n\t\treturn summarizeDiff(files);\n\t} catch (error) {\n\t\tthrow new Error(`Failed to get commit diff: ${error}`);\n\t}\n}\n\n/** The diff between two refs (base -> compare). */\nexport async function getDiffBetweenRefs(\n\trepo: Repo,\n\tbaseRef: string,\n\tcompareRef: string,\n): Promise<DiffResult> {\n\tconst [baseOid, compareOid] = await Promise.all([\n\t\tgit.resolveRef({ ...repo, ref: qualifyBranchRef(baseRef) }),\n\t\tgit.resolveRef({ ...repo, ref: qualifyBranchRef(compareRef) }),\n\t]);\n\n\tconst files = await walkTreeDiff(repo, baseOid, compareOid);\n\treturn summarizeDiff(files);\n}\n","import git from \"isomorphic-git\";\nimport { decodeUtf8, hasNullByte, toBase64 } from \"../edge-utils.js\";\nimport {\n\tGitObjectNotFoundError,\n\tGitPathNotFoundError,\n\tGitRefNotFoundError,\n} from \"../git-errors.js\";\nimport { qualifyBranchRef } from \"../refs.js\";\nimport { findTreeEntry, listTreeEntries, type TreeEntry } from \"./tree.js\";\nimport { type OpsHooks, type Repo, runStep } from \"./types.js\";\n\n/**\n * A resolved ref (branch/commit) not existing is a normal, expected condition\n * (empty repo, unborn branch) and is handled by each caller. An object that\n * fails to resolve *underneath* an already-resolved ref (a tree/blob the\n * stored pack doesn't actually contain) means the repo's storage is\n * inconsistent — surface that distinctly so callers don't render it as\n * \"empty\" and clients don't see a raw isomorphic-git NotFoundError.\n */\nfunction wrapMissingObject<T>(\n\tpromise: Promise<T>,\n\tcontext: string,\n): Promise<T> {\n\treturn promise.catch((err: unknown) => {\n\t\tif ((err as { code?: string })?.code === \"NotFoundError\") {\n\t\t\tthrow new GitObjectNotFoundError(\n\t\t\t\t`Git data for ${context} is missing from storage. The repository may need to be re-pushed to repair it.`,\n\t\t\t);\n\t\t}\n\t\tthrow err;\n\t});\n}\n\nconst isNotFound = (err: unknown) =>\n\t(err as { code?: string })?.code === \"NotFoundError\";\n\nexport interface CommitInfo {\n\toid: string;\n\tcommit: {\n\t\tmessage: string;\n\t\ttree: string;\n\t\tparent: string[];\n\t\tauthor: {\n\t\t\tname: string;\n\t\t\temail: string;\n\t\t\ttimestamp: number;\n\t\t\ttimezoneOffset: number;\n\t\t};\n\t\tcommitter: {\n\t\t\tname: string;\n\t\t\temail: string;\n\t\t\ttimestamp: number;\n\t\t\ttimezoneOffset: number;\n\t\t};\n\t};\n\tpayload: string;\n}\n\n/**\n * Resolve a branch name / full ref / sha to its commit. The ref not\n * resolving (unborn branch, genuinely empty repo) and the ref resolving but\n * its commit object being unreadable (storage inconsistency — see\n * `wrapMissingObject` above) are different failures with different meanings,\n * so only the first is left as a raw isomorphic-git NotFoundError for\n * callers to treat as \"empty\"; the second is wrapped into\n * GitObjectNotFoundError specifically so it can't be mistaken for the first\n * by an `isNotFound`-style check downstream (see getTreeFromRef/getCommitLog).\n */\nexport async function resolveCommit(repo: Repo, ref: string) {\n\tconst oid = await git\n\t\t.resolveRef({ ...repo, ref: qualifyBranchRef(ref) })\n\t\t.catch((err: unknown) => {\n\t\t\tif (isNotFound(err)) {\n\t\t\t\tthrow new GitRefNotFoundError(`Ref ${ref} does not exist`);\n\t\t\t}\n\t\t\tthrow err;\n\t\t});\n\tconst result = await wrapMissingObject(\n\t\tgit.readCommit({ ...repo, oid }),\n\t\t`${repo.gitdir} commit ${oid}`,\n\t);\n\treturn { oid, commit: result.commit };\n}\n\n/** Read a blob's bytes by oid. */\nexport async function getBlob(repo: Repo, sha: string): Promise<Uint8Array> {\n\tconst { blob } = await wrapMissingObject(\n\t\tgit.readBlob({ ...repo, oid: sha }),\n\t\t`${repo.gitdir} blob ${sha}`,\n\t);\n\treturn blob;\n}\n\n/** Read a file's bytes at a ref. Throws GitPathNotFoundError when absent. */\nexport async function getFileContent(\n\trepo: Repo,\n\tfilePath: string,\n\tref: string,\n\thooks?: OpsHooks,\n): Promise<Uint8Array> {\n\t// A blob page needs a commit, one or more tree objects, and the blob. On a\n\t// multi-pack object store, letting isomorphic-git discover those packs one\n\t// at a time turns that otherwise short read into a serial network walk.\n\t// Warm every pack before resolving the ref so every later object lookup can\n\t// use the local parse cache. This is the same structural optimization used\n\t// for tree and history reads, now applied to file reads as well.\n\tif (hooks?.prefetch) {\n\t\tawait runStep(hooks, \"prefetch\", hooks.prefetch);\n\t}\n\tconst { commit } = await resolveCommit(repo, ref);\n\tconst context = `${repo.gitdir}@${ref}:${filePath}`;\n\tconst entry = await wrapMissingObject(\n\t\tfindTreeEntry(repo, commit.tree, filePath),\n\t\tcontext,\n\t);\n\n\tif (entry?.type !== \"blob\") {\n\t\tthrow new GitPathNotFoundError(`File not found: ${filePath}`);\n\t}\n\n\tconst { blob } = await wrapMissingObject(\n\t\tgit.readBlob({ ...repo, oid: entry.oid }),\n\t\tcontext,\n\t);\n\treturn blob;\n}\n\n/** Read one commit by sha. */\nexport async function getCommit(repo: Repo, sha: string): Promise<CommitInfo> {\n\tconst result = await wrapMissingObject(\n\t\tgit.readCommit({ ...repo, oid: sha }),\n\t\t`${repo.gitdir} commit ${sha}`,\n\t);\n\treturn { oid: result.oid, commit: result.commit, payload: result.payload };\n}\n\nfunction isFullyWalked(commits: CommitInfo[]): boolean {\n\tconst last = commits[commits.length - 1];\n\treturn !!last && last.commit.parent.length === 0;\n}\n\nexport interface CommitLogOptions {\n\tref: string;\n\tdepth?: number;\n\t/**\n\t * Pass the head sha when the caller already resolved `ref` — resolveRef\n\t * tries several candidate paths in sequence and misses the first few every\n\t * time for a normal branch name, which is pure waste when the sha is known.\n\t */\n\tknownHeadSha?: string;\n}\n\n/**\n * The commit chain from a ref, newest first. Walking is inherently sequential\n * (each commit's oid is only discoverable by reading its child first) and\n * network-round-trip-bound against object storage, so the deepest walk seen\n * per head is memoized in `hooks.resultCache` and sliced for shallower or\n * repeated requests — don't bypass this by calling `git.log` directly.\n */\nexport async function getCommitLog(\n\trepo: Repo,\n\toptions: CommitLogOptions,\n\thooks?: OpsHooks,\n): Promise<CommitInfo[]> {\n\tconst { ref } = options;\n\tconst depth = options.depth ?? 50;\n\n\tlet headSha: string;\n\tif (options.knownHeadSha) {\n\t\theadSha = options.knownHeadSha;\n\t} else {\n\t\ttry {\n\t\t\theadSha = await git.resolveRef({ ...repo, ref: qualifyBranchRef(ref) });\n\t\t} catch (err: unknown) {\n\t\t\tif (isNotFound(err)) return [];\n\t\t\tthrow err;\n\t\t}\n\t}\n\n\tconst cacheKey = `commitlog:${repo.gitdir}:${headSha}`;\n\tconst cached = hooks?.resultCache?.get<CommitInfo[]>(cacheKey);\n\tif (cached && (cached.length >= depth || isFullyWalked(cached))) {\n\t\thooks?.onNote?.(\n\t\t\t`getCommitLog: result-cache HIT for ${cacheKey} (depth=${depth})`,\n\t\t);\n\t\treturn cached.slice(0, depth);\n\t}\n\thooks?.onNote?.(\n\t\t`getCommitLog: result-cache MISS for ${cacheKey} (depth=${depth})`,\n\t);\n\n\tif (hooks?.prefetch && depth >= (hooks.prefetchMinDepth ?? 5)) {\n\t\tawait runStep(hooks, \"prefetch\", hooks.prefetch);\n\t}\n\n\t// headSha is already known-good here (resolveRef above succeeded, or the\n\t// caller passed knownHeadSha) — a NotFoundError from the walk itself means\n\t// a commit/tree/blob it needs is missing from storage, not that the ref\n\t// doesn't exist. Unlike the resolveRef catch above, that's not \"empty\",\n\t// it's storage inconsistency, so wrapMissingObject turns it into a\n\t// GitObjectNotFoundError instead of silently returning [] — an empty\n\t// result here would otherwise get treated as \"this branch has no\n\t// history\" by every caller (and, worse, potentially cached as such).\n\tconst commits = await wrapMissingObject(\n\t\trunStep(hooks, `git.log ${ref} depth=${depth}`, () =>\n\t\t\tgit.log({ ...repo, ref: headSha, depth }),\n\t\t),\n\t\t`${repo.gitdir}@${ref} history`,\n\t);\n\tconst result = commits.map((commit) => ({\n\t\toid: commit.oid,\n\t\tcommit: commit.commit,\n\t\tpayload: commit.payload || \"\",\n\t}));\n\tif (!cached || result.length > cached.length) {\n\t\thooks?.resultCache?.set(cacheKey, result);\n\t}\n\treturn result;\n}\n\n/** A file at a ref, decoded for display: utf8 text or base64 when binary. */\nexport async function getFileFromRef(\n\trepo: Repo,\n\tfilePath: string,\n\tref: string,\n\thooks?: OpsHooks,\n): Promise<{ content: string; size: number; isBinary: boolean }> {\n\tconst bytes = await getFileContent(repo, filePath, ref, hooks);\n\tconst isBinary = hasNullByte(bytes);\n\n\treturn {\n\t\tcontent: isBinary ? toBase64(bytes) : decodeUtf8(bytes),\n\t\tsize: bytes.length,\n\t\tisBinary,\n\t};\n}\n\n/**\n * List a directory at the tip of a ref. Throws GitRefNotFoundError when the\n * ref doesn't resolve (including an empty repo's unborn branch) and\n * GitPathNotFoundError when `treePath` doesn't exist. Results are memoized per\n * head sha (auto-invalidates on push).\n */\nexport async function getTreeFromRef(\n\trepo: Repo,\n\toptions: { ref: string; treePath?: string },\n\thooks?: OpsHooks,\n): Promise<TreeEntry[]> {\n\tconst { ref } = options;\n\tconst treePath = options.treePath ?? \"\";\n\tconst { commit, oid: headSha } = await resolveCommit(repo, ref);\n\n\tconst cacheKey = `tree:${repo.gitdir}:${headSha}:${treePath}`;\n\tconst cached = hooks?.resultCache?.get<TreeEntry[]>(cacheKey);\n\tif (cached) {\n\t\thooks?.onNote?.(`getTreeFromRef: result-cache HIT for ${cacheKey}`);\n\t\treturn cached;\n\t}\n\thooks?.onNote?.(`getTreeFromRef: result-cache MISS for ${cacheKey}`);\n\n\t// Unlike getCommitLog, a tree read has no \"depth\" to gate on — it always\n\t// needs at least the head commit's tree object, and (for a non-root path)\n\t// one object per path segment on top of that, so there's no shallow case\n\t// where prefetching every pack is wasted bandwidth the way a depth=1\n\t// commit-log walk can be. Without this, a cache-miss tree read falls\n\t// through to isomorphic-git's own pack resolution, which probes indexed\n\t// packs one at a time instead of warming them all in parallel up front —\n\t// this was previously the slowest of a tree page's parallel queries in\n\t// production for exactly that reason.\n\tif (hooks?.prefetch) {\n\t\tawait runStep(hooks, \"prefetch\", hooks.prefetch);\n\t}\n\n\tconst context = `${repo.gitdir}@${ref}:${treePath || \"/\"}`;\n\tlet result: TreeEntry[];\n\tif (!treePath) {\n\t\tresult = await wrapMissingObject(\n\t\t\trunStep(hooks, \"listTreeEntries (root)\", () =>\n\t\t\t\tlistTreeEntries(repo, commit.tree),\n\t\t\t),\n\t\t\tcontext,\n\t\t);\n\t} else {\n\t\tconst entry = await wrapMissingObject(\n\t\t\trunStep(hooks, `findTreeEntry ${treePath}`, () =>\n\t\t\t\tfindTreeEntry(repo, commit.tree, treePath),\n\t\t\t),\n\t\t\tcontext,\n\t\t);\n\t\tif (!entry) {\n\t\t\tthrow new GitPathNotFoundError(\n\t\t\t\t`Path \"${treePath}\" does not exist at ${ref}`,\n\t\t\t);\n\t\t}\n\t\tresult =\n\t\t\tentry.type !== \"tree\"\n\t\t\t\t? []\n\t\t\t\t: await wrapMissingObject(\n\t\t\t\t\t\trunStep(hooks, `listTreeEntries ${treePath}`, () =>\n\t\t\t\t\t\t\tlistTreeEntries(repo, entry.oid, entry.path),\n\t\t\t\t\t\t),\n\t\t\t\t\t\tcontext,\n\t\t\t\t\t);\n\t}\n\n\thooks?.resultCache?.set(cacheKey, result);\n\treturn result;\n}\n\n/**\n * A page of commit history from a branch tip. Memoized per head sha; builds\n * on {@link getCommitLog}'s walk cache for the underlying chain.\n */\nexport async function getCommitHistory(\n\trepo: Repo,\n\toptions: { ref: string; limit?: number; skip?: number },\n\thooks?: OpsHooks,\n): Promise<CommitInfo[]> {\n\tconst limit = options.limit ?? 50;\n\tconst skip = options.skip ?? 0;\n\tconst headSha = await git\n\t\t.resolveRef({ ...repo, ref: qualifyBranchRef(options.ref) })\n\t\t.catch(() => null);\n\n\tconst cacheKey = headSha\n\t\t? `commits:${repo.gitdir}:${headSha}:${limit}:${skip}`\n\t\t: null;\n\tif (cacheKey) {\n\t\tconst cached = hooks?.resultCache?.get<CommitInfo[]>(cacheKey);\n\t\tif (cached) {\n\t\t\thooks?.onNote?.(`getCommitHistory: result-cache HIT for ${cacheKey}`);\n\t\t\treturn cached;\n\t\t}\n\t}\n\thooks?.onNote?.(\n\t\t`getCommitHistory: result-cache MISS for ${cacheKey ?? \"(no head)\"}`,\n\t);\n\tif (!headSha) return [];\n\n\tconst all = await getCommitLog(\n\t\trepo,\n\t\t{ ref: options.ref, depth: limit + skip, knownHeadSha: headSha },\n\t\thooks,\n\t);\n\tconst result = all.slice(skip, skip + limit);\n\n\tif (cacheKey) hooks?.resultCache?.set(cacheKey, result);\n\treturn result;\n}\n","import type git from \"isomorphic-git\";\n\n/** The fs shape isomorphic-git accepts, derived from its own signatures. */\nexport type IsoGitFs = Parameters<typeof git.readTree>[0][\"fs\"];\n\n/**\n * A repository handle: everything isomorphic-git needs to address one bare\n * repo. Create it once per request (sharing `cache` across calls is what lets\n * isomorphic-git reuse parsed pack indexes) and pass it to every op.\n */\nexport interface Repo {\n\tfs: IsoGitFs;\n\tgitdir: string;\n\t/** isomorphic-git's shared parse cache — strongly recommended per repo. */\n\tcache?: object;\n}\n\n/**\n * Key/value store for memoizing expensive walk results (commit logs, tree\n * listings, per-file history). Keys are namespaced `kind:gitdir:headSha:…`,\n * so entries self-invalidate on push (new head, new key) — but evict entries\n * under {@link resultKeyPrefixes} after rewriting a repo's storage out of\n * band, or stale walks leak until your store's own eviction.\n */\nexport interface ResultCache {\n\tget<T>(key: string): T | null | undefined;\n\tset(key: string, value: unknown): void;\n}\n\n/** Optional instrumentation and tuning hooks accepted by every op. */\nexport interface OpsHooks {\n\tresultCache?: ResultCache;\n\t/** Wrap a timed sub-step (network walk, tree listing). Default: run directly. */\n\tstep?: <T>(label: string, fn: () => Promise<T>) => Promise<T>;\n\t/** Diagnostic sink for cache hit/miss and walk summaries. */\n\tonNote?: (message: string) => void;\n\t/**\n\t * Wire pack prefetching (e.g. `GitFs.prefetchPacks`) here so a sequential\n\t * walk doesn't pay one network round trip per commit. Called once before\n\t * history walks of depth >= `prefetchMinDepth` (see below), and\n\t * unconditionally on every cache-miss tree read ({@link getTreeFromRef}) —\n\t * a tree read has no shallow case, it always needs at least the head\n\t * commit's tree object.\n\t */\n\tprefetch?: () => Promise<void>;\n\t/** Minimum walk depth before `prefetch` fires for history walks. Default 5. */\n\tprefetchMinDepth?: number;\n}\n\nexport const runStep = <T>(\n\thooks: OpsHooks | undefined,\n\tlabel: string,\n\tfn: () => Promise<T>,\n): Promise<T> => (hooks?.step ? hooks.step(label, fn) : fn());\n\n/**\n * The result-cache key prefixes holding entries for `gitdir` — evict these\n * from your {@link ResultCache} when the repo's storage was rewritten outside\n * a normal push (bulk sync, rename, repack cleanup).\n */\nexport function resultKeyPrefixes(gitdir: string): string[] {\n\treturn [\n\t\t`commitlog:${gitdir}:`,\n\t\t`tree:${gitdir}:`,\n\t\t`commits:${gitdir}:`,\n\t\t`last-commits:${gitdir}:`,\n\t\t`file-history:${gitdir}:`,\n\t];\n}\n","import { type CommitInfo, getCommitLog } from \"./history.js\";\nimport { findTreeEntry } from \"./tree.js\";\nimport type { OpsHooks, Repo } from \"./types.js\";\nimport { runStep } from \"./types.js\";\n\nexport interface FileHistoryEntry {\n\tsha: string;\n\tmessage: string;\n\tauthorName: string;\n\tauthorEmail: string;\n\tcreatedAt: string;\n}\n\nexport interface FileHistoryResult {\n\tentries: FileHistoryEntry[];\n\t/**\n\t * True when the walk hit its depth budget (or the requested `limit`)\n\t * before exhausting the branch's full commit chain — there may be older\n\t * commits touching this file that a deeper walk would surface.\n\t */\n\ttruncated: boolean;\n}\n\n/**\n * Default walk bound for a caller that actually wants deep history (a file's\n * \"History\" tab). Walking the full chain is round-trip-bound on object\n * storage, so cap how far back a single request will look.\n */\nexport const HISTORY_WALK_DEPTH = 400;\n\n/**\n * Much shallower default for a \"latest commit touching this file\" banner that\n * only displays `entries[0]` — trades \"always finds the true last-touching\n * commit\" for \"finds it if it's reasonably recent\", the right call for a\n * banner with a full History view a click away.\n */\nexport const BANNER_WALK_DEPTH = 60;\n\n/** Tree reads are prefetched in parallel windows; see getLastCommitsForTree. */\nconst PREFETCH_WINDOW = 24;\n\nfunction toEntry(commit: CommitInfo): FileHistoryEntry {\n\treturn {\n\t\tsha: commit.oid,\n\t\tmessage: commit.commit.message.trim(),\n\t\tauthorName: commit.commit.author.name,\n\t\tauthorEmail: commit.commit.author.email,\n\t\tcreatedAt: new Date(commit.commit.author.timestamp * 1000).toISOString(),\n\t};\n}\n\n/**\n * All commits (newest first) that changed a single file's blob oid, walking\n * the first-parent chain — same approach as getLastCommitsForTree but for one\n * path and collecting every match instead of stopping at the first.\n */\nexport async function getFileHistory(\n\trepo: Repo,\n\toptions: {\n\t\tref: string;\n\t\tfilePath: string;\n\t\tlimit?: number;\n\t\tmaxDepth?: number;\n\t},\n\thooks?: OpsHooks,\n): Promise<FileHistoryResult> {\n\tconst limit = options.limit ?? 30;\n\tconst maxDepth = options.maxDepth ?? HISTORY_WALK_DEPTH;\n\tconst walkDepth = Math.max(maxDepth, limit);\n\tconst commits = await runStep(hooks, `getCommitLog depth=${walkDepth}`, () =>\n\t\tgetCommitLog(repo, { ref: options.ref, depth: walkDepth }, hooks),\n\t);\n\tconst head = commits[0];\n\tif (!head) return { entries: [], truncated: false };\n\n\tconst cacheKey = `file-history:${repo.gitdir}:${head.oid}:${options.filePath}:${limit}:${maxDepth}`;\n\tconst cached = hooks?.resultCache?.get<FileHistoryResult>(cacheKey);\n\tif (cached) {\n\t\thooks?.onNote?.(\"getFileHistory: result-cache HIT, skipping history walk\");\n\t\treturn cached;\n\t}\n\thooks?.onNote?.(\"getFileHistory: result-cache MISS, walking history\");\n\n\tconst byOid = new Map(commits.map((commit) => [commit.oid, commit]));\n\n\tconst oidByCommitTree = new Map<string, string | null>();\n\tasync function resolveOid(commitTreeOid: string): Promise<string | null> {\n\t\tconst cachedOid = oidByCommitTree.get(commitTreeOid);\n\t\tif (cachedOid !== undefined) return cachedOid;\n\t\tconst entry = await findTreeEntry(repo, commitTreeOid, options.filePath);\n\t\tconst oid = entry?.type === \"blob\" ? entry.oid : null;\n\t\toidByCommitTree.set(commitTreeOid, oid);\n\t\treturn oid;\n\t}\n\n\tconst entries: FileHistoryEntry[] = [];\n\tlet truncated = false;\n\n\touter: for (\n\t\tlet windowStart = 0;\n\t\twindowStart < commits.length;\n\t\twindowStart += PREFETCH_WINDOW\n\t) {\n\t\tconst windowEnd = Math.min(windowStart + PREFETCH_WINDOW, commits.length);\n\t\t// +1 lookahead so the last entry's parent tree is already warm too.\n\t\tconst prefetchEnd = Math.min(windowEnd + 1, commits.length);\n\n\t\tawait Promise.all(\n\t\t\tcommits\n\t\t\t\t.slice(windowStart, prefetchEnd)\n\t\t\t\t.map((commit) => resolveOid(commit.commit.tree)),\n\t\t);\n\n\t\tfor (let i = windowStart; i < windowEnd; i++) {\n\t\t\tconst commit = commits[i];\n\t\t\tif (!commit) break outer;\n\n\t\t\tconst parentSha = commit.commit.parent[0];\n\t\t\tconst parentCommit = parentSha ? byOid.get(parentSha) : undefined;\n\t\t\tif (parentSha && !parentCommit) {\n\t\t\t\t// Walked past the depth cap without reaching this commit's parent —\n\t\t\t\t// can't tell whether it changed the file; stop and report truncated.\n\t\t\t\ttruncated = true;\n\t\t\t\tbreak outer;\n\t\t\t}\n\n\t\t\tconst [oid, parentOid] = await Promise.all([\n\t\t\t\tresolveOid(commit.commit.tree),\n\t\t\t\tparentCommit\n\t\t\t\t\t? resolveOid(parentCommit.commit.tree)\n\t\t\t\t\t: Promise.resolve(null),\n\t\t\t]);\n\n\t\t\tif (oid !== parentOid) {\n\t\t\t\tentries.push(toEntry(commit));\n\t\t\t\tif (entries.length >= limit) {\n\t\t\t\t\ttruncated = i < commits.length - 1;\n\t\t\t\t\tbreak outer;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tconst result = { entries, truncated };\n\thooks?.resultCache?.set(cacheKey, result);\n\treturn result;\n}\n","import { type CommitInfo, getCommitLog } from \"./history.js\";\nimport { findTreeEntry, listTreeEntries } from \"./tree.js\";\nimport type { OpsHooks, Repo } from \"./types.js\";\nimport { runStep } from \"./types.js\";\n\nexport interface LastCommitInfo {\n\tsha: string;\n\tmessage: string;\n\tauthorName: string;\n\tauthorEmail: string;\n\tcreatedAt: string;\n}\n\n/**\n * Bounds how far back the history walk looks to resolve \"last commit touching\n * this path\" for a directory listing. Entries whose last change is older\n * simply show no last-commit info rather than paying an unbounded scan.\n */\nconst HISTORY_WALK_DEPTH = 400;\n\n/**\n * The walk itself is inherently sequential (each step needs to know what's\n * still \"remaining\" from the step before), but the tree-object reads that\n * back it are not — each commit's tree oid is already known upfront from the\n * commit log. Prefetching a window of tree reads in parallel turns ~1 network\n * round trip per commit (serialized) into ~1 round trip per window, which is\n * where nearly all of this function's wall-clock time goes on object storage.\n */\nconst PREFETCH_WINDOW = 24;\n\nfunction toLastCommitInfo(commit: CommitInfo): LastCommitInfo {\n\treturn {\n\t\tsha: commit.oid,\n\t\tmessage: commit.commit.message.trim(),\n\t\tauthorName: commit.commit.author.name,\n\t\tauthorEmail: commit.commit.author.email,\n\t\tcreatedAt: new Date(commit.commit.author.timestamp * 1000).toISOString(),\n\t};\n}\n\n/**\n * For each direct child of `treePath` (at the tip of `ref`), find the most\n * recent commit that changed it — the tree view's \"last commit\" column. Walks\n * history newest-to-oldest, comparing the directory's tree oid\n * commit-to-commit and only descending one level to diff child oids when\n * something under the directory actually changed. Preserve the two-phase\n * structure (parallel prefetch, then sequential resolve) when touching this —\n * the \"which entries are still unresolved\" state must advance\n * commit-by-commit.\n */\nexport async function getLastCommitsForTree(\n\trepo: Repo,\n\toptions: { ref: string; treePath?: string; depth?: number },\n\thooks?: OpsHooks,\n): Promise<Record<string, LastCommitInfo>> {\n\tconst treePath = options.treePath ?? \"\";\n\tconst depth = options.depth ?? HISTORY_WALK_DEPTH;\n\tconst commits = await runStep(hooks, `getCommitLog depth=${depth}`, () =>\n\t\tgetCommitLog(repo, { ref: options.ref, depth }, hooks),\n\t);\n\thooks?.onNote?.(`getLastCommitsForTree: ${commits.length} commits in log`);\n\tconst head = commits[0];\n\tif (!head) return {};\n\n\tconst cacheKey = `last-commits:${repo.gitdir}:${head.oid}:${treePath}`;\n\tconst cachedResult =\n\t\thooks?.resultCache?.get<Record<string, LastCommitInfo>>(cacheKey);\n\tif (cachedResult) {\n\t\thooks?.onNote?.(\n\t\t\t\"getLastCommitsForTree: result-cache HIT, skipping history walk\",\n\t\t);\n\t\treturn cachedResult;\n\t}\n\thooks?.onNote?.(\"getLastCommitsForTree: result-cache MISS, walking history\");\n\n\tconst byOid = new Map(commits.map((commit) => [commit.oid, commit]));\n\n\t// In a linear history, the \"parent tree\" resolved at commit[i] is the same\n\t// tree already resolved as the \"current tree\" at commit[i-1] — memoize by\n\t// commit-tree oid (and by resolved dir oid) so each distinct tree is only\n\t// walked/listed once across the whole scan instead of twice per commit.\n\tconst dirOidByCommitTree = new Map<string, string | null>();\n\tconst childrenByDirOid = new Map<\n\t\tstring,\n\t\tAwaited<ReturnType<typeof listTreeEntries>>\n\t>();\n\n\tasync function resolveDirOid(commitTreeOid: string): Promise<string | null> {\n\t\tconst cached = dirOidByCommitTree.get(commitTreeOid);\n\t\tif (cached !== undefined) return cached;\n\t\tconst entry = await findTreeEntry(repo, commitTreeOid, treePath);\n\t\tconst dirOid = entry?.type === \"tree\" ? entry.oid : null;\n\t\tdirOidByCommitTree.set(commitTreeOid, dirOid);\n\t\treturn dirOid;\n\t}\n\n\tasync function resolveChildren(dirOid: string | null) {\n\t\tif (dirOid === null) return [];\n\t\tconst cached = childrenByDirOid.get(dirOid);\n\t\tif (cached) return cached;\n\t\tconst children = await listTreeEntries(repo, dirOid, treePath);\n\t\tchildrenByDirOid.set(dirOid, children);\n\t\treturn children;\n\t}\n\n\tconst headDirOid = await resolveDirOid(head.commit.tree);\n\tif (headDirOid === null) return {};\n\n\t// listTreeEntries is prefixed with treePath so result keys match the full\n\t// paths that callers key their file listing by.\n\tconst headChildren = await resolveChildren(headDirOid);\n\tconst remaining = new Set(headChildren.map((entry) => entry.path));\n\tconst result: Record<string, LastCommitInfo> = {};\n\n\tlet commitsWalked = 0;\n\tlet prefetchWindows = 0;\n\tconst walkStart = performance.now();\n\n\touter: for (\n\t\tlet windowStart = 0;\n\t\twindowStart < commits.length && remaining.size > 0;\n\t\twindowStart += PREFETCH_WINDOW\n\t) {\n\t\tconst windowEnd = Math.min(windowStart + PREFETCH_WINDOW, commits.length);\n\t\t// +1 lookahead commit so the last entry's parent tree is already warm too.\n\t\tconst prefetchEnd = Math.min(windowEnd + 1, commits.length);\n\t\tprefetchWindows++;\n\n\t\t// Phase A: resolve this window's directory oid for every commit's tree in\n\t\t// parallel (a no-op await when treePath is \"\" — the root tree oid IS the\n\t\t// commit tree, no lookup needed).\n\t\tconst dirOids = await Promise.all(\n\t\t\tcommits\n\t\t\t\t.slice(windowStart, prefetchEnd)\n\t\t\t\t.map((commit) => resolveDirOid(commit.commit.tree)),\n\t\t);\n\n\t\t// Phase B: resolve children for every distinct directory oid the window\n\t\t// touched, in parallel — the actual tree-object read for the common case.\n\t\tawait Promise.all(\n\t\t\t[...new Set(dirOids)].map((dirOid) => resolveChildren(dirOid)),\n\t\t);\n\n\t\tfor (let i = windowStart; i < windowEnd; i++) {\n\t\t\tif (remaining.size === 0) break outer;\n\t\t\tconst commit = commits[i];\n\t\t\tif (!commit) break outer;\n\t\t\tcommitsWalked++;\n\n\t\t\tconst parentSha = commit.commit.parent[0];\n\t\t\tconst parentCommit = parentSha ? byOid.get(parentSha) : undefined;\n\t\t\tif (parentSha && !parentCommit) break outer; // walked past the depth cap\n\n\t\t\t// Already resolved above (or memoized from an earlier window/commit) —\n\t\t\t// these awaits resolve immediately from cache.\n\t\t\tconst [dirOid, parentDirOid] = await Promise.all([\n\t\t\t\tresolveDirOid(commit.commit.tree),\n\t\t\t\tparentCommit\n\t\t\t\t\t? resolveDirOid(parentCommit.commit.tree)\n\t\t\t\t\t: Promise.resolve(null),\n\t\t\t]);\n\n\t\t\tif (dirOid === parentDirOid) continue;\n\n\t\t\tconst [children, parentChildren] = await Promise.all([\n\t\t\t\tresolveChildren(dirOid),\n\t\t\t\tresolveChildren(parentDirOid),\n\t\t\t]);\n\t\t\tconst childByName = new Map(\n\t\t\t\tchildren.map((entry) => [entry.path, entry.oid]),\n\t\t\t);\n\t\t\tconst parentChildByName = new Map(\n\t\t\t\tparentChildren.map((entry) => [entry.path, entry.oid]),\n\t\t\t);\n\n\t\t\tfor (const name of remaining) {\n\t\t\t\tif (childByName.get(name) !== parentChildByName.get(name)) {\n\t\t\t\t\tresult[name] = toLastCommitInfo(commit);\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor (const name of Object.keys(result)) {\n\t\t\t\tremaining.delete(name);\n\t\t\t}\n\t\t}\n\t}\n\n\thooks?.onNote?.(\n\t\t`getLastCommitsForTree: walked ${commitsWalked}/${commits.length} commits across ${prefetchWindows} prefetch windows in ${(performance.now() - walkStart).toFixed(1)}ms, ${dirOidByCommitTree.size} unique tree lookups, ${remaining.size} entries never resolved`,\n\t);\n\n\thooks?.resultCache?.set(cacheKey, result);\n\treturn result;\n}\n","import git from \"isomorphic-git\";\nimport { qualifyBranchRef } from \"../refs.js\";\nimport { assertSafeBranchName } from \"./branch.js\";\nimport type { Repo } from \"./types.js\";\n\nexport interface MergeAnalysis {\n\tcanMerge: boolean;\n\thasConflicts: boolean;\n\tconflictingFiles: string[];\n\tfastForward: boolean;\n}\n\n/**\n * Cheap pre-merge check: do both branches exist, and is this a fast-forward?\n * `canMerge`/`fastForward` are the only fields this actually determines —\n * `hasConflicts`/`conflictingFiles` are NOT a real content-conflict check\n * (isomorphic-git's `git.merge` doesn't expose a dry-run), they only ever\n * reflect \"one of the branches couldn't be resolved\" (`canMerge: false`).\n * Real merge conflicts are only discoverable by actually attempting the\n * merge.\n */\nexport async function analyzeMerge(\n\trepo: Repo,\n\tsourceBranch: string,\n\ttargetBranch: string,\n): Promise<MergeAnalysis> {\n\tassertSafeBranchName(sourceBranch);\n\tassertSafeBranchName(targetBranch);\n\n\ttry {\n\t\tconst [sourceOid, targetOid] = await Promise.all([\n\t\t\tgit.resolveRef({ ...repo, ref: qualifyBranchRef(sourceBranch) }),\n\t\t\tgit.resolveRef({ ...repo, ref: qualifyBranchRef(targetBranch) }),\n\t\t]);\n\n\t\tconst isDescendant = await git.isDescendent({\n\t\t\t...repo,\n\t\t\toid: sourceOid,\n\t\t\tancestor: targetOid,\n\t\t});\n\n\t\treturn {\n\t\t\tcanMerge: true,\n\t\t\thasConflicts: false,\n\t\t\tconflictingFiles: [],\n\t\t\tfastForward: isDescendant,\n\t\t};\n\t} catch (err) {\n\t\tif ((err as { code?: string })?.code !== \"NotFoundError\") {\n\t\t\tthrow err;\n\t\t}\n\t\t// A branch ref failed to resolve — not a content conflict, just \"can't\n\t\t// merge because one side doesn't exist.\" hasConflicts here is a\n\t\t// misnomer kept for MergeAnalysis's existing shape; canMerge is the\n\t\t// field that actually matters to callers.\n\t\treturn {\n\t\t\tcanMerge: false,\n\t\t\thasConflicts: true,\n\t\t\tconflictingFiles: [],\n\t\t\tfastForward: false,\n\t\t};\n\t}\n}\n\n/**\n * Attempt a fast-forward merge directly against the bare repo: when source is\n * a descendant of target, just move the target ref — no worktree, no new\n * commit. Returns null when the merge is not a fast-forward (callers fall\n * back to a real three-way merge, which needs a worktree). Serialize with a\n * per-repo lock: resolve → writeRef is not atomic.\n */\nexport async function fastForwardMerge(\n\trepo: Repo,\n\tsourceBranch: string,\n\ttargetBranch: string,\n): Promise<{ success: true; commitSha: string } | null> {\n\tassertSafeBranchName(sourceBranch);\n\tassertSafeBranchName(targetBranch);\n\tconst [sourceOid, targetOid] = await Promise.all([\n\t\tgit.resolveRef({ ...repo, ref: `refs/heads/${sourceBranch}` }),\n\t\tgit.resolveRef({ ...repo, ref: `refs/heads/${targetBranch}` }),\n\t]);\n\tconst isFF = await git.isDescendent({\n\t\t...repo,\n\t\toid: sourceOid,\n\t\tancestor: targetOid,\n\t});\n\tif (!isFF) return null;\n\tawait git.writeRef({\n\t\t...repo,\n\t\tref: `refs/heads/${targetBranch}`,\n\t\tvalue: sourceOid,\n\t\tforce: true,\n\t});\n\treturn { success: true, commitSha: sourceOid };\n}\n"],"mappings":"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","names":["git","git","git","git","git","git","oidA","oidB","files","HISTORY_WALK_DEPTH","PREFETCH_WINDOW","git","git"]}
@@ -2,7 +2,7 @@ import type { Repo } from "../ops/types.js";
2
2
  import { type GitHttpResult, type HttpHooks } from "./types.js";
3
3
  export type GitService = "git-upload-pack" | "git-receive-pack";
4
4
  /** All refs with resolved oids, plus the symref HEAD should advertise. */
5
- export declare function listAllRefs(repo: Repo, defaultBranch?: string): Promise<{
5
+ export declare function listAllRefs(repo: Repo, defaultBranch: string): Promise<{
6
6
  refs: {
7
7
  name: string;
8
8
  oid: string;
@@ -11,7 +11,8 @@ export declare function listAllRefs(repo: Repo, defaultBranch?: string): Promise
11
11
  }>;
12
12
  export interface InfoRefsOptions {
13
13
  service: GitService;
14
- defaultBranch?: string;
14
+ /** Advertised as HEAD when HEAD itself doesn't name an existing branch. */
15
+ defaultBranch: string;
15
16
  /** Advertised in the agent capability. Default "git-fs-s3". */
16
17
  agent?: string;
17
18
  }
@@ -17,7 +17,7 @@ export declare function parseReceivePackBody(body: Uint8Array): {
17
17
  packData: Uint8Array;
18
18
  };
19
19
  /** Initialize the repo when its HEAD doesn't exist yet (first push). */
20
- export declare function ensureRepoInitialized(repo: Repo, defaultBranch?: string): Promise<void>;
20
+ export declare function ensureRepoInitialized(repo: Repo, defaultBranch: string): Promise<void>;
21
21
  /** Write the incoming PACK into objects/pack/ and index it. */
22
22
  export declare function indexIncomingPack(repo: Repo, packData: Uint8Array, hooks?: HttpHooks): Promise<string[]>;
23
23
  /**
@@ -29,7 +29,8 @@ export declare function applyRefUpdates(repo: Repo, refUpdates: RefUpdateCommand
29
29
  /** The report-status response body for a set of ref-update results. */
30
30
  export declare function receivePackResponse(results: RefUpdateResult[]): GitHttpResult;
31
31
  export interface ApplyReceivePackOptions {
32
- defaultBranch?: string;
32
+ /** HEAD's branch when a push initializes an empty repository. */
33
+ defaultBranch: string;
33
34
  /**
34
35
  * Repack tuning, or `false` to skip repacking entirely. The returned
35
36
  * `stalePackPaths` are gitdir-relative pack files the repack deleted from
@@ -48,7 +49,7 @@ export interface ApplyReceivePackOptions {
48
49
  export declare function applyReceivePack(repo: Repo, parsed: {
49
50
  refUpdates: RefUpdateCommand[];
50
51
  packData: Uint8Array;
51
- }, options?: ApplyReceivePackOptions, hooks?: HttpHooks): Promise<{
52
+ }, options: ApplyReceivePackOptions, hooks?: HttpHooks): Promise<{
52
53
  results: RefUpdateResult[];
53
54
  stalePackPaths: string[];
54
55
  }>;
@@ -31,7 +31,7 @@ export declare function resolveLooseRefFast(repo: Repo, ref: string): Promise<st
31
31
  /** All branches with their tip commits; [] for an empty repository. */
32
32
  export declare function listBranches(repo: Repo): Promise<Branch[]>;
33
33
  /** Create `name` pointing at the tip of `startPoint` (no checkout). */
34
- export declare function createBranchFrom(repo: Repo, name: string, startPoint?: string): Promise<void>;
34
+ export declare function createBranchFrom(repo: Repo, name: string, startPoint: string): Promise<void>;
35
35
  /** Delete a branch ref (validated — deleteBranch has no internal ref check). */
36
36
  export declare function deleteBranchByName(repo: Repo, name: string): Promise<void>;
37
37
  /** Throws (NotFoundError) unless the branch resolves. */
@@ -38,11 +38,11 @@ export declare function resolveCommit(repo: Repo, ref: string): Promise<{
38
38
  /** Read a blob's bytes by oid. */
39
39
  export declare function getBlob(repo: Repo, sha: string): Promise<Uint8Array>;
40
40
  /** Read a file's bytes at a ref. Throws GitPathNotFoundError when absent. */
41
- export declare function getFileContent(repo: Repo, filePath: string, ref?: string, hooks?: OpsHooks): Promise<Uint8Array>;
41
+ export declare function getFileContent(repo: Repo, filePath: string, ref: string, hooks?: OpsHooks): Promise<Uint8Array>;
42
42
  /** Read one commit by sha. */
43
43
  export declare function getCommit(repo: Repo, sha: string): Promise<CommitInfo>;
44
44
  export interface CommitLogOptions {
45
- ref?: string;
45
+ ref: string;
46
46
  depth?: number;
47
47
  /**
48
48
  * Pass the head sha when the caller already resolved `ref` — resolveRef
@@ -58,7 +58,7 @@ export interface CommitLogOptions {
58
58
  * per head is memoized in `hooks.resultCache` and sliced for shallower or
59
59
  * repeated requests — don't bypass this by calling `git.log` directly.
60
60
  */
61
- export declare function getCommitLog(repo: Repo, options?: CommitLogOptions, hooks?: OpsHooks): Promise<CommitInfo[]>;
61
+ export declare function getCommitLog(repo: Repo, options: CommitLogOptions, hooks?: OpsHooks): Promise<CommitInfo[]>;
62
62
  /** A file at a ref, decoded for display: utf8 text or base64 when binary. */
63
63
  export declare function getFileFromRef(repo: Repo, filePath: string, ref: string, hooks?: OpsHooks): Promise<{
64
64
  content: string;
@@ -66,12 +66,13 @@ export declare function getFileFromRef(repo: Repo, filePath: string, ref: string
66
66
  isBinary: boolean;
67
67
  }>;
68
68
  /**
69
- * List a directory at the tip of a ref. Returns [] for an empty repo/unborn
70
- * branch; throws GitPathNotFoundError when `treePath` doesn't exist. Results
71
- * are memoized per head sha (auto-invalidates on push).
69
+ * List a directory at the tip of a ref. Throws GitRefNotFoundError when the
70
+ * ref doesn't resolve (including an empty repo's unborn branch) and
71
+ * GitPathNotFoundError when `treePath` doesn't exist. Results are memoized per
72
+ * head sha (auto-invalidates on push).
72
73
  */
73
- export declare function getTreeFromRef(repo: Repo, options?: {
74
- ref?: string;
74
+ export declare function getTreeFromRef(repo: Repo, options: {
75
+ ref: string;
75
76
  treePath?: string;
76
77
  }, hooks?: OpsHooks): Promise<TreeEntry[]>;
77
78
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "git-fs-s3",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "isomorphic-git filesystem backend for S3-compatible object storage (AWS S3, Cloudflare R2, MinIO) — run git servers without a disk",
5
5
  "type": "module",
6
6
  "exports": {