git-fs-s3 0.3.8 → 0.3.9
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/dist/ops.cjs +6 -3
- package/dist/ops.cjs.map +1 -1
- package/dist/ops.d.cts +2 -2
- package/dist/ops.d.ts +2 -2
- package/dist/ops.js +6 -3
- package/dist/ops.js.map +1 -1
- package/package.json +1 -1
package/dist/ops.cjs
CHANGED
|
@@ -465,7 +465,10 @@ async function getBlob(repo, sha) {
|
|
|
465
465
|
);
|
|
466
466
|
return blob;
|
|
467
467
|
}
|
|
468
|
-
async function getFileContent(repo, filePath, ref = "main") {
|
|
468
|
+
async function getFileContent(repo, filePath, ref = "main", hooks) {
|
|
469
|
+
if (hooks?.prefetch) {
|
|
470
|
+
await runStep(hooks, "prefetch", hooks.prefetch);
|
|
471
|
+
}
|
|
469
472
|
const { commit } = await resolveCommit(repo, ref);
|
|
470
473
|
const context = `${repo.gitdir}@${ref}:${filePath}`;
|
|
471
474
|
const entry = await wrapMissingObject(
|
|
@@ -538,8 +541,8 @@ async function getCommitLog(repo, options = {}, hooks) {
|
|
|
538
541
|
}
|
|
539
542
|
return result;
|
|
540
543
|
}
|
|
541
|
-
async function getFileFromRef(repo, filePath, ref) {
|
|
542
|
-
const bytes = await getFileContent(repo, filePath, ref);
|
|
544
|
+
async function getFileFromRef(repo, filePath, ref, hooks) {
|
|
545
|
+
const bytes = await getFileContent(repo, filePath, ref, hooks);
|
|
543
546
|
const isBinary = hasNullByte(bytes);
|
|
544
547
|
return {
|
|
545
548
|
content: isBinary ? toBase64(bytes) : decodeUtf8(bytes),
|
package/dist/ops.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/ops/index.ts","../src/ops/branch.ts","../src/git-errors.ts","../src/refs.ts","../src/ops/commit.ts","../src/ops/tree.ts","../src/ops/diff.ts","../src/edge-utils.ts","../src/ops/history.ts","../src/ops/types.ts","../src/ops/file-history.ts","../src/ops/last-commit.ts","../src/ops/merge.ts"],"sourcesContent":["export {\n\tassertBranchExists,\n\tassertSafeBranchName,\n\ttype Branch,\n\tcreateBranchFrom,\n\tdeleteBranchByName,\n\tlistBranches,\n\tresolveLooseRefFast,\n} from \"./branch.js\";\nexport {\n\tauthorNow,\n\ttype CommitAuthor,\n\tcommitFilesToBare,\n\tdeleteFileFromBare,\n\twriteCommitToBare,\n} from \"./commit.js\";\nexport {\n\ttype DiffFile,\n\ttype DiffResult,\n\tgetCommitDiff,\n\tgetDiffBetweenRefs,\n} from \"./diff.js\";\nexport {\n\tBANNER_WALK_DEPTH,\n\ttype FileHistoryEntry,\n\ttype FileHistoryResult,\n\tgetFileHistory,\n\tHISTORY_WALK_DEPTH,\n} from \"./file-history.js\";\nexport {\n\ttype CommitInfo,\n\ttype CommitLogOptions,\n\tgetBlob,\n\tgetCommit,\n\tgetCommitHistory,\n\tgetCommitLog,\n\tgetFileContent,\n\tgetFileFromRef,\n\tgetTreeFromRef,\n\tresolveCommit,\n} from \"./history.js\";\nexport {\n\tgetLastCommitsForTree,\n\ttype LastCommitInfo,\n} from \"./last-commit.js\";\nexport { analyzeMerge, fastForwardMerge, type MergeAnalysis } from \"./merge.js\";\nexport {\n\tdeleteFromTree,\n\tfindTreeEntry,\n\tlistTreeEntries,\n\ttype TreeEntry,\n\tupsertTree,\n} from \"./tree.js\";\nexport {\n\ttype IsoGitFs,\n\ttype OpsHooks,\n\ttype Repo,\n\ttype ResultCache,\n\tresultKeyPrefixes,\n} from \"./types.js\";\n","import git from \"isomorphic-git\";\nimport { GitInvalidRequestError } from \"../git-errors.js\";\nimport { isSafeBranchName } from \"../refs.js\";\nimport type { Repo } from \"./types.js\";\n\nexport interface Branch {\n\tname: string;\n\tcommit: string;\n\tisDefault: boolean;\n}\n\n/**\n * Defense in depth for every branch-name argument below: `git.deleteBranch`\n * and raw resolveRef/writeRef reads don't validate ref names internally the\n * way `git.branch` does (see refs.ts) — guard at the point the primitives are\n * actually called, not just at an API boundary far above.\n */\nexport function assertSafeBranchName(name: string): void {\n\tif (!isSafeBranchName(name)) {\n\t\tthrow new GitInvalidRequestError(`Invalid branch name: ${name}`);\n\t}\n}\n\nconst FULL_SHA_RE = /^[0-9a-f]{40}$/i;\n\ntype RecursiveFileLister = {\n\tlistFilesRecursively(dirpath: string): Promise<string[]>;\n};\n\nfunction hasRecursiveFileLister(\n\tfs: Repo[\"fs\"],\n): fs is Repo[\"fs\"] & RecursiveFileLister {\n\treturn (\n\t\ttypeof fs === \"object\" &&\n\t\tfs !== null &&\n\t\t\"listFilesRecursively\" in fs &&\n\t\ttypeof (fs as Partial<RecursiveFileLister>).listFilesRecursively ===\n\t\t\t\"function\"\n\t);\n}\n\n/**\n * Lists loose branch names without isomorphic-git's recursive readdir/stat\n * traversal. Object stores already return only leaf keys in a recursive LIST,\n * so this is one request regardless of branch-name nesting. Ordinary\n * filesystems retain isomorphic-git's implementation and packed refs remain\n * supported there.\n */\nasync function listLooseBranches(repo: Repo): Promise<string[] | null> {\n\tif (!hasRecursiveFileLister(repo.fs)) return null;\n\tconst names = await repo.fs.listFilesRecursively(`${repo.gitdir}/refs/heads`);\n\treturn names.filter(isSafeBranchName);\n}\n\n/** Read packed branch refs so the object-store fast path preserves Git semantics. */\nasync function readPackedBranches(repo: Repo): Promise<Map<string, string>> {\n\tif (!hasRecursiveFileLister(repo.fs)) return new Map();\n\tconst promisesFs = (\n\t\trepo.fs as {\n\t\t\tpromises?: {\n\t\t\t\treadFile(path: string, encoding: \"utf8\"): Promise<string | Uint8Array>;\n\t\t\t};\n\t\t}\n\t).promises;\n\tif (!promisesFs) return new Map();\n\ttry {\n\t\tconst content = await promisesFs.readFile(\n\t\t\t`${repo.gitdir}/packed-refs`,\n\t\t\t\"utf8\",\n\t\t);\n\t\tconst refs = new Map<string, string>();\n\t\tfor (const line of (typeof content === \"string\"\n\t\t\t? content\n\t\t\t: new TextDecoder().decode(content)\n\t\t).split(\"\\n\")) {\n\t\t\tconst match = /^([0-9a-f]{40}) refs\\/heads\\/(.+)$/i.exec(line);\n\t\t\tif (match?.[1] && match[2] && isSafeBranchName(match[2])) {\n\t\t\t\trefs.set(match[2], match[1]);\n\t\t\t}\n\t\t}\n\t\treturn refs;\n\t} catch {\n\t\treturn new Map();\n\t}\n}\n\n/** Read the symbolic HEAD directly when the object-store fs is available. */\nasync function getCurrentLooseBranch(repo: Repo): Promise<string | null> {\n\tif (!hasRecursiveFileLister(repo.fs)) return null;\n\tconst promisesFs = (\n\t\trepo.fs as {\n\t\t\tpromises?: {\n\t\t\t\treadFile(path: string, encoding: \"utf8\"): Promise<string | Uint8Array>;\n\t\t\t};\n\t\t}\n\t).promises;\n\tif (!promisesFs) return null;\n\ttry {\n\t\tconst content = await promisesFs.readFile(`${repo.gitdir}/HEAD`, \"utf8\");\n\t\tconst head = (\n\t\t\ttypeof content === \"string\" ? content : new TextDecoder().decode(content)\n\t\t).trim();\n\t\tconst match = /^ref: refs\\/heads\\/(.+)$/.exec(head);\n\t\treturn match?.[1] && isSafeBranchName(match[1]) ? match[1] : null;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\n/**\n * Resolve a fully-qualified ref's oid with one read instead of isomorphic-\n * git's own `resolveRef`, which does a stat *then* a read for a loose ref\n * (two round trips against object storage) — wasteful specifically here,\n * where the caller already knows the ref exists (it came from a directory\n * listing moments earlier). Reads the loose file directly and falls back to\n * `git.resolveRef` only when that doesn't pan out (packed-refs, or anything\n * else the plain-loose-file assumption doesn't cover), so correctness for\n * those cases is unchanged — this only removes the redundant round trip on\n * the common path.\n */\nexport async function resolveLooseRefFast(\n\trepo: Repo,\n\tref: string,\n): Promise<string> {\n\t// Repo.fs is typed as isomorphic-git's own fs union (promise- or\n\t// callback-based) since that's what git.readTree's signature allows, but\n\t// every fs this package actually constructs (git-fs.ts) is promise-based\n\t// — the runtime check just keeps the callback-style branch honest instead\n\t// of crashing on it.\n\tconst promisesFs = (\n\t\trepo.fs as {\n\t\t\tpromises?: {\n\t\t\t\treadFile(path: string, encoding: \"utf8\"): Promise<string | Uint8Array>;\n\t\t\t};\n\t\t}\n\t).promises;\n\tif (promisesFs) {\n\t\ttry {\n\t\t\tconst content = await promisesFs.readFile(\n\t\t\t\t`${repo.gitdir}/${ref}`,\n\t\t\t\t\"utf8\",\n\t\t\t);\n\t\t\tconst oid = (\n\t\t\t\ttypeof content === \"string\"\n\t\t\t\t\t? content\n\t\t\t\t\t: new TextDecoder().decode(content)\n\t\t\t).trim();\n\t\t\tif (FULL_SHA_RE.test(oid)) return oid;\n\t\t} catch {\n\t\t\t// Not a loose file (packed-refs, or genuinely absent) — fall through.\n\t\t}\n\t}\n\treturn git.resolveRef({ ...repo, ref });\n}\n\n/** All branches with their tip commits; [] for an empty repository. */\nexport async function listBranches(repo: Repo): Promise<Branch[]> {\n\ttry {\n\t\tconst [looseBranches, looseCurrentBranch, packedBranches] =\n\t\t\tawait Promise.all([\n\t\t\t\tlistLooseBranches(repo),\n\t\t\t\tgetCurrentLooseBranch(repo),\n\t\t\t\treadPackedBranches(repo),\n\t\t\t]);\n\t\tconst branches =\n\t\t\tlooseBranches === null\n\t\t\t\t? await git.listBranches(repo)\n\t\t\t\t: [...new Set([...looseBranches, ...packedBranches.keys()])].sort();\n\t\tconst currentBranch =\n\t\t\tlooseBranches === null\n\t\t\t\t? await git\n\t\t\t\t\t\t.currentBranch({ ...repo, fullname: false })\n\t\t\t\t\t\t.catch(() => null)\n\t\t\t\t: looseCurrentBranch;\n\n\t\treturn Promise.all(\n\t\t\tbranches.map(async (branch) => {\n\t\t\t\tconst packedCommit = packedBranches.get(branch);\n\t\t\t\treturn {\n\t\t\t\t\tname: branch,\n\t\t\t\t\tcommit:\n\t\t\t\t\t\tpackedCommit && !looseBranches?.includes(branch)\n\t\t\t\t\t\t\t? packedCommit\n\t\t\t\t\t\t\t: await resolveLooseRefFast(repo, `refs/heads/${branch}`),\n\t\t\t\t\tisDefault: branch === currentBranch,\n\t\t\t\t};\n\t\t\t}),\n\t\t);\n\t} catch (err: unknown) {\n\t\tif ((err as { code?: string }).code === \"NotFoundError\") return [];\n\t\tthrow err;\n\t}\n}\n\n/** Create `name` pointing at the tip of `startPoint` (no checkout). */\nexport async function createBranchFrom(\n\trepo: Repo,\n\tname: string,\n\tstartPoint = \"main\",\n): Promise<void> {\n\tassertSafeBranchName(name);\n\tassertSafeBranchName(startPoint);\n\tconst object = await resolveLooseRefFast(repo, `refs/heads/${startPoint}`);\n\tawait git.branch({ ...repo, ref: name, checkout: false, object });\n}\n\n/** Delete a branch ref (validated — deleteBranch has no internal ref check). */\nexport async function deleteBranchByName(\n\trepo: Repo,\n\tname: string,\n): Promise<void> {\n\tassertSafeBranchName(name);\n\tawait git.deleteBranch({ ...repo, ref: name });\n}\n\n/** Throws (NotFoundError) unless the branch resolves. */\nexport async function assertBranchExists(\n\trepo: Repo,\n\tname: string,\n): Promise<void> {\n\tassertSafeBranchName(name);\n\tawait resolveLooseRefFast(repo, `refs/heads/${name}`);\n}\n","/**\n * Git-server error types carrying an HTTP status and a retryability flag, so\n * transport layers can map internal failures to responses without inspecting\n * messages. Extend {@link GitError} for app-specific cases (storage backends,\n * quota, …) and {@link formatErrorResponse} keeps working for them.\n */\nexport class GitError extends Error {\n\tstatusCode: number;\n\tretryable: boolean;\n\n\tconstructor(message: string, statusCode = 500, retryable = false) {\n\t\tsuper(message);\n\t\tthis.name = this.constructor.name;\n\t\tthis.statusCode = statusCode;\n\t\tthis.retryable = retryable;\n\t\tError.captureStackTrace?.(this, this.constructor);\n\t}\n\n\ttoJSON(): Record<string, unknown> {\n\t\treturn {\n\t\t\terror: this.name,\n\t\t\tmessage: this.message,\n\t\t\tstatusCode: this.statusCode,\n\t\t\tretryable: this.retryable,\n\t\t};\n\t}\n}\n\n/** A file/directory path not found within a tree (404). */\nexport class GitPathNotFoundError extends GitError {\n\tconstructor(message: string) {\n\t\tsuper(message, 404, false);\n\t}\n}\n\n/** A git object not found (404). */\nexport class GitObjectNotFoundError extends GitError {\n\tconstructor(message: string) {\n\t\tsuper(message, 404, false);\n\t}\n}\n\n/** A ref (branch/tag) not found (404). */\nexport class GitRefNotFoundError extends GitError {\n\tconstructor(message: string) {\n\t\tsuper(message, 404, false);\n\t}\n}\n\n/** The repository itself not found (404). */\nexport class GitRepositoryNotFoundError extends GitError {\n\tconstructor(message: string) {\n\t\tsuper(message, 404, false);\n\t}\n}\n\nexport interface MergeConflictDetail {\n\tfile: string;\n\tbaseLines?: string[];\n\tsourceLines?: string[];\n\ttargetLines?: string[];\n}\n\n/** A merge conflict (409), carrying per-file conflict detail. */\nexport class GitConflictError extends GitError {\n\tconflicts: MergeConflictDetail[];\n\n\tconstructor(message: string, conflicts: MergeConflictDetail[] = []) {\n\t\tsuper(message, 409, false);\n\t\tthis.conflicts = conflicts;\n\t}\n\n\toverride toJSON(): Record<string, unknown> {\n\t\treturn { ...super.toJSON(), conflicts: this.conflicts };\n\t}\n}\n\n/** Authentication failed (401). */\nexport class GitAuthenticationError extends GitError {\n\tconstructor(message: string) {\n\t\tsuper(message, 401, false);\n\t}\n}\n\n/** Authorization failed (403). */\nexport class GitAuthorizationError extends GitError {\n\tconstructor(message: string) {\n\t\tsuper(message, 403, false);\n\t}\n}\n\n/** Too many failed attempts (429). */\nexport class GitRateLimitError extends GitError {\n\tconstructor(message: string) {\n\t\tsuper(message, 429, false);\n\t}\n}\n\n/** Malformed request (400). */\nexport class GitInvalidRequestError extends GitError {\n\tconstructor(message: string) {\n\t\tsuper(message, 400, false);\n\t}\n}\n\n/** Git wire-protocol violation (400). */\nexport class GitProtocolError extends GitError {\n\tconstructor(message: string) {\n\t\tsuper(message, 400, false);\n\t}\n}\n\n/**\n * Map any error to an HTTP response shape. 401s carry the WWW-Authenticate\n * header git clients need before they will prompt for credentials. Non-GitError\n * failures are masked as opaque 500s — internal messages don't leak.\n */\nexport function formatErrorResponse(error: unknown): {\n\tstatus: number;\n\tbody: Record<string, unknown>;\n\theaders?: Record<string, string>;\n} {\n\tif (error instanceof GitError) {\n\t\treturn {\n\t\t\tstatus: error.statusCode,\n\t\t\tbody: error.toJSON(),\n\t\t\theaders:\n\t\t\t\terror.statusCode === 401\n\t\t\t\t\t? { \"WWW-Authenticate\": 'Basic realm=\"Git Repository\"' }\n\t\t\t\t\t: undefined,\n\t\t};\n\t}\n\n\tif (error instanceof Error) {\n\t\treturn {\n\t\t\tstatus: 500,\n\t\t\tbody: {\n\t\t\t\terror: \"InternalServerError\",\n\t\t\t\tmessage: \"An internal error occurred\",\n\t\t\t\tretryable: true,\n\t\t\t},\n\t\t};\n\t}\n\n\treturn {\n\t\tstatus: 500,\n\t\tbody: {\n\t\t\terror: \"UnknownError\",\n\t\t\tmessage: \"An unknown error occurred\",\n\t\t\tretryable: true,\n\t\t},\n\t};\n}\n","/**\n * Git ref-name validation, mirroring isomorphic-git's own internal `isValidRef`\n * character-class rules (the check `git.branch` and top-level `git.writeRef`\n * run before touching disk).\n *\n * Several of isomorphic-git's OTHER ref-touching primitives — `git.commit`,\n * `git.merge`, `git.deleteBranch`, and top-level `git.resolveRef`/\n * `git.deleteRef` — do NOT run this check internally: they resolve straight\n * through `fs.write`/`fs.rm(join(gitdir, ref))` with no jail to the gitdir.\n * On a shared-storage server (many repos under one prefix or base directory),\n * every branch/ref name that originates from request input must be validated\n * against these predicates before it reaches any of those primitives —\n * otherwise a `\"../\"`-laden name lets a caller with write access to any single\n * repo read, corrupt, or delete another repo's ref/object files.\n */\n\nconst BAD_REF_COMPONENT =\n\t// biome-ignore lint/suspicious/noControlCharactersInRegex: control chars are exactly what git's own ref-name rules reject — this needs to match the same range.\n\t/(^|[/.])([/.]|$)|^@$|@\\{|[\\x00-\\x20\\x7f~^:?*[\\\\]|\\.lock(\\/|$)/;\n\nconst FULL_SHA_RE = /^[0-9a-f]{40}$/i;\n\n/** Validates a fully-qualified ref (must start with refs/heads/ or refs/tags/). */\nexport function isSafeFullRefName(ref: string): boolean {\n\tif (!ref.startsWith(\"refs/heads/\") && !ref.startsWith(\"refs/tags/\")) {\n\t\treturn false;\n\t}\n\treturn !BAD_REF_COMPONENT.test(ref);\n}\n\n/**\n * Validates a bare branch name (no refs/ prefix). Rejects anything that looks\n * like a full ref path — a name of `\"refs/heads/x\"` would otherwise sail\n * through unprefixed at call sites that build `refs/heads/${name}` themselves\n * (doubling the prefix into something that still resolves), or be used as-is\n * at call sites that pass a name already containing `\"refs/\"` straight\n * through. Also rejects 40-hex SHA-shaped values so a stored branch name can\n * never be ambiguous with a commit SHA at write time; use\n * {@link isSafeRefName} on read paths that accept both shapes.\n */\nexport function isSafeBranchName(name: string): boolean {\n\tif (!name || name.startsWith(\"refs/\") || name === \"HEAD\") return false;\n\tif (FULL_SHA_RE.test(name)) return false;\n\treturn !BAD_REF_COMPONENT.test(name);\n}\n\n/** True for a full 40-hex-char commit SHA — the shape {@link isSafeBranchName} deliberately rejects. */\nexport function isFullSha(value: string): boolean {\n\treturn FULL_SHA_RE.test(value);\n}\n\n/**\n * Validates a \"ref\" field that may name either a branch or a commit SHA it's\n * pinned to — the shape read-path route params take (permalinks, raw links).\n * Both shapes still go through the traversal check.\n */\nexport function isSafeRefName(value: string): boolean {\n\treturn isSafeBranchName(value) || isFullSha(value);\n}\n\n/**\n * Validates a repo-relative file path from request input: relative, no `..`\n * segments, no `.git/` prefix, no null bytes. Use this anywhere a path\n * segment comes straight off a URL or form field rather than re-deriving the\n * checks ad hoc.\n */\nexport function isSafeRepoPath(p: string): boolean {\n\tif (p.startsWith(\"/\")) return false;\n\tif (p.split(\"/\").some((segment) => segment === \"..\")) return false;\n\tif (/^\\.git(\\/|$)/i.test(p)) return false;\n\tif (p.includes(\"\\0\")) return false;\n\treturn true;\n}\n\n/**\n * Qualify a bare branch name to `refs/heads/<name>` before handing it to\n * isomorphic-git. `resolveRef`/`expand` try several candidate paths in\n * sequence for a bare name — `ref`, `refs/ref`, `refs/tags/ref`,\n * `refs/heads/ref`, … — missing (and, against object storage, paying a real\n * round trip for) the first three every time. For a branch-only ref model,\n * skip straight to the winner. Left untouched: already-qualified refs,\n * `\"HEAD\"` (its own first candidate, already optimal), and 40-hex oids\n * (resolved locally by isomorphic-git with no I/O at all).\n */\nexport function qualifyBranchRef(ref: string): string {\n\tif (ref.startsWith(\"refs/\") || ref === \"HEAD\" || FULL_SHA_RE.test(ref)) {\n\t\treturn ref;\n\t}\n\treturn `refs/heads/${ref}`;\n}\n","import git from \"isomorphic-git\";\nimport { assertSafeBranchName } from \"./branch.js\";\nimport { deleteFromTree, upsertTree } from \"./tree.js\";\nimport type { Repo } from \"./types.js\";\n\nexport interface CommitAuthor {\n\tname: string;\n\temail: string;\n\ttimestamp: number;\n\ttimezoneOffset: number;\n}\n\n/** An author stamped with the current time. */\nexport function authorNow(name: string, email: string): CommitAuthor {\n\treturn {\n\t\tname,\n\t\temail,\n\t\ttimestamp: Math.floor(Date.now() / 1000),\n\t\ttimezoneOffset: 0,\n\t};\n}\n\n/**\n * Write a commit directly to a bare repository — no worktree, no checkout.\n * `buildTree` receives the parent commit's tree oid (undefined on an empty\n * repo / unborn branch) and returns the new root tree oid; this function\n * writes the commit object and force-updates `refs/heads/<branch>`.\n *\n * Serialize concurrent writers externally (a per-repo lock): the\n * resolve-ref → write-ref sequence is not atomic on object storage.\n */\nexport async function writeCommitToBare(\n\trepo: Repo,\n\toptions: {\n\t\tbranch: string;\n\t\tmessage: string;\n\t\tauthor: CommitAuthor;\n\t\tbuildTree: (parentTreeOid: string | undefined) => Promise<string>;\n\t},\n): Promise<string> {\n\tassertSafeBranchName(options.branch);\n\tlet parentOid: string | undefined;\n\tlet parentTreeOid: string | undefined;\n\ttry {\n\t\tparentOid = await git.resolveRef({\n\t\t\t...repo,\n\t\t\tref: `refs/heads/${options.branch}`,\n\t\t});\n\t\tconst { commit } = await git.readCommit({ ...repo, oid: parentOid });\n\t\tparentTreeOid = commit.tree;\n\t} catch (err) {\n\t\tif ((err as { code?: string })?.code !== \"NotFoundError\") {\n\t\t\tthrow err;\n\t\t}\n\t\t// empty repo — first commit\n\t}\n\tconst treeOid = await options.buildTree(parentTreeOid);\n\tconst commitOid = await git.writeCommit({\n\t\t...repo,\n\t\tcommit: {\n\t\t\tmessage: options.message,\n\t\t\ttree: treeOid,\n\t\t\tparent: parentOid ? [parentOid] : [],\n\t\t\tauthor: options.author,\n\t\t\tcommitter: options.author,\n\t\t},\n\t});\n\tawait git.writeRef({\n\t\t...repo,\n\t\tref: `refs/heads/${options.branch}`,\n\t\tvalue: commitOid,\n\t\tforce: true,\n\t});\n\treturn commitOid;\n}\n\n/**\n * Commit a set of files onto a branch, straight to the bare repo. Each blob\n * is written to its own content-addressed key — no shared state between\n * files, so they're written in parallel.\n */\nexport function commitFilesToBare(\n\trepo: Repo,\n\toptions: {\n\t\tbranch: string;\n\t\tmessage: string;\n\t\tauthor: CommitAuthor;\n\t\tfiles: Array<{ path: string; content: string | Uint8Array }>;\n\t},\n): Promise<string> {\n\treturn writeCommitToBare(repo, {\n\t\tbranch: options.branch,\n\t\tmessage: options.message,\n\t\tauthor: options.author,\n\t\tbuildTree: async (parentTreeOid) => {\n\t\t\tconst blobs = new Map<string, string>();\n\t\t\tawait Promise.all(\n\t\t\t\toptions.files.map(async (file) => {\n\t\t\t\t\tconst content =\n\t\t\t\t\t\ttypeof file.content === \"string\"\n\t\t\t\t\t\t\t? new TextEncoder().encode(file.content)\n\t\t\t\t\t\t\t: file.content;\n\t\t\t\t\tconst oid = await git.writeBlob({ ...repo, blob: content });\n\t\t\t\t\tblobs.set(file.path, oid);\n\t\t\t\t}),\n\t\t\t);\n\t\t\treturn upsertTree(repo, parentTreeOid, blobs);\n\t\t},\n\t});\n}\n\n/** Commit the removal of one file from a branch, straight to the bare repo. */\nexport function deleteFileFromBare(\n\trepo: Repo,\n\toptions: {\n\t\tbranch: string;\n\t\tfilePath: string;\n\t\tmessage: string;\n\t\tauthor: CommitAuthor;\n\t},\n): Promise<string> {\n\treturn writeCommitToBare(repo, {\n\t\tbranch: options.branch,\n\t\tmessage: options.message,\n\t\tauthor: options.author,\n\t\tbuildTree: async (parentTreeOid) => {\n\t\t\tif (!parentTreeOid) {\n\t\t\t\tthrow new Error(`Branch ${options.branch} is empty`);\n\t\t\t}\n\t\t\treturn deleteFromTree(repo, parentTreeOid, options.filePath);\n\t\t},\n\t});\n}\n","import git from \"isomorphic-git\";\nimport type { Repo } from \"./types.js\";\n\nexport interface TreeEntry {\n\tpath: string;\n\tmode: string;\n\ttype: \"blob\" | \"tree\";\n\toid: string;\n\tsize?: number;\n}\n\nconst joinPath = (prefix: string, name: string) =>\n\tprefix ? `${prefix.replace(/\\/+$/, \"\")}/${name}` : name;\n\n/**\n * Build/update a git tree by overlaying new blobs onto an existing tree,\n * returning the new root tree oid. `entries` maps relative paths to blob oids.\n */\nexport async function upsertTree(\n\trepo: Repo,\n\ttreeOid: string | undefined,\n\tentries: Map<string, string>,\n): Promise<string> {\n\tconst existing = treeOid\n\t\t? (await git.readTree({ ...repo, oid: treeOid })).tree\n\t\t: [];\n\tconst byName = new Map(existing.map((e) => [e.path, e]));\n\tconst direct = new Map<string, string>();\n\tconst nested = new Map<string, Map<string, string>>();\n\tfor (const [filePath, blobOid] of entries) {\n\t\tconst slash = filePath.indexOf(\"/\");\n\t\tif (slash === -1) {\n\t\t\tdirect.set(filePath, blobOid);\n\t\t} else {\n\t\t\tconst dir = filePath.slice(0, slash);\n\t\t\tconst rest = filePath.slice(slash + 1);\n\t\t\tif (!nested.has(dir)) nested.set(dir, new Map());\n\t\t\tnested.get(dir)?.set(rest, blobOid);\n\t\t}\n\t}\n\tfor (const [name, blobOid] of direct) {\n\t\tbyName.set(name, {\n\t\t\tmode: \"100644\",\n\t\t\tpath: name,\n\t\t\toid: blobOid,\n\t\t\ttype: \"blob\",\n\t\t});\n\t}\n\t// Sibling subdirectories are independent subtree writes — no reason to\n\t// serialize them for multi-file commits touching several directories.\n\tconst nestedResults = await Promise.all(\n\t\tArray.from(nested, async ([dir, subEntries]) => {\n\t\t\tconst entry = byName.get(dir);\n\t\t\tconst subtreeOid = entry?.type === \"tree\" ? entry.oid : undefined;\n\t\t\tconst newOid = await upsertTree(repo, subtreeOid, subEntries);\n\t\t\treturn [dir, newOid] as const;\n\t\t}),\n\t);\n\tfor (const [dir, newOid] of nestedResults) {\n\t\tbyName.set(dir, { mode: \"040000\", path: dir, oid: newOid, type: \"tree\" });\n\t}\n\treturn git.writeTree({ ...repo, tree: Array.from(byName.values()) });\n}\n\n/** Remove a file path from a tree, returning the new root tree oid. */\nexport async function deleteFromTree(\n\trepo: Repo,\n\ttreeOid: string,\n\tfilePath: string,\n): Promise<string> {\n\tconst existing = (await git.readTree({ ...repo, oid: treeOid })).tree;\n\tconst byName = new Map(existing.map((e) => [e.path, e]));\n\tconst slash = filePath.indexOf(\"/\");\n\tif (slash === -1) {\n\t\tbyName.delete(filePath);\n\t} else {\n\t\tconst dir = filePath.slice(0, slash);\n\t\tconst rest = filePath.slice(slash + 1);\n\t\tconst entry = byName.get(dir);\n\t\tif (entry?.type === \"tree\") {\n\t\t\tconst newOid = await deleteFromTree(repo, entry.oid, rest);\n\t\t\tbyName.set(dir, { ...entry, oid: newOid });\n\t\t}\n\t}\n\treturn git.writeTree({ ...repo, tree: Array.from(byName.values()) });\n}\n\n/** Resolve a path inside a tree to its entry, or null when absent. */\nexport async function findTreeEntry(\n\trepo: Repo,\n\trootTreeOid: string,\n\ttreePath: string,\n): Promise<TreeEntry | null> {\n\tif (!treePath) {\n\t\treturn { path: \"\", mode: \"040000\", type: \"tree\", oid: rootTreeOid };\n\t}\n\n\tconst parts = treePath.split(\"/\").filter(Boolean);\n\tlet currentTreeOid = rootTreeOid;\n\tlet currentPath = \"\";\n\n\tfor (const [index, part] of parts.entries()) {\n\t\tconst tree = await git.readTree({ ...repo, oid: currentTreeOid });\n\t\tconst entry = tree.tree.find((candidate) => candidate.path === part);\n\n\t\tif (!entry) return null;\n\n\t\tcurrentPath = currentPath ? joinPath(currentPath, entry.path) : entry.path;\n\n\t\tif (index === parts.length - 1) {\n\t\t\treturn {\n\t\t\t\tpath: currentPath,\n\t\t\t\tmode: entry.mode,\n\t\t\t\ttype: entry.type as \"blob\" | \"tree\",\n\t\t\t\toid: entry.oid,\n\t\t\t};\n\t\t}\n\n\t\tif (entry.type !== \"tree\") return null;\n\n\t\tcurrentTreeOid = entry.oid;\n\t}\n\n\treturn null;\n}\n\n/** List a tree's direct entries, with paths prefixed by `prefix`. */\nexport async function listTreeEntries(\n\trepo: Repo,\n\ttreeOid: string,\n\tprefix = \"\",\n): Promise<TreeEntry[]> {\n\tconst tree = await git.readTree({ ...repo, oid: treeOid });\n\n\treturn tree.tree.map((entry) => ({\n\t\tpath: prefix ? joinPath(prefix, entry.path) : entry.path,\n\t\tmode: entry.mode,\n\t\ttype: entry.type as \"blob\" | \"tree\",\n\t\toid: entry.oid,\n\t}));\n}\n","import { createTwoFilesPatch } from \"diff\";\nimport git from \"isomorphic-git\";\nimport { readBlobContent, toBase64 } from \"../edge-utils.js\";\nimport { qualifyBranchRef } from \"../refs.js\";\nimport { getCommit } from \"./history.js\";\nimport type { Repo } from \"./types.js\";\n\nexport interface DiffFile {\n\tpath: string;\n\tstatus: \"added\" | \"modified\" | \"deleted\" | \"renamed\";\n\tadditions: number;\n\tdeletions: number;\n\tpatch: string;\n\toldPath?: string;\n\tisBinary?: boolean;\n\toldContent?: string;\n\tnewContent?: string;\n\toldSize?: number;\n\tnewSize?: number;\n}\n\nexport interface DiffResult {\n\tfiles: DiffFile[];\n\ttotalAdditions: number;\n\ttotalDeletions: number;\n\ttotalFiles: number;\n}\n\n/** Binary detection via null-byte heuristic. */\nfunction detectBlobContent(blob: Uint8Array): {\n\tisBinary: boolean;\n\ttext: string;\n\tbytes: Uint8Array;\n} {\n\treturn readBlobContent(blob);\n}\n\nfunction countContentLines(content: string): number {\n\tif (content.length === 0) return 0;\n\tconst lines = content.split(\"\\n\");\n\tif (lines[lines.length - 1] === \"\") lines.pop();\n\treturn lines.length;\n}\n\nfunction createUnifiedPatch(params: {\n\tpath: string;\n\tbefore: string;\n\tafter: string;\n\toldPath?: string;\n\tnewPath?: string;\n}): string {\n\tconst oldPath = params.oldPath ?? `a/${params.path}`;\n\tconst newPath = params.newPath ?? `b/${params.path}`;\n\tconst patchBody = createTwoFilesPatch(\n\t\toldPath,\n\t\tnewPath,\n\t\tparams.before,\n\t\tparams.after,\n\t\t\"\",\n\t\t\"\",\n\t\t{ context: 3 },\n\t).replace(/^=+\\n/, \"\");\n\n\treturn `diff --git a/${params.path} b/${params.path}\\n${patchBody}`;\n}\n\nfunction summarizeDiff(files: DiffFile[]): DiffResult {\n\treturn {\n\t\tfiles,\n\t\ttotalAdditions: files.reduce((sum, f) => sum + f.additions, 0),\n\t\ttotalDeletions: files.reduce((sum, f) => sum + f.deletions, 0),\n\t\ttotalFiles: files.length,\n\t};\n}\n\n/**\n * Walk two trees (oldOid -> newOid) and return one DiffFile per changed path\n * — the shared core of both {@link getCommitDiff} (parent -> commit) and\n * {@link getDiffBetweenRefs} (base -> compare).\n */\nasync function walkTreeDiff(\n\trepo: Repo,\n\toldOid: string,\n\tnewOid: string,\n): Promise<DiffFile[]> {\n\tconst changes = await git.walk({\n\t\t...repo,\n\t\ttrees: [git.TREE({ ref: oldOid }), git.TREE({ ref: newOid })],\n\t\tmap: async (filepath, [A, B]) => {\n\t\t\tconst [typeA, typeB] = await Promise.all([A?.type(), B?.type()]);\n\n\t\t\tif (typeA === \"tree\" || typeB === \"tree\") return;\n\n\t\t\tif (typeA && !typeB) {\n\t\t\t\tconst oidA = A ? await A.oid() : \"\";\n\t\t\t\tconst { blob } = await git.readBlob({ ...repo, oid: oidA });\n\t\t\t\tconst before = detectBlobContent(blob);\n\t\t\t\treturn {\n\t\t\t\t\tpath: filepath,\n\t\t\t\t\tstatus: \"deleted\" as const,\n\t\t\t\t\tadditions: 0,\n\t\t\t\t\tdeletions: before.isBinary ? 0 : countContentLines(before.text),\n\t\t\t\t\tpatch: before.isBinary\n\t\t\t\t\t\t? \"\"\n\t\t\t\t\t\t: createUnifiedPatch({\n\t\t\t\t\t\t\t\tpath: filepath,\n\t\t\t\t\t\t\t\tbefore: before.text,\n\t\t\t\t\t\t\t\tafter: \"\",\n\t\t\t\t\t\t\t\tnewPath: \"/dev/null\",\n\t\t\t\t\t\t\t}),\n\t\t\t\t\tisBinary: before.isBinary,\n\t\t\t\t\toldContent: before.isBinary ? toBase64(before.bytes) : undefined,\n\t\t\t\t\toldSize: before.bytes.length,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tif (!typeA && typeB) {\n\t\t\t\tconst oidB = B ? await B.oid() : \"\";\n\t\t\t\tconst { blob } = await git.readBlob({ ...repo, oid: oidB });\n\t\t\t\tconst after = detectBlobContent(blob);\n\t\t\t\treturn {\n\t\t\t\t\tpath: filepath,\n\t\t\t\t\tstatus: \"added\" as const,\n\t\t\t\t\tadditions: after.isBinary ? 0 : countContentLines(after.text),\n\t\t\t\t\tdeletions: 0,\n\t\t\t\t\tpatch: after.isBinary\n\t\t\t\t\t\t? \"\"\n\t\t\t\t\t\t: createUnifiedPatch({\n\t\t\t\t\t\t\t\tpath: filepath,\n\t\t\t\t\t\t\t\tbefore: \"\",\n\t\t\t\t\t\t\t\tafter: after.text,\n\t\t\t\t\t\t\t\toldPath: \"/dev/null\",\n\t\t\t\t\t\t\t}),\n\t\t\t\t\tisBinary: after.isBinary,\n\t\t\t\t\tnewContent: after.isBinary ? toBase64(after.bytes) : undefined,\n\t\t\t\t\tnewSize: after.bytes.length,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tconst [oidA, oidB] = await Promise.all([\n\t\t\t\tA ? A.oid() : Promise.resolve(\"\"),\n\t\t\t\tB ? B.oid() : Promise.resolve(\"\"),\n\t\t\t]);\n\n\t\t\tif (oidA !== oidB) {\n\t\t\t\tconst [{ blob: blobA }, { blob: blobB }] = await Promise.all([\n\t\t\t\t\tgit.readBlob({ ...repo, oid: oidA }),\n\t\t\t\t\tgit.readBlob({ ...repo, oid: oidB }),\n\t\t\t\t]);\n\t\t\t\tconst before = detectBlobContent(blobA);\n\t\t\t\tconst after = detectBlobContent(blobB);\n\t\t\t\tconst isBinary = before.isBinary || after.isBinary;\n\n\t\t\t\treturn {\n\t\t\t\t\tpath: filepath,\n\t\t\t\t\tstatus: \"modified\" as const,\n\t\t\t\t\tadditions: isBinary ? 0 : countContentLines(after.text),\n\t\t\t\t\tdeletions: isBinary ? 0 : countContentLines(before.text),\n\t\t\t\t\tpatch: isBinary\n\t\t\t\t\t\t? \"\"\n\t\t\t\t\t\t: createUnifiedPatch({\n\t\t\t\t\t\t\t\tpath: filepath,\n\t\t\t\t\t\t\t\tbefore: before.text,\n\t\t\t\t\t\t\t\tafter: after.text,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\tisBinary,\n\t\t\t\t\toldContent: isBinary ? toBase64(before.bytes) : undefined,\n\t\t\t\t\tnewContent: isBinary ? toBase64(after.bytes) : undefined,\n\t\t\t\t\toldSize: before.bytes.length,\n\t\t\t\t\tnewSize: after.bytes.length,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\treturn null;\n\t\t},\n\t});\n\n\treturn (changes ?? []).filter(\n\t\t(c: DiffFile | null | undefined): c is DiffFile =>\n\t\t\tc !== null && c !== undefined,\n\t);\n}\n\n/** The diff a single commit introduced (against its first parent). */\nexport async function getCommitDiff(\n\trepo: Repo,\n\tcommitSha: string,\n): Promise<DiffResult> {\n\ttry {\n\t\tconst commit = await getCommit(repo, commitSha);\n\t\tconst parent = commit.commit.parent[0];\n\n\t\tif (!parent) {\n\t\t\tconst entries: { path: string; oid: string }[] = [];\n\t\t\tconst stack: { treeOid: string; prefix: string }[] = [\n\t\t\t\t{ treeOid: commit.commit.tree, prefix: \"\" },\n\t\t\t];\n\n\t\t\twhile (stack.length) {\n\t\t\t\tconst { treeOid, prefix } = stack.pop() as {\n\t\t\t\t\ttreeOid: string;\n\t\t\t\t\tprefix: string;\n\t\t\t\t};\n\t\t\t\tconst { tree } = await git.readTree({ ...repo, oid: treeOid });\n\t\t\t\tfor (const entry of tree) {\n\t\t\t\t\tconst full = prefix ? `${prefix}/${entry.path}` : entry.path;\n\t\t\t\t\tif (entry.type === \"tree\") {\n\t\t\t\t\t\tstack.push({ treeOid: entry.oid, prefix: full });\n\t\t\t\t\t} else if (entry.type === \"blob\") {\n\t\t\t\t\t\tentries.push({ path: full, oid: entry.oid });\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst files: DiffFile[] = await Promise.all(\n\t\t\t\tentries.map(async ({ path, oid }) => {\n\t\t\t\t\tconst { blob } = await git.readBlob({ ...repo, oid });\n\t\t\t\t\tconst after = detectBlobContent(blob);\n\t\t\t\t\treturn {\n\t\t\t\t\t\tpath,\n\t\t\t\t\t\tstatus: \"added\" as const,\n\t\t\t\t\t\tadditions: after.isBinary ? 0 : countContentLines(after.text),\n\t\t\t\t\t\tdeletions: 0,\n\t\t\t\t\t\tpatch: after.isBinary\n\t\t\t\t\t\t\t? \"\"\n\t\t\t\t\t\t\t: createUnifiedPatch({\n\t\t\t\t\t\t\t\t\tpath,\n\t\t\t\t\t\t\t\t\tbefore: \"\",\n\t\t\t\t\t\t\t\t\tafter: after.text,\n\t\t\t\t\t\t\t\t\toldPath: \"/dev/null\",\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\tisBinary: after.isBinary,\n\t\t\t\t\t\tnewContent: after.isBinary ? toBase64(after.bytes) : undefined,\n\t\t\t\t\t\tnewSize: after.bytes.length,\n\t\t\t\t\t};\n\t\t\t\t}),\n\t\t\t);\n\n\t\t\treturn summarizeDiff(files);\n\t\t}\n\n\t\tconst files = await walkTreeDiff(repo, parent, commitSha);\n\t\treturn summarizeDiff(files);\n\t} catch (error) {\n\t\tthrow new Error(`Failed to get commit diff: ${error}`);\n\t}\n}\n\n/** The diff between two refs (base -> compare). */\nexport async function getDiffBetweenRefs(\n\trepo: Repo,\n\tbaseRef: string,\n\tcompareRef: string,\n): Promise<DiffResult> {\n\tconst [baseOid, compareOid] = await Promise.all([\n\t\tgit.resolveRef({ ...repo, ref: qualifyBranchRef(baseRef) }),\n\t\tgit.resolveRef({ ...repo, ref: qualifyBranchRef(compareRef) }),\n\t]);\n\n\tconst files = await walkTreeDiff(repo, baseOid, compareOid);\n\treturn summarizeDiff(files);\n}\n","/**\n * Edge-compatible utilities replacing node:crypto, node:zlib, and Buffer.\n *\n * Every function here uses only Web APIs (SubtleCrypto, CompressionStream,\n * TextEncoder/TextDecoder) — no Node built-ins. They work on Cloudflare\n * Workers, Vercel Edge, Deno Deploy, and Node >= 18.\n */\n\nconst textEncoder = new TextEncoder();\nconst textDecoder = new TextDecoder();\n\n// ---------------------------------------------------------------------------\n// Text\n// ---------------------------------------------------------------------------\n\n/**\n * Encode a UTF-8 string to bytes.\n *\n * Return type pinned to `Uint8Array<ArrayBuffer>` (not the bare `Uint8Array`,\n * whose default type argument differs across TypeScript versions) so it's\n * always assignable to Fetch API `BodyInit` regardless of a consumer's own\n * TypeScript/lib version.\n */\nexport function encodeUtf8(data: string): Uint8Array<ArrayBuffer> {\n\treturn textEncoder.encode(data);\n}\n\n/** Decode bytes as UTF-8. */\nexport function decodeUtf8(data: Uint8Array): string {\n\treturn textDecoder.decode(data);\n}\n\n/** Decode bytes as ASCII. */\nexport function decodeAscii(data: Uint8Array): string {\n\tlet s = \"\";\n\tfor (let i = 0; i < data.length; i++)\n\t\ts += String.fromCharCode(data[i] as number);\n\treturn s;\n}\n\n// ---------------------------------------------------------------------------\n// Array manipulation\n// ---------------------------------------------------------------------------\n\n/** Concatenate any number of Uint8Arrays into one. */\nexport function concat(...parts: Uint8Array[]): Uint8Array<ArrayBuffer> {\n\tlet total = 0;\n\tfor (const p of parts) total += p.length;\n\tconst out = new Uint8Array(total);\n\tlet offset = 0;\n\tfor (const p of parts) {\n\t\tout.set(p, offset);\n\t\toffset += p.length;\n\t}\n\treturn out;\n}\n\n// ---------------------------------------------------------------------------\n// Encoding\n// ---------------------------------------------------------------------------\n\n/** Uint8Array → lowercase hex string. */\nexport function toHex(data: Uint8Array): string {\n\tlet hex = \"\";\n\tfor (let i = 0; i < data.length; i++)\n\t\thex += (data[i] as number).toString(16).padStart(2, \"0\");\n\treturn hex;\n}\n\n/** Uint8Array → base64 string. */\nexport function toBase64(data: Uint8Array): string {\n\tlet binary = \"\";\n\tfor (let i = 0; i < data.length; i++)\n\t\tbinary += String.fromCharCode(data[i] as number);\n\treturn btoa(binary);\n}\n\n/** Hex string → Uint8Array. */\nexport function fromHex(hex: string): Uint8Array<ArrayBuffer> {\n\tconst bytes = new Uint8Array(hex.length / 2);\n\tfor (let i = 0; i < bytes.length; i++) {\n\t\tbytes[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);\n\t}\n\treturn bytes;\n}\n\n// ---------------------------------------------------------------------------\n// Crypto\n// ---------------------------------------------------------------------------\n\n/** SHA-1 hash via Web Crypto API. Returns a hex string. */\nexport async function sha1(data: Uint8Array | string): Promise<string> {\n\tconst bytes = typeof data === \"string\" ? encodeUtf8(data) : data;\n\tconst hash = await globalThis.crypto.subtle.digest(\"SHA-1\", bytes);\n\treturn toHex(new Uint8Array(hash));\n}\n\n// ---------------------------------------------------------------------------\n// Compression\n// ---------------------------------------------------------------------------\n\n/**\n * Deflate compress via the CompressionStream Web API.\n * Falls back to throwing if CompressionStream is unavailable (very old runtimes).\n */\nexport async function deflate(\n\tdata: Uint8Array,\n): Promise<Uint8Array<ArrayBuffer>> {\n\tconst stream = new Blob([data])\n\t\t.stream()\n\t\t.pipeThrough(new CompressionStream(\"deflate\"));\n\treturn new Uint8Array(await new Response(stream).arrayBuffer());\n}\n\n// ---------------------------------------------------------------------------\n// Binary detection (replaces Buffer.includes(0) pattern)\n// ---------------------------------------------------------------------------\n\n/** Check if a Uint8Array contains a null byte. */\nexport function hasNullByte(data: Uint8Array): boolean {\n\treturn data.includes(0);\n}\n\n/**\n * Read a blob as text or binary metadata — the edge-compatible replacement\n * for the `Buffer.from(blob)` pattern used throughout diff.ts and history.ts.\n */\nexport function readBlobContent(blob: Uint8Array): {\n\tisBinary: boolean;\n\ttext: string;\n\tbytes: Uint8Array;\n} {\n\tconst isBinary = hasNullByte(blob);\n\treturn {\n\t\tisBinary,\n\t\ttext: isBinary ? \"\" : decodeUtf8(blob),\n\t\tbytes: blob,\n\t};\n}\n","import git from \"isomorphic-git\";\nimport { decodeUtf8, hasNullByte, toBase64 } from \"../edge-utils.js\";\nimport { GitObjectNotFoundError, GitPathNotFoundError } from \"../git-errors.js\";\nimport { qualifyBranchRef } from \"../refs.js\";\nimport { findTreeEntry, listTreeEntries, type TreeEntry } from \"./tree.js\";\nimport { type OpsHooks, type Repo, runStep } from \"./types.js\";\n\n/**\n * A resolved ref (branch/commit) not existing is a normal, expected condition\n * (empty repo, unborn branch) and is handled by each caller. An object that\n * fails to resolve *underneath* an already-resolved ref (a tree/blob the\n * stored pack doesn't actually contain) means the repo's storage is\n * inconsistent — surface that distinctly so callers don't render it as\n * \"empty\" and clients don't see a raw isomorphic-git NotFoundError.\n */\nfunction wrapMissingObject<T>(\n\tpromise: Promise<T>,\n\tcontext: string,\n): Promise<T> {\n\treturn promise.catch((err: unknown) => {\n\t\tif ((err as { code?: string })?.code === \"NotFoundError\") {\n\t\t\tthrow new GitObjectNotFoundError(\n\t\t\t\t`Git data for ${context} is missing from storage. The repository may need to be re-pushed to repair it.`,\n\t\t\t);\n\t\t}\n\t\tthrow err;\n\t});\n}\n\nconst isNotFound = (err: unknown) =>\n\t(err as { code?: string })?.code === \"NotFoundError\";\n\nexport interface CommitInfo {\n\toid: string;\n\tcommit: {\n\t\tmessage: string;\n\t\ttree: string;\n\t\tparent: string[];\n\t\tauthor: {\n\t\t\tname: string;\n\t\t\temail: string;\n\t\t\ttimestamp: number;\n\t\t\ttimezoneOffset: number;\n\t\t};\n\t\tcommitter: {\n\t\t\tname: string;\n\t\t\temail: string;\n\t\t\ttimestamp: number;\n\t\t\ttimezoneOffset: number;\n\t\t};\n\t};\n\tpayload: string;\n}\n\n/**\n * Resolve a branch name / full ref / sha to its commit. The ref not\n * resolving (unborn branch, genuinely empty repo) and the ref resolving but\n * its commit object being unreadable (storage inconsistency — see\n * `wrapMissingObject` above) are different failures with different meanings,\n * so only the first is left as a raw isomorphic-git NotFoundError for\n * callers to treat as \"empty\"; the second is wrapped into\n * GitObjectNotFoundError specifically so it can't be mistaken for the first\n * by an `isNotFound`-style check downstream (see getTreeFromRef/getCommitLog).\n */\nexport async function resolveCommit(repo: Repo, ref: string) {\n\tconst oid = await git.resolveRef({ ...repo, ref: qualifyBranchRef(ref) });\n\tconst result = await wrapMissingObject(\n\t\tgit.readCommit({ ...repo, oid }),\n\t\t`${repo.gitdir} commit ${oid}`,\n\t);\n\treturn { oid, commit: result.commit };\n}\n\n/** Read a blob's bytes by oid. */\nexport async function getBlob(repo: Repo, sha: string): Promise<Uint8Array> {\n\tconst { blob } = await wrapMissingObject(\n\t\tgit.readBlob({ ...repo, oid: sha }),\n\t\t`${repo.gitdir} blob ${sha}`,\n\t);\n\treturn blob;\n}\n\n/** Read a file's bytes at a ref. Throws GitPathNotFoundError when absent. */\nexport async function getFileContent(\n\trepo: Repo,\n\tfilePath: string,\n\tref = \"main\",\n): Promise<Uint8Array> {\n\tconst { commit } = await resolveCommit(repo, ref);\n\tconst context = `${repo.gitdir}@${ref}:${filePath}`;\n\tconst entry = await wrapMissingObject(\n\t\tfindTreeEntry(repo, commit.tree, filePath),\n\t\tcontext,\n\t);\n\n\tif (entry?.type !== \"blob\") {\n\t\tthrow new GitPathNotFoundError(`File not found: ${filePath}`);\n\t}\n\n\tconst { blob } = await wrapMissingObject(\n\t\tgit.readBlob({ ...repo, oid: entry.oid }),\n\t\tcontext,\n\t);\n\treturn blob;\n}\n\n/** Read one commit by sha. */\nexport async function getCommit(repo: Repo, sha: string): Promise<CommitInfo> {\n\tconst result = await wrapMissingObject(\n\t\tgit.readCommit({ ...repo, oid: sha }),\n\t\t`${repo.gitdir} commit ${sha}`,\n\t);\n\treturn { oid: result.oid, commit: result.commit, payload: result.payload };\n}\n\nfunction isFullyWalked(commits: CommitInfo[]): boolean {\n\tconst last = commits[commits.length - 1];\n\treturn !!last && last.commit.parent.length === 0;\n}\n\nexport interface CommitLogOptions {\n\tref?: string;\n\tdepth?: number;\n\t/**\n\t * Pass the head sha when the caller already resolved `ref` — resolveRef\n\t * tries several candidate paths in sequence and misses the first few every\n\t * time for a normal branch name, which is pure waste when the sha is known.\n\t */\n\tknownHeadSha?: string;\n}\n\n/**\n * The commit chain from a ref, newest first. Walking is inherently sequential\n * (each commit's oid is only discoverable by reading its child first) and\n * network-round-trip-bound against object storage, so the deepest walk seen\n * per head is memoized in `hooks.resultCache` and sliced for shallower or\n * repeated requests — don't bypass this by calling `git.log` directly.\n */\nexport async function getCommitLog(\n\trepo: Repo,\n\toptions: CommitLogOptions = {},\n\thooks?: OpsHooks,\n): Promise<CommitInfo[]> {\n\tconst ref = options.ref ?? \"main\";\n\tconst depth = options.depth ?? 50;\n\n\tlet headSha: string;\n\tif (options.knownHeadSha) {\n\t\theadSha = options.knownHeadSha;\n\t} else {\n\t\ttry {\n\t\t\theadSha = await git.resolveRef({ ...repo, ref: qualifyBranchRef(ref) });\n\t\t} catch (err: unknown) {\n\t\t\tif (isNotFound(err)) return [];\n\t\t\tthrow err;\n\t\t}\n\t}\n\n\tconst cacheKey = `commitlog:${repo.gitdir}:${headSha}`;\n\tconst cached = hooks?.resultCache?.get<CommitInfo[]>(cacheKey);\n\tif (cached && (cached.length >= depth || isFullyWalked(cached))) {\n\t\thooks?.onNote?.(\n\t\t\t`getCommitLog: result-cache HIT for ${cacheKey} (depth=${depth})`,\n\t\t);\n\t\treturn cached.slice(0, depth);\n\t}\n\thooks?.onNote?.(\n\t\t`getCommitLog: result-cache MISS for ${cacheKey} (depth=${depth})`,\n\t);\n\n\tif (hooks?.prefetch && depth >= (hooks.prefetchMinDepth ?? 5)) {\n\t\tawait runStep(hooks, \"prefetch\", hooks.prefetch);\n\t}\n\n\t// headSha is already known-good here (resolveRef above succeeded, or the\n\t// caller passed knownHeadSha) — a NotFoundError from the walk itself means\n\t// a commit/tree/blob it needs is missing from storage, not that the ref\n\t// doesn't exist. Unlike the resolveRef catch above, that's not \"empty\",\n\t// it's storage inconsistency, so wrapMissingObject turns it into a\n\t// GitObjectNotFoundError instead of silently returning [] — an empty\n\t// result here would otherwise get treated as \"this branch has no\n\t// history\" by every caller (and, worse, potentially cached as such).\n\tconst commits = await wrapMissingObject(\n\t\trunStep(hooks, `git.log ${ref} depth=${depth}`, () =>\n\t\t\tgit.log({ ...repo, ref: headSha, depth }),\n\t\t),\n\t\t`${repo.gitdir}@${ref} history`,\n\t);\n\tconst result = commits.map((commit) => ({\n\t\toid: commit.oid,\n\t\tcommit: commit.commit,\n\t\tpayload: commit.payload || \"\",\n\t}));\n\tif (!cached || result.length > cached.length) {\n\t\thooks?.resultCache?.set(cacheKey, result);\n\t}\n\treturn result;\n}\n\n/** A file at a ref, decoded for display: utf8 text or base64 when binary. */\nexport async function getFileFromRef(\n\trepo: Repo,\n\tfilePath: string,\n\tref: string,\n): Promise<{ content: string; size: number; isBinary: boolean }> {\n\tconst bytes = await getFileContent(repo, filePath, ref);\n\tconst isBinary = hasNullByte(bytes);\n\n\treturn {\n\t\tcontent: isBinary ? toBase64(bytes) : decodeUtf8(bytes),\n\t\tsize: bytes.length,\n\t\tisBinary,\n\t};\n}\n\n/**\n * List a directory at the tip of a ref. Returns [] for an empty repo/unborn\n * branch; throws GitPathNotFoundError when `treePath` doesn't exist. Results\n * are memoized per head sha (auto-invalidates on push).\n */\nexport async function getTreeFromRef(\n\trepo: Repo,\n\toptions: { ref?: string; treePath?: string } = {},\n\thooks?: OpsHooks,\n): Promise<TreeEntry[]> {\n\tconst ref = options.ref ?? \"main\";\n\tconst treePath = options.treePath ?? \"\";\n\n\tlet commit: Awaited<ReturnType<typeof resolveCommit>>[\"commit\"];\n\tlet headSha: string;\n\ttry {\n\t\tconst resolved = await resolveCommit(repo, ref);\n\t\tcommit = resolved.commit;\n\t\theadSha = resolved.oid;\n\t} catch (err: unknown) {\n\t\tif (isNotFound(err)) return [];\n\t\tthrow err;\n\t}\n\n\tconst cacheKey = `tree:${repo.gitdir}:${headSha}:${treePath}`;\n\tconst cached = hooks?.resultCache?.get<TreeEntry[]>(cacheKey);\n\tif (cached) {\n\t\thooks?.onNote?.(`getTreeFromRef: result-cache HIT for ${cacheKey}`);\n\t\treturn cached;\n\t}\n\thooks?.onNote?.(`getTreeFromRef: result-cache MISS for ${cacheKey}`);\n\n\t// Unlike getCommitLog, a tree read has no \"depth\" to gate on — it always\n\t// needs at least the head commit's tree object, and (for a non-root path)\n\t// one object per path segment on top of that, so there's no shallow case\n\t// where prefetching every pack is wasted bandwidth the way a depth=1\n\t// commit-log walk can be. Without this, a cache-miss tree read falls\n\t// through to isomorphic-git's own pack resolution, which probes indexed\n\t// packs one at a time instead of warming them all in parallel up front —\n\t// this was previously the slowest of a tree page's parallel queries in\n\t// production for exactly that reason.\n\tif (hooks?.prefetch) {\n\t\tawait runStep(hooks, \"prefetch\", hooks.prefetch);\n\t}\n\n\tconst context = `${repo.gitdir}@${ref}:${treePath || \"/\"}`;\n\tlet result: TreeEntry[];\n\tif (!treePath) {\n\t\tresult = await wrapMissingObject(\n\t\t\trunStep(hooks, \"listTreeEntries (root)\", () =>\n\t\t\t\tlistTreeEntries(repo, commit.tree),\n\t\t\t),\n\t\t\tcontext,\n\t\t);\n\t} else {\n\t\tconst entry = await wrapMissingObject(\n\t\t\trunStep(hooks, `findTreeEntry ${treePath}`, () =>\n\t\t\t\tfindTreeEntry(repo, commit.tree, treePath),\n\t\t\t),\n\t\t\tcontext,\n\t\t);\n\t\tif (!entry) {\n\t\t\tthrow new GitPathNotFoundError(\n\t\t\t\t`Path \"${treePath}\" does not exist at ${ref}`,\n\t\t\t);\n\t\t}\n\t\tresult =\n\t\t\tentry.type !== \"tree\"\n\t\t\t\t? []\n\t\t\t\t: await wrapMissingObject(\n\t\t\t\t\t\trunStep(hooks, `listTreeEntries ${treePath}`, () =>\n\t\t\t\t\t\t\tlistTreeEntries(repo, entry.oid, entry.path),\n\t\t\t\t\t\t),\n\t\t\t\t\t\tcontext,\n\t\t\t\t\t);\n\t}\n\n\thooks?.resultCache?.set(cacheKey, result);\n\treturn result;\n}\n\n/**\n * A page of commit history from a branch tip. Memoized per head sha; builds\n * on {@link getCommitLog}'s walk cache for the underlying chain.\n */\nexport async function getCommitHistory(\n\trepo: Repo,\n\toptions: { ref: string; limit?: number; skip?: number },\n\thooks?: OpsHooks,\n): Promise<CommitInfo[]> {\n\tconst limit = options.limit ?? 50;\n\tconst skip = options.skip ?? 0;\n\tconst headSha = await git\n\t\t.resolveRef({ ...repo, ref: qualifyBranchRef(options.ref) })\n\t\t.catch(() => null);\n\n\tconst cacheKey = headSha\n\t\t? `commits:${repo.gitdir}:${headSha}:${limit}:${skip}`\n\t\t: null;\n\tif (cacheKey) {\n\t\tconst cached = hooks?.resultCache?.get<CommitInfo[]>(cacheKey);\n\t\tif (cached) {\n\t\t\thooks?.onNote?.(`getCommitHistory: result-cache HIT for ${cacheKey}`);\n\t\t\treturn cached;\n\t\t}\n\t}\n\thooks?.onNote?.(\n\t\t`getCommitHistory: result-cache MISS for ${cacheKey ?? \"(no head)\"}`,\n\t);\n\tif (!headSha) return [];\n\n\tconst all = await getCommitLog(\n\t\trepo,\n\t\t{ ref: options.ref, depth: limit + skip, knownHeadSha: headSha },\n\t\thooks,\n\t);\n\tconst result = all.slice(skip, skip + limit);\n\n\tif (cacheKey) hooks?.resultCache?.set(cacheKey, result);\n\treturn result;\n}\n","import type git from \"isomorphic-git\";\n\n/** The fs shape isomorphic-git accepts, derived from its own signatures. */\nexport type IsoGitFs = Parameters<typeof git.readTree>[0][\"fs\"];\n\n/**\n * A repository handle: everything isomorphic-git needs to address one bare\n * repo. Create it once per request (sharing `cache` across calls is what lets\n * isomorphic-git reuse parsed pack indexes) and pass it to every op.\n */\nexport interface Repo {\n\tfs: IsoGitFs;\n\tgitdir: string;\n\t/** isomorphic-git's shared parse cache — strongly recommended per repo. */\n\tcache?: object;\n}\n\n/**\n * Key/value store for memoizing expensive walk results (commit logs, tree\n * listings, per-file history). Keys are namespaced `kind:gitdir:headSha:…`,\n * so entries self-invalidate on push (new head, new key) — but evict entries\n * under {@link resultKeyPrefixes} after rewriting a repo's storage out of\n * band, or stale walks leak until your store's own eviction.\n */\nexport interface ResultCache {\n\tget<T>(key: string): T | null | undefined;\n\tset(key: string, value: unknown): void;\n}\n\n/** Optional instrumentation and tuning hooks accepted by every op. */\nexport interface OpsHooks {\n\tresultCache?: ResultCache;\n\t/** Wrap a timed sub-step (network walk, tree listing). Default: run directly. */\n\tstep?: <T>(label: string, fn: () => Promise<T>) => Promise<T>;\n\t/** Diagnostic sink for cache hit/miss and walk summaries. */\n\tonNote?: (message: string) => void;\n\t/**\n\t * Wire pack prefetching (e.g. `GitFs.prefetchPacks`) here so a sequential\n\t * walk doesn't pay one network round trip per commit. Called once before\n\t * history walks of depth >= `prefetchMinDepth` (see below), and\n\t * unconditionally on every cache-miss tree read ({@link getTreeFromRef}) —\n\t * a tree read has no shallow case, it always needs at least the head\n\t * commit's tree object.\n\t */\n\tprefetch?: () => Promise<void>;\n\t/** Minimum walk depth before `prefetch` fires for history walks. Default 5. */\n\tprefetchMinDepth?: number;\n}\n\nexport const runStep = <T>(\n\thooks: OpsHooks | undefined,\n\tlabel: string,\n\tfn: () => Promise<T>,\n): Promise<T> => (hooks?.step ? hooks.step(label, fn) : fn());\n\n/**\n * The result-cache key prefixes holding entries for `gitdir` — evict these\n * from your {@link ResultCache} when the repo's storage was rewritten outside\n * a normal push (bulk sync, rename, repack cleanup).\n */\nexport function resultKeyPrefixes(gitdir: string): string[] {\n\treturn [\n\t\t`commitlog:${gitdir}:`,\n\t\t`tree:${gitdir}:`,\n\t\t`commits:${gitdir}:`,\n\t\t`last-commits:${gitdir}:`,\n\t\t`file-history:${gitdir}:`,\n\t];\n}\n","import { type CommitInfo, getCommitLog } from \"./history.js\";\nimport { findTreeEntry } from \"./tree.js\";\nimport type { OpsHooks, Repo } from \"./types.js\";\nimport { runStep } from \"./types.js\";\n\nexport interface FileHistoryEntry {\n\tsha: string;\n\tmessage: string;\n\tauthorName: string;\n\tauthorEmail: string;\n\tcreatedAt: string;\n}\n\nexport interface FileHistoryResult {\n\tentries: FileHistoryEntry[];\n\t/**\n\t * True when the walk hit its depth budget (or the requested `limit`)\n\t * before exhausting the branch's full commit chain — there may be older\n\t * commits touching this file that a deeper walk would surface.\n\t */\n\ttruncated: boolean;\n}\n\n/**\n * Default walk bound for a caller that actually wants deep history (a file's\n * \"History\" tab). Walking the full chain is round-trip-bound on object\n * storage, so cap how far back a single request will look.\n */\nexport const HISTORY_WALK_DEPTH = 400;\n\n/**\n * Much shallower default for a \"latest commit touching this file\" banner that\n * only displays `entries[0]` — trades \"always finds the true last-touching\n * commit\" for \"finds it if it's reasonably recent\", the right call for a\n * banner with a full History view a click away.\n */\nexport const BANNER_WALK_DEPTH = 60;\n\n/** Tree reads are prefetched in parallel windows; see getLastCommitsForTree. */\nconst PREFETCH_WINDOW = 24;\n\nfunction toEntry(commit: CommitInfo): FileHistoryEntry {\n\treturn {\n\t\tsha: commit.oid,\n\t\tmessage: commit.commit.message.trim(),\n\t\tauthorName: commit.commit.author.name,\n\t\tauthorEmail: commit.commit.author.email,\n\t\tcreatedAt: new Date(commit.commit.author.timestamp * 1000).toISOString(),\n\t};\n}\n\n/**\n * All commits (newest first) that changed a single file's blob oid, walking\n * the first-parent chain — same approach as getLastCommitsForTree but for one\n * path and collecting every match instead of stopping at the first.\n */\nexport async function getFileHistory(\n\trepo: Repo,\n\toptions: {\n\t\tref: string;\n\t\tfilePath: string;\n\t\tlimit?: number;\n\t\tmaxDepth?: number;\n\t},\n\thooks?: OpsHooks,\n): Promise<FileHistoryResult> {\n\tconst limit = options.limit ?? 30;\n\tconst maxDepth = options.maxDepth ?? HISTORY_WALK_DEPTH;\n\tconst walkDepth = Math.max(maxDepth, limit);\n\tconst commits = await runStep(hooks, `getCommitLog depth=${walkDepth}`, () =>\n\t\tgetCommitLog(repo, { ref: options.ref, depth: walkDepth }, hooks),\n\t);\n\tconst head = commits[0];\n\tif (!head) return { entries: [], truncated: false };\n\n\tconst cacheKey = `file-history:${repo.gitdir}:${head.oid}:${options.filePath}:${limit}:${maxDepth}`;\n\tconst cached = hooks?.resultCache?.get<FileHistoryResult>(cacheKey);\n\tif (cached) {\n\t\thooks?.onNote?.(\"getFileHistory: result-cache HIT, skipping history walk\");\n\t\treturn cached;\n\t}\n\thooks?.onNote?.(\"getFileHistory: result-cache MISS, walking history\");\n\n\tconst byOid = new Map(commits.map((commit) => [commit.oid, commit]));\n\n\tconst oidByCommitTree = new Map<string, string | null>();\n\tasync function resolveOid(commitTreeOid: string): Promise<string | null> {\n\t\tconst cachedOid = oidByCommitTree.get(commitTreeOid);\n\t\tif (cachedOid !== undefined) return cachedOid;\n\t\tconst entry = await findTreeEntry(repo, commitTreeOid, options.filePath);\n\t\tconst oid = entry?.type === \"blob\" ? entry.oid : null;\n\t\toidByCommitTree.set(commitTreeOid, oid);\n\t\treturn oid;\n\t}\n\n\tconst entries: FileHistoryEntry[] = [];\n\tlet truncated = false;\n\n\touter: for (\n\t\tlet windowStart = 0;\n\t\twindowStart < commits.length;\n\t\twindowStart += PREFETCH_WINDOW\n\t) {\n\t\tconst windowEnd = Math.min(windowStart + PREFETCH_WINDOW, commits.length);\n\t\t// +1 lookahead so the last entry's parent tree is already warm too.\n\t\tconst prefetchEnd = Math.min(windowEnd + 1, commits.length);\n\n\t\tawait Promise.all(\n\t\t\tcommits\n\t\t\t\t.slice(windowStart, prefetchEnd)\n\t\t\t\t.map((commit) => resolveOid(commit.commit.tree)),\n\t\t);\n\n\t\tfor (let i = windowStart; i < windowEnd; i++) {\n\t\t\tconst commit = commits[i];\n\t\t\tif (!commit) break outer;\n\n\t\t\tconst parentSha = commit.commit.parent[0];\n\t\t\tconst parentCommit = parentSha ? byOid.get(parentSha) : undefined;\n\t\t\tif (parentSha && !parentCommit) {\n\t\t\t\t// Walked past the depth cap without reaching this commit's parent —\n\t\t\t\t// can't tell whether it changed the file; stop and report truncated.\n\t\t\t\ttruncated = true;\n\t\t\t\tbreak outer;\n\t\t\t}\n\n\t\t\tconst [oid, parentOid] = await Promise.all([\n\t\t\t\tresolveOid(commit.commit.tree),\n\t\t\t\tparentCommit\n\t\t\t\t\t? resolveOid(parentCommit.commit.tree)\n\t\t\t\t\t: Promise.resolve(null),\n\t\t\t]);\n\n\t\t\tif (oid !== parentOid) {\n\t\t\t\tentries.push(toEntry(commit));\n\t\t\t\tif (entries.length >= limit) {\n\t\t\t\t\ttruncated = i < commits.length - 1;\n\t\t\t\t\tbreak outer;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tconst result = { entries, truncated };\n\thooks?.resultCache?.set(cacheKey, result);\n\treturn result;\n}\n","import { type CommitInfo, getCommitLog } from \"./history.js\";\nimport { findTreeEntry, listTreeEntries } from \"./tree.js\";\nimport type { OpsHooks, Repo } from \"./types.js\";\nimport { runStep } from \"./types.js\";\n\nexport interface LastCommitInfo {\n\tsha: string;\n\tmessage: string;\n\tauthorName: string;\n\tauthorEmail: string;\n\tcreatedAt: string;\n}\n\n/**\n * Bounds how far back the history walk looks to resolve \"last commit touching\n * this path\" for a directory listing. Entries whose last change is older\n * simply show no last-commit info rather than paying an unbounded scan.\n */\nconst HISTORY_WALK_DEPTH = 400;\n\n/**\n * The walk itself is inherently sequential (each step needs to know what's\n * still \"remaining\" from the step before), but the tree-object reads that\n * back it are not — each commit's tree oid is already known upfront from the\n * commit log. Prefetching a window of tree reads in parallel turns ~1 network\n * round trip per commit (serialized) into ~1 round trip per window, which is\n * where nearly all of this function's wall-clock time goes on object storage.\n */\nconst PREFETCH_WINDOW = 24;\n\nfunction toLastCommitInfo(commit: CommitInfo): LastCommitInfo {\n\treturn {\n\t\tsha: commit.oid,\n\t\tmessage: commit.commit.message.trim(),\n\t\tauthorName: commit.commit.author.name,\n\t\tauthorEmail: commit.commit.author.email,\n\t\tcreatedAt: new Date(commit.commit.author.timestamp * 1000).toISOString(),\n\t};\n}\n\n/**\n * For each direct child of `treePath` (at the tip of `ref`), find the most\n * recent commit that changed it — the tree view's \"last commit\" column. Walks\n * history newest-to-oldest, comparing the directory's tree oid\n * commit-to-commit and only descending one level to diff child oids when\n * something under the directory actually changed. Preserve the two-phase\n * structure (parallel prefetch, then sequential resolve) when touching this —\n * the \"which entries are still unresolved\" state must advance\n * commit-by-commit.\n */\nexport async function getLastCommitsForTree(\n\trepo: Repo,\n\toptions: { ref: string; treePath?: string; depth?: number },\n\thooks?: OpsHooks,\n): Promise<Record<string, LastCommitInfo>> {\n\tconst treePath = options.treePath ?? \"\";\n\tconst depth = options.depth ?? HISTORY_WALK_DEPTH;\n\tconst commits = await runStep(hooks, `getCommitLog depth=${depth}`, () =>\n\t\tgetCommitLog(repo, { ref: options.ref, depth }, hooks),\n\t);\n\thooks?.onNote?.(`getLastCommitsForTree: ${commits.length} commits in log`);\n\tconst head = commits[0];\n\tif (!head) return {};\n\n\tconst cacheKey = `last-commits:${repo.gitdir}:${head.oid}:${treePath}`;\n\tconst cachedResult =\n\t\thooks?.resultCache?.get<Record<string, LastCommitInfo>>(cacheKey);\n\tif (cachedResult) {\n\t\thooks?.onNote?.(\n\t\t\t\"getLastCommitsForTree: result-cache HIT, skipping history walk\",\n\t\t);\n\t\treturn cachedResult;\n\t}\n\thooks?.onNote?.(\"getLastCommitsForTree: result-cache MISS, walking history\");\n\n\tconst byOid = new Map(commits.map((commit) => [commit.oid, commit]));\n\n\t// In a linear history, the \"parent tree\" resolved at commit[i] is the same\n\t// tree already resolved as the \"current tree\" at commit[i-1] — memoize by\n\t// commit-tree oid (and by resolved dir oid) so each distinct tree is only\n\t// walked/listed once across the whole scan instead of twice per commit.\n\tconst dirOidByCommitTree = new Map<string, string | null>();\n\tconst childrenByDirOid = new Map<\n\t\tstring,\n\t\tAwaited<ReturnType<typeof listTreeEntries>>\n\t>();\n\n\tasync function resolveDirOid(commitTreeOid: string): Promise<string | null> {\n\t\tconst cached = dirOidByCommitTree.get(commitTreeOid);\n\t\tif (cached !== undefined) return cached;\n\t\tconst entry = await findTreeEntry(repo, commitTreeOid, treePath);\n\t\tconst dirOid = entry?.type === \"tree\" ? entry.oid : null;\n\t\tdirOidByCommitTree.set(commitTreeOid, dirOid);\n\t\treturn dirOid;\n\t}\n\n\tasync function resolveChildren(dirOid: string | null) {\n\t\tif (dirOid === null) return [];\n\t\tconst cached = childrenByDirOid.get(dirOid);\n\t\tif (cached) return cached;\n\t\tconst children = await listTreeEntries(repo, dirOid, treePath);\n\t\tchildrenByDirOid.set(dirOid, children);\n\t\treturn children;\n\t}\n\n\tconst headDirOid = await resolveDirOid(head.commit.tree);\n\tif (headDirOid === null) return {};\n\n\t// listTreeEntries is prefixed with treePath so result keys match the full\n\t// paths that callers key their file listing by.\n\tconst headChildren = await resolveChildren(headDirOid);\n\tconst remaining = new Set(headChildren.map((entry) => entry.path));\n\tconst result: Record<string, LastCommitInfo> = {};\n\n\tlet commitsWalked = 0;\n\tlet prefetchWindows = 0;\n\tconst walkStart = performance.now();\n\n\touter: for (\n\t\tlet windowStart = 0;\n\t\twindowStart < commits.length && remaining.size > 0;\n\t\twindowStart += PREFETCH_WINDOW\n\t) {\n\t\tconst windowEnd = Math.min(windowStart + PREFETCH_WINDOW, commits.length);\n\t\t// +1 lookahead commit so the last entry's parent tree is already warm too.\n\t\tconst prefetchEnd = Math.min(windowEnd + 1, commits.length);\n\t\tprefetchWindows++;\n\n\t\t// Phase A: resolve this window's directory oid for every commit's tree in\n\t\t// parallel (a no-op await when treePath is \"\" — the root tree oid IS the\n\t\t// commit tree, no lookup needed).\n\t\tconst dirOids = await Promise.all(\n\t\t\tcommits\n\t\t\t\t.slice(windowStart, prefetchEnd)\n\t\t\t\t.map((commit) => resolveDirOid(commit.commit.tree)),\n\t\t);\n\n\t\t// Phase B: resolve children for every distinct directory oid the window\n\t\t// touched, in parallel — the actual tree-object read for the common case.\n\t\tawait Promise.all(\n\t\t\t[...new Set(dirOids)].map((dirOid) => resolveChildren(dirOid)),\n\t\t);\n\n\t\tfor (let i = windowStart; i < windowEnd; i++) {\n\t\t\tif (remaining.size === 0) break outer;\n\t\t\tconst commit = commits[i];\n\t\t\tif (!commit) break outer;\n\t\t\tcommitsWalked++;\n\n\t\t\tconst parentSha = commit.commit.parent[0];\n\t\t\tconst parentCommit = parentSha ? byOid.get(parentSha) : undefined;\n\t\t\tif (parentSha && !parentCommit) break outer; // walked past the depth cap\n\n\t\t\t// Already resolved above (or memoized from an earlier window/commit) —\n\t\t\t// these awaits resolve immediately from cache.\n\t\t\tconst [dirOid, parentDirOid] = await Promise.all([\n\t\t\t\tresolveDirOid(commit.commit.tree),\n\t\t\t\tparentCommit\n\t\t\t\t\t? resolveDirOid(parentCommit.commit.tree)\n\t\t\t\t\t: Promise.resolve(null),\n\t\t\t]);\n\n\t\t\tif (dirOid === parentDirOid) continue;\n\n\t\t\tconst [children, parentChildren] = await Promise.all([\n\t\t\t\tresolveChildren(dirOid),\n\t\t\t\tresolveChildren(parentDirOid),\n\t\t\t]);\n\t\t\tconst childByName = new Map(\n\t\t\t\tchildren.map((entry) => [entry.path, entry.oid]),\n\t\t\t);\n\t\t\tconst parentChildByName = new Map(\n\t\t\t\tparentChildren.map((entry) => [entry.path, entry.oid]),\n\t\t\t);\n\n\t\t\tfor (const name of remaining) {\n\t\t\t\tif (childByName.get(name) !== parentChildByName.get(name)) {\n\t\t\t\t\tresult[name] = toLastCommitInfo(commit);\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor (const name of Object.keys(result)) {\n\t\t\t\tremaining.delete(name);\n\t\t\t}\n\t\t}\n\t}\n\n\thooks?.onNote?.(\n\t\t`getLastCommitsForTree: walked ${commitsWalked}/${commits.length} commits across ${prefetchWindows} prefetch windows in ${(performance.now() - walkStart).toFixed(1)}ms, ${dirOidByCommitTree.size} unique tree lookups, ${remaining.size} entries never resolved`,\n\t);\n\n\thooks?.resultCache?.set(cacheKey, result);\n\treturn result;\n}\n","import git from \"isomorphic-git\";\nimport { qualifyBranchRef } from \"../refs.js\";\nimport { assertSafeBranchName } from \"./branch.js\";\nimport type { Repo } from \"./types.js\";\n\nexport interface MergeAnalysis {\n\tcanMerge: boolean;\n\thasConflicts: boolean;\n\tconflictingFiles: string[];\n\tfastForward: boolean;\n}\n\n/**\n * Cheap pre-merge check: do both branches exist, and is this a fast-forward?\n * `canMerge`/`fastForward` are the only fields this actually determines —\n * `hasConflicts`/`conflictingFiles` are NOT a real content-conflict check\n * (isomorphic-git's `git.merge` doesn't expose a dry-run), they only ever\n * reflect \"one of the branches couldn't be resolved\" (`canMerge: false`).\n * Real merge conflicts are only discoverable by actually attempting the\n * merge.\n */\nexport async function analyzeMerge(\n\trepo: Repo,\n\tsourceBranch: string,\n\ttargetBranch: string,\n): Promise<MergeAnalysis> {\n\tassertSafeBranchName(sourceBranch);\n\tassertSafeBranchName(targetBranch);\n\n\ttry {\n\t\tconst [sourceOid, targetOid] = await Promise.all([\n\t\t\tgit.resolveRef({ ...repo, ref: qualifyBranchRef(sourceBranch) }),\n\t\t\tgit.resolveRef({ ...repo, ref: qualifyBranchRef(targetBranch) }),\n\t\t]);\n\n\t\tconst isDescendant = await git.isDescendent({\n\t\t\t...repo,\n\t\t\toid: sourceOid,\n\t\t\tancestor: targetOid,\n\t\t});\n\n\t\treturn {\n\t\t\tcanMerge: true,\n\t\t\thasConflicts: false,\n\t\t\tconflictingFiles: [],\n\t\t\tfastForward: isDescendant,\n\t\t};\n\t} catch (err) {\n\t\tif ((err as { code?: string })?.code !== \"NotFoundError\") {\n\t\t\tthrow err;\n\t\t}\n\t\t// A branch ref failed to resolve — not a content conflict, just \"can't\n\t\t// merge because one side doesn't exist.\" hasConflicts here is a\n\t\t// misnomer kept for MergeAnalysis's existing shape; canMerge is the\n\t\t// field that actually matters to callers.\n\t\treturn {\n\t\t\tcanMerge: false,\n\t\t\thasConflicts: true,\n\t\t\tconflictingFiles: [],\n\t\t\tfastForward: false,\n\t\t};\n\t}\n}\n\n/**\n * Attempt a fast-forward merge directly against the bare repo: when source is\n * a descendant of target, just move the target ref — no worktree, no new\n * commit. Returns null when the merge is not a fast-forward (callers fall\n * back to a real three-way merge, which needs a worktree). Serialize with a\n * per-repo lock: resolve → writeRef is not atomic.\n */\nexport async function fastForwardMerge(\n\trepo: Repo,\n\tsourceBranch: string,\n\ttargetBranch: string,\n): Promise<{ success: true; commitSha: string } | null> {\n\tassertSafeBranchName(sourceBranch);\n\tassertSafeBranchName(targetBranch);\n\tconst [sourceOid, targetOid] = await Promise.all([\n\t\tgit.resolveRef({ ...repo, ref: `refs/heads/${sourceBranch}` }),\n\t\tgit.resolveRef({ ...repo, ref: `refs/heads/${targetBranch}` }),\n\t]);\n\tconst isFF = await git.isDescendent({\n\t\t...repo,\n\t\toid: sourceOid,\n\t\tancestor: targetOid,\n\t});\n\tif (!isFF) return null;\n\tawait git.writeRef({\n\t\t...repo,\n\t\tref: `refs/heads/${targetBranch}`,\n\t\tvalue: sourceOid,\n\t\tforce: true,\n\t});\n\treturn { success: true, commitSha: sourceOid };\n}\n"],"mappings":"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","names":["FULL_SHA_RE","git","import_isomorphic_git","import_isomorphic_git","git","git","import_isomorphic_git","import_isomorphic_git","git","git","oidA","oidB","files","HISTORY_WALK_DEPTH","PREFETCH_WINDOW","import_isomorphic_git","git"]}
|
|
1
|
+
{"version":3,"sources":["../src/ops/index.ts","../src/ops/branch.ts","../src/git-errors.ts","../src/refs.ts","../src/ops/commit.ts","../src/ops/tree.ts","../src/ops/diff.ts","../src/edge-utils.ts","../src/ops/history.ts","../src/ops/types.ts","../src/ops/file-history.ts","../src/ops/last-commit.ts","../src/ops/merge.ts"],"sourcesContent":["export {\n\tassertBranchExists,\n\tassertSafeBranchName,\n\ttype Branch,\n\tcreateBranchFrom,\n\tdeleteBranchByName,\n\tlistBranches,\n\tresolveLooseRefFast,\n} from \"./branch.js\";\nexport {\n\tauthorNow,\n\ttype CommitAuthor,\n\tcommitFilesToBare,\n\tdeleteFileFromBare,\n\twriteCommitToBare,\n} from \"./commit.js\";\nexport {\n\ttype DiffFile,\n\ttype DiffResult,\n\tgetCommitDiff,\n\tgetDiffBetweenRefs,\n} from \"./diff.js\";\nexport {\n\tBANNER_WALK_DEPTH,\n\ttype FileHistoryEntry,\n\ttype FileHistoryResult,\n\tgetFileHistory,\n\tHISTORY_WALK_DEPTH,\n} from \"./file-history.js\";\nexport {\n\ttype CommitInfo,\n\ttype CommitLogOptions,\n\tgetBlob,\n\tgetCommit,\n\tgetCommitHistory,\n\tgetCommitLog,\n\tgetFileContent,\n\tgetFileFromRef,\n\tgetTreeFromRef,\n\tresolveCommit,\n} from \"./history.js\";\nexport {\n\tgetLastCommitsForTree,\n\ttype LastCommitInfo,\n} from \"./last-commit.js\";\nexport { analyzeMerge, fastForwardMerge, type MergeAnalysis } from \"./merge.js\";\nexport {\n\tdeleteFromTree,\n\tfindTreeEntry,\n\tlistTreeEntries,\n\ttype TreeEntry,\n\tupsertTree,\n} from \"./tree.js\";\nexport {\n\ttype IsoGitFs,\n\ttype OpsHooks,\n\ttype Repo,\n\ttype ResultCache,\n\tresultKeyPrefixes,\n} from \"./types.js\";\n","import git from \"isomorphic-git\";\nimport { GitInvalidRequestError } from \"../git-errors.js\";\nimport { isSafeBranchName } from \"../refs.js\";\nimport type { Repo } from \"./types.js\";\n\nexport interface Branch {\n\tname: string;\n\tcommit: string;\n\tisDefault: boolean;\n}\n\n/**\n * Defense in depth for every branch-name argument below: `git.deleteBranch`\n * and raw resolveRef/writeRef reads don't validate ref names internally the\n * way `git.branch` does (see refs.ts) — guard at the point the primitives are\n * actually called, not just at an API boundary far above.\n */\nexport function assertSafeBranchName(name: string): void {\n\tif (!isSafeBranchName(name)) {\n\t\tthrow new GitInvalidRequestError(`Invalid branch name: ${name}`);\n\t}\n}\n\nconst FULL_SHA_RE = /^[0-9a-f]{40}$/i;\n\ntype RecursiveFileLister = {\n\tlistFilesRecursively(dirpath: string): Promise<string[]>;\n};\n\nfunction hasRecursiveFileLister(\n\tfs: Repo[\"fs\"],\n): fs is Repo[\"fs\"] & RecursiveFileLister {\n\treturn (\n\t\ttypeof fs === \"object\" &&\n\t\tfs !== null &&\n\t\t\"listFilesRecursively\" in fs &&\n\t\ttypeof (fs as Partial<RecursiveFileLister>).listFilesRecursively ===\n\t\t\t\"function\"\n\t);\n}\n\n/**\n * Lists loose branch names without isomorphic-git's recursive readdir/stat\n * traversal. Object stores already return only leaf keys in a recursive LIST,\n * so this is one request regardless of branch-name nesting. Ordinary\n * filesystems retain isomorphic-git's implementation and packed refs remain\n * supported there.\n */\nasync function listLooseBranches(repo: Repo): Promise<string[] | null> {\n\tif (!hasRecursiveFileLister(repo.fs)) return null;\n\tconst names = await repo.fs.listFilesRecursively(`${repo.gitdir}/refs/heads`);\n\treturn names.filter(isSafeBranchName);\n}\n\n/** Read packed branch refs so the object-store fast path preserves Git semantics. */\nasync function readPackedBranches(repo: Repo): Promise<Map<string, string>> {\n\tif (!hasRecursiveFileLister(repo.fs)) return new Map();\n\tconst promisesFs = (\n\t\trepo.fs as {\n\t\t\tpromises?: {\n\t\t\t\treadFile(path: string, encoding: \"utf8\"): Promise<string | Uint8Array>;\n\t\t\t};\n\t\t}\n\t).promises;\n\tif (!promisesFs) return new Map();\n\ttry {\n\t\tconst content = await promisesFs.readFile(\n\t\t\t`${repo.gitdir}/packed-refs`,\n\t\t\t\"utf8\",\n\t\t);\n\t\tconst refs = new Map<string, string>();\n\t\tfor (const line of (typeof content === \"string\"\n\t\t\t? content\n\t\t\t: new TextDecoder().decode(content)\n\t\t).split(\"\\n\")) {\n\t\t\tconst match = /^([0-9a-f]{40}) refs\\/heads\\/(.+)$/i.exec(line);\n\t\t\tif (match?.[1] && match[2] && isSafeBranchName(match[2])) {\n\t\t\t\trefs.set(match[2], match[1]);\n\t\t\t}\n\t\t}\n\t\treturn refs;\n\t} catch {\n\t\treturn new Map();\n\t}\n}\n\n/** Read the symbolic HEAD directly when the object-store fs is available. */\nasync function getCurrentLooseBranch(repo: Repo): Promise<string | null> {\n\tif (!hasRecursiveFileLister(repo.fs)) return null;\n\tconst promisesFs = (\n\t\trepo.fs as {\n\t\t\tpromises?: {\n\t\t\t\treadFile(path: string, encoding: \"utf8\"): Promise<string | Uint8Array>;\n\t\t\t};\n\t\t}\n\t).promises;\n\tif (!promisesFs) return null;\n\ttry {\n\t\tconst content = await promisesFs.readFile(`${repo.gitdir}/HEAD`, \"utf8\");\n\t\tconst head = (\n\t\t\ttypeof content === \"string\" ? content : new TextDecoder().decode(content)\n\t\t).trim();\n\t\tconst match = /^ref: refs\\/heads\\/(.+)$/.exec(head);\n\t\treturn match?.[1] && isSafeBranchName(match[1]) ? match[1] : null;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\n/**\n * Resolve a fully-qualified ref's oid with one read instead of isomorphic-\n * git's own `resolveRef`, which does a stat *then* a read for a loose ref\n * (two round trips against object storage) — wasteful specifically here,\n * where the caller already knows the ref exists (it came from a directory\n * listing moments earlier). Reads the loose file directly and falls back to\n * `git.resolveRef` only when that doesn't pan out (packed-refs, or anything\n * else the plain-loose-file assumption doesn't cover), so correctness for\n * those cases is unchanged — this only removes the redundant round trip on\n * the common path.\n */\nexport async function resolveLooseRefFast(\n\trepo: Repo,\n\tref: string,\n): Promise<string> {\n\t// Repo.fs is typed as isomorphic-git's own fs union (promise- or\n\t// callback-based) since that's what git.readTree's signature allows, but\n\t// every fs this package actually constructs (git-fs.ts) is promise-based\n\t// — the runtime check just keeps the callback-style branch honest instead\n\t// of crashing on it.\n\tconst promisesFs = (\n\t\trepo.fs as {\n\t\t\tpromises?: {\n\t\t\t\treadFile(path: string, encoding: \"utf8\"): Promise<string | Uint8Array>;\n\t\t\t};\n\t\t}\n\t).promises;\n\tif (promisesFs) {\n\t\ttry {\n\t\t\tconst content = await promisesFs.readFile(\n\t\t\t\t`${repo.gitdir}/${ref}`,\n\t\t\t\t\"utf8\",\n\t\t\t);\n\t\t\tconst oid = (\n\t\t\t\ttypeof content === \"string\"\n\t\t\t\t\t? content\n\t\t\t\t\t: new TextDecoder().decode(content)\n\t\t\t).trim();\n\t\t\tif (FULL_SHA_RE.test(oid)) return oid;\n\t\t} catch {\n\t\t\t// Not a loose file (packed-refs, or genuinely absent) — fall through.\n\t\t}\n\t}\n\treturn git.resolveRef({ ...repo, ref });\n}\n\n/** All branches with their tip commits; [] for an empty repository. */\nexport async function listBranches(repo: Repo): Promise<Branch[]> {\n\ttry {\n\t\tconst [looseBranches, looseCurrentBranch, packedBranches] =\n\t\t\tawait Promise.all([\n\t\t\t\tlistLooseBranches(repo),\n\t\t\t\tgetCurrentLooseBranch(repo),\n\t\t\t\treadPackedBranches(repo),\n\t\t\t]);\n\t\tconst branches =\n\t\t\tlooseBranches === null\n\t\t\t\t? await git.listBranches(repo)\n\t\t\t\t: [...new Set([...looseBranches, ...packedBranches.keys()])].sort();\n\t\tconst currentBranch =\n\t\t\tlooseBranches === null\n\t\t\t\t? await git\n\t\t\t\t\t\t.currentBranch({ ...repo, fullname: false })\n\t\t\t\t\t\t.catch(() => null)\n\t\t\t\t: looseCurrentBranch;\n\n\t\treturn Promise.all(\n\t\t\tbranches.map(async (branch) => {\n\t\t\t\tconst packedCommit = packedBranches.get(branch);\n\t\t\t\treturn {\n\t\t\t\t\tname: branch,\n\t\t\t\t\tcommit:\n\t\t\t\t\t\tpackedCommit && !looseBranches?.includes(branch)\n\t\t\t\t\t\t\t? packedCommit\n\t\t\t\t\t\t\t: await resolveLooseRefFast(repo, `refs/heads/${branch}`),\n\t\t\t\t\tisDefault: branch === currentBranch,\n\t\t\t\t};\n\t\t\t}),\n\t\t);\n\t} catch (err: unknown) {\n\t\tif ((err as { code?: string }).code === \"NotFoundError\") return [];\n\t\tthrow err;\n\t}\n}\n\n/** Create `name` pointing at the tip of `startPoint` (no checkout). */\nexport async function createBranchFrom(\n\trepo: Repo,\n\tname: string,\n\tstartPoint = \"main\",\n): Promise<void> {\n\tassertSafeBranchName(name);\n\tassertSafeBranchName(startPoint);\n\tconst object = await resolveLooseRefFast(repo, `refs/heads/${startPoint}`);\n\tawait git.branch({ ...repo, ref: name, checkout: false, object });\n}\n\n/** Delete a branch ref (validated — deleteBranch has no internal ref check). */\nexport async function deleteBranchByName(\n\trepo: Repo,\n\tname: string,\n): Promise<void> {\n\tassertSafeBranchName(name);\n\tawait git.deleteBranch({ ...repo, ref: name });\n}\n\n/** Throws (NotFoundError) unless the branch resolves. */\nexport async function assertBranchExists(\n\trepo: Repo,\n\tname: string,\n): Promise<void> {\n\tassertSafeBranchName(name);\n\tawait resolveLooseRefFast(repo, `refs/heads/${name}`);\n}\n","/**\n * Git-server error types carrying an HTTP status and a retryability flag, so\n * transport layers can map internal failures to responses without inspecting\n * messages. Extend {@link GitError} for app-specific cases (storage backends,\n * quota, …) and {@link formatErrorResponse} keeps working for them.\n */\nexport class GitError extends Error {\n\tstatusCode: number;\n\tretryable: boolean;\n\n\tconstructor(message: string, statusCode = 500, retryable = false) {\n\t\tsuper(message);\n\t\tthis.name = this.constructor.name;\n\t\tthis.statusCode = statusCode;\n\t\tthis.retryable = retryable;\n\t\tError.captureStackTrace?.(this, this.constructor);\n\t}\n\n\ttoJSON(): Record<string, unknown> {\n\t\treturn {\n\t\t\terror: this.name,\n\t\t\tmessage: this.message,\n\t\t\tstatusCode: this.statusCode,\n\t\t\tretryable: this.retryable,\n\t\t};\n\t}\n}\n\n/** A file/directory path not found within a tree (404). */\nexport class GitPathNotFoundError extends GitError {\n\tconstructor(message: string) {\n\t\tsuper(message, 404, false);\n\t}\n}\n\n/** A git object not found (404). */\nexport class GitObjectNotFoundError extends GitError {\n\tconstructor(message: string) {\n\t\tsuper(message, 404, false);\n\t}\n}\n\n/** A ref (branch/tag) not found (404). */\nexport class GitRefNotFoundError extends GitError {\n\tconstructor(message: string) {\n\t\tsuper(message, 404, false);\n\t}\n}\n\n/** The repository itself not found (404). */\nexport class GitRepositoryNotFoundError extends GitError {\n\tconstructor(message: string) {\n\t\tsuper(message, 404, false);\n\t}\n}\n\nexport interface MergeConflictDetail {\n\tfile: string;\n\tbaseLines?: string[];\n\tsourceLines?: string[];\n\ttargetLines?: string[];\n}\n\n/** A merge conflict (409), carrying per-file conflict detail. */\nexport class GitConflictError extends GitError {\n\tconflicts: MergeConflictDetail[];\n\n\tconstructor(message: string, conflicts: MergeConflictDetail[] = []) {\n\t\tsuper(message, 409, false);\n\t\tthis.conflicts = conflicts;\n\t}\n\n\toverride toJSON(): Record<string, unknown> {\n\t\treturn { ...super.toJSON(), conflicts: this.conflicts };\n\t}\n}\n\n/** Authentication failed (401). */\nexport class GitAuthenticationError extends GitError {\n\tconstructor(message: string) {\n\t\tsuper(message, 401, false);\n\t}\n}\n\n/** Authorization failed (403). */\nexport class GitAuthorizationError extends GitError {\n\tconstructor(message: string) {\n\t\tsuper(message, 403, false);\n\t}\n}\n\n/** Too many failed attempts (429). */\nexport class GitRateLimitError extends GitError {\n\tconstructor(message: string) {\n\t\tsuper(message, 429, false);\n\t}\n}\n\n/** Malformed request (400). */\nexport class GitInvalidRequestError extends GitError {\n\tconstructor(message: string) {\n\t\tsuper(message, 400, false);\n\t}\n}\n\n/** Git wire-protocol violation (400). */\nexport class GitProtocolError extends GitError {\n\tconstructor(message: string) {\n\t\tsuper(message, 400, false);\n\t}\n}\n\n/**\n * Map any error to an HTTP response shape. 401s carry the WWW-Authenticate\n * header git clients need before they will prompt for credentials. Non-GitError\n * failures are masked as opaque 500s — internal messages don't leak.\n */\nexport function formatErrorResponse(error: unknown): {\n\tstatus: number;\n\tbody: Record<string, unknown>;\n\theaders?: Record<string, string>;\n} {\n\tif (error instanceof GitError) {\n\t\treturn {\n\t\t\tstatus: error.statusCode,\n\t\t\tbody: error.toJSON(),\n\t\t\theaders:\n\t\t\t\terror.statusCode === 401\n\t\t\t\t\t? { \"WWW-Authenticate\": 'Basic realm=\"Git Repository\"' }\n\t\t\t\t\t: undefined,\n\t\t};\n\t}\n\n\tif (error instanceof Error) {\n\t\treturn {\n\t\t\tstatus: 500,\n\t\t\tbody: {\n\t\t\t\terror: \"InternalServerError\",\n\t\t\t\tmessage: \"An internal error occurred\",\n\t\t\t\tretryable: true,\n\t\t\t},\n\t\t};\n\t}\n\n\treturn {\n\t\tstatus: 500,\n\t\tbody: {\n\t\t\terror: \"UnknownError\",\n\t\t\tmessage: \"An unknown error occurred\",\n\t\t\tretryable: true,\n\t\t},\n\t};\n}\n","/**\n * Git ref-name validation, mirroring isomorphic-git's own internal `isValidRef`\n * character-class rules (the check `git.branch` and top-level `git.writeRef`\n * run before touching disk).\n *\n * Several of isomorphic-git's OTHER ref-touching primitives — `git.commit`,\n * `git.merge`, `git.deleteBranch`, and top-level `git.resolveRef`/\n * `git.deleteRef` — do NOT run this check internally: they resolve straight\n * through `fs.write`/`fs.rm(join(gitdir, ref))` with no jail to the gitdir.\n * On a shared-storage server (many repos under one prefix or base directory),\n * every branch/ref name that originates from request input must be validated\n * against these predicates before it reaches any of those primitives —\n * otherwise a `\"../\"`-laden name lets a caller with write access to any single\n * repo read, corrupt, or delete another repo's ref/object files.\n */\n\nconst BAD_REF_COMPONENT =\n\t// biome-ignore lint/suspicious/noControlCharactersInRegex: control chars are exactly what git's own ref-name rules reject — this needs to match the same range.\n\t/(^|[/.])([/.]|$)|^@$|@\\{|[\\x00-\\x20\\x7f~^:?*[\\\\]|\\.lock(\\/|$)/;\n\nconst FULL_SHA_RE = /^[0-9a-f]{40}$/i;\n\n/** Validates a fully-qualified ref (must start with refs/heads/ or refs/tags/). */\nexport function isSafeFullRefName(ref: string): boolean {\n\tif (!ref.startsWith(\"refs/heads/\") && !ref.startsWith(\"refs/tags/\")) {\n\t\treturn false;\n\t}\n\treturn !BAD_REF_COMPONENT.test(ref);\n}\n\n/**\n * Validates a bare branch name (no refs/ prefix). Rejects anything that looks\n * like a full ref path — a name of `\"refs/heads/x\"` would otherwise sail\n * through unprefixed at call sites that build `refs/heads/${name}` themselves\n * (doubling the prefix into something that still resolves), or be used as-is\n * at call sites that pass a name already containing `\"refs/\"` straight\n * through. Also rejects 40-hex SHA-shaped values so a stored branch name can\n * never be ambiguous with a commit SHA at write time; use\n * {@link isSafeRefName} on read paths that accept both shapes.\n */\nexport function isSafeBranchName(name: string): boolean {\n\tif (!name || name.startsWith(\"refs/\") || name === \"HEAD\") return false;\n\tif (FULL_SHA_RE.test(name)) return false;\n\treturn !BAD_REF_COMPONENT.test(name);\n}\n\n/** True for a full 40-hex-char commit SHA — the shape {@link isSafeBranchName} deliberately rejects. */\nexport function isFullSha(value: string): boolean {\n\treturn FULL_SHA_RE.test(value);\n}\n\n/**\n * Validates a \"ref\" field that may name either a branch or a commit SHA it's\n * pinned to — the shape read-path route params take (permalinks, raw links).\n * Both shapes still go through the traversal check.\n */\nexport function isSafeRefName(value: string): boolean {\n\treturn isSafeBranchName(value) || isFullSha(value);\n}\n\n/**\n * Validates a repo-relative file path from request input: relative, no `..`\n * segments, no `.git/` prefix, no null bytes. Use this anywhere a path\n * segment comes straight off a URL or form field rather than re-deriving the\n * checks ad hoc.\n */\nexport function isSafeRepoPath(p: string): boolean {\n\tif (p.startsWith(\"/\")) return false;\n\tif (p.split(\"/\").some((segment) => segment === \"..\")) return false;\n\tif (/^\\.git(\\/|$)/i.test(p)) return false;\n\tif (p.includes(\"\\0\")) return false;\n\treturn true;\n}\n\n/**\n * Qualify a bare branch name to `refs/heads/<name>` before handing it to\n * isomorphic-git. `resolveRef`/`expand` try several candidate paths in\n * sequence for a bare name — `ref`, `refs/ref`, `refs/tags/ref`,\n * `refs/heads/ref`, … — missing (and, against object storage, paying a real\n * round trip for) the first three every time. For a branch-only ref model,\n * skip straight to the winner. Left untouched: already-qualified refs,\n * `\"HEAD\"` (its own first candidate, already optimal), and 40-hex oids\n * (resolved locally by isomorphic-git with no I/O at all).\n */\nexport function qualifyBranchRef(ref: string): string {\n\tif (ref.startsWith(\"refs/\") || ref === \"HEAD\" || FULL_SHA_RE.test(ref)) {\n\t\treturn ref;\n\t}\n\treturn `refs/heads/${ref}`;\n}\n","import git from \"isomorphic-git\";\nimport { assertSafeBranchName } from \"./branch.js\";\nimport { deleteFromTree, upsertTree } from \"./tree.js\";\nimport type { Repo } from \"./types.js\";\n\nexport interface CommitAuthor {\n\tname: string;\n\temail: string;\n\ttimestamp: number;\n\ttimezoneOffset: number;\n}\n\n/** An author stamped with the current time. */\nexport function authorNow(name: string, email: string): CommitAuthor {\n\treturn {\n\t\tname,\n\t\temail,\n\t\ttimestamp: Math.floor(Date.now() / 1000),\n\t\ttimezoneOffset: 0,\n\t};\n}\n\n/**\n * Write a commit directly to a bare repository — no worktree, no checkout.\n * `buildTree` receives the parent commit's tree oid (undefined on an empty\n * repo / unborn branch) and returns the new root tree oid; this function\n * writes the commit object and force-updates `refs/heads/<branch>`.\n *\n * Serialize concurrent writers externally (a per-repo lock): the\n * resolve-ref → write-ref sequence is not atomic on object storage.\n */\nexport async function writeCommitToBare(\n\trepo: Repo,\n\toptions: {\n\t\tbranch: string;\n\t\tmessage: string;\n\t\tauthor: CommitAuthor;\n\t\tbuildTree: (parentTreeOid: string | undefined) => Promise<string>;\n\t},\n): Promise<string> {\n\tassertSafeBranchName(options.branch);\n\tlet parentOid: string | undefined;\n\tlet parentTreeOid: string | undefined;\n\ttry {\n\t\tparentOid = await git.resolveRef({\n\t\t\t...repo,\n\t\t\tref: `refs/heads/${options.branch}`,\n\t\t});\n\t\tconst { commit } = await git.readCommit({ ...repo, oid: parentOid });\n\t\tparentTreeOid = commit.tree;\n\t} catch (err) {\n\t\tif ((err as { code?: string })?.code !== \"NotFoundError\") {\n\t\t\tthrow err;\n\t\t}\n\t\t// empty repo — first commit\n\t}\n\tconst treeOid = await options.buildTree(parentTreeOid);\n\tconst commitOid = await git.writeCommit({\n\t\t...repo,\n\t\tcommit: {\n\t\t\tmessage: options.message,\n\t\t\ttree: treeOid,\n\t\t\tparent: parentOid ? [parentOid] : [],\n\t\t\tauthor: options.author,\n\t\t\tcommitter: options.author,\n\t\t},\n\t});\n\tawait git.writeRef({\n\t\t...repo,\n\t\tref: `refs/heads/${options.branch}`,\n\t\tvalue: commitOid,\n\t\tforce: true,\n\t});\n\treturn commitOid;\n}\n\n/**\n * Commit a set of files onto a branch, straight to the bare repo. Each blob\n * is written to its own content-addressed key — no shared state between\n * files, so they're written in parallel.\n */\nexport function commitFilesToBare(\n\trepo: Repo,\n\toptions: {\n\t\tbranch: string;\n\t\tmessage: string;\n\t\tauthor: CommitAuthor;\n\t\tfiles: Array<{ path: string; content: string | Uint8Array }>;\n\t},\n): Promise<string> {\n\treturn writeCommitToBare(repo, {\n\t\tbranch: options.branch,\n\t\tmessage: options.message,\n\t\tauthor: options.author,\n\t\tbuildTree: async (parentTreeOid) => {\n\t\t\tconst blobs = new Map<string, string>();\n\t\t\tawait Promise.all(\n\t\t\t\toptions.files.map(async (file) => {\n\t\t\t\t\tconst content =\n\t\t\t\t\t\ttypeof file.content === \"string\"\n\t\t\t\t\t\t\t? new TextEncoder().encode(file.content)\n\t\t\t\t\t\t\t: file.content;\n\t\t\t\t\tconst oid = await git.writeBlob({ ...repo, blob: content });\n\t\t\t\t\tblobs.set(file.path, oid);\n\t\t\t\t}),\n\t\t\t);\n\t\t\treturn upsertTree(repo, parentTreeOid, blobs);\n\t\t},\n\t});\n}\n\n/** Commit the removal of one file from a branch, straight to the bare repo. */\nexport function deleteFileFromBare(\n\trepo: Repo,\n\toptions: {\n\t\tbranch: string;\n\t\tfilePath: string;\n\t\tmessage: string;\n\t\tauthor: CommitAuthor;\n\t},\n): Promise<string> {\n\treturn writeCommitToBare(repo, {\n\t\tbranch: options.branch,\n\t\tmessage: options.message,\n\t\tauthor: options.author,\n\t\tbuildTree: async (parentTreeOid) => {\n\t\t\tif (!parentTreeOid) {\n\t\t\t\tthrow new Error(`Branch ${options.branch} is empty`);\n\t\t\t}\n\t\t\treturn deleteFromTree(repo, parentTreeOid, options.filePath);\n\t\t},\n\t});\n}\n","import git from \"isomorphic-git\";\nimport type { Repo } from \"./types.js\";\n\nexport interface TreeEntry {\n\tpath: string;\n\tmode: string;\n\ttype: \"blob\" | \"tree\";\n\toid: string;\n\tsize?: number;\n}\n\nconst joinPath = (prefix: string, name: string) =>\n\tprefix ? `${prefix.replace(/\\/+$/, \"\")}/${name}` : name;\n\n/**\n * Build/update a git tree by overlaying new blobs onto an existing tree,\n * returning the new root tree oid. `entries` maps relative paths to blob oids.\n */\nexport async function upsertTree(\n\trepo: Repo,\n\ttreeOid: string | undefined,\n\tentries: Map<string, string>,\n): Promise<string> {\n\tconst existing = treeOid\n\t\t? (await git.readTree({ ...repo, oid: treeOid })).tree\n\t\t: [];\n\tconst byName = new Map(existing.map((e) => [e.path, e]));\n\tconst direct = new Map<string, string>();\n\tconst nested = new Map<string, Map<string, string>>();\n\tfor (const [filePath, blobOid] of entries) {\n\t\tconst slash = filePath.indexOf(\"/\");\n\t\tif (slash === -1) {\n\t\t\tdirect.set(filePath, blobOid);\n\t\t} else {\n\t\t\tconst dir = filePath.slice(0, slash);\n\t\t\tconst rest = filePath.slice(slash + 1);\n\t\t\tif (!nested.has(dir)) nested.set(dir, new Map());\n\t\t\tnested.get(dir)?.set(rest, blobOid);\n\t\t}\n\t}\n\tfor (const [name, blobOid] of direct) {\n\t\tbyName.set(name, {\n\t\t\tmode: \"100644\",\n\t\t\tpath: name,\n\t\t\toid: blobOid,\n\t\t\ttype: \"blob\",\n\t\t});\n\t}\n\t// Sibling subdirectories are independent subtree writes — no reason to\n\t// serialize them for multi-file commits touching several directories.\n\tconst nestedResults = await Promise.all(\n\t\tArray.from(nested, async ([dir, subEntries]) => {\n\t\t\tconst entry = byName.get(dir);\n\t\t\tconst subtreeOid = entry?.type === \"tree\" ? entry.oid : undefined;\n\t\t\tconst newOid = await upsertTree(repo, subtreeOid, subEntries);\n\t\t\treturn [dir, newOid] as const;\n\t\t}),\n\t);\n\tfor (const [dir, newOid] of nestedResults) {\n\t\tbyName.set(dir, { mode: \"040000\", path: dir, oid: newOid, type: \"tree\" });\n\t}\n\treturn git.writeTree({ ...repo, tree: Array.from(byName.values()) });\n}\n\n/** Remove a file path from a tree, returning the new root tree oid. */\nexport async function deleteFromTree(\n\trepo: Repo,\n\ttreeOid: string,\n\tfilePath: string,\n): Promise<string> {\n\tconst existing = (await git.readTree({ ...repo, oid: treeOid })).tree;\n\tconst byName = new Map(existing.map((e) => [e.path, e]));\n\tconst slash = filePath.indexOf(\"/\");\n\tif (slash === -1) {\n\t\tbyName.delete(filePath);\n\t} else {\n\t\tconst dir = filePath.slice(0, slash);\n\t\tconst rest = filePath.slice(slash + 1);\n\t\tconst entry = byName.get(dir);\n\t\tif (entry?.type === \"tree\") {\n\t\t\tconst newOid = await deleteFromTree(repo, entry.oid, rest);\n\t\t\tbyName.set(dir, { ...entry, oid: newOid });\n\t\t}\n\t}\n\treturn git.writeTree({ ...repo, tree: Array.from(byName.values()) });\n}\n\n/** Resolve a path inside a tree to its entry, or null when absent. */\nexport async function findTreeEntry(\n\trepo: Repo,\n\trootTreeOid: string,\n\ttreePath: string,\n): Promise<TreeEntry | null> {\n\tif (!treePath) {\n\t\treturn { path: \"\", mode: \"040000\", type: \"tree\", oid: rootTreeOid };\n\t}\n\n\tconst parts = treePath.split(\"/\").filter(Boolean);\n\tlet currentTreeOid = rootTreeOid;\n\tlet currentPath = \"\";\n\n\tfor (const [index, part] of parts.entries()) {\n\t\tconst tree = await git.readTree({ ...repo, oid: currentTreeOid });\n\t\tconst entry = tree.tree.find((candidate) => candidate.path === part);\n\n\t\tif (!entry) return null;\n\n\t\tcurrentPath = currentPath ? joinPath(currentPath, entry.path) : entry.path;\n\n\t\tif (index === parts.length - 1) {\n\t\t\treturn {\n\t\t\t\tpath: currentPath,\n\t\t\t\tmode: entry.mode,\n\t\t\t\ttype: entry.type as \"blob\" | \"tree\",\n\t\t\t\toid: entry.oid,\n\t\t\t};\n\t\t}\n\n\t\tif (entry.type !== \"tree\") return null;\n\n\t\tcurrentTreeOid = entry.oid;\n\t}\n\n\treturn null;\n}\n\n/** List a tree's direct entries, with paths prefixed by `prefix`. */\nexport async function listTreeEntries(\n\trepo: Repo,\n\ttreeOid: string,\n\tprefix = \"\",\n): Promise<TreeEntry[]> {\n\tconst tree = await git.readTree({ ...repo, oid: treeOid });\n\n\treturn tree.tree.map((entry) => ({\n\t\tpath: prefix ? joinPath(prefix, entry.path) : entry.path,\n\t\tmode: entry.mode,\n\t\ttype: entry.type as \"blob\" | \"tree\",\n\t\toid: entry.oid,\n\t}));\n}\n","import { createTwoFilesPatch } from \"diff\";\nimport git from \"isomorphic-git\";\nimport { readBlobContent, toBase64 } from \"../edge-utils.js\";\nimport { qualifyBranchRef } from \"../refs.js\";\nimport { getCommit } from \"./history.js\";\nimport type { Repo } from \"./types.js\";\n\nexport interface DiffFile {\n\tpath: string;\n\tstatus: \"added\" | \"modified\" | \"deleted\" | \"renamed\";\n\tadditions: number;\n\tdeletions: number;\n\tpatch: string;\n\toldPath?: string;\n\tisBinary?: boolean;\n\toldContent?: string;\n\tnewContent?: string;\n\toldSize?: number;\n\tnewSize?: number;\n}\n\nexport interface DiffResult {\n\tfiles: DiffFile[];\n\ttotalAdditions: number;\n\ttotalDeletions: number;\n\ttotalFiles: number;\n}\n\n/** Binary detection via null-byte heuristic. */\nfunction detectBlobContent(blob: Uint8Array): {\n\tisBinary: boolean;\n\ttext: string;\n\tbytes: Uint8Array;\n} {\n\treturn readBlobContent(blob);\n}\n\nfunction countContentLines(content: string): number {\n\tif (content.length === 0) return 0;\n\tconst lines = content.split(\"\\n\");\n\tif (lines[lines.length - 1] === \"\") lines.pop();\n\treturn lines.length;\n}\n\nfunction createUnifiedPatch(params: {\n\tpath: string;\n\tbefore: string;\n\tafter: string;\n\toldPath?: string;\n\tnewPath?: string;\n}): string {\n\tconst oldPath = params.oldPath ?? `a/${params.path}`;\n\tconst newPath = params.newPath ?? `b/${params.path}`;\n\tconst patchBody = createTwoFilesPatch(\n\t\toldPath,\n\t\tnewPath,\n\t\tparams.before,\n\t\tparams.after,\n\t\t\"\",\n\t\t\"\",\n\t\t{ context: 3 },\n\t).replace(/^=+\\n/, \"\");\n\n\treturn `diff --git a/${params.path} b/${params.path}\\n${patchBody}`;\n}\n\nfunction summarizeDiff(files: DiffFile[]): DiffResult {\n\treturn {\n\t\tfiles,\n\t\ttotalAdditions: files.reduce((sum, f) => sum + f.additions, 0),\n\t\ttotalDeletions: files.reduce((sum, f) => sum + f.deletions, 0),\n\t\ttotalFiles: files.length,\n\t};\n}\n\n/**\n * Walk two trees (oldOid -> newOid) and return one DiffFile per changed path\n * — the shared core of both {@link getCommitDiff} (parent -> commit) and\n * {@link getDiffBetweenRefs} (base -> compare).\n */\nasync function walkTreeDiff(\n\trepo: Repo,\n\toldOid: string,\n\tnewOid: string,\n): Promise<DiffFile[]> {\n\tconst changes = await git.walk({\n\t\t...repo,\n\t\ttrees: [git.TREE({ ref: oldOid }), git.TREE({ ref: newOid })],\n\t\tmap: async (filepath, [A, B]) => {\n\t\t\tconst [typeA, typeB] = await Promise.all([A?.type(), B?.type()]);\n\n\t\t\tif (typeA === \"tree\" || typeB === \"tree\") return;\n\n\t\t\tif (typeA && !typeB) {\n\t\t\t\tconst oidA = A ? await A.oid() : \"\";\n\t\t\t\tconst { blob } = await git.readBlob({ ...repo, oid: oidA });\n\t\t\t\tconst before = detectBlobContent(blob);\n\t\t\t\treturn {\n\t\t\t\t\tpath: filepath,\n\t\t\t\t\tstatus: \"deleted\" as const,\n\t\t\t\t\tadditions: 0,\n\t\t\t\t\tdeletions: before.isBinary ? 0 : countContentLines(before.text),\n\t\t\t\t\tpatch: before.isBinary\n\t\t\t\t\t\t? \"\"\n\t\t\t\t\t\t: createUnifiedPatch({\n\t\t\t\t\t\t\t\tpath: filepath,\n\t\t\t\t\t\t\t\tbefore: before.text,\n\t\t\t\t\t\t\t\tafter: \"\",\n\t\t\t\t\t\t\t\tnewPath: \"/dev/null\",\n\t\t\t\t\t\t\t}),\n\t\t\t\t\tisBinary: before.isBinary,\n\t\t\t\t\toldContent: before.isBinary ? toBase64(before.bytes) : undefined,\n\t\t\t\t\toldSize: before.bytes.length,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tif (!typeA && typeB) {\n\t\t\t\tconst oidB = B ? await B.oid() : \"\";\n\t\t\t\tconst { blob } = await git.readBlob({ ...repo, oid: oidB });\n\t\t\t\tconst after = detectBlobContent(blob);\n\t\t\t\treturn {\n\t\t\t\t\tpath: filepath,\n\t\t\t\t\tstatus: \"added\" as const,\n\t\t\t\t\tadditions: after.isBinary ? 0 : countContentLines(after.text),\n\t\t\t\t\tdeletions: 0,\n\t\t\t\t\tpatch: after.isBinary\n\t\t\t\t\t\t? \"\"\n\t\t\t\t\t\t: createUnifiedPatch({\n\t\t\t\t\t\t\t\tpath: filepath,\n\t\t\t\t\t\t\t\tbefore: \"\",\n\t\t\t\t\t\t\t\tafter: after.text,\n\t\t\t\t\t\t\t\toldPath: \"/dev/null\",\n\t\t\t\t\t\t\t}),\n\t\t\t\t\tisBinary: after.isBinary,\n\t\t\t\t\tnewContent: after.isBinary ? toBase64(after.bytes) : undefined,\n\t\t\t\t\tnewSize: after.bytes.length,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tconst [oidA, oidB] = await Promise.all([\n\t\t\t\tA ? A.oid() : Promise.resolve(\"\"),\n\t\t\t\tB ? B.oid() : Promise.resolve(\"\"),\n\t\t\t]);\n\n\t\t\tif (oidA !== oidB) {\n\t\t\t\tconst [{ blob: blobA }, { blob: blobB }] = await Promise.all([\n\t\t\t\t\tgit.readBlob({ ...repo, oid: oidA }),\n\t\t\t\t\tgit.readBlob({ ...repo, oid: oidB }),\n\t\t\t\t]);\n\t\t\t\tconst before = detectBlobContent(blobA);\n\t\t\t\tconst after = detectBlobContent(blobB);\n\t\t\t\tconst isBinary = before.isBinary || after.isBinary;\n\n\t\t\t\treturn {\n\t\t\t\t\tpath: filepath,\n\t\t\t\t\tstatus: \"modified\" as const,\n\t\t\t\t\tadditions: isBinary ? 0 : countContentLines(after.text),\n\t\t\t\t\tdeletions: isBinary ? 0 : countContentLines(before.text),\n\t\t\t\t\tpatch: isBinary\n\t\t\t\t\t\t? \"\"\n\t\t\t\t\t\t: createUnifiedPatch({\n\t\t\t\t\t\t\t\tpath: filepath,\n\t\t\t\t\t\t\t\tbefore: before.text,\n\t\t\t\t\t\t\t\tafter: after.text,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\tisBinary,\n\t\t\t\t\toldContent: isBinary ? toBase64(before.bytes) : undefined,\n\t\t\t\t\tnewContent: isBinary ? toBase64(after.bytes) : undefined,\n\t\t\t\t\toldSize: before.bytes.length,\n\t\t\t\t\tnewSize: after.bytes.length,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\treturn null;\n\t\t},\n\t});\n\n\treturn (changes ?? []).filter(\n\t\t(c: DiffFile | null | undefined): c is DiffFile =>\n\t\t\tc !== null && c !== undefined,\n\t);\n}\n\n/** The diff a single commit introduced (against its first parent). */\nexport async function getCommitDiff(\n\trepo: Repo,\n\tcommitSha: string,\n): Promise<DiffResult> {\n\ttry {\n\t\tconst commit = await getCommit(repo, commitSha);\n\t\tconst parent = commit.commit.parent[0];\n\n\t\tif (!parent) {\n\t\t\tconst entries: { path: string; oid: string }[] = [];\n\t\t\tconst stack: { treeOid: string; prefix: string }[] = [\n\t\t\t\t{ treeOid: commit.commit.tree, prefix: \"\" },\n\t\t\t];\n\n\t\t\twhile (stack.length) {\n\t\t\t\tconst { treeOid, prefix } = stack.pop() as {\n\t\t\t\t\ttreeOid: string;\n\t\t\t\t\tprefix: string;\n\t\t\t\t};\n\t\t\t\tconst { tree } = await git.readTree({ ...repo, oid: treeOid });\n\t\t\t\tfor (const entry of tree) {\n\t\t\t\t\tconst full = prefix ? `${prefix}/${entry.path}` : entry.path;\n\t\t\t\t\tif (entry.type === \"tree\") {\n\t\t\t\t\t\tstack.push({ treeOid: entry.oid, prefix: full });\n\t\t\t\t\t} else if (entry.type === \"blob\") {\n\t\t\t\t\t\tentries.push({ path: full, oid: entry.oid });\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst files: DiffFile[] = await Promise.all(\n\t\t\t\tentries.map(async ({ path, oid }) => {\n\t\t\t\t\tconst { blob } = await git.readBlob({ ...repo, oid });\n\t\t\t\t\tconst after = detectBlobContent(blob);\n\t\t\t\t\treturn {\n\t\t\t\t\t\tpath,\n\t\t\t\t\t\tstatus: \"added\" as const,\n\t\t\t\t\t\tadditions: after.isBinary ? 0 : countContentLines(after.text),\n\t\t\t\t\t\tdeletions: 0,\n\t\t\t\t\t\tpatch: after.isBinary\n\t\t\t\t\t\t\t? \"\"\n\t\t\t\t\t\t\t: createUnifiedPatch({\n\t\t\t\t\t\t\t\t\tpath,\n\t\t\t\t\t\t\t\t\tbefore: \"\",\n\t\t\t\t\t\t\t\t\tafter: after.text,\n\t\t\t\t\t\t\t\t\toldPath: \"/dev/null\",\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\tisBinary: after.isBinary,\n\t\t\t\t\t\tnewContent: after.isBinary ? toBase64(after.bytes) : undefined,\n\t\t\t\t\t\tnewSize: after.bytes.length,\n\t\t\t\t\t};\n\t\t\t\t}),\n\t\t\t);\n\n\t\t\treturn summarizeDiff(files);\n\t\t}\n\n\t\tconst files = await walkTreeDiff(repo, parent, commitSha);\n\t\treturn summarizeDiff(files);\n\t} catch (error) {\n\t\tthrow new Error(`Failed to get commit diff: ${error}`);\n\t}\n}\n\n/** The diff between two refs (base -> compare). */\nexport async function getDiffBetweenRefs(\n\trepo: Repo,\n\tbaseRef: string,\n\tcompareRef: string,\n): Promise<DiffResult> {\n\tconst [baseOid, compareOid] = await Promise.all([\n\t\tgit.resolveRef({ ...repo, ref: qualifyBranchRef(baseRef) }),\n\t\tgit.resolveRef({ ...repo, ref: qualifyBranchRef(compareRef) }),\n\t]);\n\n\tconst files = await walkTreeDiff(repo, baseOid, compareOid);\n\treturn summarizeDiff(files);\n}\n","/**\n * Edge-compatible utilities replacing node:crypto, node:zlib, and Buffer.\n *\n * Every function here uses only Web APIs (SubtleCrypto, CompressionStream,\n * TextEncoder/TextDecoder) — no Node built-ins. They work on Cloudflare\n * Workers, Vercel Edge, Deno Deploy, and Node >= 18.\n */\n\nconst textEncoder = new TextEncoder();\nconst textDecoder = new TextDecoder();\n\n// ---------------------------------------------------------------------------\n// Text\n// ---------------------------------------------------------------------------\n\n/**\n * Encode a UTF-8 string to bytes.\n *\n * Return type pinned to `Uint8Array<ArrayBuffer>` (not the bare `Uint8Array`,\n * whose default type argument differs across TypeScript versions) so it's\n * always assignable to Fetch API `BodyInit` regardless of a consumer's own\n * TypeScript/lib version.\n */\nexport function encodeUtf8(data: string): Uint8Array<ArrayBuffer> {\n\treturn textEncoder.encode(data);\n}\n\n/** Decode bytes as UTF-8. */\nexport function decodeUtf8(data: Uint8Array): string {\n\treturn textDecoder.decode(data);\n}\n\n/** Decode bytes as ASCII. */\nexport function decodeAscii(data: Uint8Array): string {\n\tlet s = \"\";\n\tfor (let i = 0; i < data.length; i++)\n\t\ts += String.fromCharCode(data[i] as number);\n\treturn s;\n}\n\n// ---------------------------------------------------------------------------\n// Array manipulation\n// ---------------------------------------------------------------------------\n\n/** Concatenate any number of Uint8Arrays into one. */\nexport function concat(...parts: Uint8Array[]): Uint8Array<ArrayBuffer> {\n\tlet total = 0;\n\tfor (const p of parts) total += p.length;\n\tconst out = new Uint8Array(total);\n\tlet offset = 0;\n\tfor (const p of parts) {\n\t\tout.set(p, offset);\n\t\toffset += p.length;\n\t}\n\treturn out;\n}\n\n// ---------------------------------------------------------------------------\n// Encoding\n// ---------------------------------------------------------------------------\n\n/** Uint8Array → lowercase hex string. */\nexport function toHex(data: Uint8Array): string {\n\tlet hex = \"\";\n\tfor (let i = 0; i < data.length; i++)\n\t\thex += (data[i] as number).toString(16).padStart(2, \"0\");\n\treturn hex;\n}\n\n/** Uint8Array → base64 string. */\nexport function toBase64(data: Uint8Array): string {\n\tlet binary = \"\";\n\tfor (let i = 0; i < data.length; i++)\n\t\tbinary += String.fromCharCode(data[i] as number);\n\treturn btoa(binary);\n}\n\n/** Hex string → Uint8Array. */\nexport function fromHex(hex: string): Uint8Array<ArrayBuffer> {\n\tconst bytes = new Uint8Array(hex.length / 2);\n\tfor (let i = 0; i < bytes.length; i++) {\n\t\tbytes[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);\n\t}\n\treturn bytes;\n}\n\n// ---------------------------------------------------------------------------\n// Crypto\n// ---------------------------------------------------------------------------\n\n/** SHA-1 hash via Web Crypto API. Returns a hex string. */\nexport async function sha1(data: Uint8Array | string): Promise<string> {\n\tconst bytes = typeof data === \"string\" ? encodeUtf8(data) : data;\n\tconst hash = await globalThis.crypto.subtle.digest(\"SHA-1\", bytes);\n\treturn toHex(new Uint8Array(hash));\n}\n\n// ---------------------------------------------------------------------------\n// Compression\n// ---------------------------------------------------------------------------\n\n/**\n * Deflate compress via the CompressionStream Web API.\n * Falls back to throwing if CompressionStream is unavailable (very old runtimes).\n */\nexport async function deflate(\n\tdata: Uint8Array,\n): Promise<Uint8Array<ArrayBuffer>> {\n\tconst stream = new Blob([data])\n\t\t.stream()\n\t\t.pipeThrough(new CompressionStream(\"deflate\"));\n\treturn new Uint8Array(await new Response(stream).arrayBuffer());\n}\n\n// ---------------------------------------------------------------------------\n// Binary detection (replaces Buffer.includes(0) pattern)\n// ---------------------------------------------------------------------------\n\n/** Check if a Uint8Array contains a null byte. */\nexport function hasNullByte(data: Uint8Array): boolean {\n\treturn data.includes(0);\n}\n\n/**\n * Read a blob as text or binary metadata — the edge-compatible replacement\n * for the `Buffer.from(blob)` pattern used throughout diff.ts and history.ts.\n */\nexport function readBlobContent(blob: Uint8Array): {\n\tisBinary: boolean;\n\ttext: string;\n\tbytes: Uint8Array;\n} {\n\tconst isBinary = hasNullByte(blob);\n\treturn {\n\t\tisBinary,\n\t\ttext: isBinary ? \"\" : decodeUtf8(blob),\n\t\tbytes: blob,\n\t};\n}\n","import git from \"isomorphic-git\";\nimport { decodeUtf8, hasNullByte, toBase64 } from \"../edge-utils.js\";\nimport { GitObjectNotFoundError, GitPathNotFoundError } from \"../git-errors.js\";\nimport { qualifyBranchRef } from \"../refs.js\";\nimport { findTreeEntry, listTreeEntries, type TreeEntry } from \"./tree.js\";\nimport { type OpsHooks, type Repo, runStep } from \"./types.js\";\n\n/**\n * A resolved ref (branch/commit) not existing is a normal, expected condition\n * (empty repo, unborn branch) and is handled by each caller. An object that\n * fails to resolve *underneath* an already-resolved ref (a tree/blob the\n * stored pack doesn't actually contain) means the repo's storage is\n * inconsistent — surface that distinctly so callers don't render it as\n * \"empty\" and clients don't see a raw isomorphic-git NotFoundError.\n */\nfunction wrapMissingObject<T>(\n\tpromise: Promise<T>,\n\tcontext: string,\n): Promise<T> {\n\treturn promise.catch((err: unknown) => {\n\t\tif ((err as { code?: string })?.code === \"NotFoundError\") {\n\t\t\tthrow new GitObjectNotFoundError(\n\t\t\t\t`Git data for ${context} is missing from storage. The repository may need to be re-pushed to repair it.`,\n\t\t\t);\n\t\t}\n\t\tthrow err;\n\t});\n}\n\nconst isNotFound = (err: unknown) =>\n\t(err as { code?: string })?.code === \"NotFoundError\";\n\nexport interface CommitInfo {\n\toid: string;\n\tcommit: {\n\t\tmessage: string;\n\t\ttree: string;\n\t\tparent: string[];\n\t\tauthor: {\n\t\t\tname: string;\n\t\t\temail: string;\n\t\t\ttimestamp: number;\n\t\t\ttimezoneOffset: number;\n\t\t};\n\t\tcommitter: {\n\t\t\tname: string;\n\t\t\temail: string;\n\t\t\ttimestamp: number;\n\t\t\ttimezoneOffset: number;\n\t\t};\n\t};\n\tpayload: string;\n}\n\n/**\n * Resolve a branch name / full ref / sha to its commit. The ref not\n * resolving (unborn branch, genuinely empty repo) and the ref resolving but\n * its commit object being unreadable (storage inconsistency — see\n * `wrapMissingObject` above) are different failures with different meanings,\n * so only the first is left as a raw isomorphic-git NotFoundError for\n * callers to treat as \"empty\"; the second is wrapped into\n * GitObjectNotFoundError specifically so it can't be mistaken for the first\n * by an `isNotFound`-style check downstream (see getTreeFromRef/getCommitLog).\n */\nexport async function resolveCommit(repo: Repo, ref: string) {\n\tconst oid = await git.resolveRef({ ...repo, ref: qualifyBranchRef(ref) });\n\tconst result = await wrapMissingObject(\n\t\tgit.readCommit({ ...repo, oid }),\n\t\t`${repo.gitdir} commit ${oid}`,\n\t);\n\treturn { oid, commit: result.commit };\n}\n\n/** Read a blob's bytes by oid. */\nexport async function getBlob(repo: Repo, sha: string): Promise<Uint8Array> {\n\tconst { blob } = await wrapMissingObject(\n\t\tgit.readBlob({ ...repo, oid: sha }),\n\t\t`${repo.gitdir} blob ${sha}`,\n\t);\n\treturn blob;\n}\n\n/** Read a file's bytes at a ref. Throws GitPathNotFoundError when absent. */\nexport async function getFileContent(\n\trepo: Repo,\n\tfilePath: string,\n\tref = \"main\",\n\thooks?: OpsHooks,\n): Promise<Uint8Array> {\n\t// A blob page needs a commit, one or more tree objects, and the blob. On a\n\t// multi-pack object store, letting isomorphic-git discover those packs one\n\t// at a time turns that otherwise short read into a serial network walk.\n\t// Warm every pack before resolving the ref so every later object lookup can\n\t// use the local parse cache. This is the same structural optimization used\n\t// for tree and history reads, now applied to file reads as well.\n\tif (hooks?.prefetch) {\n\t\tawait runStep(hooks, \"prefetch\", hooks.prefetch);\n\t}\n\tconst { commit } = await resolveCommit(repo, ref);\n\tconst context = `${repo.gitdir}@${ref}:${filePath}`;\n\tconst entry = await wrapMissingObject(\n\t\tfindTreeEntry(repo, commit.tree, filePath),\n\t\tcontext,\n\t);\n\n\tif (entry?.type !== \"blob\") {\n\t\tthrow new GitPathNotFoundError(`File not found: ${filePath}`);\n\t}\n\n\tconst { blob } = await wrapMissingObject(\n\t\tgit.readBlob({ ...repo, oid: entry.oid }),\n\t\tcontext,\n\t);\n\treturn blob;\n}\n\n/** Read one commit by sha. */\nexport async function getCommit(repo: Repo, sha: string): Promise<CommitInfo> {\n\tconst result = await wrapMissingObject(\n\t\tgit.readCommit({ ...repo, oid: sha }),\n\t\t`${repo.gitdir} commit ${sha}`,\n\t);\n\treturn { oid: result.oid, commit: result.commit, payload: result.payload };\n}\n\nfunction isFullyWalked(commits: CommitInfo[]): boolean {\n\tconst last = commits[commits.length - 1];\n\treturn !!last && last.commit.parent.length === 0;\n}\n\nexport interface CommitLogOptions {\n\tref?: string;\n\tdepth?: number;\n\t/**\n\t * Pass the head sha when the caller already resolved `ref` — resolveRef\n\t * tries several candidate paths in sequence and misses the first few every\n\t * time for a normal branch name, which is pure waste when the sha is known.\n\t */\n\tknownHeadSha?: string;\n}\n\n/**\n * The commit chain from a ref, newest first. Walking is inherently sequential\n * (each commit's oid is only discoverable by reading its child first) and\n * network-round-trip-bound against object storage, so the deepest walk seen\n * per head is memoized in `hooks.resultCache` and sliced for shallower or\n * repeated requests — don't bypass this by calling `git.log` directly.\n */\nexport async function getCommitLog(\n\trepo: Repo,\n\toptions: CommitLogOptions = {},\n\thooks?: OpsHooks,\n): Promise<CommitInfo[]> {\n\tconst ref = options.ref ?? \"main\";\n\tconst depth = options.depth ?? 50;\n\n\tlet headSha: string;\n\tif (options.knownHeadSha) {\n\t\theadSha = options.knownHeadSha;\n\t} else {\n\t\ttry {\n\t\t\theadSha = await git.resolveRef({ ...repo, ref: qualifyBranchRef(ref) });\n\t\t} catch (err: unknown) {\n\t\t\tif (isNotFound(err)) return [];\n\t\t\tthrow err;\n\t\t}\n\t}\n\n\tconst cacheKey = `commitlog:${repo.gitdir}:${headSha}`;\n\tconst cached = hooks?.resultCache?.get<CommitInfo[]>(cacheKey);\n\tif (cached && (cached.length >= depth || isFullyWalked(cached))) {\n\t\thooks?.onNote?.(\n\t\t\t`getCommitLog: result-cache HIT for ${cacheKey} (depth=${depth})`,\n\t\t);\n\t\treturn cached.slice(0, depth);\n\t}\n\thooks?.onNote?.(\n\t\t`getCommitLog: result-cache MISS for ${cacheKey} (depth=${depth})`,\n\t);\n\n\tif (hooks?.prefetch && depth >= (hooks.prefetchMinDepth ?? 5)) {\n\t\tawait runStep(hooks, \"prefetch\", hooks.prefetch);\n\t}\n\n\t// headSha is already known-good here (resolveRef above succeeded, or the\n\t// caller passed knownHeadSha) — a NotFoundError from the walk itself means\n\t// a commit/tree/blob it needs is missing from storage, not that the ref\n\t// doesn't exist. Unlike the resolveRef catch above, that's not \"empty\",\n\t// it's storage inconsistency, so wrapMissingObject turns it into a\n\t// GitObjectNotFoundError instead of silently returning [] — an empty\n\t// result here would otherwise get treated as \"this branch has no\n\t// history\" by every caller (and, worse, potentially cached as such).\n\tconst commits = await wrapMissingObject(\n\t\trunStep(hooks, `git.log ${ref} depth=${depth}`, () =>\n\t\t\tgit.log({ ...repo, ref: headSha, depth }),\n\t\t),\n\t\t`${repo.gitdir}@${ref} history`,\n\t);\n\tconst result = commits.map((commit) => ({\n\t\toid: commit.oid,\n\t\tcommit: commit.commit,\n\t\tpayload: commit.payload || \"\",\n\t}));\n\tif (!cached || result.length > cached.length) {\n\t\thooks?.resultCache?.set(cacheKey, result);\n\t}\n\treturn result;\n}\n\n/** A file at a ref, decoded for display: utf8 text or base64 when binary. */\nexport async function getFileFromRef(\n\trepo: Repo,\n\tfilePath: string,\n\tref: string,\n\thooks?: OpsHooks,\n): Promise<{ content: string; size: number; isBinary: boolean }> {\n\tconst bytes = await getFileContent(repo, filePath, ref, hooks);\n\tconst isBinary = hasNullByte(bytes);\n\n\treturn {\n\t\tcontent: isBinary ? toBase64(bytes) : decodeUtf8(bytes),\n\t\tsize: bytes.length,\n\t\tisBinary,\n\t};\n}\n\n/**\n * List a directory at the tip of a ref. Returns [] for an empty repo/unborn\n * branch; throws GitPathNotFoundError when `treePath` doesn't exist. Results\n * are memoized per head sha (auto-invalidates on push).\n */\nexport async function getTreeFromRef(\n\trepo: Repo,\n\toptions: { ref?: string; treePath?: string } = {},\n\thooks?: OpsHooks,\n): Promise<TreeEntry[]> {\n\tconst ref = options.ref ?? \"main\";\n\tconst treePath = options.treePath ?? \"\";\n\n\tlet commit: Awaited<ReturnType<typeof resolveCommit>>[\"commit\"];\n\tlet headSha: string;\n\ttry {\n\t\tconst resolved = await resolveCommit(repo, ref);\n\t\tcommit = resolved.commit;\n\t\theadSha = resolved.oid;\n\t} catch (err: unknown) {\n\t\tif (isNotFound(err)) return [];\n\t\tthrow err;\n\t}\n\n\tconst cacheKey = `tree:${repo.gitdir}:${headSha}:${treePath}`;\n\tconst cached = hooks?.resultCache?.get<TreeEntry[]>(cacheKey);\n\tif (cached) {\n\t\thooks?.onNote?.(`getTreeFromRef: result-cache HIT for ${cacheKey}`);\n\t\treturn cached;\n\t}\n\thooks?.onNote?.(`getTreeFromRef: result-cache MISS for ${cacheKey}`);\n\n\t// Unlike getCommitLog, a tree read has no \"depth\" to gate on — it always\n\t// needs at least the head commit's tree object, and (for a non-root path)\n\t// one object per path segment on top of that, so there's no shallow case\n\t// where prefetching every pack is wasted bandwidth the way a depth=1\n\t// commit-log walk can be. Without this, a cache-miss tree read falls\n\t// through to isomorphic-git's own pack resolution, which probes indexed\n\t// packs one at a time instead of warming them all in parallel up front —\n\t// this was previously the slowest of a tree page's parallel queries in\n\t// production for exactly that reason.\n\tif (hooks?.prefetch) {\n\t\tawait runStep(hooks, \"prefetch\", hooks.prefetch);\n\t}\n\n\tconst context = `${repo.gitdir}@${ref}:${treePath || \"/\"}`;\n\tlet result: TreeEntry[];\n\tif (!treePath) {\n\t\tresult = await wrapMissingObject(\n\t\t\trunStep(hooks, \"listTreeEntries (root)\", () =>\n\t\t\t\tlistTreeEntries(repo, commit.tree),\n\t\t\t),\n\t\t\tcontext,\n\t\t);\n\t} else {\n\t\tconst entry = await wrapMissingObject(\n\t\t\trunStep(hooks, `findTreeEntry ${treePath}`, () =>\n\t\t\t\tfindTreeEntry(repo, commit.tree, treePath),\n\t\t\t),\n\t\t\tcontext,\n\t\t);\n\t\tif (!entry) {\n\t\t\tthrow new GitPathNotFoundError(\n\t\t\t\t`Path \"${treePath}\" does not exist at ${ref}`,\n\t\t\t);\n\t\t}\n\t\tresult =\n\t\t\tentry.type !== \"tree\"\n\t\t\t\t? []\n\t\t\t\t: await wrapMissingObject(\n\t\t\t\t\t\trunStep(hooks, `listTreeEntries ${treePath}`, () =>\n\t\t\t\t\t\t\tlistTreeEntries(repo, entry.oid, entry.path),\n\t\t\t\t\t\t),\n\t\t\t\t\t\tcontext,\n\t\t\t\t\t);\n\t}\n\n\thooks?.resultCache?.set(cacheKey, result);\n\treturn result;\n}\n\n/**\n * A page of commit history from a branch tip. Memoized per head sha; builds\n * on {@link getCommitLog}'s walk cache for the underlying chain.\n */\nexport async function getCommitHistory(\n\trepo: Repo,\n\toptions: { ref: string; limit?: number; skip?: number },\n\thooks?: OpsHooks,\n): Promise<CommitInfo[]> {\n\tconst limit = options.limit ?? 50;\n\tconst skip = options.skip ?? 0;\n\tconst headSha = await git\n\t\t.resolveRef({ ...repo, ref: qualifyBranchRef(options.ref) })\n\t\t.catch(() => null);\n\n\tconst cacheKey = headSha\n\t\t? `commits:${repo.gitdir}:${headSha}:${limit}:${skip}`\n\t\t: null;\n\tif (cacheKey) {\n\t\tconst cached = hooks?.resultCache?.get<CommitInfo[]>(cacheKey);\n\t\tif (cached) {\n\t\t\thooks?.onNote?.(`getCommitHistory: result-cache HIT for ${cacheKey}`);\n\t\t\treturn cached;\n\t\t}\n\t}\n\thooks?.onNote?.(\n\t\t`getCommitHistory: result-cache MISS for ${cacheKey ?? \"(no head)\"}`,\n\t);\n\tif (!headSha) return [];\n\n\tconst all = await getCommitLog(\n\t\trepo,\n\t\t{ ref: options.ref, depth: limit + skip, knownHeadSha: headSha },\n\t\thooks,\n\t);\n\tconst result = all.slice(skip, skip + limit);\n\n\tif (cacheKey) hooks?.resultCache?.set(cacheKey, result);\n\treturn result;\n}\n","import type git from \"isomorphic-git\";\n\n/** The fs shape isomorphic-git accepts, derived from its own signatures. */\nexport type IsoGitFs = Parameters<typeof git.readTree>[0][\"fs\"];\n\n/**\n * A repository handle: everything isomorphic-git needs to address one bare\n * repo. Create it once per request (sharing `cache` across calls is what lets\n * isomorphic-git reuse parsed pack indexes) and pass it to every op.\n */\nexport interface Repo {\n\tfs: IsoGitFs;\n\tgitdir: string;\n\t/** isomorphic-git's shared parse cache — strongly recommended per repo. */\n\tcache?: object;\n}\n\n/**\n * Key/value store for memoizing expensive walk results (commit logs, tree\n * listings, per-file history). Keys are namespaced `kind:gitdir:headSha:…`,\n * so entries self-invalidate on push (new head, new key) — but evict entries\n * under {@link resultKeyPrefixes} after rewriting a repo's storage out of\n * band, or stale walks leak until your store's own eviction.\n */\nexport interface ResultCache {\n\tget<T>(key: string): T | null | undefined;\n\tset(key: string, value: unknown): void;\n}\n\n/** Optional instrumentation and tuning hooks accepted by every op. */\nexport interface OpsHooks {\n\tresultCache?: ResultCache;\n\t/** Wrap a timed sub-step (network walk, tree listing). Default: run directly. */\n\tstep?: <T>(label: string, fn: () => Promise<T>) => Promise<T>;\n\t/** Diagnostic sink for cache hit/miss and walk summaries. */\n\tonNote?: (message: string) => void;\n\t/**\n\t * Wire pack prefetching (e.g. `GitFs.prefetchPacks`) here so a sequential\n\t * walk doesn't pay one network round trip per commit. Called once before\n\t * history walks of depth >= `prefetchMinDepth` (see below), and\n\t * unconditionally on every cache-miss tree read ({@link getTreeFromRef}) —\n\t * a tree read has no shallow case, it always needs at least the head\n\t * commit's tree object.\n\t */\n\tprefetch?: () => Promise<void>;\n\t/** Minimum walk depth before `prefetch` fires for history walks. Default 5. */\n\tprefetchMinDepth?: number;\n}\n\nexport const runStep = <T>(\n\thooks: OpsHooks | undefined,\n\tlabel: string,\n\tfn: () => Promise<T>,\n): Promise<T> => (hooks?.step ? hooks.step(label, fn) : fn());\n\n/**\n * The result-cache key prefixes holding entries for `gitdir` — evict these\n * from your {@link ResultCache} when the repo's storage was rewritten outside\n * a normal push (bulk sync, rename, repack cleanup).\n */\nexport function resultKeyPrefixes(gitdir: string): string[] {\n\treturn [\n\t\t`commitlog:${gitdir}:`,\n\t\t`tree:${gitdir}:`,\n\t\t`commits:${gitdir}:`,\n\t\t`last-commits:${gitdir}:`,\n\t\t`file-history:${gitdir}:`,\n\t];\n}\n","import { type CommitInfo, getCommitLog } from \"./history.js\";\nimport { findTreeEntry } from \"./tree.js\";\nimport type { OpsHooks, Repo } from \"./types.js\";\nimport { runStep } from \"./types.js\";\n\nexport interface FileHistoryEntry {\n\tsha: string;\n\tmessage: string;\n\tauthorName: string;\n\tauthorEmail: string;\n\tcreatedAt: string;\n}\n\nexport interface FileHistoryResult {\n\tentries: FileHistoryEntry[];\n\t/**\n\t * True when the walk hit its depth budget (or the requested `limit`)\n\t * before exhausting the branch's full commit chain — there may be older\n\t * commits touching this file that a deeper walk would surface.\n\t */\n\ttruncated: boolean;\n}\n\n/**\n * Default walk bound for a caller that actually wants deep history (a file's\n * \"History\" tab). Walking the full chain is round-trip-bound on object\n * storage, so cap how far back a single request will look.\n */\nexport const HISTORY_WALK_DEPTH = 400;\n\n/**\n * Much shallower default for a \"latest commit touching this file\" banner that\n * only displays `entries[0]` — trades \"always finds the true last-touching\n * commit\" for \"finds it if it's reasonably recent\", the right call for a\n * banner with a full History view a click away.\n */\nexport const BANNER_WALK_DEPTH = 60;\n\n/** Tree reads are prefetched in parallel windows; see getLastCommitsForTree. */\nconst PREFETCH_WINDOW = 24;\n\nfunction toEntry(commit: CommitInfo): FileHistoryEntry {\n\treturn {\n\t\tsha: commit.oid,\n\t\tmessage: commit.commit.message.trim(),\n\t\tauthorName: commit.commit.author.name,\n\t\tauthorEmail: commit.commit.author.email,\n\t\tcreatedAt: new Date(commit.commit.author.timestamp * 1000).toISOString(),\n\t};\n}\n\n/**\n * All commits (newest first) that changed a single file's blob oid, walking\n * the first-parent chain — same approach as getLastCommitsForTree but for one\n * path and collecting every match instead of stopping at the first.\n */\nexport async function getFileHistory(\n\trepo: Repo,\n\toptions: {\n\t\tref: string;\n\t\tfilePath: string;\n\t\tlimit?: number;\n\t\tmaxDepth?: number;\n\t},\n\thooks?: OpsHooks,\n): Promise<FileHistoryResult> {\n\tconst limit = options.limit ?? 30;\n\tconst maxDepth = options.maxDepth ?? HISTORY_WALK_DEPTH;\n\tconst walkDepth = Math.max(maxDepth, limit);\n\tconst commits = await runStep(hooks, `getCommitLog depth=${walkDepth}`, () =>\n\t\tgetCommitLog(repo, { ref: options.ref, depth: walkDepth }, hooks),\n\t);\n\tconst head = commits[0];\n\tif (!head) return { entries: [], truncated: false };\n\n\tconst cacheKey = `file-history:${repo.gitdir}:${head.oid}:${options.filePath}:${limit}:${maxDepth}`;\n\tconst cached = hooks?.resultCache?.get<FileHistoryResult>(cacheKey);\n\tif (cached) {\n\t\thooks?.onNote?.(\"getFileHistory: result-cache HIT, skipping history walk\");\n\t\treturn cached;\n\t}\n\thooks?.onNote?.(\"getFileHistory: result-cache MISS, walking history\");\n\n\tconst byOid = new Map(commits.map((commit) => [commit.oid, commit]));\n\n\tconst oidByCommitTree = new Map<string, string | null>();\n\tasync function resolveOid(commitTreeOid: string): Promise<string | null> {\n\t\tconst cachedOid = oidByCommitTree.get(commitTreeOid);\n\t\tif (cachedOid !== undefined) return cachedOid;\n\t\tconst entry = await findTreeEntry(repo, commitTreeOid, options.filePath);\n\t\tconst oid = entry?.type === \"blob\" ? entry.oid : null;\n\t\toidByCommitTree.set(commitTreeOid, oid);\n\t\treturn oid;\n\t}\n\n\tconst entries: FileHistoryEntry[] = [];\n\tlet truncated = false;\n\n\touter: for (\n\t\tlet windowStart = 0;\n\t\twindowStart < commits.length;\n\t\twindowStart += PREFETCH_WINDOW\n\t) {\n\t\tconst windowEnd = Math.min(windowStart + PREFETCH_WINDOW, commits.length);\n\t\t// +1 lookahead so the last entry's parent tree is already warm too.\n\t\tconst prefetchEnd = Math.min(windowEnd + 1, commits.length);\n\n\t\tawait Promise.all(\n\t\t\tcommits\n\t\t\t\t.slice(windowStart, prefetchEnd)\n\t\t\t\t.map((commit) => resolveOid(commit.commit.tree)),\n\t\t);\n\n\t\tfor (let i = windowStart; i < windowEnd; i++) {\n\t\t\tconst commit = commits[i];\n\t\t\tif (!commit) break outer;\n\n\t\t\tconst parentSha = commit.commit.parent[0];\n\t\t\tconst parentCommit = parentSha ? byOid.get(parentSha) : undefined;\n\t\t\tif (parentSha && !parentCommit) {\n\t\t\t\t// Walked past the depth cap without reaching this commit's parent —\n\t\t\t\t// can't tell whether it changed the file; stop and report truncated.\n\t\t\t\ttruncated = true;\n\t\t\t\tbreak outer;\n\t\t\t}\n\n\t\t\tconst [oid, parentOid] = await Promise.all([\n\t\t\t\tresolveOid(commit.commit.tree),\n\t\t\t\tparentCommit\n\t\t\t\t\t? resolveOid(parentCommit.commit.tree)\n\t\t\t\t\t: Promise.resolve(null),\n\t\t\t]);\n\n\t\t\tif (oid !== parentOid) {\n\t\t\t\tentries.push(toEntry(commit));\n\t\t\t\tif (entries.length >= limit) {\n\t\t\t\t\ttruncated = i < commits.length - 1;\n\t\t\t\t\tbreak outer;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tconst result = { entries, truncated };\n\thooks?.resultCache?.set(cacheKey, result);\n\treturn result;\n}\n","import { type CommitInfo, getCommitLog } from \"./history.js\";\nimport { findTreeEntry, listTreeEntries } from \"./tree.js\";\nimport type { OpsHooks, Repo } from \"./types.js\";\nimport { runStep } from \"./types.js\";\n\nexport interface LastCommitInfo {\n\tsha: string;\n\tmessage: string;\n\tauthorName: string;\n\tauthorEmail: string;\n\tcreatedAt: string;\n}\n\n/**\n * Bounds how far back the history walk looks to resolve \"last commit touching\n * this path\" for a directory listing. Entries whose last change is older\n * simply show no last-commit info rather than paying an unbounded scan.\n */\nconst HISTORY_WALK_DEPTH = 400;\n\n/**\n * The walk itself is inherently sequential (each step needs to know what's\n * still \"remaining\" from the step before), but the tree-object reads that\n * back it are not — each commit's tree oid is already known upfront from the\n * commit log. Prefetching a window of tree reads in parallel turns ~1 network\n * round trip per commit (serialized) into ~1 round trip per window, which is\n * where nearly all of this function's wall-clock time goes on object storage.\n */\nconst PREFETCH_WINDOW = 24;\n\nfunction toLastCommitInfo(commit: CommitInfo): LastCommitInfo {\n\treturn {\n\t\tsha: commit.oid,\n\t\tmessage: commit.commit.message.trim(),\n\t\tauthorName: commit.commit.author.name,\n\t\tauthorEmail: commit.commit.author.email,\n\t\tcreatedAt: new Date(commit.commit.author.timestamp * 1000).toISOString(),\n\t};\n}\n\n/**\n * For each direct child of `treePath` (at the tip of `ref`), find the most\n * recent commit that changed it — the tree view's \"last commit\" column. Walks\n * history newest-to-oldest, comparing the directory's tree oid\n * commit-to-commit and only descending one level to diff child oids when\n * something under the directory actually changed. Preserve the two-phase\n * structure (parallel prefetch, then sequential resolve) when touching this —\n * the \"which entries are still unresolved\" state must advance\n * commit-by-commit.\n */\nexport async function getLastCommitsForTree(\n\trepo: Repo,\n\toptions: { ref: string; treePath?: string; depth?: number },\n\thooks?: OpsHooks,\n): Promise<Record<string, LastCommitInfo>> {\n\tconst treePath = options.treePath ?? \"\";\n\tconst depth = options.depth ?? HISTORY_WALK_DEPTH;\n\tconst commits = await runStep(hooks, `getCommitLog depth=${depth}`, () =>\n\t\tgetCommitLog(repo, { ref: options.ref, depth }, hooks),\n\t);\n\thooks?.onNote?.(`getLastCommitsForTree: ${commits.length} commits in log`);\n\tconst head = commits[0];\n\tif (!head) return {};\n\n\tconst cacheKey = `last-commits:${repo.gitdir}:${head.oid}:${treePath}`;\n\tconst cachedResult =\n\t\thooks?.resultCache?.get<Record<string, LastCommitInfo>>(cacheKey);\n\tif (cachedResult) {\n\t\thooks?.onNote?.(\n\t\t\t\"getLastCommitsForTree: result-cache HIT, skipping history walk\",\n\t\t);\n\t\treturn cachedResult;\n\t}\n\thooks?.onNote?.(\"getLastCommitsForTree: result-cache MISS, walking history\");\n\n\tconst byOid = new Map(commits.map((commit) => [commit.oid, commit]));\n\n\t// In a linear history, the \"parent tree\" resolved at commit[i] is the same\n\t// tree already resolved as the \"current tree\" at commit[i-1] — memoize by\n\t// commit-tree oid (and by resolved dir oid) so each distinct tree is only\n\t// walked/listed once across the whole scan instead of twice per commit.\n\tconst dirOidByCommitTree = new Map<string, string | null>();\n\tconst childrenByDirOid = new Map<\n\t\tstring,\n\t\tAwaited<ReturnType<typeof listTreeEntries>>\n\t>();\n\n\tasync function resolveDirOid(commitTreeOid: string): Promise<string | null> {\n\t\tconst cached = dirOidByCommitTree.get(commitTreeOid);\n\t\tif (cached !== undefined) return cached;\n\t\tconst entry = await findTreeEntry(repo, commitTreeOid, treePath);\n\t\tconst dirOid = entry?.type === \"tree\" ? entry.oid : null;\n\t\tdirOidByCommitTree.set(commitTreeOid, dirOid);\n\t\treturn dirOid;\n\t}\n\n\tasync function resolveChildren(dirOid: string | null) {\n\t\tif (dirOid === null) return [];\n\t\tconst cached = childrenByDirOid.get(dirOid);\n\t\tif (cached) return cached;\n\t\tconst children = await listTreeEntries(repo, dirOid, treePath);\n\t\tchildrenByDirOid.set(dirOid, children);\n\t\treturn children;\n\t}\n\n\tconst headDirOid = await resolveDirOid(head.commit.tree);\n\tif (headDirOid === null) return {};\n\n\t// listTreeEntries is prefixed with treePath so result keys match the full\n\t// paths that callers key their file listing by.\n\tconst headChildren = await resolveChildren(headDirOid);\n\tconst remaining = new Set(headChildren.map((entry) => entry.path));\n\tconst result: Record<string, LastCommitInfo> = {};\n\n\tlet commitsWalked = 0;\n\tlet prefetchWindows = 0;\n\tconst walkStart = performance.now();\n\n\touter: for (\n\t\tlet windowStart = 0;\n\t\twindowStart < commits.length && remaining.size > 0;\n\t\twindowStart += PREFETCH_WINDOW\n\t) {\n\t\tconst windowEnd = Math.min(windowStart + PREFETCH_WINDOW, commits.length);\n\t\t// +1 lookahead commit so the last entry's parent tree is already warm too.\n\t\tconst prefetchEnd = Math.min(windowEnd + 1, commits.length);\n\t\tprefetchWindows++;\n\n\t\t// Phase A: resolve this window's directory oid for every commit's tree in\n\t\t// parallel (a no-op await when treePath is \"\" — the root tree oid IS the\n\t\t// commit tree, no lookup needed).\n\t\tconst dirOids = await Promise.all(\n\t\t\tcommits\n\t\t\t\t.slice(windowStart, prefetchEnd)\n\t\t\t\t.map((commit) => resolveDirOid(commit.commit.tree)),\n\t\t);\n\n\t\t// Phase B: resolve children for every distinct directory oid the window\n\t\t// touched, in parallel — the actual tree-object read for the common case.\n\t\tawait Promise.all(\n\t\t\t[...new Set(dirOids)].map((dirOid) => resolveChildren(dirOid)),\n\t\t);\n\n\t\tfor (let i = windowStart; i < windowEnd; i++) {\n\t\t\tif (remaining.size === 0) break outer;\n\t\t\tconst commit = commits[i];\n\t\t\tif (!commit) break outer;\n\t\t\tcommitsWalked++;\n\n\t\t\tconst parentSha = commit.commit.parent[0];\n\t\t\tconst parentCommit = parentSha ? byOid.get(parentSha) : undefined;\n\t\t\tif (parentSha && !parentCommit) break outer; // walked past the depth cap\n\n\t\t\t// Already resolved above (or memoized from an earlier window/commit) —\n\t\t\t// these awaits resolve immediately from cache.\n\t\t\tconst [dirOid, parentDirOid] = await Promise.all([\n\t\t\t\tresolveDirOid(commit.commit.tree),\n\t\t\t\tparentCommit\n\t\t\t\t\t? resolveDirOid(parentCommit.commit.tree)\n\t\t\t\t\t: Promise.resolve(null),\n\t\t\t]);\n\n\t\t\tif (dirOid === parentDirOid) continue;\n\n\t\t\tconst [children, parentChildren] = await Promise.all([\n\t\t\t\tresolveChildren(dirOid),\n\t\t\t\tresolveChildren(parentDirOid),\n\t\t\t]);\n\t\t\tconst childByName = new Map(\n\t\t\t\tchildren.map((entry) => [entry.path, entry.oid]),\n\t\t\t);\n\t\t\tconst parentChildByName = new Map(\n\t\t\t\tparentChildren.map((entry) => [entry.path, entry.oid]),\n\t\t\t);\n\n\t\t\tfor (const name of remaining) {\n\t\t\t\tif (childByName.get(name) !== parentChildByName.get(name)) {\n\t\t\t\t\tresult[name] = toLastCommitInfo(commit);\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor (const name of Object.keys(result)) {\n\t\t\t\tremaining.delete(name);\n\t\t\t}\n\t\t}\n\t}\n\n\thooks?.onNote?.(\n\t\t`getLastCommitsForTree: walked ${commitsWalked}/${commits.length} commits across ${prefetchWindows} prefetch windows in ${(performance.now() - walkStart).toFixed(1)}ms, ${dirOidByCommitTree.size} unique tree lookups, ${remaining.size} entries never resolved`,\n\t);\n\n\thooks?.resultCache?.set(cacheKey, result);\n\treturn result;\n}\n","import git from \"isomorphic-git\";\nimport { qualifyBranchRef } from \"../refs.js\";\nimport { assertSafeBranchName } from \"./branch.js\";\nimport type { Repo } from \"./types.js\";\n\nexport interface MergeAnalysis {\n\tcanMerge: boolean;\n\thasConflicts: boolean;\n\tconflictingFiles: string[];\n\tfastForward: boolean;\n}\n\n/**\n * Cheap pre-merge check: do both branches exist, and is this a fast-forward?\n * `canMerge`/`fastForward` are the only fields this actually determines —\n * `hasConflicts`/`conflictingFiles` are NOT a real content-conflict check\n * (isomorphic-git's `git.merge` doesn't expose a dry-run), they only ever\n * reflect \"one of the branches couldn't be resolved\" (`canMerge: false`).\n * Real merge conflicts are only discoverable by actually attempting the\n * merge.\n */\nexport async function analyzeMerge(\n\trepo: Repo,\n\tsourceBranch: string,\n\ttargetBranch: string,\n): Promise<MergeAnalysis> {\n\tassertSafeBranchName(sourceBranch);\n\tassertSafeBranchName(targetBranch);\n\n\ttry {\n\t\tconst [sourceOid, targetOid] = await Promise.all([\n\t\t\tgit.resolveRef({ ...repo, ref: qualifyBranchRef(sourceBranch) }),\n\t\t\tgit.resolveRef({ ...repo, ref: qualifyBranchRef(targetBranch) }),\n\t\t]);\n\n\t\tconst isDescendant = await git.isDescendent({\n\t\t\t...repo,\n\t\t\toid: sourceOid,\n\t\t\tancestor: targetOid,\n\t\t});\n\n\t\treturn {\n\t\t\tcanMerge: true,\n\t\t\thasConflicts: false,\n\t\t\tconflictingFiles: [],\n\t\t\tfastForward: isDescendant,\n\t\t};\n\t} catch (err) {\n\t\tif ((err as { code?: string })?.code !== \"NotFoundError\") {\n\t\t\tthrow err;\n\t\t}\n\t\t// A branch ref failed to resolve — not a content conflict, just \"can't\n\t\t// merge because one side doesn't exist.\" hasConflicts here is a\n\t\t// misnomer kept for MergeAnalysis's existing shape; canMerge is the\n\t\t// field that actually matters to callers.\n\t\treturn {\n\t\t\tcanMerge: false,\n\t\t\thasConflicts: true,\n\t\t\tconflictingFiles: [],\n\t\t\tfastForward: false,\n\t\t};\n\t}\n}\n\n/**\n * Attempt a fast-forward merge directly against the bare repo: when source is\n * a descendant of target, just move the target ref — no worktree, no new\n * commit. Returns null when the merge is not a fast-forward (callers fall\n * back to a real three-way merge, which needs a worktree). Serialize with a\n * per-repo lock: resolve → writeRef is not atomic.\n */\nexport async function fastForwardMerge(\n\trepo: Repo,\n\tsourceBranch: string,\n\ttargetBranch: string,\n): Promise<{ success: true; commitSha: string } | null> {\n\tassertSafeBranchName(sourceBranch);\n\tassertSafeBranchName(targetBranch);\n\tconst [sourceOid, targetOid] = await Promise.all([\n\t\tgit.resolveRef({ ...repo, ref: `refs/heads/${sourceBranch}` }),\n\t\tgit.resolveRef({ ...repo, ref: `refs/heads/${targetBranch}` }),\n\t]);\n\tconst isFF = await git.isDescendent({\n\t\t...repo,\n\t\toid: sourceOid,\n\t\tancestor: targetOid,\n\t});\n\tif (!isFF) return null;\n\tawait git.writeRef({\n\t\t...repo,\n\t\tref: `refs/heads/${targetBranch}`,\n\t\tvalue: sourceOid,\n\t\tforce: true,\n\t});\n\treturn { success: true, commitSha: sourceOid };\n}\n"],"mappings":"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","names":["FULL_SHA_RE","git","import_isomorphic_git","import_isomorphic_git","git","git","import_isomorphic_git","import_isomorphic_git","git","git","oidA","oidB","files","HISTORY_WALK_DEPTH","PREFETCH_WINDOW","import_isomorphic_git","git"]}
|
package/dist/ops.d.cts
CHANGED
|
@@ -202,7 +202,7 @@ declare function resolveCommit(repo: Repo, ref: string): Promise<{
|
|
|
202
202
|
/** Read a blob's bytes by oid. */
|
|
203
203
|
declare function getBlob(repo: Repo, sha: string): Promise<Uint8Array>;
|
|
204
204
|
/** Read a file's bytes at a ref. Throws GitPathNotFoundError when absent. */
|
|
205
|
-
declare function getFileContent(repo: Repo, filePath: string, ref?: string): Promise<Uint8Array>;
|
|
205
|
+
declare function getFileContent(repo: Repo, filePath: string, ref?: string, hooks?: OpsHooks): Promise<Uint8Array>;
|
|
206
206
|
/** Read one commit by sha. */
|
|
207
207
|
declare function getCommit(repo: Repo, sha: string): Promise<CommitInfo>;
|
|
208
208
|
interface CommitLogOptions {
|
|
@@ -224,7 +224,7 @@ interface CommitLogOptions {
|
|
|
224
224
|
*/
|
|
225
225
|
declare function getCommitLog(repo: Repo, options?: CommitLogOptions, hooks?: OpsHooks): Promise<CommitInfo[]>;
|
|
226
226
|
/** A file at a ref, decoded for display: utf8 text or base64 when binary. */
|
|
227
|
-
declare function getFileFromRef(repo: Repo, filePath: string, ref: string): Promise<{
|
|
227
|
+
declare function getFileFromRef(repo: Repo, filePath: string, ref: string, hooks?: OpsHooks): Promise<{
|
|
228
228
|
content: string;
|
|
229
229
|
size: number;
|
|
230
230
|
isBinary: boolean;
|
package/dist/ops.d.ts
CHANGED
|
@@ -202,7 +202,7 @@ declare function resolveCommit(repo: Repo, ref: string): Promise<{
|
|
|
202
202
|
/** Read a blob's bytes by oid. */
|
|
203
203
|
declare function getBlob(repo: Repo, sha: string): Promise<Uint8Array>;
|
|
204
204
|
/** Read a file's bytes at a ref. Throws GitPathNotFoundError when absent. */
|
|
205
|
-
declare function getFileContent(repo: Repo, filePath: string, ref?: string): Promise<Uint8Array>;
|
|
205
|
+
declare function getFileContent(repo: Repo, filePath: string, ref?: string, hooks?: OpsHooks): Promise<Uint8Array>;
|
|
206
206
|
/** Read one commit by sha. */
|
|
207
207
|
declare function getCommit(repo: Repo, sha: string): Promise<CommitInfo>;
|
|
208
208
|
interface CommitLogOptions {
|
|
@@ -224,7 +224,7 @@ interface CommitLogOptions {
|
|
|
224
224
|
*/
|
|
225
225
|
declare function getCommitLog(repo: Repo, options?: CommitLogOptions, hooks?: OpsHooks): Promise<CommitInfo[]>;
|
|
226
226
|
/** A file at a ref, decoded for display: utf8 text or base64 when binary. */
|
|
227
|
-
declare function getFileFromRef(repo: Repo, filePath: string, ref: string): Promise<{
|
|
227
|
+
declare function getFileFromRef(repo: Repo, filePath: string, ref: string, hooks?: OpsHooks): Promise<{
|
|
228
228
|
content: string;
|
|
229
229
|
size: number;
|
|
230
230
|
isBinary: boolean;
|
package/dist/ops.js
CHANGED
|
@@ -233,7 +233,10 @@ async function getBlob(repo, sha) {
|
|
|
233
233
|
);
|
|
234
234
|
return blob;
|
|
235
235
|
}
|
|
236
|
-
async function getFileContent(repo, filePath, ref = "main") {
|
|
236
|
+
async function getFileContent(repo, filePath, ref = "main", hooks) {
|
|
237
|
+
if (hooks?.prefetch) {
|
|
238
|
+
await runStep(hooks, "prefetch", hooks.prefetch);
|
|
239
|
+
}
|
|
237
240
|
const { commit } = await resolveCommit(repo, ref);
|
|
238
241
|
const context = `${repo.gitdir}@${ref}:${filePath}`;
|
|
239
242
|
const entry = await wrapMissingObject(
|
|
@@ -306,8 +309,8 @@ async function getCommitLog(repo, options = {}, hooks) {
|
|
|
306
309
|
}
|
|
307
310
|
return result;
|
|
308
311
|
}
|
|
309
|
-
async function getFileFromRef(repo, filePath, ref) {
|
|
310
|
-
const bytes = await getFileContent(repo, filePath, ref);
|
|
312
|
+
async function getFileFromRef(repo, filePath, ref, hooks) {
|
|
313
|
+
const bytes = await getFileContent(repo, filePath, ref, hooks);
|
|
311
314
|
const isBinary = hasNullByte(bytes);
|
|
312
315
|
return {
|
|
313
316
|
content: isBinary ? toBase64(bytes) : decodeUtf8(bytes),
|
package/dist/ops.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/ops/commit.ts","../src/ops/tree.ts","../src/ops/diff.ts","../src/ops/history.ts","../src/ops/types.ts","../src/ops/file-history.ts","../src/ops/last-commit.ts","../src/ops/merge.ts"],"sourcesContent":["import git from \"isomorphic-git\";\nimport { assertSafeBranchName } from \"./branch.js\";\nimport { deleteFromTree, upsertTree } from \"./tree.js\";\nimport type { Repo } from \"./types.js\";\n\nexport interface CommitAuthor {\n\tname: string;\n\temail: string;\n\ttimestamp: number;\n\ttimezoneOffset: number;\n}\n\n/** An author stamped with the current time. */\nexport function authorNow(name: string, email: string): CommitAuthor {\n\treturn {\n\t\tname,\n\t\temail,\n\t\ttimestamp: Math.floor(Date.now() / 1000),\n\t\ttimezoneOffset: 0,\n\t};\n}\n\n/**\n * Write a commit directly to a bare repository — no worktree, no checkout.\n * `buildTree` receives the parent commit's tree oid (undefined on an empty\n * repo / unborn branch) and returns the new root tree oid; this function\n * writes the commit object and force-updates `refs/heads/<branch>`.\n *\n * Serialize concurrent writers externally (a per-repo lock): the\n * resolve-ref → write-ref sequence is not atomic on object storage.\n */\nexport async function writeCommitToBare(\n\trepo: Repo,\n\toptions: {\n\t\tbranch: string;\n\t\tmessage: string;\n\t\tauthor: CommitAuthor;\n\t\tbuildTree: (parentTreeOid: string | undefined) => Promise<string>;\n\t},\n): Promise<string> {\n\tassertSafeBranchName(options.branch);\n\tlet parentOid: string | undefined;\n\tlet parentTreeOid: string | undefined;\n\ttry {\n\t\tparentOid = await git.resolveRef({\n\t\t\t...repo,\n\t\t\tref: `refs/heads/${options.branch}`,\n\t\t});\n\t\tconst { commit } = await git.readCommit({ ...repo, oid: parentOid });\n\t\tparentTreeOid = commit.tree;\n\t} catch (err) {\n\t\tif ((err as { code?: string })?.code !== \"NotFoundError\") {\n\t\t\tthrow err;\n\t\t}\n\t\t// empty repo — first commit\n\t}\n\tconst treeOid = await options.buildTree(parentTreeOid);\n\tconst commitOid = await git.writeCommit({\n\t\t...repo,\n\t\tcommit: {\n\t\t\tmessage: options.message,\n\t\t\ttree: treeOid,\n\t\t\tparent: parentOid ? [parentOid] : [],\n\t\t\tauthor: options.author,\n\t\t\tcommitter: options.author,\n\t\t},\n\t});\n\tawait git.writeRef({\n\t\t...repo,\n\t\tref: `refs/heads/${options.branch}`,\n\t\tvalue: commitOid,\n\t\tforce: true,\n\t});\n\treturn commitOid;\n}\n\n/**\n * Commit a set of files onto a branch, straight to the bare repo. Each blob\n * is written to its own content-addressed key — no shared state between\n * files, so they're written in parallel.\n */\nexport function commitFilesToBare(\n\trepo: Repo,\n\toptions: {\n\t\tbranch: string;\n\t\tmessage: string;\n\t\tauthor: CommitAuthor;\n\t\tfiles: Array<{ path: string; content: string | Uint8Array }>;\n\t},\n): Promise<string> {\n\treturn writeCommitToBare(repo, {\n\t\tbranch: options.branch,\n\t\tmessage: options.message,\n\t\tauthor: options.author,\n\t\tbuildTree: async (parentTreeOid) => {\n\t\t\tconst blobs = new Map<string, string>();\n\t\t\tawait Promise.all(\n\t\t\t\toptions.files.map(async (file) => {\n\t\t\t\t\tconst content =\n\t\t\t\t\t\ttypeof file.content === \"string\"\n\t\t\t\t\t\t\t? new TextEncoder().encode(file.content)\n\t\t\t\t\t\t\t: file.content;\n\t\t\t\t\tconst oid = await git.writeBlob({ ...repo, blob: content });\n\t\t\t\t\tblobs.set(file.path, oid);\n\t\t\t\t}),\n\t\t\t);\n\t\t\treturn upsertTree(repo, parentTreeOid, blobs);\n\t\t},\n\t});\n}\n\n/** Commit the removal of one file from a branch, straight to the bare repo. */\nexport function deleteFileFromBare(\n\trepo: Repo,\n\toptions: {\n\t\tbranch: string;\n\t\tfilePath: string;\n\t\tmessage: string;\n\t\tauthor: CommitAuthor;\n\t},\n): Promise<string> {\n\treturn writeCommitToBare(repo, {\n\t\tbranch: options.branch,\n\t\tmessage: options.message,\n\t\tauthor: options.author,\n\t\tbuildTree: async (parentTreeOid) => {\n\t\t\tif (!parentTreeOid) {\n\t\t\t\tthrow new Error(`Branch ${options.branch} is empty`);\n\t\t\t}\n\t\t\treturn deleteFromTree(repo, parentTreeOid, options.filePath);\n\t\t},\n\t});\n}\n","import git from \"isomorphic-git\";\nimport type { Repo } from \"./types.js\";\n\nexport interface TreeEntry {\n\tpath: string;\n\tmode: string;\n\ttype: \"blob\" | \"tree\";\n\toid: string;\n\tsize?: number;\n}\n\nconst joinPath = (prefix: string, name: string) =>\n\tprefix ? `${prefix.replace(/\\/+$/, \"\")}/${name}` : name;\n\n/**\n * Build/update a git tree by overlaying new blobs onto an existing tree,\n * returning the new root tree oid. `entries` maps relative paths to blob oids.\n */\nexport async function upsertTree(\n\trepo: Repo,\n\ttreeOid: string | undefined,\n\tentries: Map<string, string>,\n): Promise<string> {\n\tconst existing = treeOid\n\t\t? (await git.readTree({ ...repo, oid: treeOid })).tree\n\t\t: [];\n\tconst byName = new Map(existing.map((e) => [e.path, e]));\n\tconst direct = new Map<string, string>();\n\tconst nested = new Map<string, Map<string, string>>();\n\tfor (const [filePath, blobOid] of entries) {\n\t\tconst slash = filePath.indexOf(\"/\");\n\t\tif (slash === -1) {\n\t\t\tdirect.set(filePath, blobOid);\n\t\t} else {\n\t\t\tconst dir = filePath.slice(0, slash);\n\t\t\tconst rest = filePath.slice(slash + 1);\n\t\t\tif (!nested.has(dir)) nested.set(dir, new Map());\n\t\t\tnested.get(dir)?.set(rest, blobOid);\n\t\t}\n\t}\n\tfor (const [name, blobOid] of direct) {\n\t\tbyName.set(name, {\n\t\t\tmode: \"100644\",\n\t\t\tpath: name,\n\t\t\toid: blobOid,\n\t\t\ttype: \"blob\",\n\t\t});\n\t}\n\t// Sibling subdirectories are independent subtree writes — no reason to\n\t// serialize them for multi-file commits touching several directories.\n\tconst nestedResults = await Promise.all(\n\t\tArray.from(nested, async ([dir, subEntries]) => {\n\t\t\tconst entry = byName.get(dir);\n\t\t\tconst subtreeOid = entry?.type === \"tree\" ? entry.oid : undefined;\n\t\t\tconst newOid = await upsertTree(repo, subtreeOid, subEntries);\n\t\t\treturn [dir, newOid] as const;\n\t\t}),\n\t);\n\tfor (const [dir, newOid] of nestedResults) {\n\t\tbyName.set(dir, { mode: \"040000\", path: dir, oid: newOid, type: \"tree\" });\n\t}\n\treturn git.writeTree({ ...repo, tree: Array.from(byName.values()) });\n}\n\n/** Remove a file path from a tree, returning the new root tree oid. */\nexport async function deleteFromTree(\n\trepo: Repo,\n\ttreeOid: string,\n\tfilePath: string,\n): Promise<string> {\n\tconst existing = (await git.readTree({ ...repo, oid: treeOid })).tree;\n\tconst byName = new Map(existing.map((e) => [e.path, e]));\n\tconst slash = filePath.indexOf(\"/\");\n\tif (slash === -1) {\n\t\tbyName.delete(filePath);\n\t} else {\n\t\tconst dir = filePath.slice(0, slash);\n\t\tconst rest = filePath.slice(slash + 1);\n\t\tconst entry = byName.get(dir);\n\t\tif (entry?.type === \"tree\") {\n\t\t\tconst newOid = await deleteFromTree(repo, entry.oid, rest);\n\t\t\tbyName.set(dir, { ...entry, oid: newOid });\n\t\t}\n\t}\n\treturn git.writeTree({ ...repo, tree: Array.from(byName.values()) });\n}\n\n/** Resolve a path inside a tree to its entry, or null when absent. */\nexport async function findTreeEntry(\n\trepo: Repo,\n\trootTreeOid: string,\n\ttreePath: string,\n): Promise<TreeEntry | null> {\n\tif (!treePath) {\n\t\treturn { path: \"\", mode: \"040000\", type: \"tree\", oid: rootTreeOid };\n\t}\n\n\tconst parts = treePath.split(\"/\").filter(Boolean);\n\tlet currentTreeOid = rootTreeOid;\n\tlet currentPath = \"\";\n\n\tfor (const [index, part] of parts.entries()) {\n\t\tconst tree = await git.readTree({ ...repo, oid: currentTreeOid });\n\t\tconst entry = tree.tree.find((candidate) => candidate.path === part);\n\n\t\tif (!entry) return null;\n\n\t\tcurrentPath = currentPath ? joinPath(currentPath, entry.path) : entry.path;\n\n\t\tif (index === parts.length - 1) {\n\t\t\treturn {\n\t\t\t\tpath: currentPath,\n\t\t\t\tmode: entry.mode,\n\t\t\t\ttype: entry.type as \"blob\" | \"tree\",\n\t\t\t\toid: entry.oid,\n\t\t\t};\n\t\t}\n\n\t\tif (entry.type !== \"tree\") return null;\n\n\t\tcurrentTreeOid = entry.oid;\n\t}\n\n\treturn null;\n}\n\n/** List a tree's direct entries, with paths prefixed by `prefix`. */\nexport async function listTreeEntries(\n\trepo: Repo,\n\ttreeOid: string,\n\tprefix = \"\",\n): Promise<TreeEntry[]> {\n\tconst tree = await git.readTree({ ...repo, oid: treeOid });\n\n\treturn tree.tree.map((entry) => ({\n\t\tpath: prefix ? joinPath(prefix, entry.path) : entry.path,\n\t\tmode: entry.mode,\n\t\ttype: entry.type as \"blob\" | \"tree\",\n\t\toid: entry.oid,\n\t}));\n}\n","import { createTwoFilesPatch } from \"diff\";\nimport git from \"isomorphic-git\";\nimport { readBlobContent, toBase64 } from \"../edge-utils.js\";\nimport { qualifyBranchRef } from \"../refs.js\";\nimport { getCommit } from \"./history.js\";\nimport type { Repo } from \"./types.js\";\n\nexport interface DiffFile {\n\tpath: string;\n\tstatus: \"added\" | \"modified\" | \"deleted\" | \"renamed\";\n\tadditions: number;\n\tdeletions: number;\n\tpatch: string;\n\toldPath?: string;\n\tisBinary?: boolean;\n\toldContent?: string;\n\tnewContent?: string;\n\toldSize?: number;\n\tnewSize?: number;\n}\n\nexport interface DiffResult {\n\tfiles: DiffFile[];\n\ttotalAdditions: number;\n\ttotalDeletions: number;\n\ttotalFiles: number;\n}\n\n/** Binary detection via null-byte heuristic. */\nfunction detectBlobContent(blob: Uint8Array): {\n\tisBinary: boolean;\n\ttext: string;\n\tbytes: Uint8Array;\n} {\n\treturn readBlobContent(blob);\n}\n\nfunction countContentLines(content: string): number {\n\tif (content.length === 0) return 0;\n\tconst lines = content.split(\"\\n\");\n\tif (lines[lines.length - 1] === \"\") lines.pop();\n\treturn lines.length;\n}\n\nfunction createUnifiedPatch(params: {\n\tpath: string;\n\tbefore: string;\n\tafter: string;\n\toldPath?: string;\n\tnewPath?: string;\n}): string {\n\tconst oldPath = params.oldPath ?? `a/${params.path}`;\n\tconst newPath = params.newPath ?? `b/${params.path}`;\n\tconst patchBody = createTwoFilesPatch(\n\t\toldPath,\n\t\tnewPath,\n\t\tparams.before,\n\t\tparams.after,\n\t\t\"\",\n\t\t\"\",\n\t\t{ context: 3 },\n\t).replace(/^=+\\n/, \"\");\n\n\treturn `diff --git a/${params.path} b/${params.path}\\n${patchBody}`;\n}\n\nfunction summarizeDiff(files: DiffFile[]): DiffResult {\n\treturn {\n\t\tfiles,\n\t\ttotalAdditions: files.reduce((sum, f) => sum + f.additions, 0),\n\t\ttotalDeletions: files.reduce((sum, f) => sum + f.deletions, 0),\n\t\ttotalFiles: files.length,\n\t};\n}\n\n/**\n * Walk two trees (oldOid -> newOid) and return one DiffFile per changed path\n * — the shared core of both {@link getCommitDiff} (parent -> commit) and\n * {@link getDiffBetweenRefs} (base -> compare).\n */\nasync function walkTreeDiff(\n\trepo: Repo,\n\toldOid: string,\n\tnewOid: string,\n): Promise<DiffFile[]> {\n\tconst changes = await git.walk({\n\t\t...repo,\n\t\ttrees: [git.TREE({ ref: oldOid }), git.TREE({ ref: newOid })],\n\t\tmap: async (filepath, [A, B]) => {\n\t\t\tconst [typeA, typeB] = await Promise.all([A?.type(), B?.type()]);\n\n\t\t\tif (typeA === \"tree\" || typeB === \"tree\") return;\n\n\t\t\tif (typeA && !typeB) {\n\t\t\t\tconst oidA = A ? await A.oid() : \"\";\n\t\t\t\tconst { blob } = await git.readBlob({ ...repo, oid: oidA });\n\t\t\t\tconst before = detectBlobContent(blob);\n\t\t\t\treturn {\n\t\t\t\t\tpath: filepath,\n\t\t\t\t\tstatus: \"deleted\" as const,\n\t\t\t\t\tadditions: 0,\n\t\t\t\t\tdeletions: before.isBinary ? 0 : countContentLines(before.text),\n\t\t\t\t\tpatch: before.isBinary\n\t\t\t\t\t\t? \"\"\n\t\t\t\t\t\t: createUnifiedPatch({\n\t\t\t\t\t\t\t\tpath: filepath,\n\t\t\t\t\t\t\t\tbefore: before.text,\n\t\t\t\t\t\t\t\tafter: \"\",\n\t\t\t\t\t\t\t\tnewPath: \"/dev/null\",\n\t\t\t\t\t\t\t}),\n\t\t\t\t\tisBinary: before.isBinary,\n\t\t\t\t\toldContent: before.isBinary ? toBase64(before.bytes) : undefined,\n\t\t\t\t\toldSize: before.bytes.length,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tif (!typeA && typeB) {\n\t\t\t\tconst oidB = B ? await B.oid() : \"\";\n\t\t\t\tconst { blob } = await git.readBlob({ ...repo, oid: oidB });\n\t\t\t\tconst after = detectBlobContent(blob);\n\t\t\t\treturn {\n\t\t\t\t\tpath: filepath,\n\t\t\t\t\tstatus: \"added\" as const,\n\t\t\t\t\tadditions: after.isBinary ? 0 : countContentLines(after.text),\n\t\t\t\t\tdeletions: 0,\n\t\t\t\t\tpatch: after.isBinary\n\t\t\t\t\t\t? \"\"\n\t\t\t\t\t\t: createUnifiedPatch({\n\t\t\t\t\t\t\t\tpath: filepath,\n\t\t\t\t\t\t\t\tbefore: \"\",\n\t\t\t\t\t\t\t\tafter: after.text,\n\t\t\t\t\t\t\t\toldPath: \"/dev/null\",\n\t\t\t\t\t\t\t}),\n\t\t\t\t\tisBinary: after.isBinary,\n\t\t\t\t\tnewContent: after.isBinary ? toBase64(after.bytes) : undefined,\n\t\t\t\t\tnewSize: after.bytes.length,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tconst [oidA, oidB] = await Promise.all([\n\t\t\t\tA ? A.oid() : Promise.resolve(\"\"),\n\t\t\t\tB ? B.oid() : Promise.resolve(\"\"),\n\t\t\t]);\n\n\t\t\tif (oidA !== oidB) {\n\t\t\t\tconst [{ blob: blobA }, { blob: blobB }] = await Promise.all([\n\t\t\t\t\tgit.readBlob({ ...repo, oid: oidA }),\n\t\t\t\t\tgit.readBlob({ ...repo, oid: oidB }),\n\t\t\t\t]);\n\t\t\t\tconst before = detectBlobContent(blobA);\n\t\t\t\tconst after = detectBlobContent(blobB);\n\t\t\t\tconst isBinary = before.isBinary || after.isBinary;\n\n\t\t\t\treturn {\n\t\t\t\t\tpath: filepath,\n\t\t\t\t\tstatus: \"modified\" as const,\n\t\t\t\t\tadditions: isBinary ? 0 : countContentLines(after.text),\n\t\t\t\t\tdeletions: isBinary ? 0 : countContentLines(before.text),\n\t\t\t\t\tpatch: isBinary\n\t\t\t\t\t\t? \"\"\n\t\t\t\t\t\t: createUnifiedPatch({\n\t\t\t\t\t\t\t\tpath: filepath,\n\t\t\t\t\t\t\t\tbefore: before.text,\n\t\t\t\t\t\t\t\tafter: after.text,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\tisBinary,\n\t\t\t\t\toldContent: isBinary ? toBase64(before.bytes) : undefined,\n\t\t\t\t\tnewContent: isBinary ? toBase64(after.bytes) : undefined,\n\t\t\t\t\toldSize: before.bytes.length,\n\t\t\t\t\tnewSize: after.bytes.length,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\treturn null;\n\t\t},\n\t});\n\n\treturn (changes ?? []).filter(\n\t\t(c: DiffFile | null | undefined): c is DiffFile =>\n\t\t\tc !== null && c !== undefined,\n\t);\n}\n\n/** The diff a single commit introduced (against its first parent). */\nexport async function getCommitDiff(\n\trepo: Repo,\n\tcommitSha: string,\n): Promise<DiffResult> {\n\ttry {\n\t\tconst commit = await getCommit(repo, commitSha);\n\t\tconst parent = commit.commit.parent[0];\n\n\t\tif (!parent) {\n\t\t\tconst entries: { path: string; oid: string }[] = [];\n\t\t\tconst stack: { treeOid: string; prefix: string }[] = [\n\t\t\t\t{ treeOid: commit.commit.tree, prefix: \"\" },\n\t\t\t];\n\n\t\t\twhile (stack.length) {\n\t\t\t\tconst { treeOid, prefix } = stack.pop() as {\n\t\t\t\t\ttreeOid: string;\n\t\t\t\t\tprefix: string;\n\t\t\t\t};\n\t\t\t\tconst { tree } = await git.readTree({ ...repo, oid: treeOid });\n\t\t\t\tfor (const entry of tree) {\n\t\t\t\t\tconst full = prefix ? `${prefix}/${entry.path}` : entry.path;\n\t\t\t\t\tif (entry.type === \"tree\") {\n\t\t\t\t\t\tstack.push({ treeOid: entry.oid, prefix: full });\n\t\t\t\t\t} else if (entry.type === \"blob\") {\n\t\t\t\t\t\tentries.push({ path: full, oid: entry.oid });\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst files: DiffFile[] = await Promise.all(\n\t\t\t\tentries.map(async ({ path, oid }) => {\n\t\t\t\t\tconst { blob } = await git.readBlob({ ...repo, oid });\n\t\t\t\t\tconst after = detectBlobContent(blob);\n\t\t\t\t\treturn {\n\t\t\t\t\t\tpath,\n\t\t\t\t\t\tstatus: \"added\" as const,\n\t\t\t\t\t\tadditions: after.isBinary ? 0 : countContentLines(after.text),\n\t\t\t\t\t\tdeletions: 0,\n\t\t\t\t\t\tpatch: after.isBinary\n\t\t\t\t\t\t\t? \"\"\n\t\t\t\t\t\t\t: createUnifiedPatch({\n\t\t\t\t\t\t\t\t\tpath,\n\t\t\t\t\t\t\t\t\tbefore: \"\",\n\t\t\t\t\t\t\t\t\tafter: after.text,\n\t\t\t\t\t\t\t\t\toldPath: \"/dev/null\",\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\tisBinary: after.isBinary,\n\t\t\t\t\t\tnewContent: after.isBinary ? toBase64(after.bytes) : undefined,\n\t\t\t\t\t\tnewSize: after.bytes.length,\n\t\t\t\t\t};\n\t\t\t\t}),\n\t\t\t);\n\n\t\t\treturn summarizeDiff(files);\n\t\t}\n\n\t\tconst files = await walkTreeDiff(repo, parent, commitSha);\n\t\treturn summarizeDiff(files);\n\t} catch (error) {\n\t\tthrow new Error(`Failed to get commit diff: ${error}`);\n\t}\n}\n\n/** The diff between two refs (base -> compare). */\nexport async function getDiffBetweenRefs(\n\trepo: Repo,\n\tbaseRef: string,\n\tcompareRef: string,\n): Promise<DiffResult> {\n\tconst [baseOid, compareOid] = await Promise.all([\n\t\tgit.resolveRef({ ...repo, ref: qualifyBranchRef(baseRef) }),\n\t\tgit.resolveRef({ ...repo, ref: qualifyBranchRef(compareRef) }),\n\t]);\n\n\tconst files = await walkTreeDiff(repo, baseOid, compareOid);\n\treturn summarizeDiff(files);\n}\n","import git from \"isomorphic-git\";\nimport { decodeUtf8, hasNullByte, toBase64 } from \"../edge-utils.js\";\nimport { GitObjectNotFoundError, GitPathNotFoundError } from \"../git-errors.js\";\nimport { qualifyBranchRef } from \"../refs.js\";\nimport { findTreeEntry, listTreeEntries, type TreeEntry } from \"./tree.js\";\nimport { type OpsHooks, type Repo, runStep } from \"./types.js\";\n\n/**\n * A resolved ref (branch/commit) not existing is a normal, expected condition\n * (empty repo, unborn branch) and is handled by each caller. An object that\n * fails to resolve *underneath* an already-resolved ref (a tree/blob the\n * stored pack doesn't actually contain) means the repo's storage is\n * inconsistent — surface that distinctly so callers don't render it as\n * \"empty\" and clients don't see a raw isomorphic-git NotFoundError.\n */\nfunction wrapMissingObject<T>(\n\tpromise: Promise<T>,\n\tcontext: string,\n): Promise<T> {\n\treturn promise.catch((err: unknown) => {\n\t\tif ((err as { code?: string })?.code === \"NotFoundError\") {\n\t\t\tthrow new GitObjectNotFoundError(\n\t\t\t\t`Git data for ${context} is missing from storage. The repository may need to be re-pushed to repair it.`,\n\t\t\t);\n\t\t}\n\t\tthrow err;\n\t});\n}\n\nconst isNotFound = (err: unknown) =>\n\t(err as { code?: string })?.code === \"NotFoundError\";\n\nexport interface CommitInfo {\n\toid: string;\n\tcommit: {\n\t\tmessage: string;\n\t\ttree: string;\n\t\tparent: string[];\n\t\tauthor: {\n\t\t\tname: string;\n\t\t\temail: string;\n\t\t\ttimestamp: number;\n\t\t\ttimezoneOffset: number;\n\t\t};\n\t\tcommitter: {\n\t\t\tname: string;\n\t\t\temail: string;\n\t\t\ttimestamp: number;\n\t\t\ttimezoneOffset: number;\n\t\t};\n\t};\n\tpayload: string;\n}\n\n/**\n * Resolve a branch name / full ref / sha to its commit. The ref not\n * resolving (unborn branch, genuinely empty repo) and the ref resolving but\n * its commit object being unreadable (storage inconsistency — see\n * `wrapMissingObject` above) are different failures with different meanings,\n * so only the first is left as a raw isomorphic-git NotFoundError for\n * callers to treat as \"empty\"; the second is wrapped into\n * GitObjectNotFoundError specifically so it can't be mistaken for the first\n * by an `isNotFound`-style check downstream (see getTreeFromRef/getCommitLog).\n */\nexport async function resolveCommit(repo: Repo, ref: string) {\n\tconst oid = await git.resolveRef({ ...repo, ref: qualifyBranchRef(ref) });\n\tconst result = await wrapMissingObject(\n\t\tgit.readCommit({ ...repo, oid }),\n\t\t`${repo.gitdir} commit ${oid}`,\n\t);\n\treturn { oid, commit: result.commit };\n}\n\n/** Read a blob's bytes by oid. */\nexport async function getBlob(repo: Repo, sha: string): Promise<Uint8Array> {\n\tconst { blob } = await wrapMissingObject(\n\t\tgit.readBlob({ ...repo, oid: sha }),\n\t\t`${repo.gitdir} blob ${sha}`,\n\t);\n\treturn blob;\n}\n\n/** Read a file's bytes at a ref. Throws GitPathNotFoundError when absent. */\nexport async function getFileContent(\n\trepo: Repo,\n\tfilePath: string,\n\tref = \"main\",\n): Promise<Uint8Array> {\n\tconst { commit } = await resolveCommit(repo, ref);\n\tconst context = `${repo.gitdir}@${ref}:${filePath}`;\n\tconst entry = await wrapMissingObject(\n\t\tfindTreeEntry(repo, commit.tree, filePath),\n\t\tcontext,\n\t);\n\n\tif (entry?.type !== \"blob\") {\n\t\tthrow new GitPathNotFoundError(`File not found: ${filePath}`);\n\t}\n\n\tconst { blob } = await wrapMissingObject(\n\t\tgit.readBlob({ ...repo, oid: entry.oid }),\n\t\tcontext,\n\t);\n\treturn blob;\n}\n\n/** Read one commit by sha. */\nexport async function getCommit(repo: Repo, sha: string): Promise<CommitInfo> {\n\tconst result = await wrapMissingObject(\n\t\tgit.readCommit({ ...repo, oid: sha }),\n\t\t`${repo.gitdir} commit ${sha}`,\n\t);\n\treturn { oid: result.oid, commit: result.commit, payload: result.payload };\n}\n\nfunction isFullyWalked(commits: CommitInfo[]): boolean {\n\tconst last = commits[commits.length - 1];\n\treturn !!last && last.commit.parent.length === 0;\n}\n\nexport interface CommitLogOptions {\n\tref?: string;\n\tdepth?: number;\n\t/**\n\t * Pass the head sha when the caller already resolved `ref` — resolveRef\n\t * tries several candidate paths in sequence and misses the first few every\n\t * time for a normal branch name, which is pure waste when the sha is known.\n\t */\n\tknownHeadSha?: string;\n}\n\n/**\n * The commit chain from a ref, newest first. Walking is inherently sequential\n * (each commit's oid is only discoverable by reading its child first) and\n * network-round-trip-bound against object storage, so the deepest walk seen\n * per head is memoized in `hooks.resultCache` and sliced for shallower or\n * repeated requests — don't bypass this by calling `git.log` directly.\n */\nexport async function getCommitLog(\n\trepo: Repo,\n\toptions: CommitLogOptions = {},\n\thooks?: OpsHooks,\n): Promise<CommitInfo[]> {\n\tconst ref = options.ref ?? \"main\";\n\tconst depth = options.depth ?? 50;\n\n\tlet headSha: string;\n\tif (options.knownHeadSha) {\n\t\theadSha = options.knownHeadSha;\n\t} else {\n\t\ttry {\n\t\t\theadSha = await git.resolveRef({ ...repo, ref: qualifyBranchRef(ref) });\n\t\t} catch (err: unknown) {\n\t\t\tif (isNotFound(err)) return [];\n\t\t\tthrow err;\n\t\t}\n\t}\n\n\tconst cacheKey = `commitlog:${repo.gitdir}:${headSha}`;\n\tconst cached = hooks?.resultCache?.get<CommitInfo[]>(cacheKey);\n\tif (cached && (cached.length >= depth || isFullyWalked(cached))) {\n\t\thooks?.onNote?.(\n\t\t\t`getCommitLog: result-cache HIT for ${cacheKey} (depth=${depth})`,\n\t\t);\n\t\treturn cached.slice(0, depth);\n\t}\n\thooks?.onNote?.(\n\t\t`getCommitLog: result-cache MISS for ${cacheKey} (depth=${depth})`,\n\t);\n\n\tif (hooks?.prefetch && depth >= (hooks.prefetchMinDepth ?? 5)) {\n\t\tawait runStep(hooks, \"prefetch\", hooks.prefetch);\n\t}\n\n\t// headSha is already known-good here (resolveRef above succeeded, or the\n\t// caller passed knownHeadSha) — a NotFoundError from the walk itself means\n\t// a commit/tree/blob it needs is missing from storage, not that the ref\n\t// doesn't exist. Unlike the resolveRef catch above, that's not \"empty\",\n\t// it's storage inconsistency, so wrapMissingObject turns it into a\n\t// GitObjectNotFoundError instead of silently returning [] — an empty\n\t// result here would otherwise get treated as \"this branch has no\n\t// history\" by every caller (and, worse, potentially cached as such).\n\tconst commits = await wrapMissingObject(\n\t\trunStep(hooks, `git.log ${ref} depth=${depth}`, () =>\n\t\t\tgit.log({ ...repo, ref: headSha, depth }),\n\t\t),\n\t\t`${repo.gitdir}@${ref} history`,\n\t);\n\tconst result = commits.map((commit) => ({\n\t\toid: commit.oid,\n\t\tcommit: commit.commit,\n\t\tpayload: commit.payload || \"\",\n\t}));\n\tif (!cached || result.length > cached.length) {\n\t\thooks?.resultCache?.set(cacheKey, result);\n\t}\n\treturn result;\n}\n\n/** A file at a ref, decoded for display: utf8 text or base64 when binary. */\nexport async function getFileFromRef(\n\trepo: Repo,\n\tfilePath: string,\n\tref: string,\n): Promise<{ content: string; size: number; isBinary: boolean }> {\n\tconst bytes = await getFileContent(repo, filePath, ref);\n\tconst isBinary = hasNullByte(bytes);\n\n\treturn {\n\t\tcontent: isBinary ? toBase64(bytes) : decodeUtf8(bytes),\n\t\tsize: bytes.length,\n\t\tisBinary,\n\t};\n}\n\n/**\n * List a directory at the tip of a ref. Returns [] for an empty repo/unborn\n * branch; throws GitPathNotFoundError when `treePath` doesn't exist. Results\n * are memoized per head sha (auto-invalidates on push).\n */\nexport async function getTreeFromRef(\n\trepo: Repo,\n\toptions: { ref?: string; treePath?: string } = {},\n\thooks?: OpsHooks,\n): Promise<TreeEntry[]> {\n\tconst ref = options.ref ?? \"main\";\n\tconst treePath = options.treePath ?? \"\";\n\n\tlet commit: Awaited<ReturnType<typeof resolveCommit>>[\"commit\"];\n\tlet headSha: string;\n\ttry {\n\t\tconst resolved = await resolveCommit(repo, ref);\n\t\tcommit = resolved.commit;\n\t\theadSha = resolved.oid;\n\t} catch (err: unknown) {\n\t\tif (isNotFound(err)) return [];\n\t\tthrow err;\n\t}\n\n\tconst cacheKey = `tree:${repo.gitdir}:${headSha}:${treePath}`;\n\tconst cached = hooks?.resultCache?.get<TreeEntry[]>(cacheKey);\n\tif (cached) {\n\t\thooks?.onNote?.(`getTreeFromRef: result-cache HIT for ${cacheKey}`);\n\t\treturn cached;\n\t}\n\thooks?.onNote?.(`getTreeFromRef: result-cache MISS for ${cacheKey}`);\n\n\t// Unlike getCommitLog, a tree read has no \"depth\" to gate on — it always\n\t// needs at least the head commit's tree object, and (for a non-root path)\n\t// one object per path segment on top of that, so there's no shallow case\n\t// where prefetching every pack is wasted bandwidth the way a depth=1\n\t// commit-log walk can be. Without this, a cache-miss tree read falls\n\t// through to isomorphic-git's own pack resolution, which probes indexed\n\t// packs one at a time instead of warming them all in parallel up front —\n\t// this was previously the slowest of a tree page's parallel queries in\n\t// production for exactly that reason.\n\tif (hooks?.prefetch) {\n\t\tawait runStep(hooks, \"prefetch\", hooks.prefetch);\n\t}\n\n\tconst context = `${repo.gitdir}@${ref}:${treePath || \"/\"}`;\n\tlet result: TreeEntry[];\n\tif (!treePath) {\n\t\tresult = await wrapMissingObject(\n\t\t\trunStep(hooks, \"listTreeEntries (root)\", () =>\n\t\t\t\tlistTreeEntries(repo, commit.tree),\n\t\t\t),\n\t\t\tcontext,\n\t\t);\n\t} else {\n\t\tconst entry = await wrapMissingObject(\n\t\t\trunStep(hooks, `findTreeEntry ${treePath}`, () =>\n\t\t\t\tfindTreeEntry(repo, commit.tree, treePath),\n\t\t\t),\n\t\t\tcontext,\n\t\t);\n\t\tif (!entry) {\n\t\t\tthrow new GitPathNotFoundError(\n\t\t\t\t`Path \"${treePath}\" does not exist at ${ref}`,\n\t\t\t);\n\t\t}\n\t\tresult =\n\t\t\tentry.type !== \"tree\"\n\t\t\t\t? []\n\t\t\t\t: await wrapMissingObject(\n\t\t\t\t\t\trunStep(hooks, `listTreeEntries ${treePath}`, () =>\n\t\t\t\t\t\t\tlistTreeEntries(repo, entry.oid, entry.path),\n\t\t\t\t\t\t),\n\t\t\t\t\t\tcontext,\n\t\t\t\t\t);\n\t}\n\n\thooks?.resultCache?.set(cacheKey, result);\n\treturn result;\n}\n\n/**\n * A page of commit history from a branch tip. Memoized per head sha; builds\n * on {@link getCommitLog}'s walk cache for the underlying chain.\n */\nexport async function getCommitHistory(\n\trepo: Repo,\n\toptions: { ref: string; limit?: number; skip?: number },\n\thooks?: OpsHooks,\n): Promise<CommitInfo[]> {\n\tconst limit = options.limit ?? 50;\n\tconst skip = options.skip ?? 0;\n\tconst headSha = await git\n\t\t.resolveRef({ ...repo, ref: qualifyBranchRef(options.ref) })\n\t\t.catch(() => null);\n\n\tconst cacheKey = headSha\n\t\t? `commits:${repo.gitdir}:${headSha}:${limit}:${skip}`\n\t\t: null;\n\tif (cacheKey) {\n\t\tconst cached = hooks?.resultCache?.get<CommitInfo[]>(cacheKey);\n\t\tif (cached) {\n\t\t\thooks?.onNote?.(`getCommitHistory: result-cache HIT for ${cacheKey}`);\n\t\t\treturn cached;\n\t\t}\n\t}\n\thooks?.onNote?.(\n\t\t`getCommitHistory: result-cache MISS for ${cacheKey ?? \"(no head)\"}`,\n\t);\n\tif (!headSha) return [];\n\n\tconst all = await getCommitLog(\n\t\trepo,\n\t\t{ ref: options.ref, depth: limit + skip, knownHeadSha: headSha },\n\t\thooks,\n\t);\n\tconst result = all.slice(skip, skip + limit);\n\n\tif (cacheKey) hooks?.resultCache?.set(cacheKey, result);\n\treturn result;\n}\n","import type git from \"isomorphic-git\";\n\n/** The fs shape isomorphic-git accepts, derived from its own signatures. */\nexport type IsoGitFs = Parameters<typeof git.readTree>[0][\"fs\"];\n\n/**\n * A repository handle: everything isomorphic-git needs to address one bare\n * repo. Create it once per request (sharing `cache` across calls is what lets\n * isomorphic-git reuse parsed pack indexes) and pass it to every op.\n */\nexport interface Repo {\n\tfs: IsoGitFs;\n\tgitdir: string;\n\t/** isomorphic-git's shared parse cache — strongly recommended per repo. */\n\tcache?: object;\n}\n\n/**\n * Key/value store for memoizing expensive walk results (commit logs, tree\n * listings, per-file history). Keys are namespaced `kind:gitdir:headSha:…`,\n * so entries self-invalidate on push (new head, new key) — but evict entries\n * under {@link resultKeyPrefixes} after rewriting a repo's storage out of\n * band, or stale walks leak until your store's own eviction.\n */\nexport interface ResultCache {\n\tget<T>(key: string): T | null | undefined;\n\tset(key: string, value: unknown): void;\n}\n\n/** Optional instrumentation and tuning hooks accepted by every op. */\nexport interface OpsHooks {\n\tresultCache?: ResultCache;\n\t/** Wrap a timed sub-step (network walk, tree listing). Default: run directly. */\n\tstep?: <T>(label: string, fn: () => Promise<T>) => Promise<T>;\n\t/** Diagnostic sink for cache hit/miss and walk summaries. */\n\tonNote?: (message: string) => void;\n\t/**\n\t * Wire pack prefetching (e.g. `GitFs.prefetchPacks`) here so a sequential\n\t * walk doesn't pay one network round trip per commit. Called once before\n\t * history walks of depth >= `prefetchMinDepth` (see below), and\n\t * unconditionally on every cache-miss tree read ({@link getTreeFromRef}) —\n\t * a tree read has no shallow case, it always needs at least the head\n\t * commit's tree object.\n\t */\n\tprefetch?: () => Promise<void>;\n\t/** Minimum walk depth before `prefetch` fires for history walks. Default 5. */\n\tprefetchMinDepth?: number;\n}\n\nexport const runStep = <T>(\n\thooks: OpsHooks | undefined,\n\tlabel: string,\n\tfn: () => Promise<T>,\n): Promise<T> => (hooks?.step ? hooks.step(label, fn) : fn());\n\n/**\n * The result-cache key prefixes holding entries for `gitdir` — evict these\n * from your {@link ResultCache} when the repo's storage was rewritten outside\n * a normal push (bulk sync, rename, repack cleanup).\n */\nexport function resultKeyPrefixes(gitdir: string): string[] {\n\treturn [\n\t\t`commitlog:${gitdir}:`,\n\t\t`tree:${gitdir}:`,\n\t\t`commits:${gitdir}:`,\n\t\t`last-commits:${gitdir}:`,\n\t\t`file-history:${gitdir}:`,\n\t];\n}\n","import { type CommitInfo, getCommitLog } from \"./history.js\";\nimport { findTreeEntry } from \"./tree.js\";\nimport type { OpsHooks, Repo } from \"./types.js\";\nimport { runStep } from \"./types.js\";\n\nexport interface FileHistoryEntry {\n\tsha: string;\n\tmessage: string;\n\tauthorName: string;\n\tauthorEmail: string;\n\tcreatedAt: string;\n}\n\nexport interface FileHistoryResult {\n\tentries: FileHistoryEntry[];\n\t/**\n\t * True when the walk hit its depth budget (or the requested `limit`)\n\t * before exhausting the branch's full commit chain — there may be older\n\t * commits touching this file that a deeper walk would surface.\n\t */\n\ttruncated: boolean;\n}\n\n/**\n * Default walk bound for a caller that actually wants deep history (a file's\n * \"History\" tab). Walking the full chain is round-trip-bound on object\n * storage, so cap how far back a single request will look.\n */\nexport const HISTORY_WALK_DEPTH = 400;\n\n/**\n * Much shallower default for a \"latest commit touching this file\" banner that\n * only displays `entries[0]` — trades \"always finds the true last-touching\n * commit\" for \"finds it if it's reasonably recent\", the right call for a\n * banner with a full History view a click away.\n */\nexport const BANNER_WALK_DEPTH = 60;\n\n/** Tree reads are prefetched in parallel windows; see getLastCommitsForTree. */\nconst PREFETCH_WINDOW = 24;\n\nfunction toEntry(commit: CommitInfo): FileHistoryEntry {\n\treturn {\n\t\tsha: commit.oid,\n\t\tmessage: commit.commit.message.trim(),\n\t\tauthorName: commit.commit.author.name,\n\t\tauthorEmail: commit.commit.author.email,\n\t\tcreatedAt: new Date(commit.commit.author.timestamp * 1000).toISOString(),\n\t};\n}\n\n/**\n * All commits (newest first) that changed a single file's blob oid, walking\n * the first-parent chain — same approach as getLastCommitsForTree but for one\n * path and collecting every match instead of stopping at the first.\n */\nexport async function getFileHistory(\n\trepo: Repo,\n\toptions: {\n\t\tref: string;\n\t\tfilePath: string;\n\t\tlimit?: number;\n\t\tmaxDepth?: number;\n\t},\n\thooks?: OpsHooks,\n): Promise<FileHistoryResult> {\n\tconst limit = options.limit ?? 30;\n\tconst maxDepth = options.maxDepth ?? HISTORY_WALK_DEPTH;\n\tconst walkDepth = Math.max(maxDepth, limit);\n\tconst commits = await runStep(hooks, `getCommitLog depth=${walkDepth}`, () =>\n\t\tgetCommitLog(repo, { ref: options.ref, depth: walkDepth }, hooks),\n\t);\n\tconst head = commits[0];\n\tif (!head) return { entries: [], truncated: false };\n\n\tconst cacheKey = `file-history:${repo.gitdir}:${head.oid}:${options.filePath}:${limit}:${maxDepth}`;\n\tconst cached = hooks?.resultCache?.get<FileHistoryResult>(cacheKey);\n\tif (cached) {\n\t\thooks?.onNote?.(\"getFileHistory: result-cache HIT, skipping history walk\");\n\t\treturn cached;\n\t}\n\thooks?.onNote?.(\"getFileHistory: result-cache MISS, walking history\");\n\n\tconst byOid = new Map(commits.map((commit) => [commit.oid, commit]));\n\n\tconst oidByCommitTree = new Map<string, string | null>();\n\tasync function resolveOid(commitTreeOid: string): Promise<string | null> {\n\t\tconst cachedOid = oidByCommitTree.get(commitTreeOid);\n\t\tif (cachedOid !== undefined) return cachedOid;\n\t\tconst entry = await findTreeEntry(repo, commitTreeOid, options.filePath);\n\t\tconst oid = entry?.type === \"blob\" ? entry.oid : null;\n\t\toidByCommitTree.set(commitTreeOid, oid);\n\t\treturn oid;\n\t}\n\n\tconst entries: FileHistoryEntry[] = [];\n\tlet truncated = false;\n\n\touter: for (\n\t\tlet windowStart = 0;\n\t\twindowStart < commits.length;\n\t\twindowStart += PREFETCH_WINDOW\n\t) {\n\t\tconst windowEnd = Math.min(windowStart + PREFETCH_WINDOW, commits.length);\n\t\t// +1 lookahead so the last entry's parent tree is already warm too.\n\t\tconst prefetchEnd = Math.min(windowEnd + 1, commits.length);\n\n\t\tawait Promise.all(\n\t\t\tcommits\n\t\t\t\t.slice(windowStart, prefetchEnd)\n\t\t\t\t.map((commit) => resolveOid(commit.commit.tree)),\n\t\t);\n\n\t\tfor (let i = windowStart; i < windowEnd; i++) {\n\t\t\tconst commit = commits[i];\n\t\t\tif (!commit) break outer;\n\n\t\t\tconst parentSha = commit.commit.parent[0];\n\t\t\tconst parentCommit = parentSha ? byOid.get(parentSha) : undefined;\n\t\t\tif (parentSha && !parentCommit) {\n\t\t\t\t// Walked past the depth cap without reaching this commit's parent —\n\t\t\t\t// can't tell whether it changed the file; stop and report truncated.\n\t\t\t\ttruncated = true;\n\t\t\t\tbreak outer;\n\t\t\t}\n\n\t\t\tconst [oid, parentOid] = await Promise.all([\n\t\t\t\tresolveOid(commit.commit.tree),\n\t\t\t\tparentCommit\n\t\t\t\t\t? resolveOid(parentCommit.commit.tree)\n\t\t\t\t\t: Promise.resolve(null),\n\t\t\t]);\n\n\t\t\tif (oid !== parentOid) {\n\t\t\t\tentries.push(toEntry(commit));\n\t\t\t\tif (entries.length >= limit) {\n\t\t\t\t\ttruncated = i < commits.length - 1;\n\t\t\t\t\tbreak outer;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tconst result = { entries, truncated };\n\thooks?.resultCache?.set(cacheKey, result);\n\treturn result;\n}\n","import { type CommitInfo, getCommitLog } from \"./history.js\";\nimport { findTreeEntry, listTreeEntries } from \"./tree.js\";\nimport type { OpsHooks, Repo } from \"./types.js\";\nimport { runStep } from \"./types.js\";\n\nexport interface LastCommitInfo {\n\tsha: string;\n\tmessage: string;\n\tauthorName: string;\n\tauthorEmail: string;\n\tcreatedAt: string;\n}\n\n/**\n * Bounds how far back the history walk looks to resolve \"last commit touching\n * this path\" for a directory listing. Entries whose last change is older\n * simply show no last-commit info rather than paying an unbounded scan.\n */\nconst HISTORY_WALK_DEPTH = 400;\n\n/**\n * The walk itself is inherently sequential (each step needs to know what's\n * still \"remaining\" from the step before), but the tree-object reads that\n * back it are not — each commit's tree oid is already known upfront from the\n * commit log. Prefetching a window of tree reads in parallel turns ~1 network\n * round trip per commit (serialized) into ~1 round trip per window, which is\n * where nearly all of this function's wall-clock time goes on object storage.\n */\nconst PREFETCH_WINDOW = 24;\n\nfunction toLastCommitInfo(commit: CommitInfo): LastCommitInfo {\n\treturn {\n\t\tsha: commit.oid,\n\t\tmessage: commit.commit.message.trim(),\n\t\tauthorName: commit.commit.author.name,\n\t\tauthorEmail: commit.commit.author.email,\n\t\tcreatedAt: new Date(commit.commit.author.timestamp * 1000).toISOString(),\n\t};\n}\n\n/**\n * For each direct child of `treePath` (at the tip of `ref`), find the most\n * recent commit that changed it — the tree view's \"last commit\" column. Walks\n * history newest-to-oldest, comparing the directory's tree oid\n * commit-to-commit and only descending one level to diff child oids when\n * something under the directory actually changed. Preserve the two-phase\n * structure (parallel prefetch, then sequential resolve) when touching this —\n * the \"which entries are still unresolved\" state must advance\n * commit-by-commit.\n */\nexport async function getLastCommitsForTree(\n\trepo: Repo,\n\toptions: { ref: string; treePath?: string; depth?: number },\n\thooks?: OpsHooks,\n): Promise<Record<string, LastCommitInfo>> {\n\tconst treePath = options.treePath ?? \"\";\n\tconst depth = options.depth ?? HISTORY_WALK_DEPTH;\n\tconst commits = await runStep(hooks, `getCommitLog depth=${depth}`, () =>\n\t\tgetCommitLog(repo, { ref: options.ref, depth }, hooks),\n\t);\n\thooks?.onNote?.(`getLastCommitsForTree: ${commits.length} commits in log`);\n\tconst head = commits[0];\n\tif (!head) return {};\n\n\tconst cacheKey = `last-commits:${repo.gitdir}:${head.oid}:${treePath}`;\n\tconst cachedResult =\n\t\thooks?.resultCache?.get<Record<string, LastCommitInfo>>(cacheKey);\n\tif (cachedResult) {\n\t\thooks?.onNote?.(\n\t\t\t\"getLastCommitsForTree: result-cache HIT, skipping history walk\",\n\t\t);\n\t\treturn cachedResult;\n\t}\n\thooks?.onNote?.(\"getLastCommitsForTree: result-cache MISS, walking history\");\n\n\tconst byOid = new Map(commits.map((commit) => [commit.oid, commit]));\n\n\t// In a linear history, the \"parent tree\" resolved at commit[i] is the same\n\t// tree already resolved as the \"current tree\" at commit[i-1] — memoize by\n\t// commit-tree oid (and by resolved dir oid) so each distinct tree is only\n\t// walked/listed once across the whole scan instead of twice per commit.\n\tconst dirOidByCommitTree = new Map<string, string | null>();\n\tconst childrenByDirOid = new Map<\n\t\tstring,\n\t\tAwaited<ReturnType<typeof listTreeEntries>>\n\t>();\n\n\tasync function resolveDirOid(commitTreeOid: string): Promise<string | null> {\n\t\tconst cached = dirOidByCommitTree.get(commitTreeOid);\n\t\tif (cached !== undefined) return cached;\n\t\tconst entry = await findTreeEntry(repo, commitTreeOid, treePath);\n\t\tconst dirOid = entry?.type === \"tree\" ? entry.oid : null;\n\t\tdirOidByCommitTree.set(commitTreeOid, dirOid);\n\t\treturn dirOid;\n\t}\n\n\tasync function resolveChildren(dirOid: string | null) {\n\t\tif (dirOid === null) return [];\n\t\tconst cached = childrenByDirOid.get(dirOid);\n\t\tif (cached) return cached;\n\t\tconst children = await listTreeEntries(repo, dirOid, treePath);\n\t\tchildrenByDirOid.set(dirOid, children);\n\t\treturn children;\n\t}\n\n\tconst headDirOid = await resolveDirOid(head.commit.tree);\n\tif (headDirOid === null) return {};\n\n\t// listTreeEntries is prefixed with treePath so result keys match the full\n\t// paths that callers key their file listing by.\n\tconst headChildren = await resolveChildren(headDirOid);\n\tconst remaining = new Set(headChildren.map((entry) => entry.path));\n\tconst result: Record<string, LastCommitInfo> = {};\n\n\tlet commitsWalked = 0;\n\tlet prefetchWindows = 0;\n\tconst walkStart = performance.now();\n\n\touter: for (\n\t\tlet windowStart = 0;\n\t\twindowStart < commits.length && remaining.size > 0;\n\t\twindowStart += PREFETCH_WINDOW\n\t) {\n\t\tconst windowEnd = Math.min(windowStart + PREFETCH_WINDOW, commits.length);\n\t\t// +1 lookahead commit so the last entry's parent tree is already warm too.\n\t\tconst prefetchEnd = Math.min(windowEnd + 1, commits.length);\n\t\tprefetchWindows++;\n\n\t\t// Phase A: resolve this window's directory oid for every commit's tree in\n\t\t// parallel (a no-op await when treePath is \"\" — the root tree oid IS the\n\t\t// commit tree, no lookup needed).\n\t\tconst dirOids = await Promise.all(\n\t\t\tcommits\n\t\t\t\t.slice(windowStart, prefetchEnd)\n\t\t\t\t.map((commit) => resolveDirOid(commit.commit.tree)),\n\t\t);\n\n\t\t// Phase B: resolve children for every distinct directory oid the window\n\t\t// touched, in parallel — the actual tree-object read for the common case.\n\t\tawait Promise.all(\n\t\t\t[...new Set(dirOids)].map((dirOid) => resolveChildren(dirOid)),\n\t\t);\n\n\t\tfor (let i = windowStart; i < windowEnd; i++) {\n\t\t\tif (remaining.size === 0) break outer;\n\t\t\tconst commit = commits[i];\n\t\t\tif (!commit) break outer;\n\t\t\tcommitsWalked++;\n\n\t\t\tconst parentSha = commit.commit.parent[0];\n\t\t\tconst parentCommit = parentSha ? byOid.get(parentSha) : undefined;\n\t\t\tif (parentSha && !parentCommit) break outer; // walked past the depth cap\n\n\t\t\t// Already resolved above (or memoized from an earlier window/commit) —\n\t\t\t// these awaits resolve immediately from cache.\n\t\t\tconst [dirOid, parentDirOid] = await Promise.all([\n\t\t\t\tresolveDirOid(commit.commit.tree),\n\t\t\t\tparentCommit\n\t\t\t\t\t? resolveDirOid(parentCommit.commit.tree)\n\t\t\t\t\t: Promise.resolve(null),\n\t\t\t]);\n\n\t\t\tif (dirOid === parentDirOid) continue;\n\n\t\t\tconst [children, parentChildren] = await Promise.all([\n\t\t\t\tresolveChildren(dirOid),\n\t\t\t\tresolveChildren(parentDirOid),\n\t\t\t]);\n\t\t\tconst childByName = new Map(\n\t\t\t\tchildren.map((entry) => [entry.path, entry.oid]),\n\t\t\t);\n\t\t\tconst parentChildByName = new Map(\n\t\t\t\tparentChildren.map((entry) => [entry.path, entry.oid]),\n\t\t\t);\n\n\t\t\tfor (const name of remaining) {\n\t\t\t\tif (childByName.get(name) !== parentChildByName.get(name)) {\n\t\t\t\t\tresult[name] = toLastCommitInfo(commit);\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor (const name of Object.keys(result)) {\n\t\t\t\tremaining.delete(name);\n\t\t\t}\n\t\t}\n\t}\n\n\thooks?.onNote?.(\n\t\t`getLastCommitsForTree: walked ${commitsWalked}/${commits.length} commits across ${prefetchWindows} prefetch windows in ${(performance.now() - walkStart).toFixed(1)}ms, ${dirOidByCommitTree.size} unique tree lookups, ${remaining.size} entries never resolved`,\n\t);\n\n\thooks?.resultCache?.set(cacheKey, result);\n\treturn result;\n}\n","import git from \"isomorphic-git\";\nimport { qualifyBranchRef } from \"../refs.js\";\nimport { assertSafeBranchName } from \"./branch.js\";\nimport type { Repo } from \"./types.js\";\n\nexport interface MergeAnalysis {\n\tcanMerge: boolean;\n\thasConflicts: boolean;\n\tconflictingFiles: string[];\n\tfastForward: boolean;\n}\n\n/**\n * Cheap pre-merge check: do both branches exist, and is this a fast-forward?\n * `canMerge`/`fastForward` are the only fields this actually determines —\n * `hasConflicts`/`conflictingFiles` are NOT a real content-conflict check\n * (isomorphic-git's `git.merge` doesn't expose a dry-run), they only ever\n * reflect \"one of the branches couldn't be resolved\" (`canMerge: false`).\n * Real merge conflicts are only discoverable by actually attempting the\n * merge.\n */\nexport async function analyzeMerge(\n\trepo: Repo,\n\tsourceBranch: string,\n\ttargetBranch: string,\n): Promise<MergeAnalysis> {\n\tassertSafeBranchName(sourceBranch);\n\tassertSafeBranchName(targetBranch);\n\n\ttry {\n\t\tconst [sourceOid, targetOid] = await Promise.all([\n\t\t\tgit.resolveRef({ ...repo, ref: qualifyBranchRef(sourceBranch) }),\n\t\t\tgit.resolveRef({ ...repo, ref: qualifyBranchRef(targetBranch) }),\n\t\t]);\n\n\t\tconst isDescendant = await git.isDescendent({\n\t\t\t...repo,\n\t\t\toid: sourceOid,\n\t\t\tancestor: targetOid,\n\t\t});\n\n\t\treturn {\n\t\t\tcanMerge: true,\n\t\t\thasConflicts: false,\n\t\t\tconflictingFiles: [],\n\t\t\tfastForward: isDescendant,\n\t\t};\n\t} catch (err) {\n\t\tif ((err as { code?: string })?.code !== \"NotFoundError\") {\n\t\t\tthrow err;\n\t\t}\n\t\t// A branch ref failed to resolve — not a content conflict, just \"can't\n\t\t// merge because one side doesn't exist.\" hasConflicts here is a\n\t\t// misnomer kept for MergeAnalysis's existing shape; canMerge is the\n\t\t// field that actually matters to callers.\n\t\treturn {\n\t\t\tcanMerge: false,\n\t\t\thasConflicts: true,\n\t\t\tconflictingFiles: [],\n\t\t\tfastForward: false,\n\t\t};\n\t}\n}\n\n/**\n * Attempt a fast-forward merge directly against the bare repo: when source is\n * a descendant of target, just move the target ref — no worktree, no new\n * commit. Returns null when the merge is not a fast-forward (callers fall\n * back to a real three-way merge, which needs a worktree). Serialize with a\n * per-repo lock: resolve → writeRef is not atomic.\n */\nexport async function fastForwardMerge(\n\trepo: Repo,\n\tsourceBranch: string,\n\ttargetBranch: string,\n): Promise<{ success: true; commitSha: string } | null> {\n\tassertSafeBranchName(sourceBranch);\n\tassertSafeBranchName(targetBranch);\n\tconst [sourceOid, targetOid] = await Promise.all([\n\t\tgit.resolveRef({ ...repo, ref: `refs/heads/${sourceBranch}` }),\n\t\tgit.resolveRef({ ...repo, ref: `refs/heads/${targetBranch}` }),\n\t]);\n\tconst isFF = await git.isDescendent({\n\t\t...repo,\n\t\toid: sourceOid,\n\t\tancestor: targetOid,\n\t});\n\tif (!isFF) return null;\n\tawait git.writeRef({\n\t\t...repo,\n\t\tref: `refs/heads/${targetBranch}`,\n\t\tvalue: sourceOid,\n\t\tforce: true,\n\t});\n\treturn { success: true, commitSha: sourceOid };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAAA,OAAOA,UAAS;;;ACAhB,OAAO,SAAS;AAWhB,IAAM,WAAW,CAAC,QAAgB,SACjC,SAAS,GAAG,OAAO,QAAQ,QAAQ,EAAE,CAAC,IAAI,IAAI,KAAK;AAMpD,eAAsB,WACrB,MACA,SACA,SACkB;AAClB,QAAM,WAAW,WACb,MAAM,IAAI,SAAS,EAAE,GAAG,MAAM,KAAK,QAAQ,CAAC,GAAG,OAChD,CAAC;AACJ,QAAM,SAAS,IAAI,IAAI,SAAS,IAAI,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;AACvD,QAAM,SAAS,oBAAI,IAAoB;AACvC,QAAM,SAAS,oBAAI,IAAiC;AACpD,aAAW,CAAC,UAAU,OAAO,KAAK,SAAS;AAC1C,UAAM,QAAQ,SAAS,QAAQ,GAAG;AAClC,QAAI,UAAU,IAAI;AACjB,aAAO,IAAI,UAAU,OAAO;AAAA,IAC7B,OAAO;AACN,YAAM,MAAM,SAAS,MAAM,GAAG,KAAK;AACnC,YAAM,OAAO,SAAS,MAAM,QAAQ,CAAC;AACrC,UAAI,CAAC,OAAO,IAAI,GAAG,EAAG,QAAO,IAAI,KAAK,oBAAI,IAAI,CAAC;AAC/C,aAAO,IAAI,GAAG,GAAG,IAAI,MAAM,OAAO;AAAA,IACnC;AAAA,EACD;AACA,aAAW,CAAC,MAAM,OAAO,KAAK,QAAQ;AACrC,WAAO,IAAI,MAAM;AAAA,MAChB,MAAM;AAAA,MACN,MAAM;AAAA,MACN,KAAK;AAAA,MACL,MAAM;AAAA,IACP,CAAC;AAAA,EACF;AAGA,QAAM,gBAAgB,MAAM,QAAQ;AAAA,IACnC,MAAM,KAAK,QAAQ,OAAO,CAAC,KAAK,UAAU,MAAM;AAC/C,YAAM,QAAQ,OAAO,IAAI,GAAG;AAC5B,YAAM,aAAa,OAAO,SAAS,SAAS,MAAM,MAAM;AACxD,YAAM,SAAS,MAAM,WAAW,MAAM,YAAY,UAAU;AAC5D,aAAO,CAAC,KAAK,MAAM;AAAA,IACpB,CAAC;AAAA,EACF;AACA,aAAW,CAAC,KAAK,MAAM,KAAK,eAAe;AAC1C,WAAO,IAAI,KAAK,EAAE,MAAM,UAAU,MAAM,KAAK,KAAK,QAAQ,MAAM,OAAO,CAAC;AAAA,EACzE;AACA,SAAO,IAAI,UAAU,EAAE,GAAG,MAAM,MAAM,MAAM,KAAK,OAAO,OAAO,CAAC,EAAE,CAAC;AACpE;AAGA,eAAsB,eACrB,MACA,SACA,UACkB;AAClB,QAAM,YAAY,MAAM,IAAI,SAAS,EAAE,GAAG,MAAM,KAAK,QAAQ,CAAC,GAAG;AACjE,QAAM,SAAS,IAAI,IAAI,SAAS,IAAI,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;AACvD,QAAM,QAAQ,SAAS,QAAQ,GAAG;AAClC,MAAI,UAAU,IAAI;AACjB,WAAO,OAAO,QAAQ;AAAA,EACvB,OAAO;AACN,UAAM,MAAM,SAAS,MAAM,GAAG,KAAK;AACnC,UAAM,OAAO,SAAS,MAAM,QAAQ,CAAC;AACrC,UAAM,QAAQ,OAAO,IAAI,GAAG;AAC5B,QAAI,OAAO,SAAS,QAAQ;AAC3B,YAAM,SAAS,MAAM,eAAe,MAAM,MAAM,KAAK,IAAI;AACzD,aAAO,IAAI,KAAK,EAAE,GAAG,OAAO,KAAK,OAAO,CAAC;AAAA,IAC1C;AAAA,EACD;AACA,SAAO,IAAI,UAAU,EAAE,GAAG,MAAM,MAAM,MAAM,KAAK,OAAO,OAAO,CAAC,EAAE,CAAC;AACpE;AAGA,eAAsB,cACrB,MACA,aACA,UAC4B;AAC5B,MAAI,CAAC,UAAU;AACd,WAAO,EAAE,MAAM,IAAI,MAAM,UAAU,MAAM,QAAQ,KAAK,YAAY;AAAA,EACnE;AAEA,QAAM,QAAQ,SAAS,MAAM,GAAG,EAAE,OAAO,OAAO;AAChD,MAAI,iBAAiB;AACrB,MAAI,cAAc;AAElB,aAAW,CAAC,OAAO,IAAI,KAAK,MAAM,QAAQ,GAAG;AAC5C,UAAM,OAAO,MAAM,IAAI,SAAS,EAAE,GAAG,MAAM,KAAK,eAAe,CAAC;AAChE,UAAM,QAAQ,KAAK,KAAK,KAAK,CAAC,cAAc,UAAU,SAAS,IAAI;AAEnE,QAAI,CAAC,MAAO,QAAO;AAEnB,kBAAc,cAAc,SAAS,aAAa,MAAM,IAAI,IAAI,MAAM;AAEtE,QAAI,UAAU,MAAM,SAAS,GAAG;AAC/B,aAAO;AAAA,QACN,MAAM;AAAA,QACN,MAAM,MAAM;AAAA,QACZ,MAAM,MAAM;AAAA,QACZ,KAAK,MAAM;AAAA,MACZ;AAAA,IACD;AAEA,QAAI,MAAM,SAAS,OAAQ,QAAO;AAElC,qBAAiB,MAAM;AAAA,EACxB;AAEA,SAAO;AACR;AAGA,eAAsB,gBACrB,MACA,SACA,SAAS,IACc;AACvB,QAAM,OAAO,MAAM,IAAI,SAAS,EAAE,GAAG,MAAM,KAAK,QAAQ,CAAC;AAEzD,SAAO,KAAK,KAAK,IAAI,CAAC,WAAW;AAAA,IAChC,MAAM,SAAS,SAAS,QAAQ,MAAM,IAAI,IAAI,MAAM;AAAA,IACpD,MAAM,MAAM;AAAA,IACZ,MAAM,MAAM;AAAA,IACZ,KAAK,MAAM;AAAA,EACZ,EAAE;AACH;;;AD/HO,SAAS,UAAU,MAAc,OAA6B;AACpE,SAAO;AAAA,IACN;AAAA,IACA;AAAA,IACA,WAAW,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;AAAA,IACvC,gBAAgB;AAAA,EACjB;AACD;AAWA,eAAsB,kBACrB,MACA,SAMkB;AAClB,uBAAqB,QAAQ,MAAM;AACnC,MAAI;AACJ,MAAI;AACJ,MAAI;AACH,gBAAY,MAAMC,KAAI,WAAW;AAAA,MAChC,GAAG;AAAA,MACH,KAAK,cAAc,QAAQ,MAAM;AAAA,IAClC,CAAC;AACD,UAAM,EAAE,OAAO,IAAI,MAAMA,KAAI,WAAW,EAAE,GAAG,MAAM,KAAK,UAAU,CAAC;AACnE,oBAAgB,OAAO;AAAA,EACxB,SAAS,KAAK;AACb,QAAK,KAA2B,SAAS,iBAAiB;AACzD,YAAM;AAAA,IACP;AAAA,EAED;AACA,QAAM,UAAU,MAAM,QAAQ,UAAU,aAAa;AACrD,QAAM,YAAY,MAAMA,KAAI,YAAY;AAAA,IACvC,GAAG;AAAA,IACH,QAAQ;AAAA,MACP,SAAS,QAAQ;AAAA,MACjB,MAAM;AAAA,MACN,QAAQ,YAAY,CAAC,SAAS,IAAI,CAAC;AAAA,MACnC,QAAQ,QAAQ;AAAA,MAChB,WAAW,QAAQ;AAAA,IACpB;AAAA,EACD,CAAC;AACD,QAAMA,KAAI,SAAS;AAAA,IAClB,GAAG;AAAA,IACH,KAAK,cAAc,QAAQ,MAAM;AAAA,IACjC,OAAO;AAAA,IACP,OAAO;AAAA,EACR,CAAC;AACD,SAAO;AACR;AAOO,SAAS,kBACf,MACA,SAMkB;AAClB,SAAO,kBAAkB,MAAM;AAAA,IAC9B,QAAQ,QAAQ;AAAA,IAChB,SAAS,QAAQ;AAAA,IACjB,QAAQ,QAAQ;AAAA,IAChB,WAAW,OAAO,kBAAkB;AACnC,YAAM,QAAQ,oBAAI,IAAoB;AACtC,YAAM,QAAQ;AAAA,QACb,QAAQ,MAAM,IAAI,OAAO,SAAS;AACjC,gBAAM,UACL,OAAO,KAAK,YAAY,WACrB,IAAI,YAAY,EAAE,OAAO,KAAK,OAAO,IACrC,KAAK;AACT,gBAAM,MAAM,MAAMA,KAAI,UAAU,EAAE,GAAG,MAAM,MAAM,QAAQ,CAAC;AAC1D,gBAAM,IAAI,KAAK,MAAM,GAAG;AAAA,QACzB,CAAC;AAAA,MACF;AACA,aAAO,WAAW,MAAM,eAAe,KAAK;AAAA,IAC7C;AAAA,EACD,CAAC;AACF;AAGO,SAAS,mBACf,MACA,SAMkB;AAClB,SAAO,kBAAkB,MAAM;AAAA,IAC9B,QAAQ,QAAQ;AAAA,IAChB,SAAS,QAAQ;AAAA,IACjB,QAAQ,QAAQ;AAAA,IAChB,WAAW,OAAO,kBAAkB;AACnC,UAAI,CAAC,eAAe;AACnB,cAAM,IAAI,MAAM,UAAU,QAAQ,MAAM,WAAW;AAAA,MACpD;AACA,aAAO,eAAe,MAAM,eAAe,QAAQ,QAAQ;AAAA,IAC5D;AAAA,EACD,CAAC;AACF;;;AEpIA,SAAS,2BAA2B;AACpC,OAAOC,UAAS;;;ACDhB,OAAOC,UAAS;;;ACiDT,IAAM,UAAU,CACtB,OACA,OACA,OACiB,OAAO,OAAO,MAAM,KAAK,OAAO,EAAE,IAAI,GAAG;AAOpD,SAAS,kBAAkB,QAA0B;AAC3D,SAAO;AAAA,IACN,aAAa,MAAM;AAAA,IACnB,QAAQ,MAAM;AAAA,IACd,WAAW,MAAM;AAAA,IACjB,gBAAgB,MAAM;AAAA,IACtB,gBAAgB,MAAM;AAAA,EACvB;AACD;;;ADrDA,SAAS,kBACR,SACA,SACa;AACb,SAAO,QAAQ,MAAM,CAAC,QAAiB;AACtC,QAAK,KAA2B,SAAS,iBAAiB;AACzD,YAAM,IAAI;AAAA,QACT,gBAAgB,OAAO;AAAA,MACxB;AAAA,IACD;AACA,UAAM;AAAA,EACP,CAAC;AACF;AAEA,IAAM,aAAa,CAAC,QAClB,KAA2B,SAAS;AAkCtC,eAAsB,cAAc,MAAY,KAAa;AAC5D,QAAM,MAAM,MAAMC,KAAI,WAAW,EAAE,GAAG,MAAM,KAAK,iBAAiB,GAAG,EAAE,CAAC;AACxE,QAAM,SAAS,MAAM;AAAA,IACpBA,KAAI,WAAW,EAAE,GAAG,MAAM,IAAI,CAAC;AAAA,IAC/B,GAAG,KAAK,MAAM,WAAW,GAAG;AAAA,EAC7B;AACA,SAAO,EAAE,KAAK,QAAQ,OAAO,OAAO;AACrC;AAGA,eAAsB,QAAQ,MAAY,KAAkC;AAC3E,QAAM,EAAE,KAAK,IAAI,MAAM;AAAA,IACtBA,KAAI,SAAS,EAAE,GAAG,MAAM,KAAK,IAAI,CAAC;AAAA,IAClC,GAAG,KAAK,MAAM,SAAS,GAAG;AAAA,EAC3B;AACA,SAAO;AACR;AAGA,eAAsB,eACrB,MACA,UACA,MAAM,QACgB;AACtB,QAAM,EAAE,OAAO,IAAI,MAAM,cAAc,MAAM,GAAG;AAChD,QAAM,UAAU,GAAG,KAAK,MAAM,IAAI,GAAG,IAAI,QAAQ;AACjD,QAAM,QAAQ,MAAM;AAAA,IACnB,cAAc,MAAM,OAAO,MAAM,QAAQ;AAAA,IACzC;AAAA,EACD;AAEA,MAAI,OAAO,SAAS,QAAQ;AAC3B,UAAM,IAAI,qBAAqB,mBAAmB,QAAQ,EAAE;AAAA,EAC7D;AAEA,QAAM,EAAE,KAAK,IAAI,MAAM;AAAA,IACtBA,KAAI,SAAS,EAAE,GAAG,MAAM,KAAK,MAAM,IAAI,CAAC;AAAA,IACxC;AAAA,EACD;AACA,SAAO;AACR;AAGA,eAAsB,UAAU,MAAY,KAAkC;AAC7E,QAAM,SAAS,MAAM;AAAA,IACpBA,KAAI,WAAW,EAAE,GAAG,MAAM,KAAK,IAAI,CAAC;AAAA,IACpC,GAAG,KAAK,MAAM,WAAW,GAAG;AAAA,EAC7B;AACA,SAAO,EAAE,KAAK,OAAO,KAAK,QAAQ,OAAO,QAAQ,SAAS,OAAO,QAAQ;AAC1E;AAEA,SAAS,cAAc,SAAgC;AACtD,QAAM,OAAO,QAAQ,QAAQ,SAAS,CAAC;AACvC,SAAO,CAAC,CAAC,QAAQ,KAAK,OAAO,OAAO,WAAW;AAChD;AAoBA,eAAsB,aACrB,MACA,UAA4B,CAAC,GAC7B,OACwB;AACxB,QAAM,MAAM,QAAQ,OAAO;AAC3B,QAAM,QAAQ,QAAQ,SAAS;AAE/B,MAAI;AACJ,MAAI,QAAQ,cAAc;AACzB,cAAU,QAAQ;AAAA,EACnB,OAAO;AACN,QAAI;AACH,gBAAU,MAAMA,KAAI,WAAW,EAAE,GAAG,MAAM,KAAK,iBAAiB,GAAG,EAAE,CAAC;AAAA,IACvE,SAAS,KAAc;AACtB,UAAI,WAAW,GAAG,EAAG,QAAO,CAAC;AAC7B,YAAM;AAAA,IACP;AAAA,EACD;AAEA,QAAM,WAAW,aAAa,KAAK,MAAM,IAAI,OAAO;AACpD,QAAM,SAAS,OAAO,aAAa,IAAkB,QAAQ;AAC7D,MAAI,WAAW,OAAO,UAAU,SAAS,cAAc,MAAM,IAAI;AAChE,WAAO;AAAA,MACN,sCAAsC,QAAQ,WAAW,KAAK;AAAA,IAC/D;AACA,WAAO,OAAO,MAAM,GAAG,KAAK;AAAA,EAC7B;AACA,SAAO;AAAA,IACN,uCAAuC,QAAQ,WAAW,KAAK;AAAA,EAChE;AAEA,MAAI,OAAO,YAAY,UAAU,MAAM,oBAAoB,IAAI;AAC9D,UAAM,QAAQ,OAAO,YAAY,MAAM,QAAQ;AAAA,EAChD;AAUA,QAAM,UAAU,MAAM;AAAA,IACrB;AAAA,MAAQ;AAAA,MAAO,WAAW,GAAG,UAAU,KAAK;AAAA,MAAI,MAC/CA,KAAI,IAAI,EAAE,GAAG,MAAM,KAAK,SAAS,MAAM,CAAC;AAAA,IACzC;AAAA,IACA,GAAG,KAAK,MAAM,IAAI,GAAG;AAAA,EACtB;AACA,QAAM,SAAS,QAAQ,IAAI,CAAC,YAAY;AAAA,IACvC,KAAK,OAAO;AAAA,IACZ,QAAQ,OAAO;AAAA,IACf,SAAS,OAAO,WAAW;AAAA,EAC5B,EAAE;AACF,MAAI,CAAC,UAAU,OAAO,SAAS,OAAO,QAAQ;AAC7C,WAAO,aAAa,IAAI,UAAU,MAAM;AAAA,EACzC;AACA,SAAO;AACR;AAGA,eAAsB,eACrB,MACA,UACA,KACgE;AAChE,QAAM,QAAQ,MAAM,eAAe,MAAM,UAAU,GAAG;AACtD,QAAM,WAAW,YAAY,KAAK;AAElC,SAAO;AAAA,IACN,SAAS,WAAW,SAAS,KAAK,IAAI,WAAW,KAAK;AAAA,IACtD,MAAM,MAAM;AAAA,IACZ;AAAA,EACD;AACD;AAOA,eAAsB,eACrB,MACA,UAA+C,CAAC,GAChD,OACuB;AACvB,QAAM,MAAM,QAAQ,OAAO;AAC3B,QAAM,WAAW,QAAQ,YAAY;AAErC,MAAI;AACJ,MAAI;AACJ,MAAI;AACH,UAAM,WAAW,MAAM,cAAc,MAAM,GAAG;AAC9C,aAAS,SAAS;AAClB,cAAU,SAAS;AAAA,EACpB,SAAS,KAAc;AACtB,QAAI,WAAW,GAAG,EAAG,QAAO,CAAC;AAC7B,UAAM;AAAA,EACP;AAEA,QAAM,WAAW,QAAQ,KAAK,MAAM,IAAI,OAAO,IAAI,QAAQ;AAC3D,QAAM,SAAS,OAAO,aAAa,IAAiB,QAAQ;AAC5D,MAAI,QAAQ;AACX,WAAO,SAAS,wCAAwC,QAAQ,EAAE;AAClE,WAAO;AAAA,EACR;AACA,SAAO,SAAS,yCAAyC,QAAQ,EAAE;AAWnE,MAAI,OAAO,UAAU;AACpB,UAAM,QAAQ,OAAO,YAAY,MAAM,QAAQ;AAAA,EAChD;AAEA,QAAM,UAAU,GAAG,KAAK,MAAM,IAAI,GAAG,IAAI,YAAY,GAAG;AACxD,MAAI;AACJ,MAAI,CAAC,UAAU;AACd,aAAS,MAAM;AAAA,MACd;AAAA,QAAQ;AAAA,QAAO;AAAA,QAA0B,MACxC,gBAAgB,MAAM,OAAO,IAAI;AAAA,MAClC;AAAA,MACA;AAAA,IACD;AAAA,EACD,OAAO;AACN,UAAM,QAAQ,MAAM;AAAA,MACnB;AAAA,QAAQ;AAAA,QAAO,iBAAiB,QAAQ;AAAA,QAAI,MAC3C,cAAc,MAAM,OAAO,MAAM,QAAQ;AAAA,MAC1C;AAAA,MACA;AAAA,IACD;AACA,QAAI,CAAC,OAAO;AACX,YAAM,IAAI;AAAA,QACT,SAAS,QAAQ,uBAAuB,GAAG;AAAA,MAC5C;AAAA,IACD;AACA,aACC,MAAM,SAAS,SACZ,CAAC,IACD,MAAM;AAAA,MACN;AAAA,QAAQ;AAAA,QAAO,mBAAmB,QAAQ;AAAA,QAAI,MAC7C,gBAAgB,MAAM,MAAM,KAAK,MAAM,IAAI;AAAA,MAC5C;AAAA,MACA;AAAA,IACD;AAAA,EACJ;AAEA,SAAO,aAAa,IAAI,UAAU,MAAM;AACxC,SAAO;AACR;AAMA,eAAsB,iBACrB,MACA,SACA,OACwB;AACxB,QAAM,QAAQ,QAAQ,SAAS;AAC/B,QAAM,OAAO,QAAQ,QAAQ;AAC7B,QAAM,UAAU,MAAMA,KACpB,WAAW,EAAE,GAAG,MAAM,KAAK,iBAAiB,QAAQ,GAAG,EAAE,CAAC,EAC1D,MAAM,MAAM,IAAI;AAElB,QAAM,WAAW,UACd,WAAW,KAAK,MAAM,IAAI,OAAO,IAAI,KAAK,IAAI,IAAI,KAClD;AACH,MAAI,UAAU;AACb,UAAM,SAAS,OAAO,aAAa,IAAkB,QAAQ;AAC7D,QAAI,QAAQ;AACX,aAAO,SAAS,0CAA0C,QAAQ,EAAE;AACpE,aAAO;AAAA,IACR;AAAA,EACD;AACA,SAAO;AAAA,IACN,2CAA2C,YAAY,WAAW;AAAA,EACnE;AACA,MAAI,CAAC,QAAS,QAAO,CAAC;AAEtB,QAAM,MAAM,MAAM;AAAA,IACjB;AAAA,IACA,EAAE,KAAK,QAAQ,KAAK,OAAO,QAAQ,MAAM,cAAc,QAAQ;AAAA,IAC/D;AAAA,EACD;AACA,QAAM,SAAS,IAAI,MAAM,MAAM,OAAO,KAAK;AAE3C,MAAI,SAAU,QAAO,aAAa,IAAI,UAAU,MAAM;AACtD,SAAO;AACR;;;ADlTA,SAAS,kBAAkB,MAIzB;AACD,SAAO,gBAAgB,IAAI;AAC5B;AAEA,SAAS,kBAAkB,SAAyB;AACnD,MAAI,QAAQ,WAAW,EAAG,QAAO;AACjC,QAAM,QAAQ,QAAQ,MAAM,IAAI;AAChC,MAAI,MAAM,MAAM,SAAS,CAAC,MAAM,GAAI,OAAM,IAAI;AAC9C,SAAO,MAAM;AACd;AAEA,SAAS,mBAAmB,QAMjB;AACV,QAAM,UAAU,OAAO,WAAW,KAAK,OAAO,IAAI;AAClD,QAAM,UAAU,OAAO,WAAW,KAAK,OAAO,IAAI;AAClD,QAAM,YAAY;AAAA,IACjB;AAAA,IACA;AAAA,IACA,OAAO;AAAA,IACP,OAAO;AAAA,IACP;AAAA,IACA;AAAA,IACA,EAAE,SAAS,EAAE;AAAA,EACd,EAAE,QAAQ,SAAS,EAAE;AAErB,SAAO,gBAAgB,OAAO,IAAI,MAAM,OAAO,IAAI;AAAA,EAAK,SAAS;AAClE;AAEA,SAAS,cAAc,OAA+B;AACrD,SAAO;AAAA,IACN;AAAA,IACA,gBAAgB,MAAM,OAAO,CAAC,KAAK,MAAM,MAAM,EAAE,WAAW,CAAC;AAAA,IAC7D,gBAAgB,MAAM,OAAO,CAAC,KAAK,MAAM,MAAM,EAAE,WAAW,CAAC;AAAA,IAC7D,YAAY,MAAM;AAAA,EACnB;AACD;AAOA,eAAe,aACd,MACA,QACA,QACsB;AACtB,QAAM,UAAU,MAAMC,KAAI,KAAK;AAAA,IAC9B,GAAG;AAAA,IACH,OAAO,CAACA,KAAI,KAAK,EAAE,KAAK,OAAO,CAAC,GAAGA,KAAI,KAAK,EAAE,KAAK,OAAO,CAAC,CAAC;AAAA,IAC5D,KAAK,OAAO,UAAU,CAAC,GAAG,CAAC,MAAM;AAChC,YAAM,CAAC,OAAO,KAAK,IAAI,MAAM,QAAQ,IAAI,CAAC,GAAG,KAAK,GAAG,GAAG,KAAK,CAAC,CAAC;AAE/D,UAAI,UAAU,UAAU,UAAU,OAAQ;AAE1C,UAAI,SAAS,CAAC,OAAO;AACpB,cAAMC,QAAO,IAAI,MAAM,EAAE,IAAI,IAAI;AACjC,cAAM,EAAE,KAAK,IAAI,MAAMD,KAAI,SAAS,EAAE,GAAG,MAAM,KAAKC,MAAK,CAAC;AAC1D,cAAM,SAAS,kBAAkB,IAAI;AACrC,eAAO;AAAA,UACN,MAAM;AAAA,UACN,QAAQ;AAAA,UACR,WAAW;AAAA,UACX,WAAW,OAAO,WAAW,IAAI,kBAAkB,OAAO,IAAI;AAAA,UAC9D,OAAO,OAAO,WACX,KACA,mBAAmB;AAAA,YACnB,MAAM;AAAA,YACN,QAAQ,OAAO;AAAA,YACf,OAAO;AAAA,YACP,SAAS;AAAA,UACV,CAAC;AAAA,UACH,UAAU,OAAO;AAAA,UACjB,YAAY,OAAO,WAAW,SAAS,OAAO,KAAK,IAAI;AAAA,UACvD,SAAS,OAAO,MAAM;AAAA,QACvB;AAAA,MACD;AAEA,UAAI,CAAC,SAAS,OAAO;AACpB,cAAMC,QAAO,IAAI,MAAM,EAAE,IAAI,IAAI;AACjC,cAAM,EAAE,KAAK,IAAI,MAAMF,KAAI,SAAS,EAAE,GAAG,MAAM,KAAKE,MAAK,CAAC;AAC1D,cAAM,QAAQ,kBAAkB,IAAI;AACpC,eAAO;AAAA,UACN,MAAM;AAAA,UACN,QAAQ;AAAA,UACR,WAAW,MAAM,WAAW,IAAI,kBAAkB,MAAM,IAAI;AAAA,UAC5D,WAAW;AAAA,UACX,OAAO,MAAM,WACV,KACA,mBAAmB;AAAA,YACnB,MAAM;AAAA,YACN,QAAQ;AAAA,YACR,OAAO,MAAM;AAAA,YACb,SAAS;AAAA,UACV,CAAC;AAAA,UACH,UAAU,MAAM;AAAA,UAChB,YAAY,MAAM,WAAW,SAAS,MAAM,KAAK,IAAI;AAAA,UACrD,SAAS,MAAM,MAAM;AAAA,QACtB;AAAA,MACD;AAEA,YAAM,CAAC,MAAM,IAAI,IAAI,MAAM,QAAQ,IAAI;AAAA,QACtC,IAAI,EAAE,IAAI,IAAI,QAAQ,QAAQ,EAAE;AAAA,QAChC,IAAI,EAAE,IAAI,IAAI,QAAQ,QAAQ,EAAE;AAAA,MACjC,CAAC;AAED,UAAI,SAAS,MAAM;AAClB,cAAM,CAAC,EAAE,MAAM,MAAM,GAAG,EAAE,MAAM,MAAM,CAAC,IAAI,MAAM,QAAQ,IAAI;AAAA,UAC5DF,KAAI,SAAS,EAAE,GAAG,MAAM,KAAK,KAAK,CAAC;AAAA,UACnCA,KAAI,SAAS,EAAE,GAAG,MAAM,KAAK,KAAK,CAAC;AAAA,QACpC,CAAC;AACD,cAAM,SAAS,kBAAkB,KAAK;AACtC,cAAM,QAAQ,kBAAkB,KAAK;AACrC,cAAM,WAAW,OAAO,YAAY,MAAM;AAE1C,eAAO;AAAA,UACN,MAAM;AAAA,UACN,QAAQ;AAAA,UACR,WAAW,WAAW,IAAI,kBAAkB,MAAM,IAAI;AAAA,UACtD,WAAW,WAAW,IAAI,kBAAkB,OAAO,IAAI;AAAA,UACvD,OAAO,WACJ,KACA,mBAAmB;AAAA,YACnB,MAAM;AAAA,YACN,QAAQ,OAAO;AAAA,YACf,OAAO,MAAM;AAAA,UACd,CAAC;AAAA,UACH;AAAA,UACA,YAAY,WAAW,SAAS,OAAO,KAAK,IAAI;AAAA,UAChD,YAAY,WAAW,SAAS,MAAM,KAAK,IAAI;AAAA,UAC/C,SAAS,OAAO,MAAM;AAAA,UACtB,SAAS,MAAM,MAAM;AAAA,QACtB;AAAA,MACD;AAEA,aAAO;AAAA,IACR;AAAA,EACD,CAAC;AAED,UAAQ,WAAW,CAAC,GAAG;AAAA,IACtB,CAAC,MACA,MAAM,QAAQ,MAAM;AAAA,EACtB;AACD;AAGA,eAAsB,cACrB,MACA,WACsB;AACtB,MAAI;AACH,UAAM,SAAS,MAAM,UAAU,MAAM,SAAS;AAC9C,UAAM,SAAS,OAAO,OAAO,OAAO,CAAC;AAErC,QAAI,CAAC,QAAQ;AACZ,YAAM,UAA2C,CAAC;AAClD,YAAM,QAA+C;AAAA,QACpD,EAAE,SAAS,OAAO,OAAO,MAAM,QAAQ,GAAG;AAAA,MAC3C;AAEA,aAAO,MAAM,QAAQ;AACpB,cAAM,EAAE,SAAS,OAAO,IAAI,MAAM,IAAI;AAItC,cAAM,EAAE,KAAK,IAAI,MAAMA,KAAI,SAAS,EAAE,GAAG,MAAM,KAAK,QAAQ,CAAC;AAC7D,mBAAW,SAAS,MAAM;AACzB,gBAAM,OAAO,SAAS,GAAG,MAAM,IAAI,MAAM,IAAI,KAAK,MAAM;AACxD,cAAI,MAAM,SAAS,QAAQ;AAC1B,kBAAM,KAAK,EAAE,SAAS,MAAM,KAAK,QAAQ,KAAK,CAAC;AAAA,UAChD,WAAW,MAAM,SAAS,QAAQ;AACjC,oBAAQ,KAAK,EAAE,MAAM,MAAM,KAAK,MAAM,IAAI,CAAC;AAAA,UAC5C;AAAA,QACD;AAAA,MACD;AAEA,YAAMG,SAAoB,MAAM,QAAQ;AAAA,QACvC,QAAQ,IAAI,OAAO,EAAE,MAAM,IAAI,MAAM;AACpC,gBAAM,EAAE,KAAK,IAAI,MAAMH,KAAI,SAAS,EAAE,GAAG,MAAM,IAAI,CAAC;AACpD,gBAAM,QAAQ,kBAAkB,IAAI;AACpC,iBAAO;AAAA,YACN;AAAA,YACA,QAAQ;AAAA,YACR,WAAW,MAAM,WAAW,IAAI,kBAAkB,MAAM,IAAI;AAAA,YAC5D,WAAW;AAAA,YACX,OAAO,MAAM,WACV,KACA,mBAAmB;AAAA,cACnB;AAAA,cACA,QAAQ;AAAA,cACR,OAAO,MAAM;AAAA,cACb,SAAS;AAAA,YACV,CAAC;AAAA,YACH,UAAU,MAAM;AAAA,YAChB,YAAY,MAAM,WAAW,SAAS,MAAM,KAAK,IAAI;AAAA,YACrD,SAAS,MAAM,MAAM;AAAA,UACtB;AAAA,QACD,CAAC;AAAA,MACF;AAEA,aAAO,cAAcG,MAAK;AAAA,IAC3B;AAEA,UAAM,QAAQ,MAAM,aAAa,MAAM,QAAQ,SAAS;AACxD,WAAO,cAAc,KAAK;AAAA,EAC3B,SAAS,OAAO;AACf,UAAM,IAAI,MAAM,8BAA8B,KAAK,EAAE;AAAA,EACtD;AACD;AAGA,eAAsB,mBACrB,MACA,SACA,YACsB;AACtB,QAAM,CAAC,SAAS,UAAU,IAAI,MAAM,QAAQ,IAAI;AAAA,IAC/CH,KAAI,WAAW,EAAE,GAAG,MAAM,KAAK,iBAAiB,OAAO,EAAE,CAAC;AAAA,IAC1DA,KAAI,WAAW,EAAE,GAAG,MAAM,KAAK,iBAAiB,UAAU,EAAE,CAAC;AAAA,EAC9D,CAAC;AAED,QAAM,QAAQ,MAAM,aAAa,MAAM,SAAS,UAAU;AAC1D,SAAO,cAAc,KAAK;AAC3B;;;AGzOO,IAAM,qBAAqB;AAQ3B,IAAM,oBAAoB;AAGjC,IAAM,kBAAkB;AAExB,SAAS,QAAQ,QAAsC;AACtD,SAAO;AAAA,IACN,KAAK,OAAO;AAAA,IACZ,SAAS,OAAO,OAAO,QAAQ,KAAK;AAAA,IACpC,YAAY,OAAO,OAAO,OAAO;AAAA,IACjC,aAAa,OAAO,OAAO,OAAO;AAAA,IAClC,WAAW,IAAI,KAAK,OAAO,OAAO,OAAO,YAAY,GAAI,EAAE,YAAY;AAAA,EACxE;AACD;AAOA,eAAsB,eACrB,MACA,SAMA,OAC6B;AAC7B,QAAM,QAAQ,QAAQ,SAAS;AAC/B,QAAM,WAAW,QAAQ,YAAY;AACrC,QAAM,YAAY,KAAK,IAAI,UAAU,KAAK;AAC1C,QAAM,UAAU,MAAM;AAAA,IAAQ;AAAA,IAAO,sBAAsB,SAAS;AAAA,IAAI,MACvE,aAAa,MAAM,EAAE,KAAK,QAAQ,KAAK,OAAO,UAAU,GAAG,KAAK;AAAA,EACjE;AACA,QAAM,OAAO,QAAQ,CAAC;AACtB,MAAI,CAAC,KAAM,QAAO,EAAE,SAAS,CAAC,GAAG,WAAW,MAAM;AAElD,QAAM,WAAW,gBAAgB,KAAK,MAAM,IAAI,KAAK,GAAG,IAAI,QAAQ,QAAQ,IAAI,KAAK,IAAI,QAAQ;AACjG,QAAM,SAAS,OAAO,aAAa,IAAuB,QAAQ;AAClE,MAAI,QAAQ;AACX,WAAO,SAAS,yDAAyD;AACzE,WAAO;AAAA,EACR;AACA,SAAO,SAAS,oDAAoD;AAEpE,QAAM,QAAQ,IAAI,IAAI,QAAQ,IAAI,CAAC,WAAW,CAAC,OAAO,KAAK,MAAM,CAAC,CAAC;AAEnE,QAAM,kBAAkB,oBAAI,IAA2B;AACvD,iBAAe,WAAW,eAA+C;AACxE,UAAM,YAAY,gBAAgB,IAAI,aAAa;AACnD,QAAI,cAAc,OAAW,QAAO;AACpC,UAAM,QAAQ,MAAM,cAAc,MAAM,eAAe,QAAQ,QAAQ;AACvE,UAAM,MAAM,OAAO,SAAS,SAAS,MAAM,MAAM;AACjD,oBAAgB,IAAI,eAAe,GAAG;AACtC,WAAO;AAAA,EACR;AAEA,QAAM,UAA8B,CAAC;AACrC,MAAI,YAAY;AAEhB,QAAO,UACF,cAAc,GAClB,cAAc,QAAQ,QACtB,eAAe,iBACd;AACD,UAAM,YAAY,KAAK,IAAI,cAAc,iBAAiB,QAAQ,MAAM;AAExE,UAAM,cAAc,KAAK,IAAI,YAAY,GAAG,QAAQ,MAAM;AAE1D,UAAM,QAAQ;AAAA,MACb,QACE,MAAM,aAAa,WAAW,EAC9B,IAAI,CAAC,WAAW,WAAW,OAAO,OAAO,IAAI,CAAC;AAAA,IACjD;AAEA,aAAS,IAAI,aAAa,IAAI,WAAW,KAAK;AAC7C,YAAM,SAAS,QAAQ,CAAC;AACxB,UAAI,CAAC,OAAQ,OAAM;AAEnB,YAAM,YAAY,OAAO,OAAO,OAAO,CAAC;AACxC,YAAM,eAAe,YAAY,MAAM,IAAI,SAAS,IAAI;AACxD,UAAI,aAAa,CAAC,cAAc;AAG/B,oBAAY;AACZ,cAAM;AAAA,MACP;AAEA,YAAM,CAAC,KAAK,SAAS,IAAI,MAAM,QAAQ,IAAI;AAAA,QAC1C,WAAW,OAAO,OAAO,IAAI;AAAA,QAC7B,eACG,WAAW,aAAa,OAAO,IAAI,IACnC,QAAQ,QAAQ,IAAI;AAAA,MACxB,CAAC;AAED,UAAI,QAAQ,WAAW;AACtB,gBAAQ,KAAK,QAAQ,MAAM,CAAC;AAC5B,YAAI,QAAQ,UAAU,OAAO;AAC5B,sBAAY,IAAI,QAAQ,SAAS;AACjC,gBAAM;AAAA,QACP;AAAA,MACD;AAAA,IACD;AAAA,EACD;AAEA,QAAM,SAAS,EAAE,SAAS,UAAU;AACpC,SAAO,aAAa,IAAI,UAAU,MAAM;AACxC,SAAO;AACR;;;AChIA,IAAMI,sBAAqB;AAU3B,IAAMC,mBAAkB;AAExB,SAAS,iBAAiB,QAAoC;AAC7D,SAAO;AAAA,IACN,KAAK,OAAO;AAAA,IACZ,SAAS,OAAO,OAAO,QAAQ,KAAK;AAAA,IACpC,YAAY,OAAO,OAAO,OAAO;AAAA,IACjC,aAAa,OAAO,OAAO,OAAO;AAAA,IAClC,WAAW,IAAI,KAAK,OAAO,OAAO,OAAO,YAAY,GAAI,EAAE,YAAY;AAAA,EACxE;AACD;AAYA,eAAsB,sBACrB,MACA,SACA,OAC0C;AAC1C,QAAM,WAAW,QAAQ,YAAY;AACrC,QAAM,QAAQ,QAAQ,SAASD;AAC/B,QAAM,UAAU,MAAM;AAAA,IAAQ;AAAA,IAAO,sBAAsB,KAAK;AAAA,IAAI,MACnE,aAAa,MAAM,EAAE,KAAK,QAAQ,KAAK,MAAM,GAAG,KAAK;AAAA,EACtD;AACA,SAAO,SAAS,0BAA0B,QAAQ,MAAM,iBAAiB;AACzE,QAAM,OAAO,QAAQ,CAAC;AACtB,MAAI,CAAC,KAAM,QAAO,CAAC;AAEnB,QAAM,WAAW,gBAAgB,KAAK,MAAM,IAAI,KAAK,GAAG,IAAI,QAAQ;AACpE,QAAM,eACL,OAAO,aAAa,IAAoC,QAAQ;AACjE,MAAI,cAAc;AACjB,WAAO;AAAA,MACN;AAAA,IACD;AACA,WAAO;AAAA,EACR;AACA,SAAO,SAAS,2DAA2D;AAE3E,QAAM,QAAQ,IAAI,IAAI,QAAQ,IAAI,CAAC,WAAW,CAAC,OAAO,KAAK,MAAM,CAAC,CAAC;AAMnE,QAAM,qBAAqB,oBAAI,IAA2B;AAC1D,QAAM,mBAAmB,oBAAI,IAG3B;AAEF,iBAAe,cAAc,eAA+C;AAC3E,UAAM,SAAS,mBAAmB,IAAI,aAAa;AACnD,QAAI,WAAW,OAAW,QAAO;AACjC,UAAM,QAAQ,MAAM,cAAc,MAAM,eAAe,QAAQ;AAC/D,UAAM,SAAS,OAAO,SAAS,SAAS,MAAM,MAAM;AACpD,uBAAmB,IAAI,eAAe,MAAM;AAC5C,WAAO;AAAA,EACR;AAEA,iBAAe,gBAAgB,QAAuB;AACrD,QAAI,WAAW,KAAM,QAAO,CAAC;AAC7B,UAAM,SAAS,iBAAiB,IAAI,MAAM;AAC1C,QAAI,OAAQ,QAAO;AACnB,UAAM,WAAW,MAAM,gBAAgB,MAAM,QAAQ,QAAQ;AAC7D,qBAAiB,IAAI,QAAQ,QAAQ;AACrC,WAAO;AAAA,EACR;AAEA,QAAM,aAAa,MAAM,cAAc,KAAK,OAAO,IAAI;AACvD,MAAI,eAAe,KAAM,QAAO,CAAC;AAIjC,QAAM,eAAe,MAAM,gBAAgB,UAAU;AACrD,QAAM,YAAY,IAAI,IAAI,aAAa,IAAI,CAAC,UAAU,MAAM,IAAI,CAAC;AACjE,QAAM,SAAyC,CAAC;AAEhD,MAAI,gBAAgB;AACpB,MAAI,kBAAkB;AACtB,QAAM,YAAY,YAAY,IAAI;AAElC,QAAO,UACF,cAAc,GAClB,cAAc,QAAQ,UAAU,UAAU,OAAO,GACjD,eAAeC,kBACd;AACD,UAAM,YAAY,KAAK,IAAI,cAAcA,kBAAiB,QAAQ,MAAM;AAExE,UAAM,cAAc,KAAK,IAAI,YAAY,GAAG,QAAQ,MAAM;AAC1D;AAKA,UAAM,UAAU,MAAM,QAAQ;AAAA,MAC7B,QACE,MAAM,aAAa,WAAW,EAC9B,IAAI,CAAC,WAAW,cAAc,OAAO,OAAO,IAAI,CAAC;AAAA,IACpD;AAIA,UAAM,QAAQ;AAAA,MACb,CAAC,GAAG,IAAI,IAAI,OAAO,CAAC,EAAE,IAAI,CAAC,WAAW,gBAAgB,MAAM,CAAC;AAAA,IAC9D;AAEA,aAAS,IAAI,aAAa,IAAI,WAAW,KAAK;AAC7C,UAAI,UAAU,SAAS,EAAG,OAAM;AAChC,YAAM,SAAS,QAAQ,CAAC;AACxB,UAAI,CAAC,OAAQ,OAAM;AACnB;AAEA,YAAM,YAAY,OAAO,OAAO,OAAO,CAAC;AACxC,YAAM,eAAe,YAAY,MAAM,IAAI,SAAS,IAAI;AACxD,UAAI,aAAa,CAAC,aAAc,OAAM;AAItC,YAAM,CAAC,QAAQ,YAAY,IAAI,MAAM,QAAQ,IAAI;AAAA,QAChD,cAAc,OAAO,OAAO,IAAI;AAAA,QAChC,eACG,cAAc,aAAa,OAAO,IAAI,IACtC,QAAQ,QAAQ,IAAI;AAAA,MACxB,CAAC;AAED,UAAI,WAAW,aAAc;AAE7B,YAAM,CAAC,UAAU,cAAc,IAAI,MAAM,QAAQ,IAAI;AAAA,QACpD,gBAAgB,MAAM;AAAA,QACtB,gBAAgB,YAAY;AAAA,MAC7B,CAAC;AACD,YAAM,cAAc,IAAI;AAAA,QACvB,SAAS,IAAI,CAAC,UAAU,CAAC,MAAM,MAAM,MAAM,GAAG,CAAC;AAAA,MAChD;AACA,YAAM,oBAAoB,IAAI;AAAA,QAC7B,eAAe,IAAI,CAAC,UAAU,CAAC,MAAM,MAAM,MAAM,GAAG,CAAC;AAAA,MACtD;AAEA,iBAAW,QAAQ,WAAW;AAC7B,YAAI,YAAY,IAAI,IAAI,MAAM,kBAAkB,IAAI,IAAI,GAAG;AAC1D,iBAAO,IAAI,IAAI,iBAAiB,MAAM;AAAA,QACvC;AAAA,MACD;AACA,iBAAW,QAAQ,OAAO,KAAK,MAAM,GAAG;AACvC,kBAAU,OAAO,IAAI;AAAA,MACtB;AAAA,IACD;AAAA,EACD;AAEA,SAAO;AAAA,IACN,iCAAiC,aAAa,IAAI,QAAQ,MAAM,mBAAmB,eAAe,yBAAyB,YAAY,IAAI,IAAI,WAAW,QAAQ,CAAC,CAAC,OAAO,mBAAmB,IAAI,yBAAyB,UAAU,IAAI;AAAA,EAC1O;AAEA,SAAO,aAAa,IAAI,UAAU,MAAM;AACxC,SAAO;AACR;;;AChMA,OAAOC,UAAS;AAqBhB,eAAsB,aACrB,MACA,cACA,cACyB;AACzB,uBAAqB,YAAY;AACjC,uBAAqB,YAAY;AAEjC,MAAI;AACH,UAAM,CAAC,WAAW,SAAS,IAAI,MAAM,QAAQ,IAAI;AAAA,MAChDC,KAAI,WAAW,EAAE,GAAG,MAAM,KAAK,iBAAiB,YAAY,EAAE,CAAC;AAAA,MAC/DA,KAAI,WAAW,EAAE,GAAG,MAAM,KAAK,iBAAiB,YAAY,EAAE,CAAC;AAAA,IAChE,CAAC;AAED,UAAM,eAAe,MAAMA,KAAI,aAAa;AAAA,MAC3C,GAAG;AAAA,MACH,KAAK;AAAA,MACL,UAAU;AAAA,IACX,CAAC;AAED,WAAO;AAAA,MACN,UAAU;AAAA,MACV,cAAc;AAAA,MACd,kBAAkB,CAAC;AAAA,MACnB,aAAa;AAAA,IACd;AAAA,EACD,SAAS,KAAK;AACb,QAAK,KAA2B,SAAS,iBAAiB;AACzD,YAAM;AAAA,IACP;AAKA,WAAO;AAAA,MACN,UAAU;AAAA,MACV,cAAc;AAAA,MACd,kBAAkB,CAAC;AAAA,MACnB,aAAa;AAAA,IACd;AAAA,EACD;AACD;AASA,eAAsB,iBACrB,MACA,cACA,cACuD;AACvD,uBAAqB,YAAY;AACjC,uBAAqB,YAAY;AACjC,QAAM,CAAC,WAAW,SAAS,IAAI,MAAM,QAAQ,IAAI;AAAA,IAChDA,KAAI,WAAW,EAAE,GAAG,MAAM,KAAK,cAAc,YAAY,GAAG,CAAC;AAAA,IAC7DA,KAAI,WAAW,EAAE,GAAG,MAAM,KAAK,cAAc,YAAY,GAAG,CAAC;AAAA,EAC9D,CAAC;AACD,QAAM,OAAO,MAAMA,KAAI,aAAa;AAAA,IACnC,GAAG;AAAA,IACH,KAAK;AAAA,IACL,UAAU;AAAA,EACX,CAAC;AACD,MAAI,CAAC,KAAM,QAAO;AAClB,QAAMA,KAAI,SAAS;AAAA,IAClB,GAAG;AAAA,IACH,KAAK,cAAc,YAAY;AAAA,IAC/B,OAAO;AAAA,IACP,OAAO;AAAA,EACR,CAAC;AACD,SAAO,EAAE,SAAS,MAAM,WAAW,UAAU;AAC9C;","names":["git","git","git","git","git","git","oidA","oidB","files","HISTORY_WALK_DEPTH","PREFETCH_WINDOW","git","git"]}
|
|
1
|
+
{"version":3,"sources":["../src/ops/commit.ts","../src/ops/tree.ts","../src/ops/diff.ts","../src/ops/history.ts","../src/ops/types.ts","../src/ops/file-history.ts","../src/ops/last-commit.ts","../src/ops/merge.ts"],"sourcesContent":["import git from \"isomorphic-git\";\nimport { assertSafeBranchName } from \"./branch.js\";\nimport { deleteFromTree, upsertTree } from \"./tree.js\";\nimport type { Repo } from \"./types.js\";\n\nexport interface CommitAuthor {\n\tname: string;\n\temail: string;\n\ttimestamp: number;\n\ttimezoneOffset: number;\n}\n\n/** An author stamped with the current time. */\nexport function authorNow(name: string, email: string): CommitAuthor {\n\treturn {\n\t\tname,\n\t\temail,\n\t\ttimestamp: Math.floor(Date.now() / 1000),\n\t\ttimezoneOffset: 0,\n\t};\n}\n\n/**\n * Write a commit directly to a bare repository — no worktree, no checkout.\n * `buildTree` receives the parent commit's tree oid (undefined on an empty\n * repo / unborn branch) and returns the new root tree oid; this function\n * writes the commit object and force-updates `refs/heads/<branch>`.\n *\n * Serialize concurrent writers externally (a per-repo lock): the\n * resolve-ref → write-ref sequence is not atomic on object storage.\n */\nexport async function writeCommitToBare(\n\trepo: Repo,\n\toptions: {\n\t\tbranch: string;\n\t\tmessage: string;\n\t\tauthor: CommitAuthor;\n\t\tbuildTree: (parentTreeOid: string | undefined) => Promise<string>;\n\t},\n): Promise<string> {\n\tassertSafeBranchName(options.branch);\n\tlet parentOid: string | undefined;\n\tlet parentTreeOid: string | undefined;\n\ttry {\n\t\tparentOid = await git.resolveRef({\n\t\t\t...repo,\n\t\t\tref: `refs/heads/${options.branch}`,\n\t\t});\n\t\tconst { commit } = await git.readCommit({ ...repo, oid: parentOid });\n\t\tparentTreeOid = commit.tree;\n\t} catch (err) {\n\t\tif ((err as { code?: string })?.code !== \"NotFoundError\") {\n\t\t\tthrow err;\n\t\t}\n\t\t// empty repo — first commit\n\t}\n\tconst treeOid = await options.buildTree(parentTreeOid);\n\tconst commitOid = await git.writeCommit({\n\t\t...repo,\n\t\tcommit: {\n\t\t\tmessage: options.message,\n\t\t\ttree: treeOid,\n\t\t\tparent: parentOid ? [parentOid] : [],\n\t\t\tauthor: options.author,\n\t\t\tcommitter: options.author,\n\t\t},\n\t});\n\tawait git.writeRef({\n\t\t...repo,\n\t\tref: `refs/heads/${options.branch}`,\n\t\tvalue: commitOid,\n\t\tforce: true,\n\t});\n\treturn commitOid;\n}\n\n/**\n * Commit a set of files onto a branch, straight to the bare repo. Each blob\n * is written to its own content-addressed key — no shared state between\n * files, so they're written in parallel.\n */\nexport function commitFilesToBare(\n\trepo: Repo,\n\toptions: {\n\t\tbranch: string;\n\t\tmessage: string;\n\t\tauthor: CommitAuthor;\n\t\tfiles: Array<{ path: string; content: string | Uint8Array }>;\n\t},\n): Promise<string> {\n\treturn writeCommitToBare(repo, {\n\t\tbranch: options.branch,\n\t\tmessage: options.message,\n\t\tauthor: options.author,\n\t\tbuildTree: async (parentTreeOid) => {\n\t\t\tconst blobs = new Map<string, string>();\n\t\t\tawait Promise.all(\n\t\t\t\toptions.files.map(async (file) => {\n\t\t\t\t\tconst content =\n\t\t\t\t\t\ttypeof file.content === \"string\"\n\t\t\t\t\t\t\t? new TextEncoder().encode(file.content)\n\t\t\t\t\t\t\t: file.content;\n\t\t\t\t\tconst oid = await git.writeBlob({ ...repo, blob: content });\n\t\t\t\t\tblobs.set(file.path, oid);\n\t\t\t\t}),\n\t\t\t);\n\t\t\treturn upsertTree(repo, parentTreeOid, blobs);\n\t\t},\n\t});\n}\n\n/** Commit the removal of one file from a branch, straight to the bare repo. */\nexport function deleteFileFromBare(\n\trepo: Repo,\n\toptions: {\n\t\tbranch: string;\n\t\tfilePath: string;\n\t\tmessage: string;\n\t\tauthor: CommitAuthor;\n\t},\n): Promise<string> {\n\treturn writeCommitToBare(repo, {\n\t\tbranch: options.branch,\n\t\tmessage: options.message,\n\t\tauthor: options.author,\n\t\tbuildTree: async (parentTreeOid) => {\n\t\t\tif (!parentTreeOid) {\n\t\t\t\tthrow new Error(`Branch ${options.branch} is empty`);\n\t\t\t}\n\t\t\treturn deleteFromTree(repo, parentTreeOid, options.filePath);\n\t\t},\n\t});\n}\n","import git from \"isomorphic-git\";\nimport type { Repo } from \"./types.js\";\n\nexport interface TreeEntry {\n\tpath: string;\n\tmode: string;\n\ttype: \"blob\" | \"tree\";\n\toid: string;\n\tsize?: number;\n}\n\nconst joinPath = (prefix: string, name: string) =>\n\tprefix ? `${prefix.replace(/\\/+$/, \"\")}/${name}` : name;\n\n/**\n * Build/update a git tree by overlaying new blobs onto an existing tree,\n * returning the new root tree oid. `entries` maps relative paths to blob oids.\n */\nexport async function upsertTree(\n\trepo: Repo,\n\ttreeOid: string | undefined,\n\tentries: Map<string, string>,\n): Promise<string> {\n\tconst existing = treeOid\n\t\t? (await git.readTree({ ...repo, oid: treeOid })).tree\n\t\t: [];\n\tconst byName = new Map(existing.map((e) => [e.path, e]));\n\tconst direct = new Map<string, string>();\n\tconst nested = new Map<string, Map<string, string>>();\n\tfor (const [filePath, blobOid] of entries) {\n\t\tconst slash = filePath.indexOf(\"/\");\n\t\tif (slash === -1) {\n\t\t\tdirect.set(filePath, blobOid);\n\t\t} else {\n\t\t\tconst dir = filePath.slice(0, slash);\n\t\t\tconst rest = filePath.slice(slash + 1);\n\t\t\tif (!nested.has(dir)) nested.set(dir, new Map());\n\t\t\tnested.get(dir)?.set(rest, blobOid);\n\t\t}\n\t}\n\tfor (const [name, blobOid] of direct) {\n\t\tbyName.set(name, {\n\t\t\tmode: \"100644\",\n\t\t\tpath: name,\n\t\t\toid: blobOid,\n\t\t\ttype: \"blob\",\n\t\t});\n\t}\n\t// Sibling subdirectories are independent subtree writes — no reason to\n\t// serialize them for multi-file commits touching several directories.\n\tconst nestedResults = await Promise.all(\n\t\tArray.from(nested, async ([dir, subEntries]) => {\n\t\t\tconst entry = byName.get(dir);\n\t\t\tconst subtreeOid = entry?.type === \"tree\" ? entry.oid : undefined;\n\t\t\tconst newOid = await upsertTree(repo, subtreeOid, subEntries);\n\t\t\treturn [dir, newOid] as const;\n\t\t}),\n\t);\n\tfor (const [dir, newOid] of nestedResults) {\n\t\tbyName.set(dir, { mode: \"040000\", path: dir, oid: newOid, type: \"tree\" });\n\t}\n\treturn git.writeTree({ ...repo, tree: Array.from(byName.values()) });\n}\n\n/** Remove a file path from a tree, returning the new root tree oid. */\nexport async function deleteFromTree(\n\trepo: Repo,\n\ttreeOid: string,\n\tfilePath: string,\n): Promise<string> {\n\tconst existing = (await git.readTree({ ...repo, oid: treeOid })).tree;\n\tconst byName = new Map(existing.map((e) => [e.path, e]));\n\tconst slash = filePath.indexOf(\"/\");\n\tif (slash === -1) {\n\t\tbyName.delete(filePath);\n\t} else {\n\t\tconst dir = filePath.slice(0, slash);\n\t\tconst rest = filePath.slice(slash + 1);\n\t\tconst entry = byName.get(dir);\n\t\tif (entry?.type === \"tree\") {\n\t\t\tconst newOid = await deleteFromTree(repo, entry.oid, rest);\n\t\t\tbyName.set(dir, { ...entry, oid: newOid });\n\t\t}\n\t}\n\treturn git.writeTree({ ...repo, tree: Array.from(byName.values()) });\n}\n\n/** Resolve a path inside a tree to its entry, or null when absent. */\nexport async function findTreeEntry(\n\trepo: Repo,\n\trootTreeOid: string,\n\ttreePath: string,\n): Promise<TreeEntry | null> {\n\tif (!treePath) {\n\t\treturn { path: \"\", mode: \"040000\", type: \"tree\", oid: rootTreeOid };\n\t}\n\n\tconst parts = treePath.split(\"/\").filter(Boolean);\n\tlet currentTreeOid = rootTreeOid;\n\tlet currentPath = \"\";\n\n\tfor (const [index, part] of parts.entries()) {\n\t\tconst tree = await git.readTree({ ...repo, oid: currentTreeOid });\n\t\tconst entry = tree.tree.find((candidate) => candidate.path === part);\n\n\t\tif (!entry) return null;\n\n\t\tcurrentPath = currentPath ? joinPath(currentPath, entry.path) : entry.path;\n\n\t\tif (index === parts.length - 1) {\n\t\t\treturn {\n\t\t\t\tpath: currentPath,\n\t\t\t\tmode: entry.mode,\n\t\t\t\ttype: entry.type as \"blob\" | \"tree\",\n\t\t\t\toid: entry.oid,\n\t\t\t};\n\t\t}\n\n\t\tif (entry.type !== \"tree\") return null;\n\n\t\tcurrentTreeOid = entry.oid;\n\t}\n\n\treturn null;\n}\n\n/** List a tree's direct entries, with paths prefixed by `prefix`. */\nexport async function listTreeEntries(\n\trepo: Repo,\n\ttreeOid: string,\n\tprefix = \"\",\n): Promise<TreeEntry[]> {\n\tconst tree = await git.readTree({ ...repo, oid: treeOid });\n\n\treturn tree.tree.map((entry) => ({\n\t\tpath: prefix ? joinPath(prefix, entry.path) : entry.path,\n\t\tmode: entry.mode,\n\t\ttype: entry.type as \"blob\" | \"tree\",\n\t\toid: entry.oid,\n\t}));\n}\n","import { createTwoFilesPatch } from \"diff\";\nimport git from \"isomorphic-git\";\nimport { readBlobContent, toBase64 } from \"../edge-utils.js\";\nimport { qualifyBranchRef } from \"../refs.js\";\nimport { getCommit } from \"./history.js\";\nimport type { Repo } from \"./types.js\";\n\nexport interface DiffFile {\n\tpath: string;\n\tstatus: \"added\" | \"modified\" | \"deleted\" | \"renamed\";\n\tadditions: number;\n\tdeletions: number;\n\tpatch: string;\n\toldPath?: string;\n\tisBinary?: boolean;\n\toldContent?: string;\n\tnewContent?: string;\n\toldSize?: number;\n\tnewSize?: number;\n}\n\nexport interface DiffResult {\n\tfiles: DiffFile[];\n\ttotalAdditions: number;\n\ttotalDeletions: number;\n\ttotalFiles: number;\n}\n\n/** Binary detection via null-byte heuristic. */\nfunction detectBlobContent(blob: Uint8Array): {\n\tisBinary: boolean;\n\ttext: string;\n\tbytes: Uint8Array;\n} {\n\treturn readBlobContent(blob);\n}\n\nfunction countContentLines(content: string): number {\n\tif (content.length === 0) return 0;\n\tconst lines = content.split(\"\\n\");\n\tif (lines[lines.length - 1] === \"\") lines.pop();\n\treturn lines.length;\n}\n\nfunction createUnifiedPatch(params: {\n\tpath: string;\n\tbefore: string;\n\tafter: string;\n\toldPath?: string;\n\tnewPath?: string;\n}): string {\n\tconst oldPath = params.oldPath ?? `a/${params.path}`;\n\tconst newPath = params.newPath ?? `b/${params.path}`;\n\tconst patchBody = createTwoFilesPatch(\n\t\toldPath,\n\t\tnewPath,\n\t\tparams.before,\n\t\tparams.after,\n\t\t\"\",\n\t\t\"\",\n\t\t{ context: 3 },\n\t).replace(/^=+\\n/, \"\");\n\n\treturn `diff --git a/${params.path} b/${params.path}\\n${patchBody}`;\n}\n\nfunction summarizeDiff(files: DiffFile[]): DiffResult {\n\treturn {\n\t\tfiles,\n\t\ttotalAdditions: files.reduce((sum, f) => sum + f.additions, 0),\n\t\ttotalDeletions: files.reduce((sum, f) => sum + f.deletions, 0),\n\t\ttotalFiles: files.length,\n\t};\n}\n\n/**\n * Walk two trees (oldOid -> newOid) and return one DiffFile per changed path\n * — the shared core of both {@link getCommitDiff} (parent -> commit) and\n * {@link getDiffBetweenRefs} (base -> compare).\n */\nasync function walkTreeDiff(\n\trepo: Repo,\n\toldOid: string,\n\tnewOid: string,\n): Promise<DiffFile[]> {\n\tconst changes = await git.walk({\n\t\t...repo,\n\t\ttrees: [git.TREE({ ref: oldOid }), git.TREE({ ref: newOid })],\n\t\tmap: async (filepath, [A, B]) => {\n\t\t\tconst [typeA, typeB] = await Promise.all([A?.type(), B?.type()]);\n\n\t\t\tif (typeA === \"tree\" || typeB === \"tree\") return;\n\n\t\t\tif (typeA && !typeB) {\n\t\t\t\tconst oidA = A ? await A.oid() : \"\";\n\t\t\t\tconst { blob } = await git.readBlob({ ...repo, oid: oidA });\n\t\t\t\tconst before = detectBlobContent(blob);\n\t\t\t\treturn {\n\t\t\t\t\tpath: filepath,\n\t\t\t\t\tstatus: \"deleted\" as const,\n\t\t\t\t\tadditions: 0,\n\t\t\t\t\tdeletions: before.isBinary ? 0 : countContentLines(before.text),\n\t\t\t\t\tpatch: before.isBinary\n\t\t\t\t\t\t? \"\"\n\t\t\t\t\t\t: createUnifiedPatch({\n\t\t\t\t\t\t\t\tpath: filepath,\n\t\t\t\t\t\t\t\tbefore: before.text,\n\t\t\t\t\t\t\t\tafter: \"\",\n\t\t\t\t\t\t\t\tnewPath: \"/dev/null\",\n\t\t\t\t\t\t\t}),\n\t\t\t\t\tisBinary: before.isBinary,\n\t\t\t\t\toldContent: before.isBinary ? toBase64(before.bytes) : undefined,\n\t\t\t\t\toldSize: before.bytes.length,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tif (!typeA && typeB) {\n\t\t\t\tconst oidB = B ? await B.oid() : \"\";\n\t\t\t\tconst { blob } = await git.readBlob({ ...repo, oid: oidB });\n\t\t\t\tconst after = detectBlobContent(blob);\n\t\t\t\treturn {\n\t\t\t\t\tpath: filepath,\n\t\t\t\t\tstatus: \"added\" as const,\n\t\t\t\t\tadditions: after.isBinary ? 0 : countContentLines(after.text),\n\t\t\t\t\tdeletions: 0,\n\t\t\t\t\tpatch: after.isBinary\n\t\t\t\t\t\t? \"\"\n\t\t\t\t\t\t: createUnifiedPatch({\n\t\t\t\t\t\t\t\tpath: filepath,\n\t\t\t\t\t\t\t\tbefore: \"\",\n\t\t\t\t\t\t\t\tafter: after.text,\n\t\t\t\t\t\t\t\toldPath: \"/dev/null\",\n\t\t\t\t\t\t\t}),\n\t\t\t\t\tisBinary: after.isBinary,\n\t\t\t\t\tnewContent: after.isBinary ? toBase64(after.bytes) : undefined,\n\t\t\t\t\tnewSize: after.bytes.length,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tconst [oidA, oidB] = await Promise.all([\n\t\t\t\tA ? A.oid() : Promise.resolve(\"\"),\n\t\t\t\tB ? B.oid() : Promise.resolve(\"\"),\n\t\t\t]);\n\n\t\t\tif (oidA !== oidB) {\n\t\t\t\tconst [{ blob: blobA }, { blob: blobB }] = await Promise.all([\n\t\t\t\t\tgit.readBlob({ ...repo, oid: oidA }),\n\t\t\t\t\tgit.readBlob({ ...repo, oid: oidB }),\n\t\t\t\t]);\n\t\t\t\tconst before = detectBlobContent(blobA);\n\t\t\t\tconst after = detectBlobContent(blobB);\n\t\t\t\tconst isBinary = before.isBinary || after.isBinary;\n\n\t\t\t\treturn {\n\t\t\t\t\tpath: filepath,\n\t\t\t\t\tstatus: \"modified\" as const,\n\t\t\t\t\tadditions: isBinary ? 0 : countContentLines(after.text),\n\t\t\t\t\tdeletions: isBinary ? 0 : countContentLines(before.text),\n\t\t\t\t\tpatch: isBinary\n\t\t\t\t\t\t? \"\"\n\t\t\t\t\t\t: createUnifiedPatch({\n\t\t\t\t\t\t\t\tpath: filepath,\n\t\t\t\t\t\t\t\tbefore: before.text,\n\t\t\t\t\t\t\t\tafter: after.text,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\tisBinary,\n\t\t\t\t\toldContent: isBinary ? toBase64(before.bytes) : undefined,\n\t\t\t\t\tnewContent: isBinary ? toBase64(after.bytes) : undefined,\n\t\t\t\t\toldSize: before.bytes.length,\n\t\t\t\t\tnewSize: after.bytes.length,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\treturn null;\n\t\t},\n\t});\n\n\treturn (changes ?? []).filter(\n\t\t(c: DiffFile | null | undefined): c is DiffFile =>\n\t\t\tc !== null && c !== undefined,\n\t);\n}\n\n/** The diff a single commit introduced (against its first parent). */\nexport async function getCommitDiff(\n\trepo: Repo,\n\tcommitSha: string,\n): Promise<DiffResult> {\n\ttry {\n\t\tconst commit = await getCommit(repo, commitSha);\n\t\tconst parent = commit.commit.parent[0];\n\n\t\tif (!parent) {\n\t\t\tconst entries: { path: string; oid: string }[] = [];\n\t\t\tconst stack: { treeOid: string; prefix: string }[] = [\n\t\t\t\t{ treeOid: commit.commit.tree, prefix: \"\" },\n\t\t\t];\n\n\t\t\twhile (stack.length) {\n\t\t\t\tconst { treeOid, prefix } = stack.pop() as {\n\t\t\t\t\ttreeOid: string;\n\t\t\t\t\tprefix: string;\n\t\t\t\t};\n\t\t\t\tconst { tree } = await git.readTree({ ...repo, oid: treeOid });\n\t\t\t\tfor (const entry of tree) {\n\t\t\t\t\tconst full = prefix ? `${prefix}/${entry.path}` : entry.path;\n\t\t\t\t\tif (entry.type === \"tree\") {\n\t\t\t\t\t\tstack.push({ treeOid: entry.oid, prefix: full });\n\t\t\t\t\t} else if (entry.type === \"blob\") {\n\t\t\t\t\t\tentries.push({ path: full, oid: entry.oid });\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst files: DiffFile[] = await Promise.all(\n\t\t\t\tentries.map(async ({ path, oid }) => {\n\t\t\t\t\tconst { blob } = await git.readBlob({ ...repo, oid });\n\t\t\t\t\tconst after = detectBlobContent(blob);\n\t\t\t\t\treturn {\n\t\t\t\t\t\tpath,\n\t\t\t\t\t\tstatus: \"added\" as const,\n\t\t\t\t\t\tadditions: after.isBinary ? 0 : countContentLines(after.text),\n\t\t\t\t\t\tdeletions: 0,\n\t\t\t\t\t\tpatch: after.isBinary\n\t\t\t\t\t\t\t? \"\"\n\t\t\t\t\t\t\t: createUnifiedPatch({\n\t\t\t\t\t\t\t\t\tpath,\n\t\t\t\t\t\t\t\t\tbefore: \"\",\n\t\t\t\t\t\t\t\t\tafter: after.text,\n\t\t\t\t\t\t\t\t\toldPath: \"/dev/null\",\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\tisBinary: after.isBinary,\n\t\t\t\t\t\tnewContent: after.isBinary ? toBase64(after.bytes) : undefined,\n\t\t\t\t\t\tnewSize: after.bytes.length,\n\t\t\t\t\t};\n\t\t\t\t}),\n\t\t\t);\n\n\t\t\treturn summarizeDiff(files);\n\t\t}\n\n\t\tconst files = await walkTreeDiff(repo, parent, commitSha);\n\t\treturn summarizeDiff(files);\n\t} catch (error) {\n\t\tthrow new Error(`Failed to get commit diff: ${error}`);\n\t}\n}\n\n/** The diff between two refs (base -> compare). */\nexport async function getDiffBetweenRefs(\n\trepo: Repo,\n\tbaseRef: string,\n\tcompareRef: string,\n): Promise<DiffResult> {\n\tconst [baseOid, compareOid] = await Promise.all([\n\t\tgit.resolveRef({ ...repo, ref: qualifyBranchRef(baseRef) }),\n\t\tgit.resolveRef({ ...repo, ref: qualifyBranchRef(compareRef) }),\n\t]);\n\n\tconst files = await walkTreeDiff(repo, baseOid, compareOid);\n\treturn summarizeDiff(files);\n}\n","import git from \"isomorphic-git\";\nimport { decodeUtf8, hasNullByte, toBase64 } from \"../edge-utils.js\";\nimport { GitObjectNotFoundError, GitPathNotFoundError } from \"../git-errors.js\";\nimport { qualifyBranchRef } from \"../refs.js\";\nimport { findTreeEntry, listTreeEntries, type TreeEntry } from \"./tree.js\";\nimport { type OpsHooks, type Repo, runStep } from \"./types.js\";\n\n/**\n * A resolved ref (branch/commit) not existing is a normal, expected condition\n * (empty repo, unborn branch) and is handled by each caller. An object that\n * fails to resolve *underneath* an already-resolved ref (a tree/blob the\n * stored pack doesn't actually contain) means the repo's storage is\n * inconsistent — surface that distinctly so callers don't render it as\n * \"empty\" and clients don't see a raw isomorphic-git NotFoundError.\n */\nfunction wrapMissingObject<T>(\n\tpromise: Promise<T>,\n\tcontext: string,\n): Promise<T> {\n\treturn promise.catch((err: unknown) => {\n\t\tif ((err as { code?: string })?.code === \"NotFoundError\") {\n\t\t\tthrow new GitObjectNotFoundError(\n\t\t\t\t`Git data for ${context} is missing from storage. The repository may need to be re-pushed to repair it.`,\n\t\t\t);\n\t\t}\n\t\tthrow err;\n\t});\n}\n\nconst isNotFound = (err: unknown) =>\n\t(err as { code?: string })?.code === \"NotFoundError\";\n\nexport interface CommitInfo {\n\toid: string;\n\tcommit: {\n\t\tmessage: string;\n\t\ttree: string;\n\t\tparent: string[];\n\t\tauthor: {\n\t\t\tname: string;\n\t\t\temail: string;\n\t\t\ttimestamp: number;\n\t\t\ttimezoneOffset: number;\n\t\t};\n\t\tcommitter: {\n\t\t\tname: string;\n\t\t\temail: string;\n\t\t\ttimestamp: number;\n\t\t\ttimezoneOffset: number;\n\t\t};\n\t};\n\tpayload: string;\n}\n\n/**\n * Resolve a branch name / full ref / sha to its commit. The ref not\n * resolving (unborn branch, genuinely empty repo) and the ref resolving but\n * its commit object being unreadable (storage inconsistency — see\n * `wrapMissingObject` above) are different failures with different meanings,\n * so only the first is left as a raw isomorphic-git NotFoundError for\n * callers to treat as \"empty\"; the second is wrapped into\n * GitObjectNotFoundError specifically so it can't be mistaken for the first\n * by an `isNotFound`-style check downstream (see getTreeFromRef/getCommitLog).\n */\nexport async function resolveCommit(repo: Repo, ref: string) {\n\tconst oid = await git.resolveRef({ ...repo, ref: qualifyBranchRef(ref) });\n\tconst result = await wrapMissingObject(\n\t\tgit.readCommit({ ...repo, oid }),\n\t\t`${repo.gitdir} commit ${oid}`,\n\t);\n\treturn { oid, commit: result.commit };\n}\n\n/** Read a blob's bytes by oid. */\nexport async function getBlob(repo: Repo, sha: string): Promise<Uint8Array> {\n\tconst { blob } = await wrapMissingObject(\n\t\tgit.readBlob({ ...repo, oid: sha }),\n\t\t`${repo.gitdir} blob ${sha}`,\n\t);\n\treturn blob;\n}\n\n/** Read a file's bytes at a ref. Throws GitPathNotFoundError when absent. */\nexport async function getFileContent(\n\trepo: Repo,\n\tfilePath: string,\n\tref = \"main\",\n\thooks?: OpsHooks,\n): Promise<Uint8Array> {\n\t// A blob page needs a commit, one or more tree objects, and the blob. On a\n\t// multi-pack object store, letting isomorphic-git discover those packs one\n\t// at a time turns that otherwise short read into a serial network walk.\n\t// Warm every pack before resolving the ref so every later object lookup can\n\t// use the local parse cache. This is the same structural optimization used\n\t// for tree and history reads, now applied to file reads as well.\n\tif (hooks?.prefetch) {\n\t\tawait runStep(hooks, \"prefetch\", hooks.prefetch);\n\t}\n\tconst { commit } = await resolveCommit(repo, ref);\n\tconst context = `${repo.gitdir}@${ref}:${filePath}`;\n\tconst entry = await wrapMissingObject(\n\t\tfindTreeEntry(repo, commit.tree, filePath),\n\t\tcontext,\n\t);\n\n\tif (entry?.type !== \"blob\") {\n\t\tthrow new GitPathNotFoundError(`File not found: ${filePath}`);\n\t}\n\n\tconst { blob } = await wrapMissingObject(\n\t\tgit.readBlob({ ...repo, oid: entry.oid }),\n\t\tcontext,\n\t);\n\treturn blob;\n}\n\n/** Read one commit by sha. */\nexport async function getCommit(repo: Repo, sha: string): Promise<CommitInfo> {\n\tconst result = await wrapMissingObject(\n\t\tgit.readCommit({ ...repo, oid: sha }),\n\t\t`${repo.gitdir} commit ${sha}`,\n\t);\n\treturn { oid: result.oid, commit: result.commit, payload: result.payload };\n}\n\nfunction isFullyWalked(commits: CommitInfo[]): boolean {\n\tconst last = commits[commits.length - 1];\n\treturn !!last && last.commit.parent.length === 0;\n}\n\nexport interface CommitLogOptions {\n\tref?: string;\n\tdepth?: number;\n\t/**\n\t * Pass the head sha when the caller already resolved `ref` — resolveRef\n\t * tries several candidate paths in sequence and misses the first few every\n\t * time for a normal branch name, which is pure waste when the sha is known.\n\t */\n\tknownHeadSha?: string;\n}\n\n/**\n * The commit chain from a ref, newest first. Walking is inherently sequential\n * (each commit's oid is only discoverable by reading its child first) and\n * network-round-trip-bound against object storage, so the deepest walk seen\n * per head is memoized in `hooks.resultCache` and sliced for shallower or\n * repeated requests — don't bypass this by calling `git.log` directly.\n */\nexport async function getCommitLog(\n\trepo: Repo,\n\toptions: CommitLogOptions = {},\n\thooks?: OpsHooks,\n): Promise<CommitInfo[]> {\n\tconst ref = options.ref ?? \"main\";\n\tconst depth = options.depth ?? 50;\n\n\tlet headSha: string;\n\tif (options.knownHeadSha) {\n\t\theadSha = options.knownHeadSha;\n\t} else {\n\t\ttry {\n\t\t\theadSha = await git.resolveRef({ ...repo, ref: qualifyBranchRef(ref) });\n\t\t} catch (err: unknown) {\n\t\t\tif (isNotFound(err)) return [];\n\t\t\tthrow err;\n\t\t}\n\t}\n\n\tconst cacheKey = `commitlog:${repo.gitdir}:${headSha}`;\n\tconst cached = hooks?.resultCache?.get<CommitInfo[]>(cacheKey);\n\tif (cached && (cached.length >= depth || isFullyWalked(cached))) {\n\t\thooks?.onNote?.(\n\t\t\t`getCommitLog: result-cache HIT for ${cacheKey} (depth=${depth})`,\n\t\t);\n\t\treturn cached.slice(0, depth);\n\t}\n\thooks?.onNote?.(\n\t\t`getCommitLog: result-cache MISS for ${cacheKey} (depth=${depth})`,\n\t);\n\n\tif (hooks?.prefetch && depth >= (hooks.prefetchMinDepth ?? 5)) {\n\t\tawait runStep(hooks, \"prefetch\", hooks.prefetch);\n\t}\n\n\t// headSha is already known-good here (resolveRef above succeeded, or the\n\t// caller passed knownHeadSha) — a NotFoundError from the walk itself means\n\t// a commit/tree/blob it needs is missing from storage, not that the ref\n\t// doesn't exist. Unlike the resolveRef catch above, that's not \"empty\",\n\t// it's storage inconsistency, so wrapMissingObject turns it into a\n\t// GitObjectNotFoundError instead of silently returning [] — an empty\n\t// result here would otherwise get treated as \"this branch has no\n\t// history\" by every caller (and, worse, potentially cached as such).\n\tconst commits = await wrapMissingObject(\n\t\trunStep(hooks, `git.log ${ref} depth=${depth}`, () =>\n\t\t\tgit.log({ ...repo, ref: headSha, depth }),\n\t\t),\n\t\t`${repo.gitdir}@${ref} history`,\n\t);\n\tconst result = commits.map((commit) => ({\n\t\toid: commit.oid,\n\t\tcommit: commit.commit,\n\t\tpayload: commit.payload || \"\",\n\t}));\n\tif (!cached || result.length > cached.length) {\n\t\thooks?.resultCache?.set(cacheKey, result);\n\t}\n\treturn result;\n}\n\n/** A file at a ref, decoded for display: utf8 text or base64 when binary. */\nexport async function getFileFromRef(\n\trepo: Repo,\n\tfilePath: string,\n\tref: string,\n\thooks?: OpsHooks,\n): Promise<{ content: string; size: number; isBinary: boolean }> {\n\tconst bytes = await getFileContent(repo, filePath, ref, hooks);\n\tconst isBinary = hasNullByte(bytes);\n\n\treturn {\n\t\tcontent: isBinary ? toBase64(bytes) : decodeUtf8(bytes),\n\t\tsize: bytes.length,\n\t\tisBinary,\n\t};\n}\n\n/**\n * List a directory at the tip of a ref. Returns [] for an empty repo/unborn\n * branch; throws GitPathNotFoundError when `treePath` doesn't exist. Results\n * are memoized per head sha (auto-invalidates on push).\n */\nexport async function getTreeFromRef(\n\trepo: Repo,\n\toptions: { ref?: string; treePath?: string } = {},\n\thooks?: OpsHooks,\n): Promise<TreeEntry[]> {\n\tconst ref = options.ref ?? \"main\";\n\tconst treePath = options.treePath ?? \"\";\n\n\tlet commit: Awaited<ReturnType<typeof resolveCommit>>[\"commit\"];\n\tlet headSha: string;\n\ttry {\n\t\tconst resolved = await resolveCommit(repo, ref);\n\t\tcommit = resolved.commit;\n\t\theadSha = resolved.oid;\n\t} catch (err: unknown) {\n\t\tif (isNotFound(err)) return [];\n\t\tthrow err;\n\t}\n\n\tconst cacheKey = `tree:${repo.gitdir}:${headSha}:${treePath}`;\n\tconst cached = hooks?.resultCache?.get<TreeEntry[]>(cacheKey);\n\tif (cached) {\n\t\thooks?.onNote?.(`getTreeFromRef: result-cache HIT for ${cacheKey}`);\n\t\treturn cached;\n\t}\n\thooks?.onNote?.(`getTreeFromRef: result-cache MISS for ${cacheKey}`);\n\n\t// Unlike getCommitLog, a tree read has no \"depth\" to gate on — it always\n\t// needs at least the head commit's tree object, and (for a non-root path)\n\t// one object per path segment on top of that, so there's no shallow case\n\t// where prefetching every pack is wasted bandwidth the way a depth=1\n\t// commit-log walk can be. Without this, a cache-miss tree read falls\n\t// through to isomorphic-git's own pack resolution, which probes indexed\n\t// packs one at a time instead of warming them all in parallel up front —\n\t// this was previously the slowest of a tree page's parallel queries in\n\t// production for exactly that reason.\n\tif (hooks?.prefetch) {\n\t\tawait runStep(hooks, \"prefetch\", hooks.prefetch);\n\t}\n\n\tconst context = `${repo.gitdir}@${ref}:${treePath || \"/\"}`;\n\tlet result: TreeEntry[];\n\tif (!treePath) {\n\t\tresult = await wrapMissingObject(\n\t\t\trunStep(hooks, \"listTreeEntries (root)\", () =>\n\t\t\t\tlistTreeEntries(repo, commit.tree),\n\t\t\t),\n\t\t\tcontext,\n\t\t);\n\t} else {\n\t\tconst entry = await wrapMissingObject(\n\t\t\trunStep(hooks, `findTreeEntry ${treePath}`, () =>\n\t\t\t\tfindTreeEntry(repo, commit.tree, treePath),\n\t\t\t),\n\t\t\tcontext,\n\t\t);\n\t\tif (!entry) {\n\t\t\tthrow new GitPathNotFoundError(\n\t\t\t\t`Path \"${treePath}\" does not exist at ${ref}`,\n\t\t\t);\n\t\t}\n\t\tresult =\n\t\t\tentry.type !== \"tree\"\n\t\t\t\t? []\n\t\t\t\t: await wrapMissingObject(\n\t\t\t\t\t\trunStep(hooks, `listTreeEntries ${treePath}`, () =>\n\t\t\t\t\t\t\tlistTreeEntries(repo, entry.oid, entry.path),\n\t\t\t\t\t\t),\n\t\t\t\t\t\tcontext,\n\t\t\t\t\t);\n\t}\n\n\thooks?.resultCache?.set(cacheKey, result);\n\treturn result;\n}\n\n/**\n * A page of commit history from a branch tip. Memoized per head sha; builds\n * on {@link getCommitLog}'s walk cache for the underlying chain.\n */\nexport async function getCommitHistory(\n\trepo: Repo,\n\toptions: { ref: string; limit?: number; skip?: number },\n\thooks?: OpsHooks,\n): Promise<CommitInfo[]> {\n\tconst limit = options.limit ?? 50;\n\tconst skip = options.skip ?? 0;\n\tconst headSha = await git\n\t\t.resolveRef({ ...repo, ref: qualifyBranchRef(options.ref) })\n\t\t.catch(() => null);\n\n\tconst cacheKey = headSha\n\t\t? `commits:${repo.gitdir}:${headSha}:${limit}:${skip}`\n\t\t: null;\n\tif (cacheKey) {\n\t\tconst cached = hooks?.resultCache?.get<CommitInfo[]>(cacheKey);\n\t\tif (cached) {\n\t\t\thooks?.onNote?.(`getCommitHistory: result-cache HIT for ${cacheKey}`);\n\t\t\treturn cached;\n\t\t}\n\t}\n\thooks?.onNote?.(\n\t\t`getCommitHistory: result-cache MISS for ${cacheKey ?? \"(no head)\"}`,\n\t);\n\tif (!headSha) return [];\n\n\tconst all = await getCommitLog(\n\t\trepo,\n\t\t{ ref: options.ref, depth: limit + skip, knownHeadSha: headSha },\n\t\thooks,\n\t);\n\tconst result = all.slice(skip, skip + limit);\n\n\tif (cacheKey) hooks?.resultCache?.set(cacheKey, result);\n\treturn result;\n}\n","import type git from \"isomorphic-git\";\n\n/** The fs shape isomorphic-git accepts, derived from its own signatures. */\nexport type IsoGitFs = Parameters<typeof git.readTree>[0][\"fs\"];\n\n/**\n * A repository handle: everything isomorphic-git needs to address one bare\n * repo. Create it once per request (sharing `cache` across calls is what lets\n * isomorphic-git reuse parsed pack indexes) and pass it to every op.\n */\nexport interface Repo {\n\tfs: IsoGitFs;\n\tgitdir: string;\n\t/** isomorphic-git's shared parse cache — strongly recommended per repo. */\n\tcache?: object;\n}\n\n/**\n * Key/value store for memoizing expensive walk results (commit logs, tree\n * listings, per-file history). Keys are namespaced `kind:gitdir:headSha:…`,\n * so entries self-invalidate on push (new head, new key) — but evict entries\n * under {@link resultKeyPrefixes} after rewriting a repo's storage out of\n * band, or stale walks leak until your store's own eviction.\n */\nexport interface ResultCache {\n\tget<T>(key: string): T | null | undefined;\n\tset(key: string, value: unknown): void;\n}\n\n/** Optional instrumentation and tuning hooks accepted by every op. */\nexport interface OpsHooks {\n\tresultCache?: ResultCache;\n\t/** Wrap a timed sub-step (network walk, tree listing). Default: run directly. */\n\tstep?: <T>(label: string, fn: () => Promise<T>) => Promise<T>;\n\t/** Diagnostic sink for cache hit/miss and walk summaries. */\n\tonNote?: (message: string) => void;\n\t/**\n\t * Wire pack prefetching (e.g. `GitFs.prefetchPacks`) here so a sequential\n\t * walk doesn't pay one network round trip per commit. Called once before\n\t * history walks of depth >= `prefetchMinDepth` (see below), and\n\t * unconditionally on every cache-miss tree read ({@link getTreeFromRef}) —\n\t * a tree read has no shallow case, it always needs at least the head\n\t * commit's tree object.\n\t */\n\tprefetch?: () => Promise<void>;\n\t/** Minimum walk depth before `prefetch` fires for history walks. Default 5. */\n\tprefetchMinDepth?: number;\n}\n\nexport const runStep = <T>(\n\thooks: OpsHooks | undefined,\n\tlabel: string,\n\tfn: () => Promise<T>,\n): Promise<T> => (hooks?.step ? hooks.step(label, fn) : fn());\n\n/**\n * The result-cache key prefixes holding entries for `gitdir` — evict these\n * from your {@link ResultCache} when the repo's storage was rewritten outside\n * a normal push (bulk sync, rename, repack cleanup).\n */\nexport function resultKeyPrefixes(gitdir: string): string[] {\n\treturn [\n\t\t`commitlog:${gitdir}:`,\n\t\t`tree:${gitdir}:`,\n\t\t`commits:${gitdir}:`,\n\t\t`last-commits:${gitdir}:`,\n\t\t`file-history:${gitdir}:`,\n\t];\n}\n","import { type CommitInfo, getCommitLog } from \"./history.js\";\nimport { findTreeEntry } from \"./tree.js\";\nimport type { OpsHooks, Repo } from \"./types.js\";\nimport { runStep } from \"./types.js\";\n\nexport interface FileHistoryEntry {\n\tsha: string;\n\tmessage: string;\n\tauthorName: string;\n\tauthorEmail: string;\n\tcreatedAt: string;\n}\n\nexport interface FileHistoryResult {\n\tentries: FileHistoryEntry[];\n\t/**\n\t * True when the walk hit its depth budget (or the requested `limit`)\n\t * before exhausting the branch's full commit chain — there may be older\n\t * commits touching this file that a deeper walk would surface.\n\t */\n\ttruncated: boolean;\n}\n\n/**\n * Default walk bound for a caller that actually wants deep history (a file's\n * \"History\" tab). Walking the full chain is round-trip-bound on object\n * storage, so cap how far back a single request will look.\n */\nexport const HISTORY_WALK_DEPTH = 400;\n\n/**\n * Much shallower default for a \"latest commit touching this file\" banner that\n * only displays `entries[0]` — trades \"always finds the true last-touching\n * commit\" for \"finds it if it's reasonably recent\", the right call for a\n * banner with a full History view a click away.\n */\nexport const BANNER_WALK_DEPTH = 60;\n\n/** Tree reads are prefetched in parallel windows; see getLastCommitsForTree. */\nconst PREFETCH_WINDOW = 24;\n\nfunction toEntry(commit: CommitInfo): FileHistoryEntry {\n\treturn {\n\t\tsha: commit.oid,\n\t\tmessage: commit.commit.message.trim(),\n\t\tauthorName: commit.commit.author.name,\n\t\tauthorEmail: commit.commit.author.email,\n\t\tcreatedAt: new Date(commit.commit.author.timestamp * 1000).toISOString(),\n\t};\n}\n\n/**\n * All commits (newest first) that changed a single file's blob oid, walking\n * the first-parent chain — same approach as getLastCommitsForTree but for one\n * path and collecting every match instead of stopping at the first.\n */\nexport async function getFileHistory(\n\trepo: Repo,\n\toptions: {\n\t\tref: string;\n\t\tfilePath: string;\n\t\tlimit?: number;\n\t\tmaxDepth?: number;\n\t},\n\thooks?: OpsHooks,\n): Promise<FileHistoryResult> {\n\tconst limit = options.limit ?? 30;\n\tconst maxDepth = options.maxDepth ?? HISTORY_WALK_DEPTH;\n\tconst walkDepth = Math.max(maxDepth, limit);\n\tconst commits = await runStep(hooks, `getCommitLog depth=${walkDepth}`, () =>\n\t\tgetCommitLog(repo, { ref: options.ref, depth: walkDepth }, hooks),\n\t);\n\tconst head = commits[0];\n\tif (!head) return { entries: [], truncated: false };\n\n\tconst cacheKey = `file-history:${repo.gitdir}:${head.oid}:${options.filePath}:${limit}:${maxDepth}`;\n\tconst cached = hooks?.resultCache?.get<FileHistoryResult>(cacheKey);\n\tif (cached) {\n\t\thooks?.onNote?.(\"getFileHistory: result-cache HIT, skipping history walk\");\n\t\treturn cached;\n\t}\n\thooks?.onNote?.(\"getFileHistory: result-cache MISS, walking history\");\n\n\tconst byOid = new Map(commits.map((commit) => [commit.oid, commit]));\n\n\tconst oidByCommitTree = new Map<string, string | null>();\n\tasync function resolveOid(commitTreeOid: string): Promise<string | null> {\n\t\tconst cachedOid = oidByCommitTree.get(commitTreeOid);\n\t\tif (cachedOid !== undefined) return cachedOid;\n\t\tconst entry = await findTreeEntry(repo, commitTreeOid, options.filePath);\n\t\tconst oid = entry?.type === \"blob\" ? entry.oid : null;\n\t\toidByCommitTree.set(commitTreeOid, oid);\n\t\treturn oid;\n\t}\n\n\tconst entries: FileHistoryEntry[] = [];\n\tlet truncated = false;\n\n\touter: for (\n\t\tlet windowStart = 0;\n\t\twindowStart < commits.length;\n\t\twindowStart += PREFETCH_WINDOW\n\t) {\n\t\tconst windowEnd = Math.min(windowStart + PREFETCH_WINDOW, commits.length);\n\t\t// +1 lookahead so the last entry's parent tree is already warm too.\n\t\tconst prefetchEnd = Math.min(windowEnd + 1, commits.length);\n\n\t\tawait Promise.all(\n\t\t\tcommits\n\t\t\t\t.slice(windowStart, prefetchEnd)\n\t\t\t\t.map((commit) => resolveOid(commit.commit.tree)),\n\t\t);\n\n\t\tfor (let i = windowStart; i < windowEnd; i++) {\n\t\t\tconst commit = commits[i];\n\t\t\tif (!commit) break outer;\n\n\t\t\tconst parentSha = commit.commit.parent[0];\n\t\t\tconst parentCommit = parentSha ? byOid.get(parentSha) : undefined;\n\t\t\tif (parentSha && !parentCommit) {\n\t\t\t\t// Walked past the depth cap without reaching this commit's parent —\n\t\t\t\t// can't tell whether it changed the file; stop and report truncated.\n\t\t\t\ttruncated = true;\n\t\t\t\tbreak outer;\n\t\t\t}\n\n\t\t\tconst [oid, parentOid] = await Promise.all([\n\t\t\t\tresolveOid(commit.commit.tree),\n\t\t\t\tparentCommit\n\t\t\t\t\t? resolveOid(parentCommit.commit.tree)\n\t\t\t\t\t: Promise.resolve(null),\n\t\t\t]);\n\n\t\t\tif (oid !== parentOid) {\n\t\t\t\tentries.push(toEntry(commit));\n\t\t\t\tif (entries.length >= limit) {\n\t\t\t\t\ttruncated = i < commits.length - 1;\n\t\t\t\t\tbreak outer;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tconst result = { entries, truncated };\n\thooks?.resultCache?.set(cacheKey, result);\n\treturn result;\n}\n","import { type CommitInfo, getCommitLog } from \"./history.js\";\nimport { findTreeEntry, listTreeEntries } from \"./tree.js\";\nimport type { OpsHooks, Repo } from \"./types.js\";\nimport { runStep } from \"./types.js\";\n\nexport interface LastCommitInfo {\n\tsha: string;\n\tmessage: string;\n\tauthorName: string;\n\tauthorEmail: string;\n\tcreatedAt: string;\n}\n\n/**\n * Bounds how far back the history walk looks to resolve \"last commit touching\n * this path\" for a directory listing. Entries whose last change is older\n * simply show no last-commit info rather than paying an unbounded scan.\n */\nconst HISTORY_WALK_DEPTH = 400;\n\n/**\n * The walk itself is inherently sequential (each step needs to know what's\n * still \"remaining\" from the step before), but the tree-object reads that\n * back it are not — each commit's tree oid is already known upfront from the\n * commit log. Prefetching a window of tree reads in parallel turns ~1 network\n * round trip per commit (serialized) into ~1 round trip per window, which is\n * where nearly all of this function's wall-clock time goes on object storage.\n */\nconst PREFETCH_WINDOW = 24;\n\nfunction toLastCommitInfo(commit: CommitInfo): LastCommitInfo {\n\treturn {\n\t\tsha: commit.oid,\n\t\tmessage: commit.commit.message.trim(),\n\t\tauthorName: commit.commit.author.name,\n\t\tauthorEmail: commit.commit.author.email,\n\t\tcreatedAt: new Date(commit.commit.author.timestamp * 1000).toISOString(),\n\t};\n}\n\n/**\n * For each direct child of `treePath` (at the tip of `ref`), find the most\n * recent commit that changed it — the tree view's \"last commit\" column. Walks\n * history newest-to-oldest, comparing the directory's tree oid\n * commit-to-commit and only descending one level to diff child oids when\n * something under the directory actually changed. Preserve the two-phase\n * structure (parallel prefetch, then sequential resolve) when touching this —\n * the \"which entries are still unresolved\" state must advance\n * commit-by-commit.\n */\nexport async function getLastCommitsForTree(\n\trepo: Repo,\n\toptions: { ref: string; treePath?: string; depth?: number },\n\thooks?: OpsHooks,\n): Promise<Record<string, LastCommitInfo>> {\n\tconst treePath = options.treePath ?? \"\";\n\tconst depth = options.depth ?? HISTORY_WALK_DEPTH;\n\tconst commits = await runStep(hooks, `getCommitLog depth=${depth}`, () =>\n\t\tgetCommitLog(repo, { ref: options.ref, depth }, hooks),\n\t);\n\thooks?.onNote?.(`getLastCommitsForTree: ${commits.length} commits in log`);\n\tconst head = commits[0];\n\tif (!head) return {};\n\n\tconst cacheKey = `last-commits:${repo.gitdir}:${head.oid}:${treePath}`;\n\tconst cachedResult =\n\t\thooks?.resultCache?.get<Record<string, LastCommitInfo>>(cacheKey);\n\tif (cachedResult) {\n\t\thooks?.onNote?.(\n\t\t\t\"getLastCommitsForTree: result-cache HIT, skipping history walk\",\n\t\t);\n\t\treturn cachedResult;\n\t}\n\thooks?.onNote?.(\"getLastCommitsForTree: result-cache MISS, walking history\");\n\n\tconst byOid = new Map(commits.map((commit) => [commit.oid, commit]));\n\n\t// In a linear history, the \"parent tree\" resolved at commit[i] is the same\n\t// tree already resolved as the \"current tree\" at commit[i-1] — memoize by\n\t// commit-tree oid (and by resolved dir oid) so each distinct tree is only\n\t// walked/listed once across the whole scan instead of twice per commit.\n\tconst dirOidByCommitTree = new Map<string, string | null>();\n\tconst childrenByDirOid = new Map<\n\t\tstring,\n\t\tAwaited<ReturnType<typeof listTreeEntries>>\n\t>();\n\n\tasync function resolveDirOid(commitTreeOid: string): Promise<string | null> {\n\t\tconst cached = dirOidByCommitTree.get(commitTreeOid);\n\t\tif (cached !== undefined) return cached;\n\t\tconst entry = await findTreeEntry(repo, commitTreeOid, treePath);\n\t\tconst dirOid = entry?.type === \"tree\" ? entry.oid : null;\n\t\tdirOidByCommitTree.set(commitTreeOid, dirOid);\n\t\treturn dirOid;\n\t}\n\n\tasync function resolveChildren(dirOid: string | null) {\n\t\tif (dirOid === null) return [];\n\t\tconst cached = childrenByDirOid.get(dirOid);\n\t\tif (cached) return cached;\n\t\tconst children = await listTreeEntries(repo, dirOid, treePath);\n\t\tchildrenByDirOid.set(dirOid, children);\n\t\treturn children;\n\t}\n\n\tconst headDirOid = await resolveDirOid(head.commit.tree);\n\tif (headDirOid === null) return {};\n\n\t// listTreeEntries is prefixed with treePath so result keys match the full\n\t// paths that callers key their file listing by.\n\tconst headChildren = await resolveChildren(headDirOid);\n\tconst remaining = new Set(headChildren.map((entry) => entry.path));\n\tconst result: Record<string, LastCommitInfo> = {};\n\n\tlet commitsWalked = 0;\n\tlet prefetchWindows = 0;\n\tconst walkStart = performance.now();\n\n\touter: for (\n\t\tlet windowStart = 0;\n\t\twindowStart < commits.length && remaining.size > 0;\n\t\twindowStart += PREFETCH_WINDOW\n\t) {\n\t\tconst windowEnd = Math.min(windowStart + PREFETCH_WINDOW, commits.length);\n\t\t// +1 lookahead commit so the last entry's parent tree is already warm too.\n\t\tconst prefetchEnd = Math.min(windowEnd + 1, commits.length);\n\t\tprefetchWindows++;\n\n\t\t// Phase A: resolve this window's directory oid for every commit's tree in\n\t\t// parallel (a no-op await when treePath is \"\" — the root tree oid IS the\n\t\t// commit tree, no lookup needed).\n\t\tconst dirOids = await Promise.all(\n\t\t\tcommits\n\t\t\t\t.slice(windowStart, prefetchEnd)\n\t\t\t\t.map((commit) => resolveDirOid(commit.commit.tree)),\n\t\t);\n\n\t\t// Phase B: resolve children for every distinct directory oid the window\n\t\t// touched, in parallel — the actual tree-object read for the common case.\n\t\tawait Promise.all(\n\t\t\t[...new Set(dirOids)].map((dirOid) => resolveChildren(dirOid)),\n\t\t);\n\n\t\tfor (let i = windowStart; i < windowEnd; i++) {\n\t\t\tif (remaining.size === 0) break outer;\n\t\t\tconst commit = commits[i];\n\t\t\tif (!commit) break outer;\n\t\t\tcommitsWalked++;\n\n\t\t\tconst parentSha = commit.commit.parent[0];\n\t\t\tconst parentCommit = parentSha ? byOid.get(parentSha) : undefined;\n\t\t\tif (parentSha && !parentCommit) break outer; // walked past the depth cap\n\n\t\t\t// Already resolved above (or memoized from an earlier window/commit) —\n\t\t\t// these awaits resolve immediately from cache.\n\t\t\tconst [dirOid, parentDirOid] = await Promise.all([\n\t\t\t\tresolveDirOid(commit.commit.tree),\n\t\t\t\tparentCommit\n\t\t\t\t\t? resolveDirOid(parentCommit.commit.tree)\n\t\t\t\t\t: Promise.resolve(null),\n\t\t\t]);\n\n\t\t\tif (dirOid === parentDirOid) continue;\n\n\t\t\tconst [children, parentChildren] = await Promise.all([\n\t\t\t\tresolveChildren(dirOid),\n\t\t\t\tresolveChildren(parentDirOid),\n\t\t\t]);\n\t\t\tconst childByName = new Map(\n\t\t\t\tchildren.map((entry) => [entry.path, entry.oid]),\n\t\t\t);\n\t\t\tconst parentChildByName = new Map(\n\t\t\t\tparentChildren.map((entry) => [entry.path, entry.oid]),\n\t\t\t);\n\n\t\t\tfor (const name of remaining) {\n\t\t\t\tif (childByName.get(name) !== parentChildByName.get(name)) {\n\t\t\t\t\tresult[name] = toLastCommitInfo(commit);\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor (const name of Object.keys(result)) {\n\t\t\t\tremaining.delete(name);\n\t\t\t}\n\t\t}\n\t}\n\n\thooks?.onNote?.(\n\t\t`getLastCommitsForTree: walked ${commitsWalked}/${commits.length} commits across ${prefetchWindows} prefetch windows in ${(performance.now() - walkStart).toFixed(1)}ms, ${dirOidByCommitTree.size} unique tree lookups, ${remaining.size} entries never resolved`,\n\t);\n\n\thooks?.resultCache?.set(cacheKey, result);\n\treturn result;\n}\n","import git from \"isomorphic-git\";\nimport { qualifyBranchRef } from \"../refs.js\";\nimport { assertSafeBranchName } from \"./branch.js\";\nimport type { Repo } from \"./types.js\";\n\nexport interface MergeAnalysis {\n\tcanMerge: boolean;\n\thasConflicts: boolean;\n\tconflictingFiles: string[];\n\tfastForward: boolean;\n}\n\n/**\n * Cheap pre-merge check: do both branches exist, and is this a fast-forward?\n * `canMerge`/`fastForward` are the only fields this actually determines —\n * `hasConflicts`/`conflictingFiles` are NOT a real content-conflict check\n * (isomorphic-git's `git.merge` doesn't expose a dry-run), they only ever\n * reflect \"one of the branches couldn't be resolved\" (`canMerge: false`).\n * Real merge conflicts are only discoverable by actually attempting the\n * merge.\n */\nexport async function analyzeMerge(\n\trepo: Repo,\n\tsourceBranch: string,\n\ttargetBranch: string,\n): Promise<MergeAnalysis> {\n\tassertSafeBranchName(sourceBranch);\n\tassertSafeBranchName(targetBranch);\n\n\ttry {\n\t\tconst [sourceOid, targetOid] = await Promise.all([\n\t\t\tgit.resolveRef({ ...repo, ref: qualifyBranchRef(sourceBranch) }),\n\t\t\tgit.resolveRef({ ...repo, ref: qualifyBranchRef(targetBranch) }),\n\t\t]);\n\n\t\tconst isDescendant = await git.isDescendent({\n\t\t\t...repo,\n\t\t\toid: sourceOid,\n\t\t\tancestor: targetOid,\n\t\t});\n\n\t\treturn {\n\t\t\tcanMerge: true,\n\t\t\thasConflicts: false,\n\t\t\tconflictingFiles: [],\n\t\t\tfastForward: isDescendant,\n\t\t};\n\t} catch (err) {\n\t\tif ((err as { code?: string })?.code !== \"NotFoundError\") {\n\t\t\tthrow err;\n\t\t}\n\t\t// A branch ref failed to resolve — not a content conflict, just \"can't\n\t\t// merge because one side doesn't exist.\" hasConflicts here is a\n\t\t// misnomer kept for MergeAnalysis's existing shape; canMerge is the\n\t\t// field that actually matters to callers.\n\t\treturn {\n\t\t\tcanMerge: false,\n\t\t\thasConflicts: true,\n\t\t\tconflictingFiles: [],\n\t\t\tfastForward: false,\n\t\t};\n\t}\n}\n\n/**\n * Attempt a fast-forward merge directly against the bare repo: when source is\n * a descendant of target, just move the target ref — no worktree, no new\n * commit. Returns null when the merge is not a fast-forward (callers fall\n * back to a real three-way merge, which needs a worktree). Serialize with a\n * per-repo lock: resolve → writeRef is not atomic.\n */\nexport async function fastForwardMerge(\n\trepo: Repo,\n\tsourceBranch: string,\n\ttargetBranch: string,\n): Promise<{ success: true; commitSha: string } | null> {\n\tassertSafeBranchName(sourceBranch);\n\tassertSafeBranchName(targetBranch);\n\tconst [sourceOid, targetOid] = await Promise.all([\n\t\tgit.resolveRef({ ...repo, ref: `refs/heads/${sourceBranch}` }),\n\t\tgit.resolveRef({ ...repo, ref: `refs/heads/${targetBranch}` }),\n\t]);\n\tconst isFF = await git.isDescendent({\n\t\t...repo,\n\t\toid: sourceOid,\n\t\tancestor: targetOid,\n\t});\n\tif (!isFF) return null;\n\tawait git.writeRef({\n\t\t...repo,\n\t\tref: `refs/heads/${targetBranch}`,\n\t\tvalue: sourceOid,\n\t\tforce: true,\n\t});\n\treturn { success: true, commitSha: sourceOid };\n}\n"],"mappings":"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","names":["git","git","git","git","git","git","oidA","oidB","files","HISTORY_WALK_DEPTH","PREFETCH_WINDOW","git","git"]}
|
package/package.json
CHANGED