git-fs-s3 0.9.0 → 0.10.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.
package/README.md CHANGED
@@ -266,6 +266,25 @@ const { entries } = await getCommitLog(repo, { ref: "main", depth: 50 });
266
266
 
267
267
  `resultKeyPrefixes` (from `/ops`) lists the `ResultCache` key prefixes these functions write under — evict them after rewriting a repo's storage out of band (bulk sync, rename), or a cached walk result outlives the history it describes.
268
268
 
269
+ ## Submodules
270
+
271
+ Tree entries retain Git's `type: "commit"` and mode `160000` for submodules.
272
+ HTTP reachability, receive-pack validation, upload-pack and repack treat these
273
+ as external pointers and never try to read the submodule commit in the parent.
274
+ `getFileHistory` tracks pointer changes; `getFileContent` only reads blobs.
275
+
276
+ `getCommitDiff` and `getDiffBetweenRefs` emit `kind: "submodule"` for additions,
277
+ removals, updates and conversions to/from blobs or trees. `oldEntry`/`newEntry`
278
+ carry the object ID, type and mode. Pointer changes do not count as text line
279
+ additions/deletions. Diff results also include the exact `oldRef` (parent or
280
+ merge base, absent for an empty base) and `newRef` used for navigation.
281
+
282
+ `getSubmodules(repo, ref, hooks?)` reads root `.gitmodules` at that revision;
283
+ `parseGitmodules(text)` parses its named path/URL records without I/O. Neither
284
+ expands includes nor follows URLs. Missing/oversized config returns no records;
285
+ malformed records and ambiguous duplicate paths are ignored. Browser URL
286
+ validation and authorization to other repositories belong to the application.
287
+
269
288
  ## Semantics & limitations
270
289
 
271
290
  - **Bare repositories are the target.** Plumbing (`writeBlob`/`writeTree`/`writeCommit`/`readCommit`/`log`/refs/branches) is covered by the test suite. Worktree operations (`checkout`, `add`, `status`) want a real disk — hydrate to `/tmp` for those.
package/dist/ops.js CHANGED
@@ -526,6 +526,37 @@ function buildDiffFile(path, before, after) {
526
526
  newSize: after?.bytes.length
527
527
  };
528
528
  }
529
+ async function readDiffEntry(entry) {
530
+ return entry ? {
531
+ type: await entry.type(),
532
+ oid: await entry.oid(),
533
+ mode: await entry.mode()
534
+ } : void 0;
535
+ }
536
+ function buildSubmoduleDiff(filepath, before, after) {
537
+ if (before?.type === after?.type && before?.oid === after?.oid)
538
+ return void 0;
539
+ const line = (entry) => entry ? `${entry.type === "commit" ? "Subproject commit" : entry.type === "tree" ? "Directory" : "File"} ${entry.oid}
540
+ ` : "";
541
+ return {
542
+ path: filepath,
543
+ status: !before ? "added" : !after ? "deleted" : "modified",
544
+ kind: "submodule",
545
+ additions: 0,
546
+ deletions: 0,
547
+ oldEntry: before,
548
+ newEntry: after,
549
+ patch: createTwoFilesPatch(
550
+ before ? `a/${filepath}` : "/dev/null",
551
+ after ? `b/${filepath}` : "/dev/null",
552
+ line(before),
553
+ line(after),
554
+ "",
555
+ "",
556
+ { context: 3 }
557
+ )
558
+ };
559
+ }
529
560
  async function diffTrees(repo, oldTreeOid, newTreeOid) {
530
561
  const readSide = async (entry) => {
531
562
  if (!entry || await entry.type() !== "blob") return null;
@@ -542,6 +573,13 @@ async function diffTrees(repo, oldTreeOid, newTreeOid) {
542
573
  oldEntry?.type(),
543
574
  newEntry?.type()
544
575
  ]);
576
+ if (oldType === "commit" || newType === "commit") {
577
+ const [before2, after2] = await Promise.all([
578
+ readDiffEntry(oldEntry),
579
+ readDiffEntry(newEntry)
580
+ ]);
581
+ return buildSubmoduleDiff(filepath, before2, after2);
582
+ }
545
583
  if (oldType !== "blob" && newType !== "blob") return void 0;
546
584
  if (oldEntry && newEntry && oldType === newType) {
547
585
  const [oldOid, newOid] = await Promise.all([
@@ -569,7 +607,11 @@ async function getCommitDiff(repo, commitSha) {
569
607
  const { commit } = await getCommit(repo, commitSha);
570
608
  const parent = commit.parent[0];
571
609
  const parentTree = parent ? (await getCommit(repo, parent)).commit.tree : null;
572
- return diffTrees(repo, parentTree, commit.tree);
610
+ return {
611
+ ...await diffTrees(repo, parentTree, commit.tree),
612
+ oldRef: parent,
613
+ newRef: commitSha
614
+ };
573
615
  }
574
616
  async function getDiffBetweenRefs(repo, baseRef, compareRef) {
575
617
  const [base, compare] = await Promise.all([
@@ -581,7 +623,11 @@ async function getDiffBetweenRefs(repo, baseRef, compareRef) {
581
623
  oids: [base.oid, compare.oid]
582
624
  });
583
625
  const baseTree = mergeBase === void 0 ? null : mergeBase === base.oid ? base.commit.tree : (await getCommit(repo, mergeBase)).commit.tree;
584
- return diffTrees(repo, baseTree, compare.commit.tree);
626
+ return {
627
+ ...await diffTrees(repo, baseTree, compare.commit.tree),
628
+ oldRef: mergeBase,
629
+ newRef: compare.oid
630
+ };
585
631
  }
586
632
 
587
633
  // src/ops/file-history.ts
@@ -611,7 +657,7 @@ async function getFileHistory(repo, options, hooks) {
611
657
  const cachedOid = oidByCommitTree.get(commitTreeOid);
612
658
  if (cachedOid !== void 0) return cachedOid;
613
659
  const entry = await findTreeEntry(repo, commitTreeOid, options.filePath);
614
- const oid = entry?.type === "blob" ? entry.oid : null;
660
+ const oid = entry && entry.type !== "tree" ? entry.oid : null;
615
661
  oidByCommitTree.set(commitTreeOid, oid);
616
662
  return oid;
617
663
  }
@@ -806,6 +852,97 @@ async function fastForwardMerge(repo, sourceBranch, targetBranch) {
806
852
  });
807
853
  return { success: true, commitSha: sourceOid };
808
854
  }
855
+
856
+ // src/ops/submodules.ts
857
+ function parseValue(raw) {
858
+ let value = "";
859
+ let spaces = "";
860
+ let quoted = false;
861
+ const escapes = {
862
+ n: "\n",
863
+ t: " ",
864
+ b: "\b",
865
+ "\\": "\\",
866
+ '"': '"'
867
+ };
868
+ for (let i = 0; i < raw.length; i++) {
869
+ const c = raw.charAt(i);
870
+ if (c === "\\") {
871
+ const escaped = escapes[raw.charAt(++i)];
872
+ if (escaped === void 0) return null;
873
+ value += spaces + escaped;
874
+ spaces = "";
875
+ } else if (c === '"') {
876
+ value += spaces;
877
+ spaces = "";
878
+ quoted = !quoted;
879
+ } else if (!quoted && (c === "#" || c === ";")) break;
880
+ else if (!quoted && /\s/.test(c)) {
881
+ if (value) spaces += c;
882
+ } else {
883
+ value += spaces + c;
884
+ spaces = "";
885
+ }
886
+ }
887
+ return quoted ? null : value;
888
+ }
889
+ function parseGitmodules(content) {
890
+ const sections = /* @__PURE__ */ new Map();
891
+ let current;
892
+ const lines = [];
893
+ let pending = "";
894
+ for (const line of content.replace(/\r\n/g, "\n").split("\n")) {
895
+ const slashes = /\\+$/.exec(line)?.[0].length ?? 0;
896
+ if (slashes % 2 === 1) pending += line.slice(0, -1);
897
+ else {
898
+ lines.push(pending + line);
899
+ pending = "";
900
+ }
901
+ }
902
+ for (const line of lines) {
903
+ const trimmed = line.trim();
904
+ if (!trimmed || /^[#;]/.test(trimmed)) continue;
905
+ if (trimmed.startsWith("[")) {
906
+ const match = /^\[submodule\s+"((?:[^"\\]|\\["\\])*)"\]\s*(?:[#;].*)?$/i.exec(
907
+ trimmed
908
+ );
909
+ current = void 0;
910
+ if (!match?.[1]) continue;
911
+ const name = match[1].replace(/\\(["\\])/g, "$1");
912
+ current = sections.get(name) ?? { name };
913
+ sections.set(name, current);
914
+ continue;
915
+ }
916
+ if (!current) continue;
917
+ const field = /^(path|url)\s*=\s*(.*)$/i.exec(trimmed);
918
+ if (!field?.[1] || field[2] === void 0) continue;
919
+ const key = field[1].toLowerCase();
920
+ const value = parseValue(field[2]);
921
+ if (value === null) delete current[key];
922
+ else current[key] = value;
923
+ }
924
+ const valid = [...sections.values()].filter(
925
+ (s) => !!s.name && !!s.path && !!s.url && ![...s.path + s.url].some(
926
+ (c) => c.charCodeAt(0) < 32 || c.charCodeAt(0) === 127
927
+ ) && !s.path.startsWith("/") && !s.path.includes("\\") && s.path.split("/").every(
928
+ (p) => p !== "" && p !== "." && p !== ".." && p.toLowerCase() !== ".git"
929
+ )
930
+ );
931
+ const counts = /* @__PURE__ */ new Map();
932
+ for (const s of valid) counts.set(s.path, (counts.get(s.path) ?? 0) + 1);
933
+ return valid.filter((s) => counts.get(s.path) === 1);
934
+ }
935
+ async function getSubmodules(repo, ref, hooks) {
936
+ let bytes;
937
+ try {
938
+ bytes = await getFileContent(repo, ".gitmodules", ref, hooks);
939
+ } catch (error) {
940
+ if (error instanceof GitPathNotFoundError) return [];
941
+ throw error;
942
+ }
943
+ if (bytes.length > 1024 * 1024) return [];
944
+ return parseGitmodules(new TextDecoder().decode(bytes));
945
+ }
809
946
  export {
810
947
  BANNER_WALK_DEPTH,
811
948
  HISTORY_WALK_DEPTH,
@@ -830,9 +967,11 @@ export {
830
967
  getFileContent,
831
968
  getFileHistory,
832
969
  getLastCommitsForTree,
970
+ getSubmodules,
833
971
  getTreeFromRef,
834
972
  listBranches,
835
973
  listTreeEntries,
974
+ parseGitmodules,
836
975
  resolveCommit,
837
976
  resolveLooseRefFast,
838
977
  resultKeyPrefixes,
package/dist/ops.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/ops/commit.ts","../src/ops/packed-writes.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 { GitInvalidRequestError, GitPathNotFoundError } from \"../git-errors.js\";\nimport { isSafeTreePath } from \"../refs.js\";\nimport { assertSafeBranchName } from \"./branch.js\";\nimport { withPackedWrites } from \"./packed-writes.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 * Every object the commit creates (blobs, trees, the commit) is stored as one\n * pack (see {@link withPackedWrites}), written before the ref.\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\t/**\n\t\t * Writes go through `writeRepo` (the packed-writes view of the repo),\n\t\t * never the outer repo — objects written around it would bypass the\n\t\t * pack and land loose.\n\t\t */\n\t\tbuildTree: (\n\t\t\tparentTreeOid: string | undefined,\n\t\t\twriteRepo: Repo,\n\t\t) => Promise<string>;\n\t},\n): Promise<string> {\n\tassertSafeBranchName(options.branch);\n\treturn withPackedWrites(repo, (packed) => writeCommit(packed, options));\n}\n\nasync function writeCommit(\n\trepo: Repo,\n\toptions: Parameters<typeof writeCommitToBare>[1],\n): Promise<string> {\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, repo);\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 async 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\tconst paths = new Set<string>();\n\tfor (const file of options.files) {\n\t\tif (!isSafeTreePath(file.path)) {\n\t\t\tthrow new GitInvalidRequestError(`Invalid file path: ${file.path}`);\n\t\t}\n\t\tif (paths.has(file.path)) {\n\t\t\tthrow new GitInvalidRequestError(`Duplicate file path: ${file.path}`);\n\t\t}\n\t\tpaths.add(file.path);\n\t}\n\tif (paths.size === 0) {\n\t\tthrow new GitInvalidRequestError(\"A commit needs at least one file\");\n\t}\n\treturn writeCommitToBare(repo, {\n\t\tbranch: options.branch,\n\t\tmessage: options.message,\n\t\tauthor: options.author,\n\t\tbuildTree: async (parentTreeOid, writeRepo) => {\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({ ...writeRepo, 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(writeRepo, 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 async 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\tif (!isSafeTreePath(options.filePath)) {\n\t\tthrow new GitInvalidRequestError(`Invalid file path: ${options.filePath}`);\n\t}\n\treturn writeCommitToBare(repo, {\n\t\tbranch: options.branch,\n\t\tmessage: options.message,\n\t\tauthor: options.author,\n\t\tbuildTree: async (parentTreeOid, writeRepo) => {\n\t\t\tif (!parentTreeOid) {\n\t\t\t\tthrow new GitPathNotFoundError(`Branch ${options.branch} is empty`);\n\t\t\t}\n\t\t\treturn deleteFromTree(writeRepo, parentTreeOid, options.filePath);\n\t\t},\n\t});\n}\n","/**\n * Packed writes: run git plumbing (writeBlob/writeTree/writeCommit, and\n * anything built on them) against a repository, but store the objects it\n * creates as one pack instead of one loose object each.\n *\n * Why it matters on object storage: one PUT per object becomes two per\n * operation (pack + index), and a repository that is never given loose\n * objects stays \"fully packed\" forever — which is what makes the loose-object\n * hint (`GitFs.detectLooseObjects`) safe across processes. A process that\n * once saw \"no loose objects\" in a gitdir can't be told when another process\n * writes one; if nothing ever writes loose objects, that answer stays true.\n *\n * Ordering is preserved: the pending objects are flushed (pack, then its\n * index) before any other file is written — so a ref written inside `fn`\n * never becomes visible before the objects it names.\n */\nimport { inflate } from \"../edge-utils.js\";\nimport { parseLooseObjectPath } from \"../git-layout.js\";\nimport {\n\tencodePack,\n\ttype PackObject,\n\ttype PackObjectType,\n\tstorePack,\n} from \"../pack-write.js\";\nimport type { IsoGitFs, Repo } from \"./types.js\";\n\ninterface PromiseFs {\n\treadFile(path: string, options?: unknown): Promise<Uint8Array | string>;\n\twriteFile(\n\t\tpath: string,\n\t\tdata: Uint8Array | string,\n\t\toptions?: unknown,\n\t): Promise<void>;\n\tstat(path: string): Promise<unknown>;\n\tlstat(path: string): Promise<unknown>;\n\t[key: string]: unknown;\n}\n\nconst OBJECT_TYPES = new Set<PackObjectType>([\"commit\", \"tree\", \"blob\", \"tag\"]);\n\n/** Split a loose object's inflated bytes (\"<type> <size>\\0<content>\"). */\nfunction parseLooseObject(raw: Uint8Array<ArrayBuffer>): PackObject {\n\tconst nul = raw.indexOf(0);\n\tconst header = new TextDecoder().decode(raw.subarray(0, nul));\n\tconst [type, size] = header.split(\" \");\n\tconst content = raw.slice(nul + 1);\n\tif (\n\t\t!OBJECT_TYPES.has(type as PackObjectType) ||\n\t\tNumber(size) !== content.length\n\t) {\n\t\tthrow new Error(`packed writes: malformed object header \"${header}\"`);\n\t}\n\treturn { type: type as PackObjectType, content };\n}\n\nfunction fileStat(size: number) {\n\tconst epoch = new Date(0);\n\treturn {\n\t\ttype: \"file\",\n\t\tmode: 0o100644,\n\t\tsize,\n\t\tino: 0,\n\t\tmtimeMs: 0,\n\t\tctimeMs: 0,\n\t\tuid: 0,\n\t\tgid: 0,\n\t\tdev: 0,\n\t\tmtime: epoch,\n\t\tctime: epoch,\n\t\tisFile: () => true,\n\t\tisDirectory: () => false,\n\t\tisSymbolicLink: () => false,\n\t};\n}\n\n/**\n * Run `fn` with a repo whose new objects are buffered and stored as a single\n * pack. Objects are flushed before any non-object write inside `fn`, and once\n * more when `fn` resolves; if `fn` throws, still-unflushed objects are\n * discarded (they'd be unreachable anyway). Serialize concurrent writers to\n * the same repository externally, as for any write.\n */\nexport async function withPackedWrites<T>(\n\trepo: Repo,\n\tfn: (packedRepo: Repo) => Promise<T>,\n): Promise<T> {\n\tconst base = (repo.fs as unknown as { promises: PromiseFs }).promises;\n\tconst pending = new Map<string, Uint8Array<ArrayBuffer>>(); // path → deflated loose bytes\n\n\tasync function flush(): Promise<void> {\n\t\tif (pending.size === 0) return;\n\t\tconst objects = await Promise.all(\n\t\t\t[...pending.values()].map(async (deflated) =>\n\t\t\t\tparseLooseObject(await inflate(deflated)),\n\t\t\t),\n\t\t);\n\t\tawait storePack(repo, await encodePack(objects));\n\t\tpending.clear();\n\t}\n\n\tconst promises: PromiseFs = new Proxy(base, {\n\t\tget(target, property, receiver) {\n\t\t\tswitch (property) {\n\t\t\t\tcase \"writeFile\":\n\t\t\t\t\treturn async (\n\t\t\t\t\t\tpath: string,\n\t\t\t\t\t\tdata: Uint8Array | string,\n\t\t\t\t\t\toptions?: unknown,\n\t\t\t\t\t) => {\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\tparseLooseObjectPath(path) !== null &&\n\t\t\t\t\t\t\ttypeof data !== \"string\"\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\tpending.set(path, new Uint8Array(data));\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tawait flush();\n\t\t\t\t\t\treturn target.writeFile(path, data, options);\n\t\t\t\t\t};\n\t\t\t\tcase \"readFile\":\n\t\t\t\t\treturn async (path: string, options?: unknown) => {\n\t\t\t\t\t\tconst data = pending.get(path);\n\t\t\t\t\t\tif (data === undefined) return target.readFile(path, options);\n\t\t\t\t\t\treturn data;\n\t\t\t\t\t};\n\t\t\t\tcase \"stat\":\n\t\t\t\tcase \"lstat\":\n\t\t\t\t\treturn async (path: string) => {\n\t\t\t\t\t\tconst data = pending.get(path);\n\t\t\t\t\t\tif (data !== undefined) return fileStat(data.length);\n\t\t\t\t\t\treturn target[property](path);\n\t\t\t\t\t};\n\t\t\t\tdefault: {\n\t\t\t\t\tconst value = Reflect.get(target, property, receiver);\n\t\t\t\t\treturn typeof value === \"function\" ? value.bind(target) : value;\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t});\n\n\tconst packedRepo: Repo = { ...repo, fs: { promises } as unknown as IsoGitFs };\n\tconst result = await fn(packedRepo);\n\tawait flush();\n\treturn result;\n}\n","import git from \"isomorphic-git\";\nimport { GitInvalidRequestError, GitPathNotFoundError } from \"../git-errors.js\";\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/** File modes a blob entry can carry; anything else isn't a regular file. */\nconst BLOB_MODES = new Set([\"100644\", \"100755\", \"120000\"]);\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 *\n * An updated file keeps its existing mode (executable bit, symlink); a path\n * that names an existing directory, or runs through an existing file, is\n * rejected rather than silently replacing what's there.\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\tconst current = byName.get(name);\n\t\tif (current && current.type !== \"blob\") {\n\t\t\tthrow new GitInvalidRequestError(`\"${name}\" is a directory, not a file`);\n\t\t}\n\t\tbyName.set(name, {\n\t\t\tmode: current && BLOB_MODES.has(current.mode) ? current.mode : \"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\tif (entry && entry.type !== \"tree\") {\n\t\t\t\tthrow new GitInvalidRequestError(`\"${dir}\" is a file, not a directory`);\n\t\t\t}\n\t\t\tconst subtreeOid = entry?.oid;\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/**\n * Remove a file path from a tree, returning the new root tree oid. Throws\n * GitPathNotFoundError when there's no such file; directories left empty\n * are dropped, as git never stores an empty subtree.\n */\nexport async function deleteFromTree(\n\trepo: Repo,\n\ttreeOid: string,\n\tfilePath: string,\n): Promise<string> {\n\tconst result = await deleteFromSubtree(repo, treeOid, filePath, filePath);\n\treturn result ?? git.writeTree({ ...repo, tree: [] });\n}\n\n/** The new subtree oid, or null when removing the file left it empty. */\nasync function deleteFromSubtree(\n\trepo: Repo,\n\ttreeOid: string,\n\tfilePath: string,\n\tfullPath: string,\n): Promise<string | null> {\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\tconst name = slash === -1 ? filePath : filePath.slice(0, slash);\n\tconst entry = byName.get(name);\n\tif (slash === -1) {\n\t\tif (entry?.type !== \"blob\") {\n\t\t\tthrow new GitPathNotFoundError(`No such file: ${fullPath}`);\n\t\t}\n\t\tbyName.delete(name);\n\t} else {\n\t\tif (entry?.type !== \"tree\") {\n\t\t\tthrow new GitPathNotFoundError(`No such file: ${fullPath}`);\n\t\t}\n\t\tconst newOid = await deleteFromSubtree(\n\t\t\trepo,\n\t\t\tentry.oid,\n\t\t\tfilePath.slice(slash + 1),\n\t\t\tfullPath,\n\t\t);\n\t\tif (newOid === null) byName.delete(name);\n\t\telse byName.set(name, { ...entry, oid: newOid });\n\t}\n\tif (byName.size === 0) return null;\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, { type WalkerEntry } from \"isomorphic-git\";\nimport { readBlobContent } from \"../edge-utils.js\";\nimport { getCommit, resolveCommit } from \"./history.js\";\nimport type { Repo } from \"./types.js\";\n\n/** Text files larger than this get no patch (kind \"too-large\"). */\nexport const MAX_DIFF_TEXT_BYTES = 1024 * 1024;\n\nexport interface DiffFile {\n\tpath: string;\n\tstatus: \"added\" | \"modified\" | \"deleted\";\n\t/**\n\t * \"text\" has a unified `patch`; \"binary\" (a NUL byte on either side) and\n\t * \"too-large\" (text over MAX_DIFF_TEXT_BYTES) don't — fetch the sides by\n\t * path at each ref if they need showing.\n\t */\n\tkind: \"text\" | \"binary\" | \"too-large\";\n\tadditions: number;\n\tdeletions: number;\n\tpatch: string;\n\t/** Byte size before the change; absent for an added file. */\n\toldSize?: number;\n\t/** Byte size after the change; absent for a deleted file. */\n\tnewSize?: number;\n}\n\nexport interface DiffResult {\n\tfiles: DiffFile[];\n\ttotalAdditions: number;\n\ttotalDeletions: number;\n\ttotalFiles: number;\n}\n\ntype BlobContent = ReturnType<typeof readBlobContent>;\n\nfunction countContentLines(content: string): number {\n\tif (content.length === 0) return 0;\n\tconst lines = content.split(\"\\n\");\n\tif (lines.at(-1) === \"\") lines.pop();\n\treturn lines.length;\n}\n\nfunction unifiedPatch(\n\tpath: string,\n\tbefore: BlobContent | null,\n\tafter: BlobContent | null,\n): string {\n\tconst body = createTwoFilesPatch(\n\t\tbefore ? `a/${path}` : \"/dev/null\",\n\t\tafter ? `b/${path}` : \"/dev/null\",\n\t\tbefore?.text ?? \"\",\n\t\tafter?.text ?? \"\",\n\t\t\"\",\n\t\t\"\",\n\t\t{ context: 3 },\n\t).replace(/^=+\\n/, \"\");\n\treturn `diff --git a/${path} b/${path}\\n${body}`;\n}\n\n/** One changed path: `before` null for an addition, `after` null for a deletion. */\nfunction buildDiffFile(\n\tpath: string,\n\tbefore: BlobContent | null,\n\tafter: BlobContent | null,\n): DiffFile {\n\tconst kind: DiffFile[\"kind\"] =\n\t\tbefore?.isBinary || after?.isBinary\n\t\t\t? \"binary\"\n\t\t\t: Math.max(before?.bytes.length ?? 0, after?.bytes.length ?? 0) >\n\t\t\t\t\tMAX_DIFF_TEXT_BYTES\n\t\t\t\t? \"too-large\"\n\t\t\t\t: \"text\";\n\tconst isText = kind === \"text\";\n\treturn {\n\t\tpath,\n\t\tstatus: !before ? \"added\" : !after ? \"deleted\" : \"modified\",\n\t\tkind,\n\t\tadditions: isText && after ? countContentLines(after.text) : 0,\n\t\tdeletions: isText && before ? countContentLines(before.text) : 0,\n\t\tpatch: isText ? unifiedPatch(path, before, after) : \"\",\n\t\toldSize: before?.bytes.length,\n\t\tnewSize: after?.bytes.length,\n\t};\n}\n\n/**\n * Every file that differs between two trees; `oldTreeOid` null diffs against\n * nothing (a root commit: every file is an addition).\n */\nasync function diffTrees(\n\trepo: Repo,\n\toldTreeOid: string | null,\n\tnewTreeOid: string,\n): Promise<DiffResult> {\n\tconst readSide = async (entry: WalkerEntry | null) => {\n\t\tif (!entry || (await entry.type()) !== \"blob\") return null;\n\t\tconst { blob } = await git.readBlob({ ...repo, oid: await entry.oid() });\n\t\treturn readBlobContent(blob);\n\t};\n\tconst trees = oldTreeOid\n\t\t? [git.TREE({ ref: oldTreeOid }), git.TREE({ ref: newTreeOid })]\n\t\t: [git.TREE({ ref: newTreeOid })];\n\tconst changes: (DiffFile | undefined)[] = await git.walk({\n\t\t...repo,\n\t\ttrees,\n\t\tmap: async (filepath, entries) => {\n\t\t\tconst [oldEntry, newEntry] = oldTreeOid\n\t\t\t\t? [entries[0] ?? null, entries[1] ?? null]\n\t\t\t\t: [null, entries[0] ?? null];\n\t\t\tconst [oldType, newType] = await Promise.all([\n\t\t\t\toldEntry?.type(),\n\t\t\t\tnewEntry?.type(),\n\t\t\t]);\n\t\t\t// Directories recurse; only blobs become diff entries.\n\t\t\tif (oldType !== \"blob\" && newType !== \"blob\") return undefined;\n\t\t\tif (oldEntry && newEntry && oldType === newType) {\n\t\t\t\tconst [oldOid, newOid] = await Promise.all([\n\t\t\t\t\toldEntry.oid(),\n\t\t\t\t\tnewEntry.oid(),\n\t\t\t\t]);\n\t\t\t\tif (oldOid === newOid) return undefined;\n\t\t\t}\n\t\t\tconst [before, after] = await Promise.all([\n\t\t\t\treadSide(oldEntry),\n\t\t\t\treadSide(newEntry),\n\t\t\t]);\n\t\t\treturn before || after\n\t\t\t\t? buildDiffFile(filepath, before, after)\n\t\t\t\t: undefined;\n\t\t},\n\t});\n\tconst files = (changes ?? []).filter((file): file is DiffFile => !!file);\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\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\tconst { commit } = await getCommit(repo, commitSha);\n\tconst parent = commit.parent[0];\n\tconst parentTree = parent\n\t\t? (await getCommit(repo, parent)).commit.tree\n\t\t: null;\n\treturn diffTrees(repo, parentTree, commit.tree);\n}\n\n/**\n * What `compareRef` changed since it diverged from `baseRef` — a pull\n * request's diff (git's three-dot `base...compare`): compared against their\n * merge base, so commits that landed on `baseRef` since the branch point\n * don't show up as reverted. Throws GitRefNotFoundError for a missing ref.\n */\nexport async function getDiffBetweenRefs(\n\trepo: Repo,\n\tbaseRef: string,\n\tcompareRef: string,\n): Promise<DiffResult> {\n\tconst [base, compare] = await Promise.all([\n\t\tresolveCommit(repo, baseRef),\n\t\tresolveCommit(repo, compareRef),\n\t]);\n\tconst [mergeBase] = await git.findMergeBase({\n\t\t...repo,\n\t\toids: [base.oid, compare.oid],\n\t});\n\tconst baseTree =\n\t\tmergeBase === undefined\n\t\t\t? null\n\t\t\t: mergeBase === base.oid\n\t\t\t\t? base.commit.tree\n\t\t\t\t: (await getCommit(repo, mergeBase)).commit.tree;\n\treturn diffTrees(repo, baseTree, compare.commit.tree);\n}\n","import git from \"isomorphic-git\";\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/** How far back history walks look by default (commit count). */\nexport const HISTORY_WALK_DEPTH = 400;\n\nexport interface GitIdentity {\n\tname: string;\n\temail: string;\n\ttimestamp: number;\n\ttimezoneOffset: number;\n}\n\nexport interface CommitInfo {\n\toid: string;\n\tcommit: {\n\t\tmessage: string;\n\t\ttree: string;\n\t\tparent: string[];\n\t\tauthor: GitIdentity;\n\t\tcommitter: GitIdentity;\n\t};\n\tpayload: string;\n}\n\n/** The display summary of a commit (history lists, \"last commit\" columns). */\nexport interface CommitSummary {\n\tsha: string;\n\tmessage: string;\n\tauthorName: string;\n\tauthorEmail: string;\n\t/** ISO 8601 author date. */\n\tcreatedAt: string;\n}\n\nexport function summarizeCommit(commit: CommitInfo): CommitSummary {\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\nconst isNotFound = (err: unknown) =>\n\t(err as { code?: string } | null)?.code === \"NotFoundError\";\n\n/**\n * A ref that doesn't resolve is an expected condition (empty repo, unborn\n * branch) each caller handles. An object missing *underneath* a resolved ref\n * means storage is inconsistent — raised as GitObjectNotFoundError so it is\n * never mistaken for \"empty\".\n */\nfunction wrapMissingObject<T>(\n\tpromise: Promise<T>,\n\tcontext: string,\n): Promise<T> {\n\treturn promise.catch((err: unknown) => {\n\t\tif (isNotFound(err)) {\n\t\t\tthrow new GitObjectNotFoundError(\n\t\t\t\t`Git data for ${context} is missing from storage`,\n\t\t\t);\n\t\t}\n\t\tthrow err;\n\t});\n}\n\n/** The commit a branch name, full ref or sha points at; GitRefNotFoundError if none. */\nasync function resolveRefOid(repo: Repo, ref: string): Promise<string> {\n\ttry {\n\t\treturn await git.resolveRef({ ...repo, ref: qualifyBranchRef(ref) });\n\t} catch (err) {\n\t\tif (isNotFound(err))\n\t\t\tthrow new GitRefNotFoundError(`Ref ${ref} does not exist`);\n\t\tthrow err;\n\t}\n}\n\n/** Resolve a branch name / full ref / sha to its commit. */\nexport async function resolveCommit(repo: Repo, ref: string) {\n\tconst oid = await resolveRefOid(repo, ref);\n\tconst { commit } = await wrapMissingObject(\n\t\tgit.readCommit({ ...repo, oid }),\n\t\t`${repo.gitdir} commit ${oid}`,\n\t);\n\treturn { oid, 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 commit, a tree per path segment, then the blob: warm every pack first\n\t// rather than discovering them one round trip at a time.\n\tif (hooks?.prefetch) await runStep(hooks, \"prefetch\", hooks.prefetch);\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\tif (entry?.type !== \"blob\") {\n\t\tthrow new GitPathNotFoundError(`File not found: ${filePath}`);\n\t}\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\nexport interface CommitLogOptions {\n\tref: string;\n\tdepth?: number;\n\t/** The ref's commit, when the caller already resolved it (saves a lookup). */\n\tknownHeadSha?: string;\n}\n\n/**\n * The commit chain from a ref, newest first; [] when the ref doesn't exist.\n * Walking is sequential and round-trip-bound on object storage, so the\n * deepest walk seen per head is memoized in `hooks.resultCache` and sliced\n * for shallower requests.\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\tlet headSha = options.knownHeadSha;\n\tif (headSha === undefined) {\n\t\ttry {\n\t\t\theadSha = await resolveRefOid(repo, ref);\n\t\t} catch (err) {\n\t\t\tif (err instanceof GitRefNotFoundError) 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\tconst fullyWalked = cached?.at(-1)?.commit.parent.length === 0;\n\tif (cached && (cached.length >= depth || fullyWalked)) {\n\t\thooks?.onNote?.(`getCommitLog: cache hit ${cacheKey} (depth=${depth})`);\n\t\treturn cached.slice(0, depth);\n\t}\n\thooks?.onNote?.(`getCommitLog: cache miss ${cacheKey} (depth=${depth})`);\n\tif (hooks?.prefetch) await runStep(hooks, \"prefetch\", hooks.prefetch);\n\n\tconst log = 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 = log.map(({ oid, commit, payload }) => ({\n\t\toid,\n\t\tcommit,\n\t\tpayload: 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/**\n * List a directory at the tip of a ref. Throws GitRefNotFoundError when the\n * ref doesn't resolve and GitPathNotFoundError when `treePath` doesn't exist.\n * Memoized per head sha.\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: cache hit ${cacheKey}`);\n\t\treturn cached;\n\t}\n\thooks?.onNote?.(`getTreeFromRef: cache miss ${cacheKey}`);\n\tif (hooks?.prefetch) await runStep(hooks, \"prefetch\", hooks.prefetch);\n\n\tconst context = `${repo.gitdir}@${ref}:${treePath || \"/\"}`;\n\tconst dir = treePath\n\t\t? await wrapMissingObject(\n\t\t\t\trunStep(hooks, `findTreeEntry ${treePath}`, () =>\n\t\t\t\t\tfindTreeEntry(repo, commit.tree, treePath),\n\t\t\t\t),\n\t\t\t\tcontext,\n\t\t\t)\n\t\t: { path: \"\", type: \"tree\" as const, oid: commit.tree };\n\tif (!dir) {\n\t\tthrow new GitPathNotFoundError(\n\t\t\t`Path \"${treePath}\" does not exist at ${ref}`,\n\t\t);\n\t}\n\tconst result =\n\t\tdir.type === \"tree\"\n\t\t\t? await wrapMissingObject(\n\t\t\t\t\trunStep(hooks, `listTreeEntries ${treePath || \"/\"}`, () =>\n\t\t\t\t\t\tlistTreeEntries(repo, dir.oid, dir.path),\n\t\t\t\t\t),\n\t\t\t\t\tcontext,\n\t\t\t\t)\n\t\t\t: [];\n\thooks?.resultCache?.set(cacheKey, result);\n\treturn result;\n}\n\n/**\n * A page of commit history from a ref tip; [] when the ref doesn't exist.\n * Pages are slices of {@link getCommitLog}'s memoized walk.\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 log = await getCommitLog(\n\t\trepo,\n\t\t{ ref: options.ref, depth: limit + skip },\n\t\thooks,\n\t);\n\treturn log.slice(skip, skip + limit);\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-path history). Keys are namespaced `kind:gitdir:headSha:…`,\n * so entries self-invalidate on push (new head, new key); evict\n * {@link resultKeyPrefixes} to drop a repo's superseded entries early.\n * Values are always objects (arrays or records).\n */\nexport interface ResultCache {\n\tget<T extends object>(key: string): T | null | undefined;\n\tset(key: string, value: object): void;\n}\n\n/** Optional instrumentation 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 * Warm every pack (e.g. `GitFs.prefetchPacks`) before a cache-miss read,\n\t * so a sequential walk doesn't discover packs one round trip at a time.\n\t */\n\tprefetch?: () => Promise<void>;\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 (rename, repack).\n */\nexport function resultKeyPrefixes(gitdir: string): string[] {\n\treturn [\n\t\t`commitlog:${gitdir}:`,\n\t\t`tree:${gitdir}:`,\n\t\t`last-commits:${gitdir}:`,\n\t\t`file-history:${gitdir}:`,\n\t];\n}\n","import {\n\ttype CommitSummary,\n\tgetCommitLog,\n\tHISTORY_WALK_DEPTH,\n\tsummarizeCommit,\n} from \"./history.js\";\nimport { findTreeEntry } from \"./tree.js\";\nimport type { OpsHooks, Repo } from \"./types.js\";\nimport { runStep } from \"./types.js\";\n\nexport interface FileHistoryResult {\n\tentries: CommitSummary[];\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 * A shallow walk for a \"latest commit touching this file\" banner, which only\n * shows `entries[0]`; a full History view is 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\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: CommitSummary[] = [];\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(summarizeCommit(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 {\n\ttype CommitSummary,\n\tgetCommitLog,\n\tHISTORY_WALK_DEPTH,\n\tsummarizeCommit,\n} from \"./history.js\";\nimport { findTreeEntry, listTreeEntries } from \"./tree.js\";\nimport type { OpsHooks, Repo } from \"./types.js\";\nimport { runStep } from \"./types.js\";\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\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, CommitSummary>> {\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, CommitSummary>>(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, CommitSummary> = {};\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] = summarizeCommit(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 =\n\t\t\tsourceOid === targetOid ||\n\t\t\t(await git.isDescendent({\n\t\t\t\t...repo,\n\t\t\t\toid: sourceOid,\n\t\t\t\tancestor: targetOid,\n\t\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\t// Nothing to merge when the target already contains the source (same\n\t// commit, or source is an ancestor) — never write a no-op merge commit.\n\tif (\n\t\tsourceOid === targetOid ||\n\t\t(await git.isDescendent({ ...repo, oid: targetOid, ancestor: sourceOid }))\n\t) {\n\t\treturn { success: true, commitSha: targetOid };\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","PREFETCH_WINDOW","git","git"]}
1
+ {"version":3,"sources":["../src/ops/commit.ts","../src/ops/packed-writes.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","../src/ops/submodules.ts"],"sourcesContent":["import git from \"isomorphic-git\";\nimport { GitInvalidRequestError, GitPathNotFoundError } from \"../git-errors.js\";\nimport { isSafeTreePath } from \"../refs.js\";\nimport { assertSafeBranchName } from \"./branch.js\";\nimport { withPackedWrites } from \"./packed-writes.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 * Every object the commit creates (blobs, trees, the commit) is stored as one\n * pack (see {@link withPackedWrites}), written before the ref.\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\t/**\n\t\t * Writes go through `writeRepo` (the packed-writes view of the repo),\n\t\t * never the outer repo — objects written around it would bypass the\n\t\t * pack and land loose.\n\t\t */\n\t\tbuildTree: (\n\t\t\tparentTreeOid: string | undefined,\n\t\t\twriteRepo: Repo,\n\t\t) => Promise<string>;\n\t},\n): Promise<string> {\n\tassertSafeBranchName(options.branch);\n\treturn withPackedWrites(repo, (packed) => writeCommit(packed, options));\n}\n\nasync function writeCommit(\n\trepo: Repo,\n\toptions: Parameters<typeof writeCommitToBare>[1],\n): Promise<string> {\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, repo);\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 async 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\tconst paths = new Set<string>();\n\tfor (const file of options.files) {\n\t\tif (!isSafeTreePath(file.path)) {\n\t\t\tthrow new GitInvalidRequestError(`Invalid file path: ${file.path}`);\n\t\t}\n\t\tif (paths.has(file.path)) {\n\t\t\tthrow new GitInvalidRequestError(`Duplicate file path: ${file.path}`);\n\t\t}\n\t\tpaths.add(file.path);\n\t}\n\tif (paths.size === 0) {\n\t\tthrow new GitInvalidRequestError(\"A commit needs at least one file\");\n\t}\n\treturn writeCommitToBare(repo, {\n\t\tbranch: options.branch,\n\t\tmessage: options.message,\n\t\tauthor: options.author,\n\t\tbuildTree: async (parentTreeOid, writeRepo) => {\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({ ...writeRepo, 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(writeRepo, 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 async 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\tif (!isSafeTreePath(options.filePath)) {\n\t\tthrow new GitInvalidRequestError(`Invalid file path: ${options.filePath}`);\n\t}\n\treturn writeCommitToBare(repo, {\n\t\tbranch: options.branch,\n\t\tmessage: options.message,\n\t\tauthor: options.author,\n\t\tbuildTree: async (parentTreeOid, writeRepo) => {\n\t\t\tif (!parentTreeOid) {\n\t\t\t\tthrow new GitPathNotFoundError(`Branch ${options.branch} is empty`);\n\t\t\t}\n\t\t\treturn deleteFromTree(writeRepo, parentTreeOid, options.filePath);\n\t\t},\n\t});\n}\n","/**\n * Packed writes: run git plumbing (writeBlob/writeTree/writeCommit, and\n * anything built on them) against a repository, but store the objects it\n * creates as one pack instead of one loose object each.\n *\n * Why it matters on object storage: one PUT per object becomes two per\n * operation (pack + index), and a repository that is never given loose\n * objects stays \"fully packed\" forever — which is what makes the loose-object\n * hint (`GitFs.detectLooseObjects`) safe across processes. A process that\n * once saw \"no loose objects\" in a gitdir can't be told when another process\n * writes one; if nothing ever writes loose objects, that answer stays true.\n *\n * Ordering is preserved: the pending objects are flushed (pack, then its\n * index) before any other file is written — so a ref written inside `fn`\n * never becomes visible before the objects it names.\n */\nimport { inflate } from \"../edge-utils.js\";\nimport { parseLooseObjectPath } from \"../git-layout.js\";\nimport {\n\tencodePack,\n\ttype PackObject,\n\ttype PackObjectType,\n\tstorePack,\n} from \"../pack-write.js\";\nimport type { IsoGitFs, Repo } from \"./types.js\";\n\ninterface PromiseFs {\n\treadFile(path: string, options?: unknown): Promise<Uint8Array | string>;\n\twriteFile(\n\t\tpath: string,\n\t\tdata: Uint8Array | string,\n\t\toptions?: unknown,\n\t): Promise<void>;\n\tstat(path: string): Promise<unknown>;\n\tlstat(path: string): Promise<unknown>;\n\t[key: string]: unknown;\n}\n\nconst OBJECT_TYPES = new Set<PackObjectType>([\"commit\", \"tree\", \"blob\", \"tag\"]);\n\n/** Split a loose object's inflated bytes (\"<type> <size>\\0<content>\"). */\nfunction parseLooseObject(raw: Uint8Array<ArrayBuffer>): PackObject {\n\tconst nul = raw.indexOf(0);\n\tconst header = new TextDecoder().decode(raw.subarray(0, nul));\n\tconst [type, size] = header.split(\" \");\n\tconst content = raw.slice(nul + 1);\n\tif (\n\t\t!OBJECT_TYPES.has(type as PackObjectType) ||\n\t\tNumber(size) !== content.length\n\t) {\n\t\tthrow new Error(`packed writes: malformed object header \"${header}\"`);\n\t}\n\treturn { type: type as PackObjectType, content };\n}\n\nfunction fileStat(size: number) {\n\tconst epoch = new Date(0);\n\treturn {\n\t\ttype: \"file\",\n\t\tmode: 0o100644,\n\t\tsize,\n\t\tino: 0,\n\t\tmtimeMs: 0,\n\t\tctimeMs: 0,\n\t\tuid: 0,\n\t\tgid: 0,\n\t\tdev: 0,\n\t\tmtime: epoch,\n\t\tctime: epoch,\n\t\tisFile: () => true,\n\t\tisDirectory: () => false,\n\t\tisSymbolicLink: () => false,\n\t};\n}\n\n/**\n * Run `fn` with a repo whose new objects are buffered and stored as a single\n * pack. Objects are flushed before any non-object write inside `fn`, and once\n * more when `fn` resolves; if `fn` throws, still-unflushed objects are\n * discarded (they'd be unreachable anyway). Serialize concurrent writers to\n * the same repository externally, as for any write.\n */\nexport async function withPackedWrites<T>(\n\trepo: Repo,\n\tfn: (packedRepo: Repo) => Promise<T>,\n): Promise<T> {\n\tconst base = (repo.fs as unknown as { promises: PromiseFs }).promises;\n\tconst pending = new Map<string, Uint8Array<ArrayBuffer>>(); // path → deflated loose bytes\n\n\tasync function flush(): Promise<void> {\n\t\tif (pending.size === 0) return;\n\t\tconst objects = await Promise.all(\n\t\t\t[...pending.values()].map(async (deflated) =>\n\t\t\t\tparseLooseObject(await inflate(deflated)),\n\t\t\t),\n\t\t);\n\t\tawait storePack(repo, await encodePack(objects));\n\t\tpending.clear();\n\t}\n\n\tconst promises: PromiseFs = new Proxy(base, {\n\t\tget(target, property, receiver) {\n\t\t\tswitch (property) {\n\t\t\t\tcase \"writeFile\":\n\t\t\t\t\treturn async (\n\t\t\t\t\t\tpath: string,\n\t\t\t\t\t\tdata: Uint8Array | string,\n\t\t\t\t\t\toptions?: unknown,\n\t\t\t\t\t) => {\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\tparseLooseObjectPath(path) !== null &&\n\t\t\t\t\t\t\ttypeof data !== \"string\"\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\tpending.set(path, new Uint8Array(data));\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tawait flush();\n\t\t\t\t\t\treturn target.writeFile(path, data, options);\n\t\t\t\t\t};\n\t\t\t\tcase \"readFile\":\n\t\t\t\t\treturn async (path: string, options?: unknown) => {\n\t\t\t\t\t\tconst data = pending.get(path);\n\t\t\t\t\t\tif (data === undefined) return target.readFile(path, options);\n\t\t\t\t\t\treturn data;\n\t\t\t\t\t};\n\t\t\t\tcase \"stat\":\n\t\t\t\tcase \"lstat\":\n\t\t\t\t\treturn async (path: string) => {\n\t\t\t\t\t\tconst data = pending.get(path);\n\t\t\t\t\t\tif (data !== undefined) return fileStat(data.length);\n\t\t\t\t\t\treturn target[property](path);\n\t\t\t\t\t};\n\t\t\t\tdefault: {\n\t\t\t\t\tconst value = Reflect.get(target, property, receiver);\n\t\t\t\t\treturn typeof value === \"function\" ? value.bind(target) : value;\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t});\n\n\tconst packedRepo: Repo = { ...repo, fs: { promises } as unknown as IsoGitFs };\n\tconst result = await fn(packedRepo);\n\tawait flush();\n\treturn result;\n}\n","import git from \"isomorphic-git\";\nimport { GitInvalidRequestError, GitPathNotFoundError } from \"../git-errors.js\";\nimport type { Repo } from \"./types.js\";\n\nexport interface TreeEntry {\n\tpath: string;\n\tmode: string;\n\ttype: \"blob\" | \"tree\" | \"commit\";\n\toid: string;\n\tsize?: number;\n}\n\nconst joinPath = (prefix: string, name: string) =>\n\tprefix ? `${prefix.replace(/\\/+$/, \"\")}/${name}` : name;\n\n/** File modes a blob entry can carry; anything else isn't a regular file. */\nconst BLOB_MODES = new Set([\"100644\", \"100755\", \"120000\"]);\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 *\n * An updated file keeps its existing mode (executable bit, symlink); a path\n * that names an existing directory, or runs through an existing file, is\n * rejected rather than silently replacing what's there.\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\tconst current = byName.get(name);\n\t\tif (current && current.type !== \"blob\") {\n\t\t\tthrow new GitInvalidRequestError(`\"${name}\" is a directory, not a file`);\n\t\t}\n\t\tbyName.set(name, {\n\t\t\tmode: current && BLOB_MODES.has(current.mode) ? current.mode : \"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\tif (entry && entry.type !== \"tree\") {\n\t\t\t\tthrow new GitInvalidRequestError(`\"${dir}\" is a file, not a directory`);\n\t\t\t}\n\t\t\tconst subtreeOid = entry?.oid;\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/**\n * Remove a file path from a tree, returning the new root tree oid. Throws\n * GitPathNotFoundError when there's no such file; directories left empty\n * are dropped, as git never stores an empty subtree.\n */\nexport async function deleteFromTree(\n\trepo: Repo,\n\ttreeOid: string,\n\tfilePath: string,\n): Promise<string> {\n\tconst result = await deleteFromSubtree(repo, treeOid, filePath, filePath);\n\treturn result ?? git.writeTree({ ...repo, tree: [] });\n}\n\n/** The new subtree oid, or null when removing the file left it empty. */\nasync function deleteFromSubtree(\n\trepo: Repo,\n\ttreeOid: string,\n\tfilePath: string,\n\tfullPath: string,\n): Promise<string | null> {\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\tconst name = slash === -1 ? filePath : filePath.slice(0, slash);\n\tconst entry = byName.get(name);\n\tif (slash === -1) {\n\t\tif (entry?.type !== \"blob\") {\n\t\t\tthrow new GitPathNotFoundError(`No such file: ${fullPath}`);\n\t\t}\n\t\tbyName.delete(name);\n\t} else {\n\t\tif (entry?.type !== \"tree\") {\n\t\t\tthrow new GitPathNotFoundError(`No such file: ${fullPath}`);\n\t\t}\n\t\tconst newOid = await deleteFromSubtree(\n\t\t\trepo,\n\t\t\tentry.oid,\n\t\t\tfilePath.slice(slash + 1),\n\t\t\tfullPath,\n\t\t);\n\t\tif (newOid === null) byName.delete(name);\n\t\telse byName.set(name, { ...entry, oid: newOid });\n\t}\n\tif (byName.size === 0) return null;\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,\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,\n\t\toid: entry.oid,\n\t}));\n}\n","import { createTwoFilesPatch } from \"diff\";\nimport git, { type WalkerEntry } from \"isomorphic-git\";\nimport { readBlobContent } from \"../edge-utils.js\";\nimport { getCommit, resolveCommit } from \"./history.js\";\nimport type { Repo } from \"./types.js\";\n\n/** Text files larger than this get no patch (kind \"too-large\"). */\nexport const MAX_DIFF_TEXT_BYTES = 1024 * 1024;\n\nexport interface DiffFile {\n\tpath: string;\n\tstatus: \"added\" | \"modified\" | \"deleted\";\n\t/**\n\t * \"text\" has a unified `patch`; \"binary\" (a NUL byte on either side) and\n\t * \"too-large\" (text over MAX_DIFF_TEXT_BYTES) don't — fetch the sides by\n\t * path at each ref if they need showing. \"submodule\" compares external\n\t * pointers using oldEntry/newEntry, including conversions to files/trees.\n\t */\n\tkind: \"text\" | \"binary\" | \"too-large\" | \"submodule\";\n\t/** Present on a submodule diff, including file/directory type changes. */\n\toldEntry?: DiffEntry;\n\tnewEntry?: DiffEntry;\n\tadditions: number;\n\tdeletions: number;\n\tpatch: string;\n\t/** Byte size before the change; absent for an added file. */\n\toldSize?: number;\n\t/** Byte size after the change; absent for a deleted file. */\n\tnewSize?: number;\n}\n\nexport interface DiffEntry {\n\ttype: Awaited<ReturnType<WalkerEntry[\"type\"]>>;\n\toid: string;\n\tmode: number;\n}\n\nexport interface DiffResult {\n\t/** Exact parent/merge-base and head used to compute this diff. */\n\toldRef?: string;\n\tnewRef: string;\n\tfiles: DiffFile[];\n\ttotalAdditions: number;\n\ttotalDeletions: number;\n\ttotalFiles: number;\n}\n\ntype BlobContent = ReturnType<typeof readBlobContent>;\n\nfunction countContentLines(content: string): number {\n\tif (content.length === 0) return 0;\n\tconst lines = content.split(\"\\n\");\n\tif (lines.at(-1) === \"\") lines.pop();\n\treturn lines.length;\n}\n\nfunction unifiedPatch(\n\tpath: string,\n\tbefore: BlobContent | null,\n\tafter: BlobContent | null,\n): string {\n\tconst body = createTwoFilesPatch(\n\t\tbefore ? `a/${path}` : \"/dev/null\",\n\t\tafter ? `b/${path}` : \"/dev/null\",\n\t\tbefore?.text ?? \"\",\n\t\tafter?.text ?? \"\",\n\t\t\"\",\n\t\t\"\",\n\t\t{ context: 3 },\n\t).replace(/^=+\\n/, \"\");\n\treturn `diff --git a/${path} b/${path}\\n${body}`;\n}\n\n/** One changed path: `before` null for an addition, `after` null for a deletion. */\nfunction buildDiffFile(\n\tpath: string,\n\tbefore: BlobContent | null,\n\tafter: BlobContent | null,\n): DiffFile {\n\tconst kind: DiffFile[\"kind\"] =\n\t\tbefore?.isBinary || after?.isBinary\n\t\t\t? \"binary\"\n\t\t\t: Math.max(before?.bytes.length ?? 0, after?.bytes.length ?? 0) >\n\t\t\t\t\tMAX_DIFF_TEXT_BYTES\n\t\t\t\t? \"too-large\"\n\t\t\t\t: \"text\";\n\tconst isText = kind === \"text\";\n\treturn {\n\t\tpath,\n\t\tstatus: !before ? \"added\" : !after ? \"deleted\" : \"modified\",\n\t\tkind,\n\t\tadditions: isText && after ? countContentLines(after.text) : 0,\n\t\tdeletions: isText && before ? countContentLines(before.text) : 0,\n\t\tpatch: isText ? unifiedPatch(path, before, after) : \"\",\n\t\toldSize: before?.bytes.length,\n\t\tnewSize: after?.bytes.length,\n\t};\n}\n\nasync function readDiffEntry(\n\tentry: WalkerEntry | null,\n): Promise<DiffEntry | undefined> {\n\treturn entry\n\t\t? {\n\t\t\t\ttype: await entry.type(),\n\t\t\t\toid: await entry.oid(),\n\t\t\t\tmode: await entry.mode(),\n\t\t\t}\n\t\t: undefined;\n}\n\n/** Submodules are pointer diffs even when replacing a regular file or directory. */\nfunction buildSubmoduleDiff(\n\tfilepath: string,\n\tbefore: DiffEntry | undefined,\n\tafter: DiffEntry | undefined,\n): DiffFile | undefined {\n\tif (before?.type === after?.type && before?.oid === after?.oid)\n\t\treturn undefined;\n\tconst line = (entry: DiffEntry | undefined) =>\n\t\tentry\n\t\t\t? `${entry.type === \"commit\" ? \"Subproject commit\" : entry.type === \"tree\" ? \"Directory\" : \"File\"} ${entry.oid}\\n`\n\t\t\t: \"\";\n\treturn {\n\t\tpath: filepath,\n\t\tstatus: !before ? \"added\" : !after ? \"deleted\" : \"modified\",\n\t\tkind: \"submodule\",\n\t\tadditions: 0,\n\t\tdeletions: 0,\n\t\toldEntry: before,\n\t\tnewEntry: after,\n\t\tpatch: createTwoFilesPatch(\n\t\t\tbefore ? `a/${filepath}` : \"/dev/null\",\n\t\t\tafter ? `b/${filepath}` : \"/dev/null\",\n\t\t\tline(before),\n\t\t\tline(after),\n\t\t\t\"\",\n\t\t\t\"\",\n\t\t\t{ context: 3 },\n\t\t),\n\t};\n}\n\n/**\n * Every file that differs between two trees; `oldTreeOid` null diffs against\n * nothing (a root commit: every file is an addition).\n */\nasync function diffTrees(\n\trepo: Repo,\n\toldTreeOid: string | null,\n\tnewTreeOid: string,\n): Promise<Omit<DiffResult, \"oldRef\" | \"newRef\">> {\n\tconst readSide = async (entry: WalkerEntry | null) => {\n\t\tif (!entry || (await entry.type()) !== \"blob\") return null;\n\t\tconst { blob } = await git.readBlob({ ...repo, oid: await entry.oid() });\n\t\treturn readBlobContent(blob);\n\t};\n\tconst trees = oldTreeOid\n\t\t? [git.TREE({ ref: oldTreeOid }), git.TREE({ ref: newTreeOid })]\n\t\t: [git.TREE({ ref: newTreeOid })];\n\tconst changes: (DiffFile | undefined)[] = await git.walk({\n\t\t...repo,\n\t\ttrees,\n\t\tmap: async (filepath, entries) => {\n\t\t\tconst [oldEntry, newEntry] = oldTreeOid\n\t\t\t\t? [entries[0] ?? null, entries[1] ?? null]\n\t\t\t\t: [null, entries[0] ?? null];\n\t\t\tconst [oldType, newType] = await Promise.all([\n\t\t\t\toldEntry?.type(),\n\t\t\t\tnewEntry?.type(),\n\t\t\t]);\n\t\t\t// Gitlinks name external commits: compare the pointers, never read them.\n\t\t\tif (oldType === \"commit\" || newType === \"commit\") {\n\t\t\t\tconst [before, after] = await Promise.all([\n\t\t\t\t\treadDiffEntry(oldEntry),\n\t\t\t\t\treadDiffEntry(newEntry),\n\t\t\t\t]);\n\t\t\t\treturn buildSubmoduleDiff(filepath, before, after);\n\t\t\t}\n\t\t\t// Directories recurse; blobs become content diff entries.\n\t\t\tif (oldType !== \"blob\" && newType !== \"blob\") return undefined;\n\t\t\tif (oldEntry && newEntry && oldType === newType) {\n\t\t\t\tconst [oldOid, newOid] = await Promise.all([\n\t\t\t\t\toldEntry.oid(),\n\t\t\t\t\tnewEntry.oid(),\n\t\t\t\t]);\n\t\t\t\tif (oldOid === newOid) return undefined;\n\t\t\t}\n\t\t\tconst [before, after] = await Promise.all([\n\t\t\t\treadSide(oldEntry),\n\t\t\t\treadSide(newEntry),\n\t\t\t]);\n\t\t\treturn before || after\n\t\t\t\t? buildDiffFile(filepath, before, after)\n\t\t\t\t: undefined;\n\t\t},\n\t});\n\tconst files = (changes ?? []).filter((file): file is DiffFile => !!file);\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\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\tconst { commit } = await getCommit(repo, commitSha);\n\tconst parent = commit.parent[0];\n\tconst parentTree = parent\n\t\t? (await getCommit(repo, parent)).commit.tree\n\t\t: null;\n\treturn {\n\t\t...(await diffTrees(repo, parentTree, commit.tree)),\n\t\toldRef: parent,\n\t\tnewRef: commitSha,\n\t};\n}\n\n/**\n * What `compareRef` changed since it diverged from `baseRef` — a pull\n * request's diff (git's three-dot `base...compare`): compared against their\n * merge base, so commits that landed on `baseRef` since the branch point\n * don't show up as reverted. Throws GitRefNotFoundError for a missing ref.\n */\nexport async function getDiffBetweenRefs(\n\trepo: Repo,\n\tbaseRef: string,\n\tcompareRef: string,\n): Promise<DiffResult> {\n\tconst [base, compare] = await Promise.all([\n\t\tresolveCommit(repo, baseRef),\n\t\tresolveCommit(repo, compareRef),\n\t]);\n\tconst [mergeBase] = await git.findMergeBase({\n\t\t...repo,\n\t\toids: [base.oid, compare.oid],\n\t});\n\tconst baseTree =\n\t\tmergeBase === undefined\n\t\t\t? null\n\t\t\t: mergeBase === base.oid\n\t\t\t\t? base.commit.tree\n\t\t\t\t: (await getCommit(repo, mergeBase)).commit.tree;\n\treturn {\n\t\t...(await diffTrees(repo, baseTree, compare.commit.tree)),\n\t\toldRef: mergeBase,\n\t\tnewRef: compare.oid,\n\t};\n}\n","import git from \"isomorphic-git\";\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/** How far back history walks look by default (commit count). */\nexport const HISTORY_WALK_DEPTH = 400;\n\nexport interface GitIdentity {\n\tname: string;\n\temail: string;\n\ttimestamp: number;\n\ttimezoneOffset: number;\n}\n\nexport interface CommitInfo {\n\toid: string;\n\tcommit: {\n\t\tmessage: string;\n\t\ttree: string;\n\t\tparent: string[];\n\t\tauthor: GitIdentity;\n\t\tcommitter: GitIdentity;\n\t};\n\tpayload: string;\n}\n\n/** The display summary of a commit (history lists, \"last commit\" columns). */\nexport interface CommitSummary {\n\tsha: string;\n\tmessage: string;\n\tauthorName: string;\n\tauthorEmail: string;\n\t/** ISO 8601 author date. */\n\tcreatedAt: string;\n}\n\nexport function summarizeCommit(commit: CommitInfo): CommitSummary {\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\nconst isNotFound = (err: unknown) =>\n\t(err as { code?: string } | null)?.code === \"NotFoundError\";\n\n/**\n * A ref that doesn't resolve is an expected condition (empty repo, unborn\n * branch) each caller handles. An object missing *underneath* a resolved ref\n * means storage is inconsistent — raised as GitObjectNotFoundError so it is\n * never mistaken for \"empty\".\n */\nfunction wrapMissingObject<T>(\n\tpromise: Promise<T>,\n\tcontext: string,\n): Promise<T> {\n\treturn promise.catch((err: unknown) => {\n\t\tif (isNotFound(err)) {\n\t\t\tthrow new GitObjectNotFoundError(\n\t\t\t\t`Git data for ${context} is missing from storage`,\n\t\t\t);\n\t\t}\n\t\tthrow err;\n\t});\n}\n\n/** The commit a branch name, full ref or sha points at; GitRefNotFoundError if none. */\nasync function resolveRefOid(repo: Repo, ref: string): Promise<string> {\n\ttry {\n\t\treturn await git.resolveRef({ ...repo, ref: qualifyBranchRef(ref) });\n\t} catch (err) {\n\t\tif (isNotFound(err))\n\t\t\tthrow new GitRefNotFoundError(`Ref ${ref} does not exist`);\n\t\tthrow err;\n\t}\n}\n\n/** Resolve a branch name / full ref / sha to its commit. */\nexport async function resolveCommit(repo: Repo, ref: string) {\n\tconst oid = await resolveRefOid(repo, ref);\n\tconst { commit } = await wrapMissingObject(\n\t\tgit.readCommit({ ...repo, oid }),\n\t\t`${repo.gitdir} commit ${oid}`,\n\t);\n\treturn { oid, 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 commit, a tree per path segment, then the blob: warm every pack first\n\t// rather than discovering them one round trip at a time.\n\tif (hooks?.prefetch) await runStep(hooks, \"prefetch\", hooks.prefetch);\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\tif (entry?.type !== \"blob\") {\n\t\tthrow new GitPathNotFoundError(`File not found: ${filePath}`);\n\t}\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\nexport interface CommitLogOptions {\n\tref: string;\n\tdepth?: number;\n\t/** The ref's commit, when the caller already resolved it (saves a lookup). */\n\tknownHeadSha?: string;\n}\n\n/**\n * The commit chain from a ref, newest first; [] when the ref doesn't exist.\n * Walking is sequential and round-trip-bound on object storage, so the\n * deepest walk seen per head is memoized in `hooks.resultCache` and sliced\n * for shallower requests.\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\tlet headSha = options.knownHeadSha;\n\tif (headSha === undefined) {\n\t\ttry {\n\t\t\theadSha = await resolveRefOid(repo, ref);\n\t\t} catch (err) {\n\t\t\tif (err instanceof GitRefNotFoundError) 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\tconst fullyWalked = cached?.at(-1)?.commit.parent.length === 0;\n\tif (cached && (cached.length >= depth || fullyWalked)) {\n\t\thooks?.onNote?.(`getCommitLog: cache hit ${cacheKey} (depth=${depth})`);\n\t\treturn cached.slice(0, depth);\n\t}\n\thooks?.onNote?.(`getCommitLog: cache miss ${cacheKey} (depth=${depth})`);\n\tif (hooks?.prefetch) await runStep(hooks, \"prefetch\", hooks.prefetch);\n\n\tconst log = 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 = log.map(({ oid, commit, payload }) => ({\n\t\toid,\n\t\tcommit,\n\t\tpayload: 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/**\n * List a directory at the tip of a ref. Throws GitRefNotFoundError when the\n * ref doesn't resolve and GitPathNotFoundError when `treePath` doesn't exist.\n * Memoized per head sha.\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: cache hit ${cacheKey}`);\n\t\treturn cached;\n\t}\n\thooks?.onNote?.(`getTreeFromRef: cache miss ${cacheKey}`);\n\tif (hooks?.prefetch) await runStep(hooks, \"prefetch\", hooks.prefetch);\n\n\tconst context = `${repo.gitdir}@${ref}:${treePath || \"/\"}`;\n\tconst dir = treePath\n\t\t? await wrapMissingObject(\n\t\t\t\trunStep(hooks, `findTreeEntry ${treePath}`, () =>\n\t\t\t\t\tfindTreeEntry(repo, commit.tree, treePath),\n\t\t\t\t),\n\t\t\t\tcontext,\n\t\t\t)\n\t\t: { path: \"\", type: \"tree\" as const, oid: commit.tree };\n\tif (!dir) {\n\t\tthrow new GitPathNotFoundError(\n\t\t\t`Path \"${treePath}\" does not exist at ${ref}`,\n\t\t);\n\t}\n\tconst result =\n\t\tdir.type === \"tree\"\n\t\t\t? await wrapMissingObject(\n\t\t\t\t\trunStep(hooks, `listTreeEntries ${treePath || \"/\"}`, () =>\n\t\t\t\t\t\tlistTreeEntries(repo, dir.oid, dir.path),\n\t\t\t\t\t),\n\t\t\t\t\tcontext,\n\t\t\t\t)\n\t\t\t: [];\n\thooks?.resultCache?.set(cacheKey, result);\n\treturn result;\n}\n\n/**\n * A page of commit history from a ref tip; [] when the ref doesn't exist.\n * Pages are slices of {@link getCommitLog}'s memoized walk.\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 log = await getCommitLog(\n\t\trepo,\n\t\t{ ref: options.ref, depth: limit + skip },\n\t\thooks,\n\t);\n\treturn log.slice(skip, skip + limit);\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-path history). Keys are namespaced `kind:gitdir:headSha:…`,\n * so entries self-invalidate on push (new head, new key); evict\n * {@link resultKeyPrefixes} to drop a repo's superseded entries early.\n * Values are always objects (arrays or records).\n */\nexport interface ResultCache {\n\tget<T extends object>(key: string): T | null | undefined;\n\tset(key: string, value: object): void;\n}\n\n/** Optional instrumentation 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 * Warm every pack (e.g. `GitFs.prefetchPacks`) before a cache-miss read,\n\t * so a sequential walk doesn't discover packs one round trip at a time.\n\t */\n\tprefetch?: () => Promise<void>;\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 (rename, repack).\n */\nexport function resultKeyPrefixes(gitdir: string): string[] {\n\treturn [\n\t\t`commitlog:${gitdir}:`,\n\t\t`tree:${gitdir}:`,\n\t\t`last-commits:${gitdir}:`,\n\t\t`file-history:${gitdir}:`,\n\t];\n}\n","import {\n\ttype CommitSummary,\n\tgetCommitLog,\n\tHISTORY_WALK_DEPTH,\n\tsummarizeCommit,\n} from \"./history.js\";\nimport { findTreeEntry } from \"./tree.js\";\nimport type { OpsHooks, Repo } from \"./types.js\";\nimport { runStep } from \"./types.js\";\n\nexport interface FileHistoryResult {\n\tentries: CommitSummary[];\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 * A shallow walk for a \"latest commit touching this file\" banner, which only\n * shows `entries[0]`; a full History view is 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\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 && entry.type !== \"tree\" ? entry.oid : null;\n\t\toidByCommitTree.set(commitTreeOid, oid);\n\t\treturn oid;\n\t}\n\n\tconst entries: CommitSummary[] = [];\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(summarizeCommit(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 {\n\ttype CommitSummary,\n\tgetCommitLog,\n\tHISTORY_WALK_DEPTH,\n\tsummarizeCommit,\n} from \"./history.js\";\nimport { findTreeEntry, listTreeEntries } from \"./tree.js\";\nimport type { OpsHooks, Repo } from \"./types.js\";\nimport { runStep } from \"./types.js\";\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\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, CommitSummary>> {\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, CommitSummary>>(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, CommitSummary> = {};\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] = summarizeCommit(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 =\n\t\t\tsourceOid === targetOid ||\n\t\t\t(await git.isDescendent({\n\t\t\t\t...repo,\n\t\t\t\toid: sourceOid,\n\t\t\t\tancestor: targetOid,\n\t\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\t// Nothing to merge when the target already contains the source (same\n\t// commit, or source is an ancestor) — never write a no-op merge commit.\n\tif (\n\t\tsourceOid === targetOid ||\n\t\t(await git.isDescendent({ ...repo, oid: targetOid, ancestor: sourceOid }))\n\t) {\n\t\treturn { success: true, commitSha: targetOid };\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","import { GitPathNotFoundError } from \"../git-errors.js\";\nimport { getFileContent } from \"./history.js\";\nimport type { OpsHooks, Repo } from \"./types.js\";\n\nexport interface SubmoduleConfig {\n\tname: string;\n\tpath: string;\n\turl: string;\n}\n\n/** Git config values: comments outside quotes, quoted whitespace, and Git's escapes. */\nfunction parseValue(raw: string): string | null {\n\tlet value = \"\";\n\tlet spaces = \"\";\n\tlet quoted = false;\n\tconst escapes: Record<string, string> = {\n\t\tn: \"\\n\",\n\t\tt: \"\\t\",\n\t\tb: \"\\b\",\n\t\t\"\\\\\": \"\\\\\",\n\t\t'\"': '\"',\n\t};\n\tfor (let i = 0; i < raw.length; i++) {\n\t\tconst c = raw.charAt(i);\n\t\tif (c === \"\\\\\") {\n\t\t\tconst escaped = escapes[raw.charAt(++i)];\n\t\t\tif (escaped === undefined) return null;\n\t\t\tvalue += spaces + escaped;\n\t\t\tspaces = \"\";\n\t\t} else if (c === '\"') {\n\t\t\tvalue += spaces;\n\t\t\tspaces = \"\";\n\t\t\tquoted = !quoted;\n\t\t} else if (!quoted && (c === \"#\" || c === \";\")) break;\n\t\telse if (!quoted && /\\s/.test(c)) {\n\t\t\tif (value) spaces += c;\n\t\t} else {\n\t\t\tvalue += spaces + c;\n\t\t\tspaces = \"\";\n\t\t}\n\t}\n\treturn quoted ? null : value;\n}\n\n/**\n * Read only submodule path/url records. Never expands includes, executes Git\n * commands, or fetches URLs. Ambiguous paths and malformed records are ignored.\n */\nexport function parseGitmodules(content: string): SubmoduleConfig[] {\n\tconst sections = new Map<string, Partial<SubmoduleConfig>>();\n\tlet current: Partial<SubmoduleConfig> | undefined;\n\tconst lines: string[] = [];\n\tlet pending = \"\";\n\tfor (const line of content.replace(/\\r\\n/g, \"\\n\").split(\"\\n\")) {\n\t\tconst slashes = /\\\\+$/.exec(line)?.[0].length ?? 0;\n\t\tif (slashes % 2 === 1) pending += line.slice(0, -1);\n\t\telse {\n\t\t\tlines.push(pending + line);\n\t\t\tpending = \"\";\n\t\t}\n\t}\n\tfor (const line of lines) {\n\t\tconst trimmed = line.trim();\n\t\tif (!trimmed || /^[#;]/.test(trimmed)) continue;\n\t\tif (trimmed.startsWith(\"[\")) {\n\t\t\tconst match =\n\t\t\t\t/^\\[submodule\\s+\"((?:[^\"\\\\]|\\\\[\"\\\\])*)\"\\]\\s*(?:[#;].*)?$/i.exec(\n\t\t\t\t\ttrimmed,\n\t\t\t\t);\n\t\t\tcurrent = undefined;\n\t\t\tif (!match?.[1]) continue;\n\t\t\tconst name = match[1].replace(/\\\\([\"\\\\])/g, \"$1\");\n\t\t\tcurrent = sections.get(name) ?? { name };\n\t\t\tsections.set(name, current);\n\t\t\tcontinue;\n\t\t}\n\t\tif (!current) continue;\n\t\tconst field = /^(path|url)\\s*=\\s*(.*)$/i.exec(trimmed);\n\t\tif (!field?.[1] || field[2] === undefined) continue;\n\t\tconst key = field[1].toLowerCase() as \"path\" | \"url\";\n\t\tconst value = parseValue(field[2]);\n\t\tif (value === null) delete current[key];\n\t\telse current[key] = value;\n\t}\n\tconst valid = [...sections.values()].filter(\n\t\t(s): s is SubmoduleConfig =>\n\t\t\t!!s.name &&\n\t\t\t!!s.path &&\n\t\t\t!!s.url &&\n\t\t\t![...(s.path + s.url)].some(\n\t\t\t\t(c) => c.charCodeAt(0) < 32 || c.charCodeAt(0) === 127,\n\t\t\t) &&\n\t\t\t!s.path.startsWith(\"/\") &&\n\t\t\t!s.path.includes(\"\\\\\") &&\n\t\t\ts.path\n\t\t\t\t.split(\"/\")\n\t\t\t\t.every(\n\t\t\t\t\t(p) =>\n\t\t\t\t\t\tp !== \"\" && p !== \".\" && p !== \"..\" && p.toLowerCase() !== \".git\",\n\t\t\t\t),\n\t);\n\tconst counts = new Map<string, number>();\n\tfor (const s of valid) counts.set(s.path, (counts.get(s.path) ?? 0) + 1);\n\treturn valid.filter((s) => counts.get(s.path) === 1);\n}\n\n/** The root .gitmodules at this exact revision; absent/oversized config yields no links. */\nexport async function getSubmodules(\n\trepo: Repo,\n\tref: string,\n\thooks?: OpsHooks,\n): Promise<SubmoduleConfig[]> {\n\tlet bytes: Uint8Array;\n\ttry {\n\t\tbytes = await getFileContent(repo, \".gitmodules\", ref, hooks);\n\t} catch (error) {\n\t\tif (error instanceof GitPathNotFoundError) return [];\n\t\tthrow error;\n\t}\n\tif (bytes.length > 1024 * 1024) return [];\n\treturn parseGitmodules(new TextDecoder().decode(bytes));\n}\n"],"mappings":"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","names":["git","git","git","git","git","git","before","after","PREFETCH_WINDOW","git","git"]}
@@ -1,3 +1,4 @@
1
+ import { type WalkerEntry } from "isomorphic-git";
1
2
  import type { Repo } from "./types.js";
2
3
  /** Text files larger than this get no patch (kind "too-large"). */
3
4
  export declare const MAX_DIFF_TEXT_BYTES: number;
@@ -7,9 +8,13 @@ export interface DiffFile {
7
8
  /**
8
9
  * "text" has a unified `patch`; "binary" (a NUL byte on either side) and
9
10
  * "too-large" (text over MAX_DIFF_TEXT_BYTES) don't — fetch the sides by
10
- * path at each ref if they need showing.
11
+ * path at each ref if they need showing. "submodule" compares external
12
+ * pointers using oldEntry/newEntry, including conversions to files/trees.
11
13
  */
12
- kind: "text" | "binary" | "too-large";
14
+ kind: "text" | "binary" | "too-large" | "submodule";
15
+ /** Present on a submodule diff, including file/directory type changes. */
16
+ oldEntry?: DiffEntry;
17
+ newEntry?: DiffEntry;
13
18
  additions: number;
14
19
  deletions: number;
15
20
  patch: string;
@@ -18,7 +23,15 @@ export interface DiffFile {
18
23
  /** Byte size after the change; absent for a deleted file. */
19
24
  newSize?: number;
20
25
  }
26
+ export interface DiffEntry {
27
+ type: Awaited<ReturnType<WalkerEntry["type"]>>;
28
+ oid: string;
29
+ mode: number;
30
+ }
21
31
  export interface DiffResult {
32
+ /** Exact parent/merge-base and head used to compute this diff. */
33
+ oldRef?: string;
34
+ newRef: string;
22
35
  files: DiffFile[];
23
36
  totalAdditions: number;
24
37
  totalDeletions: number;
@@ -1,10 +1,11 @@
1
1
  export { assertBranchExists, assertSafeBranchName, type Branch, createBranchFrom, deleteBranchByName, listBranches, resolveLooseRefFast, } from "./branch.js";
2
2
  export { authorNow, type CommitAuthor, commitFilesToBare, deleteFileFromBare, writeCommitToBare, } from "./commit.js";
3
- export { type DiffFile, type DiffResult, getCommitDiff, getDiffBetweenRefs, MAX_DIFF_TEXT_BYTES, } from "./diff.js";
3
+ export { type DiffEntry, type DiffFile, type DiffResult, getCommitDiff, getDiffBetweenRefs, MAX_DIFF_TEXT_BYTES, } from "./diff.js";
4
4
  export { BANNER_WALK_DEPTH, type FileHistoryResult, getFileHistory, } from "./file-history.js";
5
5
  export { type CommitInfo, type CommitLogOptions, type CommitSummary, type GitIdentity, getBlob, getCommit, getCommitHistory, getCommitLog, getFileContent, getTreeFromRef, HISTORY_WALK_DEPTH, resolveCommit, summarizeCommit, } from "./history.js";
6
6
  export { getLastCommitsForTree } from "./last-commit.js";
7
7
  export { analyzeMerge, fastForwardMerge, type MergeAnalysis } from "./merge.js";
8
8
  export { withPackedWrites } from "./packed-writes.js";
9
+ export { getSubmodules, parseGitmodules, type SubmoduleConfig, } from "./submodules.js";
9
10
  export { deleteFromTree, findTreeEntry, listTreeEntries, type TreeEntry, upsertTree, } from "./tree.js";
10
11
  export { type IsoGitFs, type OpsHooks, type Repo, type ResultCache, resultKeyPrefixes, } from "./types.js";
@@ -0,0 +1,13 @@
1
+ import type { OpsHooks, Repo } from "./types.js";
2
+ export interface SubmoduleConfig {
3
+ name: string;
4
+ path: string;
5
+ url: string;
6
+ }
7
+ /**
8
+ * Read only submodule path/url records. Never expands includes, executes Git
9
+ * commands, or fetches URLs. Ambiguous paths and malformed records are ignored.
10
+ */
11
+ export declare function parseGitmodules(content: string): SubmoduleConfig[];
12
+ /** The root .gitmodules at this exact revision; absent/oversized config yields no links. */
13
+ export declare function getSubmodules(repo: Repo, ref: string, hooks?: OpsHooks): Promise<SubmoduleConfig[]>;
@@ -2,7 +2,7 @@ import type { Repo } from "./types.js";
2
2
  export interface TreeEntry {
3
3
  path: string;
4
4
  mode: string;
5
- type: "blob" | "tree";
5
+ type: "blob" | "tree" | "commit";
6
6
  oid: string;
7
7
  size?: number;
8
8
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "git-fs-s3",
3
- "version": "0.9.0",
4
- "description": "isomorphic-git filesystem backend for S3-compatible object storage (AWS S3, Cloudflare R2, MinIO) \u2014 run git servers without a disk",
3
+ "version": "0.10.0",
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": {
7
7
  ".": {