git-edge 0.1.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -32,11 +32,8 @@ var index_exports = {};
32
32
  __export(index_exports, {
33
33
  GitEdgeError: () => GitEdgeError,
34
34
  GitMergeConflictError: () => GitMergeConflictError,
35
- analyzeMerge: () => analyzeMerge,
36
35
  createParsedObjectCache: () => createParsedObjectCache,
37
- estimateRepoSize: () => estimateRepoSize,
38
36
  getRepoCache: () => getRepoCache,
39
- initBareRepo: () => initBareRepo,
40
37
  invalidateRepoCache: () => invalidateRepoCache,
41
38
  threeWayMerge: () => threeWayMerge
42
39
  });
@@ -178,25 +175,25 @@ function mergeContents(base, ours, theirs) {
178
175
  const o = oi < oursLines.length ? oursLines[oi] : void 0;
179
176
  const t = ti < theirsLines.length ? theirsLines[ti] : void 0;
180
177
  if (b === o && b === t) {
181
- result.push(b);
178
+ result.push(b ?? "");
182
179
  bi++;
183
180
  oi++;
184
181
  ti++;
185
182
  } else if (b === o) {
186
- result.push(t);
183
+ result.push(t ?? "");
187
184
  bi++;
188
185
  oi++;
189
186
  ti++;
190
187
  } else if (b === t) {
191
- result.push(o);
188
+ result.push(o ?? "");
192
189
  bi++;
193
190
  oi++;
194
191
  ti++;
195
192
  } else {
196
193
  result.push(`<<<<<<< ours`);
197
- result.push(o);
194
+ result.push(o ?? "");
198
195
  result.push(`=======`);
199
- result.push(t);
196
+ result.push(t ?? "");
200
197
  result.push(`>>>>>>> theirs`);
201
198
  bi++;
202
199
  oi++;
@@ -220,27 +217,6 @@ function arraysEqual(a, b) {
220
217
  }
221
218
  return true;
222
219
  }
223
- async function analyzeMerge(repo, sourceRef, targetRef) {
224
- try {
225
- const sourceOid = await import_isomorphic_git.default.resolveRef({ ...repo, ref: sourceRef });
226
- const targetOid = await import_isomorphic_git.default.resolveRef({ ...repo, ref: targetRef });
227
- const isDescendant = await import_isomorphic_git.default.isDescendent({
228
- ...repo,
229
- oid: sourceOid,
230
- ancestor: targetOid
231
- });
232
- return {
233
- canMerge: true,
234
- fastForward: isDescendant,
235
- diverged: !isDescendant
236
- };
237
- } catch (err) {
238
- if (err?.code !== "NotFoundError") {
239
- throw err;
240
- }
241
- return { canMerge: false, fastForward: false, diverged: false };
242
- }
243
- }
244
220
  async function threeWayMerge(repo, sourceRef, targetRef, opts) {
245
221
  const [sourceOid, targetOid] = await Promise.all([
246
222
  import_isomorphic_git.default.resolveRef({ ...repo, ref: sourceRef }),
@@ -309,14 +285,11 @@ async function threeWayMerge(repo, sourceRef, targetRef, opts) {
309
285
  const baseOid2 = baseMap.get(filepath);
310
286
  const sourceOid2 = sourceMap.get(filepath);
311
287
  const targetOid2 = targetMap.get(filepath);
312
- const _baseExists = baseOid2 !== void 0;
313
- const sourceExists = sourceOid2 !== void 0;
314
- const targetExists = targetOid2 !== void 0;
315
- if (sourceExists && !targetExists) {
288
+ if (sourceOid2 !== void 0 && targetOid2 === void 0) {
316
289
  resultEntries.set(filepath, { oid: sourceOid2, mode: "100644" });
317
- } else if (!sourceExists && targetExists) {
290
+ } else if (sourceOid2 === void 0 && targetOid2 !== void 0) {
318
291
  resultEntries.set(filepath, { oid: targetOid2, mode: "100644" });
319
- } else if (sourceExists && targetExists) {
292
+ } else if (sourceOid2 !== void 0 && targetOid2 !== void 0) {
320
293
  const sourceChanged = sourceOid2 !== baseOid2;
321
294
  const targetChanged = targetOid2 !== baseOid2;
322
295
  if (!sourceChanged && !targetChanged) {
@@ -325,6 +298,23 @@ async function threeWayMerge(repo, sourceRef, targetRef, opts) {
325
298
  resultEntries.set(filepath, { oid: targetOid2, mode: "100644" });
326
299
  } else if (!targetChanged) {
327
300
  resultEntries.set(filepath, { oid: sourceOid2, mode: "100644" });
301
+ } else if (baseOid2 === void 0) {
302
+ if (sourceOid2 === targetOid2) {
303
+ resultEntries.set(filepath, { oid: sourceOid2, mode: "100644" });
304
+ } else {
305
+ const [sourceContent, targetContent] = await Promise.all([
306
+ readBlob(repo, sourceOid2),
307
+ readBlob(repo, targetOid2)
308
+ ]);
309
+ const merged = mergeContents(
310
+ new Uint8Array(),
311
+ sourceContent,
312
+ targetContent
313
+ );
314
+ conflictingPaths.push(filepath);
315
+ const mergedOid = await writeBlob(repo, merged);
316
+ resultEntries.set(filepath, { oid: mergedOid, mode: "100644" });
317
+ }
328
318
  } else {
329
319
  const [baseContent, sourceContent, targetContent] = await Promise.all([
330
320
  readBlob(repo, baseOid2),
@@ -386,7 +376,6 @@ async function threeWayMerge(repo, sourceRef, targetRef, opts) {
386
376
  }
387
377
 
388
378
  // src/repo.ts
389
- var import_isomorphic_git2 = __toESM(require("isomorphic-git"), 1);
390
379
  var repoCaches = /* @__PURE__ */ new Map();
391
380
  function getRepoCache(ownerKey, repoName) {
392
381
  const key = `${ownerKey}/${repoName}`;
@@ -400,48 +389,12 @@ function getRepoCache(ownerKey, repoName) {
400
389
  function invalidateRepoCache(ownerKey, repoName) {
401
390
  repoCaches.delete(`${ownerKey}/${repoName}`);
402
391
  }
403
- async function initBareRepo(repo, defaultBranch = "main") {
404
- await import_isomorphic_git2.default.init({
405
- ...repo,
406
- dir: repo.gitdir,
407
- defaultBranch,
408
- bare: true
409
- });
410
- }
411
- async function estimateRepoSize(repo, stat, list) {
412
- let total = 0;
413
- async function walk(dir) {
414
- let entries;
415
- try {
416
- entries = await list(dir);
417
- } catch {
418
- return;
419
- }
420
- const sizes = await Promise.all(
421
- entries.map(async (name) => {
422
- const fullPath = `${dir}/${name}`;
423
- try {
424
- const s = await stat(fullPath);
425
- return s.size;
426
- } catch {
427
- return 0;
428
- }
429
- })
430
- );
431
- for (const size of sizes) total += size;
432
- }
433
- await walk(`${repo.gitdir}/objects`);
434
- return total;
435
- }
436
392
  // Annotate the CommonJS export names for ESM import in node:
437
393
  0 && (module.exports = {
438
394
  GitEdgeError,
439
395
  GitMergeConflictError,
440
- analyzeMerge,
441
396
  createParsedObjectCache,
442
- estimateRepoSize,
443
397
  getRepoCache,
444
- initBareRepo,
445
398
  invalidateRepoCache,
446
399
  threeWayMerge
447
400
  });
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/cache.ts","../src/errors.ts","../src/merge.ts","../src/repo.ts"],"sourcesContent":["/**\n * git-edge\n *\n * High-level edge-compatible git operations.\n * Composes with git-fs-s3 for storage.\n */\n\nexport {\n\tcreateParsedObjectCache,\n\ttype ParsedObjectCacheOptions,\n\ttype ParsedObjectStore,\n} from \"./cache.js\";\nexport {\n\tGitEdgeError,\n\tGitMergeConflictError,\n} from \"./errors.js\";\nexport {\n\tanalyzeMerge,\n\ttype MergeAnalysis,\n\ttype MergeOpts,\n\ttype MergeResult,\n\ttype Repo,\n\tthreeWayMerge,\n} from \"./merge.js\";\nexport {\n\testimateRepoSize,\n\tgetRepoCache,\n\tinitBareRepo,\n\tinvalidateRepoCache,\n} from \"./repo.js\";\n","/**\n * Generalized parsed-object LRU cache for isomorphic-git.\n *\n * Stores JS values directly — avoids JSON.parse on every hot-path read.\n * Configurable maxSize (bytes estimate via JSON.stringify.length) and TTL.\n *\n * Edge-compatible: no node: imports.\n */\nimport { LRUCache } from \"lru-cache\";\n\nexport interface ParsedObjectCacheOptions {\n\t/** Maximum estimated size in bytes (default: 256 MB). */\n\tmaxSize?: number;\n\t/** Time-to-live in milliseconds (default: 1 hour). */\n\tttl?: number;\n}\n\nexport interface ParsedObjectStore {\n\tget<T extends object>(key: string): T | null;\n\tset<T extends object>(key: string, value: T): void;\n\tdelete(key: string): void;\n\t/** Delete all keys matching a prefix. */\n\tinvalidatePrefix(prefix: string): void;\n}\n\n/**\n * Create a parsed-object cache instance.\n *\n * `key` convention: `\"${oid}:${format}\"` or any caller-chosen string.\n * `sizeCalculation` uses `JSON.stringify(v).length` as a byte estimate.\n */\nexport function createParsedObjectCache(\n\toptions?: ParsedObjectCacheOptions,\n): ParsedObjectStore {\n\tconst maxSize = options?.maxSize ?? 256 * 1024 * 1024;\n\tconst ttl = options?.ttl ?? 3600 * 1000;\n\n\tconst cache = new LRUCache<string, object>({\n\t\tmaxSize,\n\t\tsizeCalculation: (v) => JSON.stringify(v).length,\n\t\tttl,\n\t});\n\n\treturn {\n\t\tget<T extends object>(key: string): T | null {\n\t\t\treturn (cache.get(key) as T) ?? null;\n\t\t},\n\t\tset<T extends object>(key: string, value: T): void {\n\t\t\tcache.set(key, value);\n\t\t},\n\t\tdelete(key: string): void {\n\t\t\tcache.delete(key);\n\t\t},\n\t\tinvalidatePrefix(prefix: string): void {\n\t\t\tfor (const k of cache.keys()) {\n\t\t\t\tif (k.startsWith(prefix)) cache.delete(k);\n\t\t\t}\n\t\t},\n\t};\n}\n","/**\n * Error types for git-edge.\n *\n * Edge-compatible: no node: imports.\n */\n\n/** Base class for all git-edge errors. */\nexport class GitEdgeError extends Error {\n\tconstructor(message: string) {\n\t\tsuper(message);\n\t\tthis.name = \"GitEdgeError\";\n\t\tError.captureStackTrace?.(this, this.constructor);\n\t}\n}\n\n/** Thrown when a three-way merge has unresolvable conflicts. */\nexport class GitMergeConflictError extends GitEdgeError {\n\treadonly conflictingPaths: string[];\n\n\tconstructor(conflictingPaths: string[]) {\n\t\tsuper(\n\t\t\t`Merge conflict in ${conflictingPaths.length} file(s): ${conflictingPaths.join(\", \")}`,\n\t\t);\n\t\tthis.name = \"GitMergeConflictError\";\n\t\tthis.conflictingPaths = conflictingPaths;\n\t}\n}\n","/**\n * Object-level three-way merge for isomorphic-git.\n *\n * Merges two commits by walking their trees directly — no worktree, no disk.\n * Returns the merged commit OID, or throws MergeConflictError with the list of\n * conflicting file paths.\n *\n * Edge-compatible: no node: imports, uses only isomorphic-git object APIs.\n */\n\nimport type { FsClient } from \"isomorphic-git\";\nimport git from \"isomorphic-git\";\nimport { GitMergeConflictError } from \"./errors.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** Options for threeWayMerge. */\nexport interface MergeOpts {\n\t/** Merge commit message (default: \"Merge <source> into <target>\"). */\n\tmessage?: string;\n\t/** Author name (default: \"Git Edge\"). */\n\tauthorName?: string;\n\t/** Author email (default: \"git-edge@local\"). */\n\tauthorEmail?: string;\n}\n\n/** Result of a successful merge. */\nexport interface MergeResult {\n\tcommitOid: string;\n}\n\n/** Analysis of whether a merge is possible. */\nexport interface MergeAnalysis {\n\t/** True if both refs resolved. */\n\tcanMerge: boolean;\n\t/** True if source is an ancestor of target (fast-forward possible). */\n\tfastForward: boolean;\n\t/** True if source and target diverged. */\n\tdiverged: boolean;\n}\n\n/** The repo fs+gitdir shape used by isomorphic-git. */\nexport interface Repo {\n\tfs: FsClient;\n\tgitdir: string;\n\tcache?: object;\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction defaultAuthor() {\n\treturn {\n\t\tname: \"Git Edge\",\n\t\temail: \"git-edge@local\",\n\t\ttimestamp: Math.floor(Date.now() / 1000),\n\t\ttimezoneOffset: 0,\n\t};\n}\n\n/** Read a tree object into a flat map of path → oid. */\nasync function _flattenTree(\n\trepo: Repo,\n\ttreeOid: string,\n): Promise<Map<string, string>> {\n\tconst map = new Map<string, string>();\n\tconst entries = (await git.readTree({ ...repo, oid: treeOid })).tree;\n\tfor (const entry of entries) {\n\t\tmap.set(entry.path, entry.oid);\n\t}\n\treturn map;\n}\n\n/** Recursively flatten a tree into path → oid entries. */\nasync function flattenTreeDeep(\n\trepo: Repo,\n\ttreeOid: string,\n\tprefix: string,\n): Promise<Map<string, string>> {\n\tconst map = new Map<string, string>();\n\tconst entries = (await git.readTree({ ...repo, oid: treeOid })).tree;\n\tawait Promise.all(\n\t\tentries.map(async (entry) => {\n\t\t\tconst fullPath = prefix ? `${prefix}/${entry.path}` : entry.path;\n\t\t\tif (entry.type === \"tree\") {\n\t\t\t\tconst sub = await flattenTreeDeep(repo, entry.oid, fullPath);\n\t\t\t\tfor (const [k, v] of sub) map.set(k, v);\n\t\t\t} else {\n\t\t\t\tmap.set(fullPath, entry.oid);\n\t\t\t}\n\t\t}),\n\t);\n\treturn map;\n}\n\n/** Read a blob and return it as a Uint8Array. */\nasync function readBlob(repo: Repo, oid: string): Promise<Uint8Array> {\n\tconst { blob } = await git.readBlob({ ...repo, oid });\n\treturn blob;\n}\n\n/** Write a blob into the object store. */\nasync function writeBlob(repo: Repo, content: Uint8Array): Promise<string> {\n\treturn git.writeBlob({ ...repo, blob: content });\n}\n\n/** Write a tree object from a flat path→oid map. */\nasync function writeTreeObject(\n\trepo: Repo,\n\tentries: Array<{\n\t\tpath: string;\n\t\toid: string;\n\t\tmode: string;\n\t\ttype: \"blob\" | \"tree\";\n\t}>,\n): Promise<string> {\n\treturn git.writeObject({\n\t\t...repo,\n\t\ttype: \"tree\",\n\t\tobject: entries.map((e) => ({\n\t\t\tpath: e.path,\n\t\t\toid: e.oid,\n\t\t\tmode: e.mode,\n\t\t\ttype: e.type,\n\t\t})),\n\t\tformat: \"parsed\",\n\t});\n}\n\n/**\n * Write a tree from a flat path→{oid,mode} map by recursively building\n * intermediate trees for subdirectories.\n */\nasync function writeTreeFromFlat(\n\trepo: Repo,\n\tflat: Map<string, { oid: string; mode: string }>,\n): Promise<string> {\n\t// Group entries by first path segment\n\tconst root = new Map<string, { oid: string; mode: string }>();\n\tconst children = new Map<\n\t\tstring,\n\t\tMap<string, { oid: string; mode: string }>\n\t>();\n\n\tfor (const [p, v] of flat) {\n\t\tconst slash = p.indexOf(\"/\");\n\t\tif (slash === -1) {\n\t\t\troot.set(p, v);\n\t\t} else {\n\t\t\tconst dir = p.slice(0, slash);\n\t\t\tconst rest = p.slice(slash + 1);\n\t\t\tif (!children.has(dir)) children.set(dir, new Map());\n\t\t\tchildren.get(dir)?.set(rest, v);\n\t\t}\n\t}\n\n\tconst treeEntries: Array<{\n\t\tpath: string;\n\t\toid: string;\n\t\tmode: string;\n\t\ttype: \"blob\" | \"tree\";\n\t}> = [];\n\n\tfor (const [name, { oid, mode }] of root) {\n\t\ttreeEntries.push({ path: name, oid, mode, type: \"blob\" });\n\t}\n\n\tfor (const [dirName, subMap] of children) {\n\t\tconst subOid = await writeTreeFromFlat(repo, subMap);\n\t\ttreeEntries.push({\n\t\t\tpath: dirName,\n\t\t\toid: subOid,\n\t\t\tmode: \"040000\",\n\t\t\ttype: \"tree\",\n\t\t});\n\t}\n\n\treturn writeTreeObject(repo, treeEntries);\n}\n\n/**\n * Simple byte-level 3-way merge on two Uint8Array contents.\n *\n * This is a line-level merge using a basic diff algorithm.\n * For production quality, callers should use the `diff` library —\n * this is the fallback for edge environments where `diff` is not available.\n */\nfunction mergeContents(\n\tbase: Uint8Array,\n\tours: Uint8Array,\n\ttheirs: Uint8Array,\n): Uint8Array {\n\tconst baseLines = decodeLines(base);\n\tconst oursLines = decodeLines(ours);\n\tconst theirsLines = decodeLines(theirs);\n\n\t// If either side is identical to base, take the other\n\tif (arraysEqual(baseLines, oursLines)) return theirs;\n\tif (arraysEqual(baseLines, theirsLines)) return ours;\n\tif (arraysEqual(oursLines, theirsLines)) return ours;\n\n\t// Simple line-by-line merge: walk all three in lockstep\n\tconst result: string[] = [];\n\tlet bi = 0,\n\t\toi = 0,\n\t\tti = 0;\n\n\twhile (\n\t\tbi < baseLines.length ||\n\t\toi < oursLines.length ||\n\t\tti < theirsLines.length\n\t) {\n\t\tconst b = bi < baseLines.length ? baseLines[bi] : undefined;\n\t\tconst o = oi < oursLines.length ? oursLines[oi] : undefined;\n\t\tconst t = ti < theirsLines.length ? theirsLines[ti] : undefined;\n\n\t\tif (b === o && b === t) {\n\t\t\t// All three agree\n\t\t\tresult.push(b!);\n\t\t\tbi++;\n\t\t\toi++;\n\t\t\tti++;\n\t\t} else if (b === o) {\n\t\t\t// Only theirs changed\n\t\t\tresult.push(t!);\n\t\t\tbi++;\n\t\t\toi++;\n\t\t\tti++;\n\t\t} else if (b === t) {\n\t\t\t// Only ours changed\n\t\t\tresult.push(o!);\n\t\t\tbi++;\n\t\t\toi++;\n\t\t\tti++;\n\t\t} else {\n\t\t\t// Both changed — conflict: take ours + theirs separated by conflict markers\n\t\t\tresult.push(`<<<<<<< ours`);\n\t\t\tresult.push(o!);\n\t\t\tresult.push(`=======`);\n\t\t\tresult.push(t!);\n\t\t\tresult.push(`>>>>>>> theirs`);\n\t\t\tbi++;\n\t\t\toi++;\n\t\t\tti++;\n\t\t}\n\t}\n\n\treturn encodeLines(result);\n}\n\nfunction decodeLines(data: Uint8Array): string[] {\n\tconst text = new TextDecoder().decode(data);\n\tif (text === \"\") return [];\n\treturn text.split(\"\\n\");\n}\n\nfunction encodeLines(lines: string[]): Uint8Array {\n\treturn new TextEncoder().encode(lines.join(\"\\n\"));\n}\n\nfunction arraysEqual(a: string[], b: string[]): boolean {\n\tif (a.length !== b.length) return false;\n\tfor (let i = 0; i < a.length; i++) {\n\t\tif (a[i] !== b[i]) return false;\n\t}\n\treturn true;\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Analyze whether a merge is possible between two refs.\n *\n * Returns `canMerge`, `fastForward`, and `diverged` — does not attempt\n * a real content merge (no conflict detection in analysis).\n *\n * `canMerge: false` means one of the refs didn't resolve (deleted branch,\n * typo) — a `NotFoundError` from `git.resolveRef`. Any other failure (a\n * network/storage error reading the ref, a corrupted repo) is rethrown\n * rather than folded into `canMerge: false`, which would otherwise report\n * \"this branch doesn't exist\" for a transient failure that has nothing to\n * do with whether the branches can merge.\n */\nexport async function analyzeMerge(\n\trepo: Repo,\n\tsourceRef: string,\n\ttargetRef: string,\n): Promise<MergeAnalysis> {\n\ttry {\n\t\tconst sourceOid = await git.resolveRef({ ...repo, ref: sourceRef });\n\t\tconst targetOid = await git.resolveRef({ ...repo, ref: targetRef });\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\tfastForward: isDescendant,\n\t\t\tdiverged: !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\treturn { canMerge: false, fastForward: false, diverged: false };\n\t}\n}\n\n/**\n * Perform an object-level three-way merge.\n *\n * - If source is an ancestor of target (fast-forward), moves target ref to source.\n * - If target is an ancestor of source (reverse FF), moves target ref to source.\n * - Otherwise, performs a true 3-way merge at the tree level.\n *\n * Throws `GitMergeConflictError` if the merge has unresolvable conflicts.\n */\nexport async function threeWayMerge(\n\trepo: Repo,\n\tsourceRef: string,\n\ttargetRef: string,\n\topts?: MergeOpts,\n): Promise<MergeResult> {\n\tconst [sourceOid, targetOid] = await Promise.all([\n\t\tgit.resolveRef({ ...repo, ref: sourceRef }),\n\t\tgit.resolveRef({ ...repo, ref: targetRef }),\n\t]);\n\n\tif (sourceOid === targetOid) {\n\t\treturn { commitOid: sourceOid };\n\t}\n\n\t// Fast-forward: source is ahead of target\n\tconst isSourceAhead = await git.isDescendent({\n\t\t...repo,\n\t\toid: sourceOid,\n\t\tancestor: targetOid,\n\t});\n\tif (isSourceAhead) {\n\t\tawait git.writeRef({\n\t\t\t...repo,\n\t\t\tref: targetRef,\n\t\t\tvalue: sourceOid,\n\t\t\tforce: true,\n\t\t});\n\t\treturn { commitOid: sourceOid };\n\t}\n\n\t// Reverse fast-forward: target is ahead of source\n\tconst isTargetAhead = await git.isDescendent({\n\t\t...repo,\n\t\toid: targetOid,\n\t\tancestor: sourceOid,\n\t});\n\tif (isTargetAhead) {\n\t\tawait git.writeRef({\n\t\t\t...repo,\n\t\t\tref: targetRef,\n\t\t\tvalue: sourceOid,\n\t\t\tforce: true,\n\t\t});\n\t\treturn { commitOid: sourceOid };\n\t}\n\n\t// True 3-way merge\n\tconst baseOids = await git.findMergeBase({\n\t\t...repo,\n\t\toids: [sourceOid, targetOid],\n\t});\n\n\tif (!baseOids || baseOids.length === 0) {\n\t\tthrow new Error(\"No merge base found between source and target\");\n\t}\n\n\tconst baseOid = baseOids[0];\n\n\t// Read commit objects to get their trees\n\tconst [baseCommit, sourceCommit, targetCommit] = await Promise.all([\n\t\tgit.readCommit({ ...repo, oid: baseOid }),\n\t\tgit.readCommit({ ...repo, oid: sourceOid }),\n\t\tgit.readCommit({ ...repo, oid: targetOid }),\n\t]);\n\n\tconst baseTreeOid = baseCommit.commit.tree;\n\tconst sourceTreeOid = sourceCommit.commit.tree;\n\tconst targetTreeOid = targetCommit.commit.tree;\n\n\t// Flatten all three trees\n\tconst [baseMap, sourceMap, targetMap] = await Promise.all([\n\t\tflattenTreeDeep(repo, baseTreeOid, \"\"),\n\t\tflattenTreeDeep(repo, sourceTreeOid, \"\"),\n\t\tflattenTreeDeep(repo, targetTreeOid, \"\"),\n\t]);\n\n\t// Collect all unique paths\n\tconst allPaths = new Set([\n\t\t...baseMap.keys(),\n\t\t...sourceMap.keys(),\n\t\t...targetMap.keys(),\n\t]);\n\n\tconst resultEntries = new Map<string, { oid: string; mode: string }>();\n\tconst conflictingPaths: string[] = [];\n\n\tfor (const filepath of allPaths) {\n\t\tconst baseOid = baseMap.get(filepath);\n\t\tconst sourceOid = sourceMap.get(filepath);\n\t\tconst targetOid = targetMap.get(filepath);\n\n\t\tconst _baseExists = baseOid !== undefined;\n\t\tconst sourceExists = sourceOid !== undefined;\n\t\tconst targetExists = targetOid !== undefined;\n\n\t\tif (sourceExists && !targetExists) {\n\t\t\t// Added in source, not in target — take source\n\t\t\tresultEntries.set(filepath, { oid: sourceOid!, mode: \"100644\" });\n\t\t} else if (!sourceExists && targetExists) {\n\t\t\t// Deleted in source, exists in target — keep target\n\t\t\tresultEntries.set(filepath, { oid: targetOid!, mode: \"100644\" });\n\t\t} else if (sourceExists && targetExists) {\n\t\t\t// Exists in both — check if both changed from base\n\t\t\tconst sourceChanged = sourceOid !== baseOid;\n\t\t\tconst targetChanged = targetOid !== baseOid;\n\n\t\t\tif (!sourceChanged && !targetChanged) {\n\t\t\t\t// Neither changed — keep as-is\n\t\t\t\tresultEntries.set(filepath, { oid: targetOid!, mode: \"100644\" });\n\t\t\t} else if (!sourceChanged) {\n\t\t\t\t// Only target changed — take target\n\t\t\t\tresultEntries.set(filepath, { oid: targetOid!, mode: \"100644\" });\n\t\t\t} else if (!targetChanged) {\n\t\t\t\t// Only source changed — take source\n\t\t\t\tresultEntries.set(filepath, { oid: sourceOid!, mode: \"100644\" });\n\t\t\t} else {\n\t\t\t\t// Both changed — attempt content merge\n\t\t\t\tconst [baseContent, sourceContent, targetContent] = await Promise.all([\n\t\t\t\t\treadBlob(repo, baseOid!),\n\t\t\t\t\treadBlob(repo, sourceOid!),\n\t\t\t\t\treadBlob(repo, targetOid!),\n\t\t\t\t]);\n\n\t\t\t\tif (\n\t\t\t\t\tbaseContent.length === sourceContent.length &&\n\t\t\t\t\tbaseContent.every((b, i) => b === sourceContent[i])\n\t\t\t\t) {\n\t\t\t\t\t// Source is identical to base — take target\n\t\t\t\t\tresultEntries.set(filepath, { oid: targetOid!, mode: \"100644\" });\n\t\t\t\t} else if (\n\t\t\t\t\tbaseContent.length === targetContent.length &&\n\t\t\t\t\tbaseContent.every((b, i) => b === targetContent[i])\n\t\t\t\t) {\n\t\t\t\t\t// Target is identical to base — take source\n\t\t\t\t\tresultEntries.set(filepath, { oid: sourceOid!, mode: \"100644\" });\n\t\t\t\t} else if (\n\t\t\t\t\tsourceContent.length === targetContent.length &&\n\t\t\t\t\tsourceContent.every((b, i) => b === targetContent[i])\n\t\t\t\t) {\n\t\t\t\t\t// Source and target are identical — keep either\n\t\t\t\t\tresultEntries.set(filepath, { oid: sourceOid!, mode: \"100644\" });\n\t\t\t\t} else {\n\t\t\t\t\t// Both truly changed — attempt line-level merge\n\t\t\t\t\tconst merged = mergeContents(\n\t\t\t\t\t\tbaseContent,\n\t\t\t\t\t\tsourceContent,\n\t\t\t\t\t\ttargetContent,\n\t\t\t\t\t);\n\n\t\t\t\t\t// Check for conflict markers\n\t\t\t\t\tconst mergedText = new TextDecoder().decode(merged);\n\t\t\t\t\tif (\n\t\t\t\t\t\tmergedText.includes(\"<<<<<<< ours\") ||\n\t\t\t\t\t\tmergedText.includes(\">>>>>>> theirs\")\n\t\t\t\t\t) {\n\t\t\t\t\t\tconflictingPaths.push(filepath);\n\t\t\t\t\t\t// Still take the merged result with conflict markers\n\t\t\t\t\t\t// so the caller can see what happened\n\t\t\t\t\t\tconst mergedOid = await writeBlob(repo, merged);\n\t\t\t\t\t\tresultEntries.set(filepath, { oid: mergedOid, mode: \"100644\" });\n\t\t\t\t\t} else {\n\t\t\t\t\t\tconst mergedOid = await writeBlob(repo, merged);\n\t\t\t\t\t\tresultEntries.set(filepath, { oid: mergedOid, mode: \"100644\" });\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\t// Deleted in both — no-op\n\t\t}\n\t}\n\n\tif (conflictingPaths.length > 0) {\n\t\tthrow new GitMergeConflictError(conflictingPaths);\n\t}\n\n\t// Build the result tree\n\tconst resultTreeOid = await writeTreeFromFlat(repo, resultEntries);\n\n\t// Create the merge commit\n\tconst author = opts?.authorName\n\t\t? {\n\t\t\t\tname: opts.authorName,\n\t\t\t\temail: opts.authorEmail || \"git-edge@local\",\n\t\t\t\ttimestamp: Math.floor(Date.now() / 1000),\n\t\t\t\ttimezoneOffset: 0,\n\t\t\t}\n\t\t: defaultAuthor();\n\n\tconst message = opts?.message || `Merge ${sourceRef} into ${targetRef}`;\n\n\tconst mergeCommitOid = await git.commit({\n\t\t...repo,\n\t\tmessage,\n\t\ttree: resultTreeOid,\n\t\tparent: [targetOid, sourceOid],\n\t\tauthor,\n\t\tcommitter: author,\n\t});\n\n\t// Update the target ref\n\tawait git.writeRef({\n\t\t...repo,\n\t\tref: targetRef,\n\t\tvalue: mergeCommitOid,\n\t\tforce: true,\n\t});\n\n\treturn { commitOid: mergeCommitOid };\n}\n","/**\n * Repository management utilities.\n *\n * Edge-compatible helpers for isomorphic-git repos: bare repo init,\n * packfile-only disk usage, and isomorphic-git cache management.\n *\n * No node: imports. The `fs` parameter is caller-provided.\n */\n\nimport type { FsClient } from \"isomorphic-git\";\nimport git from \"isomorphic-git\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** The repo fs+gitdir shape used by isomorphic-git. */\nexport interface Repo {\n\tfs: FsClient;\n\tgitdir: string;\n\tcache?: object;\n}\n\n// ---------------------------------------------------------------------------\n// isomorphic-git cache management\n// ---------------------------------------------------------------------------\n\n/**\n * Per-repo isomorphic-git packfile cache.\n *\n * isomorphic-git re-parses a packfile index from scratch on every\n * readTree/log/readObject call unless callers share a `cache` object\n * across calls. Without this, operations that touch many objects\n * (e.g. walking commit history) pay that parse cost hundreds of times.\n * Objects are content-addressed/immutable so a long-lived per-repo\n * cache is safe.\n */\nconst repoCaches = new Map<string, object>();\n\nexport function getRepoCache(ownerKey: string, repoName: string): object {\n\tconst key = `${ownerKey}/${repoName}`;\n\tlet cache = repoCaches.get(key);\n\tif (!cache) {\n\t\tcache = {};\n\t\trepoCaches.set(key, cache);\n\t}\n\treturn cache;\n}\n\nexport function invalidateRepoCache(ownerKey: string, repoName: string): void {\n\trepoCaches.delete(`${ownerKey}/${repoName}`);\n}\n\n// ---------------------------------------------------------------------------\n// Repo init\n// ---------------------------------------------------------------------------\n\n/**\n * Initialize a new bare git repository.\n *\n * Accepts any fs implementation — works with node:fs, S3-backed fs,\n * or any isomorphic-git-compatible filesystem.\n *\n * Returns the gitdir path.\n */\nexport async function initBareRepo(\n\trepo: Repo,\n\tdefaultBranch: string = \"main\",\n): Promise<void> {\n\tawait git.init({\n\t\t...repo,\n\t\tdir: repo.gitdir,\n\t\tdefaultBranch,\n\t\tbare: true,\n\t});\n}\n\n// ---------------------------------------------------------------------------\n// Disk usage\n// ---------------------------------------------------------------------------\n\n/**\n * Estimate the disk usage of a bare repo's git objects.\n *\n * This walks the loose object directories and reads `.pack` file sizes\n * from the `objects/` directory. For S3-backed repos, this may be\n * expensive — prefer tracking size at write time if possible.\n *\n * The caller must provide a `stat` function that works with their fs.\n * For node:fs this is `fs.stat`; for edge runtimes it may be a HEAD\n * request to the S3 object.\n */\nexport async function estimateRepoSize(\n\trepo: Repo,\n\tstat: (path: string) => Promise<{ size: number }>,\n\tlist: (path: string) => Promise<string[]>,\n): Promise<number> {\n\tlet total = 0;\n\n\tasync function walk(dir: string): Promise<void> {\n\t\tlet entries: string[];\n\t\ttry {\n\t\t\tentries = await list(dir);\n\t\t} catch {\n\t\t\treturn;\n\t\t}\n\n\t\tconst sizes = await Promise.all(\n\t\t\tentries.map(async (name) => {\n\t\t\t\tconst fullPath = `${dir}/${name}`;\n\t\t\t\ttry {\n\t\t\t\t\tconst s = await stat(fullPath);\n\t\t\t\t\treturn s.size;\n\t\t\t\t} catch {\n\t\t\t\t\treturn 0;\n\t\t\t\t}\n\t\t\t}),\n\t\t);\n\n\t\tfor (const size of sizes) total += size;\n\t}\n\n\tawait walk(`${repo.gitdir}/objects`);\n\treturn total;\n}\n"],"mappings":"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","names":["git","baseOid","sourceOid","targetOid","import_isomorphic_git","git"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/cache.ts","../src/errors.ts","../src/merge.ts","../src/repo.ts"],"sourcesContent":["/**\n * git-edge\n *\n * High-level edge-compatible git operations.\n * Composes with git-fs-s3 for storage.\n */\n\nexport {\n\tcreateParsedObjectCache,\n\ttype ParsedObjectCacheOptions,\n\ttype ParsedObjectStore,\n} from \"./cache.js\";\nexport {\n\tGitEdgeError,\n\tGitMergeConflictError,\n} from \"./errors.js\";\nexport {\n\ttype MergeOpts,\n\ttype MergeResult,\n\ttype Repo,\n\tthreeWayMerge,\n} from \"./merge.js\";\nexport { getRepoCache, invalidateRepoCache } from \"./repo.js\";\n","/**\n * Generalized parsed-object LRU cache for isomorphic-git.\n *\n * Stores JS values directly — avoids JSON.parse on every hot-path read.\n * Configurable maxSize (bytes estimate via JSON.stringify.length) and TTL.\n *\n * Edge-compatible: no node: imports.\n */\nimport { LRUCache } from \"lru-cache\";\n\nexport interface ParsedObjectCacheOptions {\n\t/** Maximum estimated size in bytes (default: 256 MB). */\n\tmaxSize?: number;\n\t/** Time-to-live in milliseconds (default: 1 hour). */\n\tttl?: number;\n}\n\nexport interface ParsedObjectStore {\n\tget<T extends object>(key: string): T | null;\n\tset<T extends object>(key: string, value: T): void;\n\tdelete(key: string): void;\n\t/** Delete all keys matching a prefix. */\n\tinvalidatePrefix(prefix: string): void;\n}\n\n/**\n * Create a parsed-object cache instance.\n *\n * `key` convention: `\"${oid}:${format}\"` or any caller-chosen string.\n * `sizeCalculation` uses `JSON.stringify(v).length` as a byte estimate.\n */\nexport function createParsedObjectCache(\n\toptions?: ParsedObjectCacheOptions,\n): ParsedObjectStore {\n\tconst maxSize = options?.maxSize ?? 256 * 1024 * 1024;\n\tconst ttl = options?.ttl ?? 3600 * 1000;\n\n\tconst cache = new LRUCache<string, object>({\n\t\tmaxSize,\n\t\tsizeCalculation: (v) => JSON.stringify(v).length,\n\t\tttl,\n\t});\n\n\treturn {\n\t\tget<T extends object>(key: string): T | null {\n\t\t\treturn (cache.get(key) as T) ?? null;\n\t\t},\n\t\tset<T extends object>(key: string, value: T): void {\n\t\t\tcache.set(key, value);\n\t\t},\n\t\tdelete(key: string): void {\n\t\t\tcache.delete(key);\n\t\t},\n\t\tinvalidatePrefix(prefix: string): void {\n\t\t\tfor (const k of cache.keys()) {\n\t\t\t\tif (k.startsWith(prefix)) cache.delete(k);\n\t\t\t}\n\t\t},\n\t};\n}\n","/**\n * Error types for git-edge.\n *\n * Edge-compatible: no node: imports.\n */\n\n/** Base class for all git-edge errors. */\nexport class GitEdgeError extends Error {\n\tconstructor(message: string) {\n\t\tsuper(message);\n\t\tthis.name = \"GitEdgeError\";\n\t\tError.captureStackTrace?.(this, this.constructor);\n\t}\n}\n\n/** Thrown when a three-way merge has unresolvable conflicts. */\nexport class GitMergeConflictError extends GitEdgeError {\n\treadonly conflictingPaths: string[];\n\n\tconstructor(conflictingPaths: string[]) {\n\t\tsuper(\n\t\t\t`Merge conflict in ${conflictingPaths.length} file(s): ${conflictingPaths.join(\", \")}`,\n\t\t);\n\t\tthis.name = \"GitMergeConflictError\";\n\t\tthis.conflictingPaths = conflictingPaths;\n\t}\n}\n","/**\n * Object-level three-way merge for isomorphic-git.\n *\n * Merges two commits by walking their trees directly — no worktree, no disk.\n * Returns the merged commit OID, or throws MergeConflictError with the list of\n * conflicting file paths.\n *\n * Edge-compatible: no node: imports, uses only isomorphic-git object APIs.\n */\n\nimport type { Repo } from \"git-fs-s3/ops\";\nimport git from \"isomorphic-git\";\nimport { GitMergeConflictError } from \"./errors.js\";\n\nexport type { Repo };\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** Options for threeWayMerge. */\nexport interface MergeOpts {\n\t/** Merge commit message (default: \"Merge <source> into <target>\"). */\n\tmessage?: string;\n\t/** Author name (default: \"Git Edge\"). */\n\tauthorName?: string;\n\t/** Author email (default: \"git-edge@local\"). */\n\tauthorEmail?: string;\n}\n\n/** Result of a successful merge. */\nexport interface MergeResult {\n\tcommitOid: string;\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction defaultAuthor() {\n\treturn {\n\t\tname: \"Git Edge\",\n\t\temail: \"git-edge@local\",\n\t\ttimestamp: Math.floor(Date.now() / 1000),\n\t\ttimezoneOffset: 0,\n\t};\n}\n\n/** Recursively flatten a tree into path → oid entries. */\nasync function flattenTreeDeep(\n\trepo: Repo,\n\ttreeOid: string,\n\tprefix: string,\n): Promise<Map<string, string>> {\n\tconst map = new Map<string, string>();\n\tconst entries = (await git.readTree({ ...repo, oid: treeOid })).tree;\n\tawait Promise.all(\n\t\tentries.map(async (entry) => {\n\t\t\tconst fullPath = prefix ? `${prefix}/${entry.path}` : entry.path;\n\t\t\tif (entry.type === \"tree\") {\n\t\t\t\tconst sub = await flattenTreeDeep(repo, entry.oid, fullPath);\n\t\t\t\tfor (const [k, v] of sub) map.set(k, v);\n\t\t\t} else {\n\t\t\t\tmap.set(fullPath, entry.oid);\n\t\t\t}\n\t\t}),\n\t);\n\treturn map;\n}\n\n/** Read a blob and return it as a Uint8Array. */\nasync function readBlob(repo: Repo, oid: string): Promise<Uint8Array> {\n\tconst { blob } = await git.readBlob({ ...repo, oid });\n\treturn blob;\n}\n\n/** Write a blob into the object store. */\nasync function writeBlob(repo: Repo, content: Uint8Array): Promise<string> {\n\treturn git.writeBlob({ ...repo, blob: content });\n}\n\n/** Write a tree object from a flat path→oid map. */\nasync function writeTreeObject(\n\trepo: Repo,\n\tentries: Array<{\n\t\tpath: string;\n\t\toid: string;\n\t\tmode: string;\n\t\ttype: \"blob\" | \"tree\";\n\t}>,\n): Promise<string> {\n\treturn git.writeObject({\n\t\t...repo,\n\t\ttype: \"tree\",\n\t\tobject: entries.map((e) => ({\n\t\t\tpath: e.path,\n\t\t\toid: e.oid,\n\t\t\tmode: e.mode,\n\t\t\ttype: e.type,\n\t\t})),\n\t\tformat: \"parsed\",\n\t});\n}\n\n/**\n * Write a tree from a flat path→{oid,mode} map by recursively building\n * intermediate trees for subdirectories.\n */\nasync function writeTreeFromFlat(\n\trepo: Repo,\n\tflat: Map<string, { oid: string; mode: string }>,\n): Promise<string> {\n\t// Group entries by first path segment\n\tconst root = new Map<string, { oid: string; mode: string }>();\n\tconst children = new Map<\n\t\tstring,\n\t\tMap<string, { oid: string; mode: string }>\n\t>();\n\n\tfor (const [p, v] of flat) {\n\t\tconst slash = p.indexOf(\"/\");\n\t\tif (slash === -1) {\n\t\t\troot.set(p, v);\n\t\t} else {\n\t\t\tconst dir = p.slice(0, slash);\n\t\t\tconst rest = p.slice(slash + 1);\n\t\t\tif (!children.has(dir)) children.set(dir, new Map());\n\t\t\tchildren.get(dir)?.set(rest, v);\n\t\t}\n\t}\n\n\tconst treeEntries: Array<{\n\t\tpath: string;\n\t\toid: string;\n\t\tmode: string;\n\t\ttype: \"blob\" | \"tree\";\n\t}> = [];\n\n\tfor (const [name, { oid, mode }] of root) {\n\t\ttreeEntries.push({ path: name, oid, mode, type: \"blob\" });\n\t}\n\n\tfor (const [dirName, subMap] of children) {\n\t\tconst subOid = await writeTreeFromFlat(repo, subMap);\n\t\ttreeEntries.push({\n\t\t\tpath: dirName,\n\t\t\toid: subOid,\n\t\t\tmode: \"040000\",\n\t\t\ttype: \"tree\",\n\t\t});\n\t}\n\n\treturn writeTreeObject(repo, treeEntries);\n}\n\n/**\n * Simple byte-level 3-way merge on two Uint8Array contents.\n *\n * This is a line-level merge using a basic diff algorithm.\n * For production quality, callers should use the `diff` library —\n * this is the fallback for edge environments where `diff` is not available.\n */\nfunction mergeContents(\n\tbase: Uint8Array,\n\tours: Uint8Array,\n\ttheirs: Uint8Array,\n): Uint8Array {\n\tconst baseLines = decodeLines(base);\n\tconst oursLines = decodeLines(ours);\n\tconst theirsLines = decodeLines(theirs);\n\n\t// If either side is identical to base, take the other\n\tif (arraysEqual(baseLines, oursLines)) return theirs;\n\tif (arraysEqual(baseLines, theirsLines)) return ours;\n\tif (arraysEqual(oursLines, theirsLines)) return ours;\n\n\t// Simple line-by-line merge: walk all three in lockstep\n\tconst result: string[] = [];\n\tlet bi = 0,\n\t\toi = 0,\n\t\tti = 0;\n\n\twhile (\n\t\tbi < baseLines.length ||\n\t\toi < oursLines.length ||\n\t\tti < theirsLines.length\n\t) {\n\t\tconst b = bi < baseLines.length ? baseLines[bi] : undefined;\n\t\tconst o = oi < oursLines.length ? oursLines[oi] : undefined;\n\t\tconst t = ti < theirsLines.length ? theirsLines[ti] : undefined;\n\n\t\tif (b === o && b === t) {\n\t\t\t// All three agree\n\t\t\tresult.push(b ?? \"\");\n\t\t\tbi++;\n\t\t\toi++;\n\t\t\tti++;\n\t\t} else if (b === o) {\n\t\t\t// Only theirs changed — including the case where theirs ran out\n\t\t\t// of lines here (t undefined, i.e. a deletion): `?? \"\"` matches\n\t\t\t// what pushing `t` directly already did once joined below\n\t\t\t// (Array.prototype.join renders an undefined element as \"\"),\n\t\t\t// just without relying on `!` to silence the type checker\n\t\t\t// about it.\n\t\t\tresult.push(t ?? \"\");\n\t\t\tbi++;\n\t\t\toi++;\n\t\t\tti++;\n\t\t} else if (b === t) {\n\t\t\t// Only ours changed — symmetric to the theirs case above.\n\t\t\tresult.push(o ?? \"\");\n\t\t\tbi++;\n\t\t\toi++;\n\t\t\tti++;\n\t\t} else {\n\t\t\t// Both changed — conflict: take ours + theirs separated by\n\t\t\t// conflict markers. o or t can individually be undefined here\n\t\t\t// too (one side ran out of lines while the other still\n\t\t\t// conflicts with base) — same `?? \"\"` reasoning as above.\n\t\t\tresult.push(`<<<<<<< ours`);\n\t\t\tresult.push(o ?? \"\");\n\t\t\tresult.push(`=======`);\n\t\t\tresult.push(t ?? \"\");\n\t\t\tresult.push(`>>>>>>> theirs`);\n\t\t\tbi++;\n\t\t\toi++;\n\t\t\tti++;\n\t\t}\n\t}\n\n\treturn encodeLines(result);\n}\n\nfunction decodeLines(data: Uint8Array): string[] {\n\tconst text = new TextDecoder().decode(data);\n\tif (text === \"\") return [];\n\treturn text.split(\"\\n\");\n}\n\nfunction encodeLines(lines: string[]): Uint8Array {\n\treturn new TextEncoder().encode(lines.join(\"\\n\"));\n}\n\nfunction arraysEqual(a: string[], b: string[]): boolean {\n\tif (a.length !== b.length) return false;\n\tfor (let i = 0; i < a.length; i++) {\n\t\tif (a[i] !== b[i]) return false;\n\t}\n\treturn true;\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Perform an object-level three-way merge.\n *\n * - If source is an ancestor of target (fast-forward), moves target ref to source.\n * - If target is an ancestor of source (reverse FF), moves target ref to source.\n * - Otherwise, performs a true 3-way merge at the tree level.\n *\n * Throws `GitMergeConflictError` if the merge has unresolvable conflicts.\n */\nexport async function threeWayMerge(\n\trepo: Repo,\n\tsourceRef: string,\n\ttargetRef: string,\n\topts?: MergeOpts,\n): Promise<MergeResult> {\n\tconst [sourceOid, targetOid] = await Promise.all([\n\t\tgit.resolveRef({ ...repo, ref: sourceRef }),\n\t\tgit.resolveRef({ ...repo, ref: targetRef }),\n\t]);\n\n\tif (sourceOid === targetOid) {\n\t\treturn { commitOid: sourceOid };\n\t}\n\n\t// Fast-forward: source is ahead of target\n\tconst isSourceAhead = await git.isDescendent({\n\t\t...repo,\n\t\toid: sourceOid,\n\t\tancestor: targetOid,\n\t});\n\tif (isSourceAhead) {\n\t\tawait git.writeRef({\n\t\t\t...repo,\n\t\t\tref: targetRef,\n\t\t\tvalue: sourceOid,\n\t\t\tforce: true,\n\t\t});\n\t\treturn { commitOid: sourceOid };\n\t}\n\n\t// Reverse fast-forward: target is ahead of source\n\tconst isTargetAhead = await git.isDescendent({\n\t\t...repo,\n\t\toid: targetOid,\n\t\tancestor: sourceOid,\n\t});\n\tif (isTargetAhead) {\n\t\tawait git.writeRef({\n\t\t\t...repo,\n\t\t\tref: targetRef,\n\t\t\tvalue: sourceOid,\n\t\t\tforce: true,\n\t\t});\n\t\treturn { commitOid: sourceOid };\n\t}\n\n\t// True 3-way merge\n\tconst baseOids = await git.findMergeBase({\n\t\t...repo,\n\t\toids: [sourceOid, targetOid],\n\t});\n\n\tif (!baseOids || baseOids.length === 0) {\n\t\tthrow new Error(\"No merge base found between source and target\");\n\t}\n\n\tconst baseOid = baseOids[0];\n\n\t// Read commit objects to get their trees\n\tconst [baseCommit, sourceCommit, targetCommit] = await Promise.all([\n\t\tgit.readCommit({ ...repo, oid: baseOid }),\n\t\tgit.readCommit({ ...repo, oid: sourceOid }),\n\t\tgit.readCommit({ ...repo, oid: targetOid }),\n\t]);\n\n\tconst baseTreeOid = baseCommit.commit.tree;\n\tconst sourceTreeOid = sourceCommit.commit.tree;\n\tconst targetTreeOid = targetCommit.commit.tree;\n\n\t// Flatten all three trees\n\tconst [baseMap, sourceMap, targetMap] = await Promise.all([\n\t\tflattenTreeDeep(repo, baseTreeOid, \"\"),\n\t\tflattenTreeDeep(repo, sourceTreeOid, \"\"),\n\t\tflattenTreeDeep(repo, targetTreeOid, \"\"),\n\t]);\n\n\t// Collect all unique paths\n\tconst allPaths = new Set([\n\t\t...baseMap.keys(),\n\t\t...sourceMap.keys(),\n\t\t...targetMap.keys(),\n\t]);\n\n\tconst resultEntries = new Map<string, { oid: string; mode: string }>();\n\tconst conflictingPaths: string[] = [];\n\n\tfor (const filepath of allPaths) {\n\t\tconst baseOid = baseMap.get(filepath);\n\t\tconst sourceOid = sourceMap.get(filepath);\n\t\tconst targetOid = targetMap.get(filepath);\n\n\t\tif (sourceOid !== undefined && targetOid === undefined) {\n\t\t\t// Added in source, not in target — take source\n\t\t\tresultEntries.set(filepath, { oid: sourceOid, mode: \"100644\" });\n\t\t} else if (sourceOid === undefined && targetOid !== undefined) {\n\t\t\t// Deleted in source, exists in target — keep target\n\t\t\tresultEntries.set(filepath, { oid: targetOid, mode: \"100644\" });\n\t\t} else if (sourceOid !== undefined && targetOid !== undefined) {\n\t\t\t// Exists in both — check if both changed from base. A path with\n\t\t\t// no base entry (both sides independently added it — no common\n\t\t\t// ancestor content to diff against) is its own case below,\n\t\t\t// compared directly against each other instead of a base blob\n\t\t\t// that doesn't exist.\n\t\t\tconst sourceChanged = sourceOid !== baseOid;\n\t\t\tconst targetChanged = targetOid !== baseOid;\n\n\t\t\tif (!sourceChanged && !targetChanged) {\n\t\t\t\t// Neither changed — keep as-is\n\t\t\t\tresultEntries.set(filepath, { oid: targetOid, mode: \"100644\" });\n\t\t\t} else if (!sourceChanged) {\n\t\t\t\t// Only target changed — take target\n\t\t\t\tresultEntries.set(filepath, { oid: targetOid, mode: \"100644\" });\n\t\t\t} else if (!targetChanged) {\n\t\t\t\t// Only source changed — take source\n\t\t\t\tresultEntries.set(filepath, { oid: sourceOid, mode: \"100644\" });\n\t\t\t} else if (baseOid === undefined) {\n\t\t\t\t// Both sides independently added this path. Same oid means\n\t\t\t\t// identical content on both sides — no real conflict. Different\n\t\t\t\t// oids is a genuine conflict with no common ancestor to 3-way\n\t\t\t\t// merge against, so run the line-level merge against an empty\n\t\t\t\t// \"base\" — mergeContents already treats an exhausted side as\n\t\t\t\t// producing full conflict markers around whatever content the\n\t\t\t\t// other side has.\n\t\t\t\tif (sourceOid === targetOid) {\n\t\t\t\t\tresultEntries.set(filepath, { oid: sourceOid, mode: \"100644\" });\n\t\t\t\t} else {\n\t\t\t\t\tconst [sourceContent, targetContent] = await Promise.all([\n\t\t\t\t\t\treadBlob(repo, sourceOid),\n\t\t\t\t\t\treadBlob(repo, targetOid),\n\t\t\t\t\t]);\n\t\t\t\t\tconst merged = mergeContents(\n\t\t\t\t\t\tnew Uint8Array(),\n\t\t\t\t\t\tsourceContent,\n\t\t\t\t\t\ttargetContent,\n\t\t\t\t\t);\n\t\t\t\t\tconflictingPaths.push(filepath);\n\t\t\t\t\tconst mergedOid = await writeBlob(repo, merged);\n\t\t\t\t\tresultEntries.set(filepath, { oid: mergedOid, mode: \"100644\" });\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Both changed — attempt content merge\n\t\t\t\tconst [baseContent, sourceContent, targetContent] = await Promise.all([\n\t\t\t\t\treadBlob(repo, baseOid),\n\t\t\t\t\treadBlob(repo, sourceOid),\n\t\t\t\t\treadBlob(repo, targetOid),\n\t\t\t\t]);\n\n\t\t\t\tif (\n\t\t\t\t\tbaseContent.length === sourceContent.length &&\n\t\t\t\t\tbaseContent.every((b, i) => b === sourceContent[i])\n\t\t\t\t) {\n\t\t\t\t\t// Source is identical to base — take target\n\t\t\t\t\tresultEntries.set(filepath, { oid: targetOid, mode: \"100644\" });\n\t\t\t\t} else if (\n\t\t\t\t\tbaseContent.length === targetContent.length &&\n\t\t\t\t\tbaseContent.every((b, i) => b === targetContent[i])\n\t\t\t\t) {\n\t\t\t\t\t// Target is identical to base — take source\n\t\t\t\t\tresultEntries.set(filepath, { oid: sourceOid, mode: \"100644\" });\n\t\t\t\t} else if (\n\t\t\t\t\tsourceContent.length === targetContent.length &&\n\t\t\t\t\tsourceContent.every((b, i) => b === targetContent[i])\n\t\t\t\t) {\n\t\t\t\t\t// Source and target are identical — keep either\n\t\t\t\t\tresultEntries.set(filepath, { oid: sourceOid, mode: \"100644\" });\n\t\t\t\t} else {\n\t\t\t\t\t// Both truly changed — attempt line-level merge\n\t\t\t\t\tconst merged = mergeContents(\n\t\t\t\t\t\tbaseContent,\n\t\t\t\t\t\tsourceContent,\n\t\t\t\t\t\ttargetContent,\n\t\t\t\t\t);\n\n\t\t\t\t\t// Check for conflict markers\n\t\t\t\t\tconst mergedText = new TextDecoder().decode(merged);\n\t\t\t\t\tif (\n\t\t\t\t\t\tmergedText.includes(\"<<<<<<< ours\") ||\n\t\t\t\t\t\tmergedText.includes(\">>>>>>> theirs\")\n\t\t\t\t\t) {\n\t\t\t\t\t\tconflictingPaths.push(filepath);\n\t\t\t\t\t\t// Still take the merged result with conflict markers\n\t\t\t\t\t\t// so the caller can see what happened\n\t\t\t\t\t\tconst mergedOid = await writeBlob(repo, merged);\n\t\t\t\t\t\tresultEntries.set(filepath, { oid: mergedOid, mode: \"100644\" });\n\t\t\t\t\t} else {\n\t\t\t\t\t\tconst mergedOid = await writeBlob(repo, merged);\n\t\t\t\t\t\tresultEntries.set(filepath, { oid: mergedOid, mode: \"100644\" });\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\t// Deleted in both — no-op\n\t\t}\n\t}\n\n\tif (conflictingPaths.length > 0) {\n\t\tthrow new GitMergeConflictError(conflictingPaths);\n\t}\n\n\t// Build the result tree\n\tconst resultTreeOid = await writeTreeFromFlat(repo, resultEntries);\n\n\t// Create the merge commit\n\tconst author = opts?.authorName\n\t\t? {\n\t\t\t\tname: opts.authorName,\n\t\t\t\temail: opts.authorEmail || \"git-edge@local\",\n\t\t\t\ttimestamp: Math.floor(Date.now() / 1000),\n\t\t\t\ttimezoneOffset: 0,\n\t\t\t}\n\t\t: defaultAuthor();\n\n\tconst message = opts?.message || `Merge ${sourceRef} into ${targetRef}`;\n\n\tconst mergeCommitOid = await git.commit({\n\t\t...repo,\n\t\tmessage,\n\t\ttree: resultTreeOid,\n\t\tparent: [targetOid, sourceOid],\n\t\tauthor,\n\t\tcommitter: author,\n\t});\n\n\t// Update the target ref\n\tawait git.writeRef({\n\t\t...repo,\n\t\tref: targetRef,\n\t\tvalue: mergeCommitOid,\n\t\tforce: true,\n\t});\n\n\treturn { commitOid: mergeCommitOid };\n}\n","/**\n * isomorphic-git per-repo parse cache management.\n *\n * No node: imports.\n */\n\n// ---------------------------------------------------------------------------\n// isomorphic-git cache management\n// ---------------------------------------------------------------------------\n\n/**\n * Per-repo isomorphic-git packfile cache.\n *\n * isomorphic-git re-parses a packfile index from scratch on every\n * readTree/log/readObject call unless callers share a `cache` object\n * across calls. Without this, operations that touch many objects\n * (e.g. walking commit history) pay that parse cost hundreds of times.\n * Objects are content-addressed/immutable so a long-lived per-repo\n * cache is safe.\n */\nconst repoCaches = new Map<string, object>();\n\nexport function getRepoCache(ownerKey: string, repoName: string): object {\n\tconst key = `${ownerKey}/${repoName}`;\n\tlet cache = repoCaches.get(key);\n\tif (!cache) {\n\t\tcache = {};\n\t\trepoCaches.set(key, cache);\n\t}\n\treturn cache;\n}\n\nexport function invalidateRepoCache(ownerKey: string, repoName: string): void {\n\trepoCaches.delete(`${ownerKey}/${repoName}`);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACQA,uBAAyB;AAuBlB,SAAS,wBACf,SACoB;AACpB,QAAM,UAAU,SAAS,WAAW,MAAM,OAAO;AACjD,QAAM,MAAM,SAAS,OAAO,OAAO;AAEnC,QAAM,QAAQ,IAAI,0BAAyB;AAAA,IAC1C;AAAA,IACA,iBAAiB,CAAC,MAAM,KAAK,UAAU,CAAC,EAAE;AAAA,IAC1C;AAAA,EACD,CAAC;AAED,SAAO;AAAA,IACN,IAAsB,KAAuB;AAC5C,aAAQ,MAAM,IAAI,GAAG,KAAW;AAAA,IACjC;AAAA,IACA,IAAsB,KAAa,OAAgB;AAClD,YAAM,IAAI,KAAK,KAAK;AAAA,IACrB;AAAA,IACA,OAAO,KAAmB;AACzB,YAAM,OAAO,GAAG;AAAA,IACjB;AAAA,IACA,iBAAiB,QAAsB;AACtC,iBAAW,KAAK,MAAM,KAAK,GAAG;AAC7B,YAAI,EAAE,WAAW,MAAM,EAAG,OAAM,OAAO,CAAC;AAAA,MACzC;AAAA,IACD;AAAA,EACD;AACD;;;ACpDO,IAAM,eAAN,cAA2B,MAAM;AAAA,EACvC,YAAY,SAAiB;AAC5B,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,UAAM,oBAAoB,MAAM,KAAK,WAAW;AAAA,EACjD;AACD;AAGO,IAAM,wBAAN,cAAoC,aAAa;AAAA,EAC9C;AAAA,EAET,YAAY,kBAA4B;AACvC;AAAA,MACC,qBAAqB,iBAAiB,MAAM,aAAa,iBAAiB,KAAK,IAAI,CAAC;AAAA,IACrF;AACA,SAAK,OAAO;AACZ,SAAK,mBAAmB;AAAA,EACzB;AACD;;;ACfA,4BAAgB;AA4BhB,SAAS,gBAAgB;AACxB,SAAO;AAAA,IACN,MAAM;AAAA,IACN,OAAO;AAAA,IACP,WAAW,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;AAAA,IACvC,gBAAgB;AAAA,EACjB;AACD;AAGA,eAAe,gBACd,MACA,SACA,QAC+B;AAC/B,QAAM,MAAM,oBAAI,IAAoB;AACpC,QAAM,WAAW,MAAM,sBAAAA,QAAI,SAAS,EAAE,GAAG,MAAM,KAAK,QAAQ,CAAC,GAAG;AAChE,QAAM,QAAQ;AAAA,IACb,QAAQ,IAAI,OAAO,UAAU;AAC5B,YAAM,WAAW,SAAS,GAAG,MAAM,IAAI,MAAM,IAAI,KAAK,MAAM;AAC5D,UAAI,MAAM,SAAS,QAAQ;AAC1B,cAAM,MAAM,MAAM,gBAAgB,MAAM,MAAM,KAAK,QAAQ;AAC3D,mBAAW,CAAC,GAAG,CAAC,KAAK,IAAK,KAAI,IAAI,GAAG,CAAC;AAAA,MACvC,OAAO;AACN,YAAI,IAAI,UAAU,MAAM,GAAG;AAAA,MAC5B;AAAA,IACD,CAAC;AAAA,EACF;AACA,SAAO;AACR;AAGA,eAAe,SAAS,MAAY,KAAkC;AACrE,QAAM,EAAE,KAAK,IAAI,MAAM,sBAAAA,QAAI,SAAS,EAAE,GAAG,MAAM,IAAI,CAAC;AACpD,SAAO;AACR;AAGA,eAAe,UAAU,MAAY,SAAsC;AAC1E,SAAO,sBAAAA,QAAI,UAAU,EAAE,GAAG,MAAM,MAAM,QAAQ,CAAC;AAChD;AAGA,eAAe,gBACd,MACA,SAMkB;AAClB,SAAO,sBAAAA,QAAI,YAAY;AAAA,IACtB,GAAG;AAAA,IACH,MAAM;AAAA,IACN,QAAQ,QAAQ,IAAI,CAAC,OAAO;AAAA,MAC3B,MAAM,EAAE;AAAA,MACR,KAAK,EAAE;AAAA,MACP,MAAM,EAAE;AAAA,MACR,MAAM,EAAE;AAAA,IACT,EAAE;AAAA,IACF,QAAQ;AAAA,EACT,CAAC;AACF;AAMA,eAAe,kBACd,MACA,MACkB;AAElB,QAAM,OAAO,oBAAI,IAA2C;AAC5D,QAAM,WAAW,oBAAI,IAGnB;AAEF,aAAW,CAAC,GAAG,CAAC,KAAK,MAAM;AAC1B,UAAM,QAAQ,EAAE,QAAQ,GAAG;AAC3B,QAAI,UAAU,IAAI;AACjB,WAAK,IAAI,GAAG,CAAC;AAAA,IACd,OAAO;AACN,YAAM,MAAM,EAAE,MAAM,GAAG,KAAK;AAC5B,YAAM,OAAO,EAAE,MAAM,QAAQ,CAAC;AAC9B,UAAI,CAAC,SAAS,IAAI,GAAG,EAAG,UAAS,IAAI,KAAK,oBAAI,IAAI,CAAC;AACnD,eAAS,IAAI,GAAG,GAAG,IAAI,MAAM,CAAC;AAAA,IAC/B;AAAA,EACD;AAEA,QAAM,cAKD,CAAC;AAEN,aAAW,CAAC,MAAM,EAAE,KAAK,KAAK,CAAC,KAAK,MAAM;AACzC,gBAAY,KAAK,EAAE,MAAM,MAAM,KAAK,MAAM,MAAM,OAAO,CAAC;AAAA,EACzD;AAEA,aAAW,CAAC,SAAS,MAAM,KAAK,UAAU;AACzC,UAAM,SAAS,MAAM,kBAAkB,MAAM,MAAM;AACnD,gBAAY,KAAK;AAAA,MAChB,MAAM;AAAA,MACN,KAAK;AAAA,MACL,MAAM;AAAA,MACN,MAAM;AAAA,IACP,CAAC;AAAA,EACF;AAEA,SAAO,gBAAgB,MAAM,WAAW;AACzC;AASA,SAAS,cACR,MACA,MACA,QACa;AACb,QAAM,YAAY,YAAY,IAAI;AAClC,QAAM,YAAY,YAAY,IAAI;AAClC,QAAM,cAAc,YAAY,MAAM;AAGtC,MAAI,YAAY,WAAW,SAAS,EAAG,QAAO;AAC9C,MAAI,YAAY,WAAW,WAAW,EAAG,QAAO;AAChD,MAAI,YAAY,WAAW,WAAW,EAAG,QAAO;AAGhD,QAAM,SAAmB,CAAC;AAC1B,MAAI,KAAK,GACR,KAAK,GACL,KAAK;AAEN,SACC,KAAK,UAAU,UACf,KAAK,UAAU,UACf,KAAK,YAAY,QAChB;AACD,UAAM,IAAI,KAAK,UAAU,SAAS,UAAU,EAAE,IAAI;AAClD,UAAM,IAAI,KAAK,UAAU,SAAS,UAAU,EAAE,IAAI;AAClD,UAAM,IAAI,KAAK,YAAY,SAAS,YAAY,EAAE,IAAI;AAEtD,QAAI,MAAM,KAAK,MAAM,GAAG;AAEvB,aAAO,KAAK,KAAK,EAAE;AACnB;AACA;AACA;AAAA,IACD,WAAW,MAAM,GAAG;AAOnB,aAAO,KAAK,KAAK,EAAE;AACnB;AACA;AACA;AAAA,IACD,WAAW,MAAM,GAAG;AAEnB,aAAO,KAAK,KAAK,EAAE;AACnB;AACA;AACA;AAAA,IACD,OAAO;AAKN,aAAO,KAAK,cAAc;AAC1B,aAAO,KAAK,KAAK,EAAE;AACnB,aAAO,KAAK,SAAS;AACrB,aAAO,KAAK,KAAK,EAAE;AACnB,aAAO,KAAK,gBAAgB;AAC5B;AACA;AACA;AAAA,IACD;AAAA,EACD;AAEA,SAAO,YAAY,MAAM;AAC1B;AAEA,SAAS,YAAY,MAA4B;AAChD,QAAM,OAAO,IAAI,YAAY,EAAE,OAAO,IAAI;AAC1C,MAAI,SAAS,GAAI,QAAO,CAAC;AACzB,SAAO,KAAK,MAAM,IAAI;AACvB;AAEA,SAAS,YAAY,OAA6B;AACjD,SAAO,IAAI,YAAY,EAAE,OAAO,MAAM,KAAK,IAAI,CAAC;AACjD;AAEA,SAAS,YAAY,GAAa,GAAsB;AACvD,MAAI,EAAE,WAAW,EAAE,OAAQ,QAAO;AAClC,WAAS,IAAI,GAAG,IAAI,EAAE,QAAQ,KAAK;AAClC,QAAI,EAAE,CAAC,MAAM,EAAE,CAAC,EAAG,QAAO;AAAA,EAC3B;AACA,SAAO;AACR;AAeA,eAAsB,cACrB,MACA,WACA,WACA,MACuB;AACvB,QAAM,CAAC,WAAW,SAAS,IAAI,MAAM,QAAQ,IAAI;AAAA,IAChD,sBAAAA,QAAI,WAAW,EAAE,GAAG,MAAM,KAAK,UAAU,CAAC;AAAA,IAC1C,sBAAAA,QAAI,WAAW,EAAE,GAAG,MAAM,KAAK,UAAU,CAAC;AAAA,EAC3C,CAAC;AAED,MAAI,cAAc,WAAW;AAC5B,WAAO,EAAE,WAAW,UAAU;AAAA,EAC/B;AAGA,QAAM,gBAAgB,MAAM,sBAAAA,QAAI,aAAa;AAAA,IAC5C,GAAG;AAAA,IACH,KAAK;AAAA,IACL,UAAU;AAAA,EACX,CAAC;AACD,MAAI,eAAe;AAClB,UAAM,sBAAAA,QAAI,SAAS;AAAA,MAClB,GAAG;AAAA,MACH,KAAK;AAAA,MACL,OAAO;AAAA,MACP,OAAO;AAAA,IACR,CAAC;AACD,WAAO,EAAE,WAAW,UAAU;AAAA,EAC/B;AAGA,QAAM,gBAAgB,MAAM,sBAAAA,QAAI,aAAa;AAAA,IAC5C,GAAG;AAAA,IACH,KAAK;AAAA,IACL,UAAU;AAAA,EACX,CAAC;AACD,MAAI,eAAe;AAClB,UAAM,sBAAAA,QAAI,SAAS;AAAA,MAClB,GAAG;AAAA,MACH,KAAK;AAAA,MACL,OAAO;AAAA,MACP,OAAO;AAAA,IACR,CAAC;AACD,WAAO,EAAE,WAAW,UAAU;AAAA,EAC/B;AAGA,QAAM,WAAW,MAAM,sBAAAA,QAAI,cAAc;AAAA,IACxC,GAAG;AAAA,IACH,MAAM,CAAC,WAAW,SAAS;AAAA,EAC5B,CAAC;AAED,MAAI,CAAC,YAAY,SAAS,WAAW,GAAG;AACvC,UAAM,IAAI,MAAM,+CAA+C;AAAA,EAChE;AAEA,QAAM,UAAU,SAAS,CAAC;AAG1B,QAAM,CAAC,YAAY,cAAc,YAAY,IAAI,MAAM,QAAQ,IAAI;AAAA,IAClE,sBAAAA,QAAI,WAAW,EAAE,GAAG,MAAM,KAAK,QAAQ,CAAC;AAAA,IACxC,sBAAAA,QAAI,WAAW,EAAE,GAAG,MAAM,KAAK,UAAU,CAAC;AAAA,IAC1C,sBAAAA,QAAI,WAAW,EAAE,GAAG,MAAM,KAAK,UAAU,CAAC;AAAA,EAC3C,CAAC;AAED,QAAM,cAAc,WAAW,OAAO;AACtC,QAAM,gBAAgB,aAAa,OAAO;AAC1C,QAAM,gBAAgB,aAAa,OAAO;AAG1C,QAAM,CAAC,SAAS,WAAW,SAAS,IAAI,MAAM,QAAQ,IAAI;AAAA,IACzD,gBAAgB,MAAM,aAAa,EAAE;AAAA,IACrC,gBAAgB,MAAM,eAAe,EAAE;AAAA,IACvC,gBAAgB,MAAM,eAAe,EAAE;AAAA,EACxC,CAAC;AAGD,QAAM,WAAW,oBAAI,IAAI;AAAA,IACxB,GAAG,QAAQ,KAAK;AAAA,IAChB,GAAG,UAAU,KAAK;AAAA,IAClB,GAAG,UAAU,KAAK;AAAA,EACnB,CAAC;AAED,QAAM,gBAAgB,oBAAI,IAA2C;AACrE,QAAM,mBAA6B,CAAC;AAEpC,aAAW,YAAY,UAAU;AAChC,UAAMC,WAAU,QAAQ,IAAI,QAAQ;AACpC,UAAMC,aAAY,UAAU,IAAI,QAAQ;AACxC,UAAMC,aAAY,UAAU,IAAI,QAAQ;AAExC,QAAID,eAAc,UAAaC,eAAc,QAAW;AAEvD,oBAAc,IAAI,UAAU,EAAE,KAAKD,YAAW,MAAM,SAAS,CAAC;AAAA,IAC/D,WAAWA,eAAc,UAAaC,eAAc,QAAW;AAE9D,oBAAc,IAAI,UAAU,EAAE,KAAKA,YAAW,MAAM,SAAS,CAAC;AAAA,IAC/D,WAAWD,eAAc,UAAaC,eAAc,QAAW;AAM9D,YAAM,gBAAgBD,eAAcD;AACpC,YAAM,gBAAgBE,eAAcF;AAEpC,UAAI,CAAC,iBAAiB,CAAC,eAAe;AAErC,sBAAc,IAAI,UAAU,EAAE,KAAKE,YAAW,MAAM,SAAS,CAAC;AAAA,MAC/D,WAAW,CAAC,eAAe;AAE1B,sBAAc,IAAI,UAAU,EAAE,KAAKA,YAAW,MAAM,SAAS,CAAC;AAAA,MAC/D,WAAW,CAAC,eAAe;AAE1B,sBAAc,IAAI,UAAU,EAAE,KAAKD,YAAW,MAAM,SAAS,CAAC;AAAA,MAC/D,WAAWD,aAAY,QAAW;AAQjC,YAAIC,eAAcC,YAAW;AAC5B,wBAAc,IAAI,UAAU,EAAE,KAAKD,YAAW,MAAM,SAAS,CAAC;AAAA,QAC/D,OAAO;AACN,gBAAM,CAAC,eAAe,aAAa,IAAI,MAAM,QAAQ,IAAI;AAAA,YACxD,SAAS,MAAMA,UAAS;AAAA,YACxB,SAAS,MAAMC,UAAS;AAAA,UACzB,CAAC;AACD,gBAAM,SAAS;AAAA,YACd,IAAI,WAAW;AAAA,YACf;AAAA,YACA;AAAA,UACD;AACA,2BAAiB,KAAK,QAAQ;AAC9B,gBAAM,YAAY,MAAM,UAAU,MAAM,MAAM;AAC9C,wBAAc,IAAI,UAAU,EAAE,KAAK,WAAW,MAAM,SAAS,CAAC;AAAA,QAC/D;AAAA,MACD,OAAO;AAEN,cAAM,CAAC,aAAa,eAAe,aAAa,IAAI,MAAM,QAAQ,IAAI;AAAA,UACrE,SAAS,MAAMF,QAAO;AAAA,UACtB,SAAS,MAAMC,UAAS;AAAA,UACxB,SAAS,MAAMC,UAAS;AAAA,QACzB,CAAC;AAED,YACC,YAAY,WAAW,cAAc,UACrC,YAAY,MAAM,CAAC,GAAG,MAAM,MAAM,cAAc,CAAC,CAAC,GACjD;AAED,wBAAc,IAAI,UAAU,EAAE,KAAKA,YAAW,MAAM,SAAS,CAAC;AAAA,QAC/D,WACC,YAAY,WAAW,cAAc,UACrC,YAAY,MAAM,CAAC,GAAG,MAAM,MAAM,cAAc,CAAC,CAAC,GACjD;AAED,wBAAc,IAAI,UAAU,EAAE,KAAKD,YAAW,MAAM,SAAS,CAAC;AAAA,QAC/D,WACC,cAAc,WAAW,cAAc,UACvC,cAAc,MAAM,CAAC,GAAG,MAAM,MAAM,cAAc,CAAC,CAAC,GACnD;AAED,wBAAc,IAAI,UAAU,EAAE,KAAKA,YAAW,MAAM,SAAS,CAAC;AAAA,QAC/D,OAAO;AAEN,gBAAM,SAAS;AAAA,YACd;AAAA,YACA;AAAA,YACA;AAAA,UACD;AAGA,gBAAM,aAAa,IAAI,YAAY,EAAE,OAAO,MAAM;AAClD,cACC,WAAW,SAAS,cAAc,KAClC,WAAW,SAAS,gBAAgB,GACnC;AACD,6BAAiB,KAAK,QAAQ;AAG9B,kBAAM,YAAY,MAAM,UAAU,MAAM,MAAM;AAC9C,0BAAc,IAAI,UAAU,EAAE,KAAK,WAAW,MAAM,SAAS,CAAC;AAAA,UAC/D,OAAO;AACN,kBAAM,YAAY,MAAM,UAAU,MAAM,MAAM;AAC9C,0BAAc,IAAI,UAAU,EAAE,KAAK,WAAW,MAAM,SAAS,CAAC;AAAA,UAC/D;AAAA,QACD;AAAA,MACD;AAAA,IACD,OAAO;AAAA,IAEP;AAAA,EACD;AAEA,MAAI,iBAAiB,SAAS,GAAG;AAChC,UAAM,IAAI,sBAAsB,gBAAgB;AAAA,EACjD;AAGA,QAAM,gBAAgB,MAAM,kBAAkB,MAAM,aAAa;AAGjE,QAAM,SAAS,MAAM,aAClB;AAAA,IACA,MAAM,KAAK;AAAA,IACX,OAAO,KAAK,eAAe;AAAA,IAC3B,WAAW,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;AAAA,IACvC,gBAAgB;AAAA,EACjB,IACC,cAAc;AAEjB,QAAM,UAAU,MAAM,WAAW,SAAS,SAAS,SAAS,SAAS;AAErE,QAAM,iBAAiB,MAAM,sBAAAF,QAAI,OAAO;AAAA,IACvC,GAAG;AAAA,IACH;AAAA,IACA,MAAM;AAAA,IACN,QAAQ,CAAC,WAAW,SAAS;AAAA,IAC7B;AAAA,IACA,WAAW;AAAA,EACZ,CAAC;AAGD,QAAM,sBAAAA,QAAI,SAAS;AAAA,IAClB,GAAG;AAAA,IACH,KAAK;AAAA,IACL,OAAO;AAAA,IACP,OAAO;AAAA,EACR,CAAC;AAED,SAAO,EAAE,WAAW,eAAe;AACpC;;;AC7dA,IAAM,aAAa,oBAAI,IAAoB;AAEpC,SAAS,aAAa,UAAkB,UAA0B;AACxE,QAAM,MAAM,GAAG,QAAQ,IAAI,QAAQ;AACnC,MAAI,QAAQ,WAAW,IAAI,GAAG;AAC9B,MAAI,CAAC,OAAO;AACX,YAAQ,CAAC;AACT,eAAW,IAAI,KAAK,KAAK;AAAA,EAC1B;AACA,SAAO;AACR;AAEO,SAAS,oBAAoB,UAAkB,UAAwB;AAC7E,aAAW,OAAO,GAAG,QAAQ,IAAI,QAAQ,EAAE;AAC5C;","names":["git","baseOid","sourceOid","targetOid"]}
package/dist/index.d.cts CHANGED
@@ -1,4 +1,5 @@
1
- import { FsClient } from 'isomorphic-git';
1
+ import { Repo } from 'git-fs-s3/ops';
2
+ export { Repo } from 'git-fs-s3/ops';
2
3
 
3
4
  interface ParsedObjectCacheOptions {
4
5
  /** Maximum estimated size in bytes (default: 256 MB). */
@@ -59,35 +60,6 @@ interface MergeOpts {
59
60
  interface MergeResult {
60
61
  commitOid: string;
61
62
  }
62
- /** Analysis of whether a merge is possible. */
63
- interface MergeAnalysis {
64
- /** True if both refs resolved. */
65
- canMerge: boolean;
66
- /** True if source is an ancestor of target (fast-forward possible). */
67
- fastForward: boolean;
68
- /** True if source and target diverged. */
69
- diverged: boolean;
70
- }
71
- /** The repo fs+gitdir shape used by isomorphic-git. */
72
- interface Repo$1 {
73
- fs: FsClient;
74
- gitdir: string;
75
- cache?: object;
76
- }
77
- /**
78
- * Analyze whether a merge is possible between two refs.
79
- *
80
- * Returns `canMerge`, `fastForward`, and `diverged` — does not attempt
81
- * a real content merge (no conflict detection in analysis).
82
- *
83
- * `canMerge: false` means one of the refs didn't resolve (deleted branch,
84
- * typo) — a `NotFoundError` from `git.resolveRef`. Any other failure (a
85
- * network/storage error reading the ref, a corrupted repo) is rethrown
86
- * rather than folded into `canMerge: false`, which would otherwise report
87
- * "this branch doesn't exist" for a transient failure that has nothing to
88
- * do with whether the branches can merge.
89
- */
90
- declare function analyzeMerge(repo: Repo$1, sourceRef: string, targetRef: string): Promise<MergeAnalysis>;
91
63
  /**
92
64
  * Perform an object-level three-way merge.
93
65
  *
@@ -97,47 +69,14 @@ declare function analyzeMerge(repo: Repo$1, sourceRef: string, targetRef: string
97
69
  *
98
70
  * Throws `GitMergeConflictError` if the merge has unresolvable conflicts.
99
71
  */
100
- declare function threeWayMerge(repo: Repo$1, sourceRef: string, targetRef: string, opts?: MergeOpts): Promise<MergeResult>;
72
+ declare function threeWayMerge(repo: Repo, sourceRef: string, targetRef: string, opts?: MergeOpts): Promise<MergeResult>;
101
73
 
102
74
  /**
103
- * Repository management utilities.
104
- *
105
- * Edge-compatible helpers for isomorphic-git repos: bare repo init,
106
- * packfile-only disk usage, and isomorphic-git cache management.
75
+ * isomorphic-git per-repo parse cache management.
107
76
  *
108
- * No node: imports. The `fs` parameter is caller-provided.
77
+ * No node: imports.
109
78
  */
110
-
111
- /** The repo fs+gitdir shape used by isomorphic-git. */
112
- interface Repo {
113
- fs: FsClient;
114
- gitdir: string;
115
- cache?: object;
116
- }
117
79
  declare function getRepoCache(ownerKey: string, repoName: string): object;
118
80
  declare function invalidateRepoCache(ownerKey: string, repoName: string): void;
119
- /**
120
- * Initialize a new bare git repository.
121
- *
122
- * Accepts any fs implementation — works with node:fs, S3-backed fs,
123
- * or any isomorphic-git-compatible filesystem.
124
- *
125
- * Returns the gitdir path.
126
- */
127
- declare function initBareRepo(repo: Repo, defaultBranch?: string): Promise<void>;
128
- /**
129
- * Estimate the disk usage of a bare repo's git objects.
130
- *
131
- * This walks the loose object directories and reads `.pack` file sizes
132
- * from the `objects/` directory. For S3-backed repos, this may be
133
- * expensive — prefer tracking size at write time if possible.
134
- *
135
- * The caller must provide a `stat` function that works with their fs.
136
- * For node:fs this is `fs.stat`; for edge runtimes it may be a HEAD
137
- * request to the S3 object.
138
- */
139
- declare function estimateRepoSize(repo: Repo, stat: (path: string) => Promise<{
140
- size: number;
141
- }>, list: (path: string) => Promise<string[]>): Promise<number>;
142
81
 
143
- export { GitEdgeError, GitMergeConflictError, type MergeAnalysis, type MergeOpts, type MergeResult, type ParsedObjectCacheOptions, type ParsedObjectStore, type Repo$1 as Repo, analyzeMerge, createParsedObjectCache, estimateRepoSize, getRepoCache, initBareRepo, invalidateRepoCache, threeWayMerge };
82
+ export { GitEdgeError, GitMergeConflictError, type MergeOpts, type MergeResult, type ParsedObjectCacheOptions, type ParsedObjectStore, createParsedObjectCache, getRepoCache, invalidateRepoCache, threeWayMerge };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
- import { FsClient } from 'isomorphic-git';
1
+ import { Repo } from 'git-fs-s3/ops';
2
+ export { Repo } from 'git-fs-s3/ops';
2
3
 
3
4
  interface ParsedObjectCacheOptions {
4
5
  /** Maximum estimated size in bytes (default: 256 MB). */
@@ -59,35 +60,6 @@ interface MergeOpts {
59
60
  interface MergeResult {
60
61
  commitOid: string;
61
62
  }
62
- /** Analysis of whether a merge is possible. */
63
- interface MergeAnalysis {
64
- /** True if both refs resolved. */
65
- canMerge: boolean;
66
- /** True if source is an ancestor of target (fast-forward possible). */
67
- fastForward: boolean;
68
- /** True if source and target diverged. */
69
- diverged: boolean;
70
- }
71
- /** The repo fs+gitdir shape used by isomorphic-git. */
72
- interface Repo$1 {
73
- fs: FsClient;
74
- gitdir: string;
75
- cache?: object;
76
- }
77
- /**
78
- * Analyze whether a merge is possible between two refs.
79
- *
80
- * Returns `canMerge`, `fastForward`, and `diverged` — does not attempt
81
- * a real content merge (no conflict detection in analysis).
82
- *
83
- * `canMerge: false` means one of the refs didn't resolve (deleted branch,
84
- * typo) — a `NotFoundError` from `git.resolveRef`. Any other failure (a
85
- * network/storage error reading the ref, a corrupted repo) is rethrown
86
- * rather than folded into `canMerge: false`, which would otherwise report
87
- * "this branch doesn't exist" for a transient failure that has nothing to
88
- * do with whether the branches can merge.
89
- */
90
- declare function analyzeMerge(repo: Repo$1, sourceRef: string, targetRef: string): Promise<MergeAnalysis>;
91
63
  /**
92
64
  * Perform an object-level three-way merge.
93
65
  *
@@ -97,47 +69,14 @@ declare function analyzeMerge(repo: Repo$1, sourceRef: string, targetRef: string
97
69
  *
98
70
  * Throws `GitMergeConflictError` if the merge has unresolvable conflicts.
99
71
  */
100
- declare function threeWayMerge(repo: Repo$1, sourceRef: string, targetRef: string, opts?: MergeOpts): Promise<MergeResult>;
72
+ declare function threeWayMerge(repo: Repo, sourceRef: string, targetRef: string, opts?: MergeOpts): Promise<MergeResult>;
101
73
 
102
74
  /**
103
- * Repository management utilities.
104
- *
105
- * Edge-compatible helpers for isomorphic-git repos: bare repo init,
106
- * packfile-only disk usage, and isomorphic-git cache management.
75
+ * isomorphic-git per-repo parse cache management.
107
76
  *
108
- * No node: imports. The `fs` parameter is caller-provided.
77
+ * No node: imports.
109
78
  */
110
-
111
- /** The repo fs+gitdir shape used by isomorphic-git. */
112
- interface Repo {
113
- fs: FsClient;
114
- gitdir: string;
115
- cache?: object;
116
- }
117
79
  declare function getRepoCache(ownerKey: string, repoName: string): object;
118
80
  declare function invalidateRepoCache(ownerKey: string, repoName: string): void;
119
- /**
120
- * Initialize a new bare git repository.
121
- *
122
- * Accepts any fs implementation — works with node:fs, S3-backed fs,
123
- * or any isomorphic-git-compatible filesystem.
124
- *
125
- * Returns the gitdir path.
126
- */
127
- declare function initBareRepo(repo: Repo, defaultBranch?: string): Promise<void>;
128
- /**
129
- * Estimate the disk usage of a bare repo's git objects.
130
- *
131
- * This walks the loose object directories and reads `.pack` file sizes
132
- * from the `objects/` directory. For S3-backed repos, this may be
133
- * expensive — prefer tracking size at write time if possible.
134
- *
135
- * The caller must provide a `stat` function that works with their fs.
136
- * For node:fs this is `fs.stat`; for edge runtimes it may be a HEAD
137
- * request to the S3 object.
138
- */
139
- declare function estimateRepoSize(repo: Repo, stat: (path: string) => Promise<{
140
- size: number;
141
- }>, list: (path: string) => Promise<string[]>): Promise<number>;
142
81
 
143
- export { GitEdgeError, GitMergeConflictError, type MergeAnalysis, type MergeOpts, type MergeResult, type ParsedObjectCacheOptions, type ParsedObjectStore, type Repo$1 as Repo, analyzeMerge, createParsedObjectCache, estimateRepoSize, getRepoCache, initBareRepo, invalidateRepoCache, threeWayMerge };
82
+ export { GitEdgeError, GitMergeConflictError, type MergeOpts, type MergeResult, type ParsedObjectCacheOptions, type ParsedObjectStore, createParsedObjectCache, getRepoCache, invalidateRepoCache, threeWayMerge };
package/dist/index.js CHANGED
@@ -134,25 +134,25 @@ function mergeContents(base, ours, theirs) {
134
134
  const o = oi < oursLines.length ? oursLines[oi] : void 0;
135
135
  const t = ti < theirsLines.length ? theirsLines[ti] : void 0;
136
136
  if (b === o && b === t) {
137
- result.push(b);
137
+ result.push(b ?? "");
138
138
  bi++;
139
139
  oi++;
140
140
  ti++;
141
141
  } else if (b === o) {
142
- result.push(t);
142
+ result.push(t ?? "");
143
143
  bi++;
144
144
  oi++;
145
145
  ti++;
146
146
  } else if (b === t) {
147
- result.push(o);
147
+ result.push(o ?? "");
148
148
  bi++;
149
149
  oi++;
150
150
  ti++;
151
151
  } else {
152
152
  result.push(`<<<<<<< ours`);
153
- result.push(o);
153
+ result.push(o ?? "");
154
154
  result.push(`=======`);
155
- result.push(t);
155
+ result.push(t ?? "");
156
156
  result.push(`>>>>>>> theirs`);
157
157
  bi++;
158
158
  oi++;
@@ -176,27 +176,6 @@ function arraysEqual(a, b) {
176
176
  }
177
177
  return true;
178
178
  }
179
- async function analyzeMerge(repo, sourceRef, targetRef) {
180
- try {
181
- const sourceOid = await git.resolveRef({ ...repo, ref: sourceRef });
182
- const targetOid = await git.resolveRef({ ...repo, ref: targetRef });
183
- const isDescendant = await git.isDescendent({
184
- ...repo,
185
- oid: sourceOid,
186
- ancestor: targetOid
187
- });
188
- return {
189
- canMerge: true,
190
- fastForward: isDescendant,
191
- diverged: !isDescendant
192
- };
193
- } catch (err) {
194
- if (err?.code !== "NotFoundError") {
195
- throw err;
196
- }
197
- return { canMerge: false, fastForward: false, diverged: false };
198
- }
199
- }
200
179
  async function threeWayMerge(repo, sourceRef, targetRef, opts) {
201
180
  const [sourceOid, targetOid] = await Promise.all([
202
181
  git.resolveRef({ ...repo, ref: sourceRef }),
@@ -265,14 +244,11 @@ async function threeWayMerge(repo, sourceRef, targetRef, opts) {
265
244
  const baseOid2 = baseMap.get(filepath);
266
245
  const sourceOid2 = sourceMap.get(filepath);
267
246
  const targetOid2 = targetMap.get(filepath);
268
- const _baseExists = baseOid2 !== void 0;
269
- const sourceExists = sourceOid2 !== void 0;
270
- const targetExists = targetOid2 !== void 0;
271
- if (sourceExists && !targetExists) {
247
+ if (sourceOid2 !== void 0 && targetOid2 === void 0) {
272
248
  resultEntries.set(filepath, { oid: sourceOid2, mode: "100644" });
273
- } else if (!sourceExists && targetExists) {
249
+ } else if (sourceOid2 === void 0 && targetOid2 !== void 0) {
274
250
  resultEntries.set(filepath, { oid: targetOid2, mode: "100644" });
275
- } else if (sourceExists && targetExists) {
251
+ } else if (sourceOid2 !== void 0 && targetOid2 !== void 0) {
276
252
  const sourceChanged = sourceOid2 !== baseOid2;
277
253
  const targetChanged = targetOid2 !== baseOid2;
278
254
  if (!sourceChanged && !targetChanged) {
@@ -281,6 +257,23 @@ async function threeWayMerge(repo, sourceRef, targetRef, opts) {
281
257
  resultEntries.set(filepath, { oid: targetOid2, mode: "100644" });
282
258
  } else if (!targetChanged) {
283
259
  resultEntries.set(filepath, { oid: sourceOid2, mode: "100644" });
260
+ } else if (baseOid2 === void 0) {
261
+ if (sourceOid2 === targetOid2) {
262
+ resultEntries.set(filepath, { oid: sourceOid2, mode: "100644" });
263
+ } else {
264
+ const [sourceContent, targetContent] = await Promise.all([
265
+ readBlob(repo, sourceOid2),
266
+ readBlob(repo, targetOid2)
267
+ ]);
268
+ const merged = mergeContents(
269
+ new Uint8Array(),
270
+ sourceContent,
271
+ targetContent
272
+ );
273
+ conflictingPaths.push(filepath);
274
+ const mergedOid = await writeBlob(repo, merged);
275
+ resultEntries.set(filepath, { oid: mergedOid, mode: "100644" });
276
+ }
284
277
  } else {
285
278
  const [baseContent, sourceContent, targetContent] = await Promise.all([
286
279
  readBlob(repo, baseOid2),
@@ -342,7 +335,6 @@ async function threeWayMerge(repo, sourceRef, targetRef, opts) {
342
335
  }
343
336
 
344
337
  // src/repo.ts
345
- import git2 from "isomorphic-git";
346
338
  var repoCaches = /* @__PURE__ */ new Map();
347
339
  function getRepoCache(ownerKey, repoName) {
348
340
  const key = `${ownerKey}/${repoName}`;
@@ -356,47 +348,11 @@ function getRepoCache(ownerKey, repoName) {
356
348
  function invalidateRepoCache(ownerKey, repoName) {
357
349
  repoCaches.delete(`${ownerKey}/${repoName}`);
358
350
  }
359
- async function initBareRepo(repo, defaultBranch = "main") {
360
- await git2.init({
361
- ...repo,
362
- dir: repo.gitdir,
363
- defaultBranch,
364
- bare: true
365
- });
366
- }
367
- async function estimateRepoSize(repo, stat, list) {
368
- let total = 0;
369
- async function walk(dir) {
370
- let entries;
371
- try {
372
- entries = await list(dir);
373
- } catch {
374
- return;
375
- }
376
- const sizes = await Promise.all(
377
- entries.map(async (name) => {
378
- const fullPath = `${dir}/${name}`;
379
- try {
380
- const s = await stat(fullPath);
381
- return s.size;
382
- } catch {
383
- return 0;
384
- }
385
- })
386
- );
387
- for (const size of sizes) total += size;
388
- }
389
- await walk(`${repo.gitdir}/objects`);
390
- return total;
391
- }
392
351
  export {
393
352
  GitEdgeError,
394
353
  GitMergeConflictError,
395
- analyzeMerge,
396
354
  createParsedObjectCache,
397
- estimateRepoSize,
398
355
  getRepoCache,
399
- initBareRepo,
400
356
  invalidateRepoCache,
401
357
  threeWayMerge
402
358
  };
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/cache.ts","../src/errors.ts","../src/merge.ts","../src/repo.ts"],"sourcesContent":["/**\n * Generalized parsed-object LRU cache for isomorphic-git.\n *\n * Stores JS values directly — avoids JSON.parse on every hot-path read.\n * Configurable maxSize (bytes estimate via JSON.stringify.length) and TTL.\n *\n * Edge-compatible: no node: imports.\n */\nimport { LRUCache } from \"lru-cache\";\n\nexport interface ParsedObjectCacheOptions {\n\t/** Maximum estimated size in bytes (default: 256 MB). */\n\tmaxSize?: number;\n\t/** Time-to-live in milliseconds (default: 1 hour). */\n\tttl?: number;\n}\n\nexport interface ParsedObjectStore {\n\tget<T extends object>(key: string): T | null;\n\tset<T extends object>(key: string, value: T): void;\n\tdelete(key: string): void;\n\t/** Delete all keys matching a prefix. */\n\tinvalidatePrefix(prefix: string): void;\n}\n\n/**\n * Create a parsed-object cache instance.\n *\n * `key` convention: `\"${oid}:${format}\"` or any caller-chosen string.\n * `sizeCalculation` uses `JSON.stringify(v).length` as a byte estimate.\n */\nexport function createParsedObjectCache(\n\toptions?: ParsedObjectCacheOptions,\n): ParsedObjectStore {\n\tconst maxSize = options?.maxSize ?? 256 * 1024 * 1024;\n\tconst ttl = options?.ttl ?? 3600 * 1000;\n\n\tconst cache = new LRUCache<string, object>({\n\t\tmaxSize,\n\t\tsizeCalculation: (v) => JSON.stringify(v).length,\n\t\tttl,\n\t});\n\n\treturn {\n\t\tget<T extends object>(key: string): T | null {\n\t\t\treturn (cache.get(key) as T) ?? null;\n\t\t},\n\t\tset<T extends object>(key: string, value: T): void {\n\t\t\tcache.set(key, value);\n\t\t},\n\t\tdelete(key: string): void {\n\t\t\tcache.delete(key);\n\t\t},\n\t\tinvalidatePrefix(prefix: string): void {\n\t\t\tfor (const k of cache.keys()) {\n\t\t\t\tif (k.startsWith(prefix)) cache.delete(k);\n\t\t\t}\n\t\t},\n\t};\n}\n","/**\n * Error types for git-edge.\n *\n * Edge-compatible: no node: imports.\n */\n\n/** Base class for all git-edge errors. */\nexport class GitEdgeError extends Error {\n\tconstructor(message: string) {\n\t\tsuper(message);\n\t\tthis.name = \"GitEdgeError\";\n\t\tError.captureStackTrace?.(this, this.constructor);\n\t}\n}\n\n/** Thrown when a three-way merge has unresolvable conflicts. */\nexport class GitMergeConflictError extends GitEdgeError {\n\treadonly conflictingPaths: string[];\n\n\tconstructor(conflictingPaths: string[]) {\n\t\tsuper(\n\t\t\t`Merge conflict in ${conflictingPaths.length} file(s): ${conflictingPaths.join(\", \")}`,\n\t\t);\n\t\tthis.name = \"GitMergeConflictError\";\n\t\tthis.conflictingPaths = conflictingPaths;\n\t}\n}\n","/**\n * Object-level three-way merge for isomorphic-git.\n *\n * Merges two commits by walking their trees directly — no worktree, no disk.\n * Returns the merged commit OID, or throws MergeConflictError with the list of\n * conflicting file paths.\n *\n * Edge-compatible: no node: imports, uses only isomorphic-git object APIs.\n */\n\nimport type { FsClient } from \"isomorphic-git\";\nimport git from \"isomorphic-git\";\nimport { GitMergeConflictError } from \"./errors.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** Options for threeWayMerge. */\nexport interface MergeOpts {\n\t/** Merge commit message (default: \"Merge <source> into <target>\"). */\n\tmessage?: string;\n\t/** Author name (default: \"Git Edge\"). */\n\tauthorName?: string;\n\t/** Author email (default: \"git-edge@local\"). */\n\tauthorEmail?: string;\n}\n\n/** Result of a successful merge. */\nexport interface MergeResult {\n\tcommitOid: string;\n}\n\n/** Analysis of whether a merge is possible. */\nexport interface MergeAnalysis {\n\t/** True if both refs resolved. */\n\tcanMerge: boolean;\n\t/** True if source is an ancestor of target (fast-forward possible). */\n\tfastForward: boolean;\n\t/** True if source and target diverged. */\n\tdiverged: boolean;\n}\n\n/** The repo fs+gitdir shape used by isomorphic-git. */\nexport interface Repo {\n\tfs: FsClient;\n\tgitdir: string;\n\tcache?: object;\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction defaultAuthor() {\n\treturn {\n\t\tname: \"Git Edge\",\n\t\temail: \"git-edge@local\",\n\t\ttimestamp: Math.floor(Date.now() / 1000),\n\t\ttimezoneOffset: 0,\n\t};\n}\n\n/** Read a tree object into a flat map of path → oid. */\nasync function _flattenTree(\n\trepo: Repo,\n\ttreeOid: string,\n): Promise<Map<string, string>> {\n\tconst map = new Map<string, string>();\n\tconst entries = (await git.readTree({ ...repo, oid: treeOid })).tree;\n\tfor (const entry of entries) {\n\t\tmap.set(entry.path, entry.oid);\n\t}\n\treturn map;\n}\n\n/** Recursively flatten a tree into path → oid entries. */\nasync function flattenTreeDeep(\n\trepo: Repo,\n\ttreeOid: string,\n\tprefix: string,\n): Promise<Map<string, string>> {\n\tconst map = new Map<string, string>();\n\tconst entries = (await git.readTree({ ...repo, oid: treeOid })).tree;\n\tawait Promise.all(\n\t\tentries.map(async (entry) => {\n\t\t\tconst fullPath = prefix ? `${prefix}/${entry.path}` : entry.path;\n\t\t\tif (entry.type === \"tree\") {\n\t\t\t\tconst sub = await flattenTreeDeep(repo, entry.oid, fullPath);\n\t\t\t\tfor (const [k, v] of sub) map.set(k, v);\n\t\t\t} else {\n\t\t\t\tmap.set(fullPath, entry.oid);\n\t\t\t}\n\t\t}),\n\t);\n\treturn map;\n}\n\n/** Read a blob and return it as a Uint8Array. */\nasync function readBlob(repo: Repo, oid: string): Promise<Uint8Array> {\n\tconst { blob } = await git.readBlob({ ...repo, oid });\n\treturn blob;\n}\n\n/** Write a blob into the object store. */\nasync function writeBlob(repo: Repo, content: Uint8Array): Promise<string> {\n\treturn git.writeBlob({ ...repo, blob: content });\n}\n\n/** Write a tree object from a flat path→oid map. */\nasync function writeTreeObject(\n\trepo: Repo,\n\tentries: Array<{\n\t\tpath: string;\n\t\toid: string;\n\t\tmode: string;\n\t\ttype: \"blob\" | \"tree\";\n\t}>,\n): Promise<string> {\n\treturn git.writeObject({\n\t\t...repo,\n\t\ttype: \"tree\",\n\t\tobject: entries.map((e) => ({\n\t\t\tpath: e.path,\n\t\t\toid: e.oid,\n\t\t\tmode: e.mode,\n\t\t\ttype: e.type,\n\t\t})),\n\t\tformat: \"parsed\",\n\t});\n}\n\n/**\n * Write a tree from a flat path→{oid,mode} map by recursively building\n * intermediate trees for subdirectories.\n */\nasync function writeTreeFromFlat(\n\trepo: Repo,\n\tflat: Map<string, { oid: string; mode: string }>,\n): Promise<string> {\n\t// Group entries by first path segment\n\tconst root = new Map<string, { oid: string; mode: string }>();\n\tconst children = new Map<\n\t\tstring,\n\t\tMap<string, { oid: string; mode: string }>\n\t>();\n\n\tfor (const [p, v] of flat) {\n\t\tconst slash = p.indexOf(\"/\");\n\t\tif (slash === -1) {\n\t\t\troot.set(p, v);\n\t\t} else {\n\t\t\tconst dir = p.slice(0, slash);\n\t\t\tconst rest = p.slice(slash + 1);\n\t\t\tif (!children.has(dir)) children.set(dir, new Map());\n\t\t\tchildren.get(dir)?.set(rest, v);\n\t\t}\n\t}\n\n\tconst treeEntries: Array<{\n\t\tpath: string;\n\t\toid: string;\n\t\tmode: string;\n\t\ttype: \"blob\" | \"tree\";\n\t}> = [];\n\n\tfor (const [name, { oid, mode }] of root) {\n\t\ttreeEntries.push({ path: name, oid, mode, type: \"blob\" });\n\t}\n\n\tfor (const [dirName, subMap] of children) {\n\t\tconst subOid = await writeTreeFromFlat(repo, subMap);\n\t\ttreeEntries.push({\n\t\t\tpath: dirName,\n\t\t\toid: subOid,\n\t\t\tmode: \"040000\",\n\t\t\ttype: \"tree\",\n\t\t});\n\t}\n\n\treturn writeTreeObject(repo, treeEntries);\n}\n\n/**\n * Simple byte-level 3-way merge on two Uint8Array contents.\n *\n * This is a line-level merge using a basic diff algorithm.\n * For production quality, callers should use the `diff` library —\n * this is the fallback for edge environments where `diff` is not available.\n */\nfunction mergeContents(\n\tbase: Uint8Array,\n\tours: Uint8Array,\n\ttheirs: Uint8Array,\n): Uint8Array {\n\tconst baseLines = decodeLines(base);\n\tconst oursLines = decodeLines(ours);\n\tconst theirsLines = decodeLines(theirs);\n\n\t// If either side is identical to base, take the other\n\tif (arraysEqual(baseLines, oursLines)) return theirs;\n\tif (arraysEqual(baseLines, theirsLines)) return ours;\n\tif (arraysEqual(oursLines, theirsLines)) return ours;\n\n\t// Simple line-by-line merge: walk all three in lockstep\n\tconst result: string[] = [];\n\tlet bi = 0,\n\t\toi = 0,\n\t\tti = 0;\n\n\twhile (\n\t\tbi < baseLines.length ||\n\t\toi < oursLines.length ||\n\t\tti < theirsLines.length\n\t) {\n\t\tconst b = bi < baseLines.length ? baseLines[bi] : undefined;\n\t\tconst o = oi < oursLines.length ? oursLines[oi] : undefined;\n\t\tconst t = ti < theirsLines.length ? theirsLines[ti] : undefined;\n\n\t\tif (b === o && b === t) {\n\t\t\t// All three agree\n\t\t\tresult.push(b!);\n\t\t\tbi++;\n\t\t\toi++;\n\t\t\tti++;\n\t\t} else if (b === o) {\n\t\t\t// Only theirs changed\n\t\t\tresult.push(t!);\n\t\t\tbi++;\n\t\t\toi++;\n\t\t\tti++;\n\t\t} else if (b === t) {\n\t\t\t// Only ours changed\n\t\t\tresult.push(o!);\n\t\t\tbi++;\n\t\t\toi++;\n\t\t\tti++;\n\t\t} else {\n\t\t\t// Both changed — conflict: take ours + theirs separated by conflict markers\n\t\t\tresult.push(`<<<<<<< ours`);\n\t\t\tresult.push(o!);\n\t\t\tresult.push(`=======`);\n\t\t\tresult.push(t!);\n\t\t\tresult.push(`>>>>>>> theirs`);\n\t\t\tbi++;\n\t\t\toi++;\n\t\t\tti++;\n\t\t}\n\t}\n\n\treturn encodeLines(result);\n}\n\nfunction decodeLines(data: Uint8Array): string[] {\n\tconst text = new TextDecoder().decode(data);\n\tif (text === \"\") return [];\n\treturn text.split(\"\\n\");\n}\n\nfunction encodeLines(lines: string[]): Uint8Array {\n\treturn new TextEncoder().encode(lines.join(\"\\n\"));\n}\n\nfunction arraysEqual(a: string[], b: string[]): boolean {\n\tif (a.length !== b.length) return false;\n\tfor (let i = 0; i < a.length; i++) {\n\t\tif (a[i] !== b[i]) return false;\n\t}\n\treturn true;\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Analyze whether a merge is possible between two refs.\n *\n * Returns `canMerge`, `fastForward`, and `diverged` — does not attempt\n * a real content merge (no conflict detection in analysis).\n *\n * `canMerge: false` means one of the refs didn't resolve (deleted branch,\n * typo) — a `NotFoundError` from `git.resolveRef`. Any other failure (a\n * network/storage error reading the ref, a corrupted repo) is rethrown\n * rather than folded into `canMerge: false`, which would otherwise report\n * \"this branch doesn't exist\" for a transient failure that has nothing to\n * do with whether the branches can merge.\n */\nexport async function analyzeMerge(\n\trepo: Repo,\n\tsourceRef: string,\n\ttargetRef: string,\n): Promise<MergeAnalysis> {\n\ttry {\n\t\tconst sourceOid = await git.resolveRef({ ...repo, ref: sourceRef });\n\t\tconst targetOid = await git.resolveRef({ ...repo, ref: targetRef });\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\tfastForward: isDescendant,\n\t\t\tdiverged: !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\treturn { canMerge: false, fastForward: false, diverged: false };\n\t}\n}\n\n/**\n * Perform an object-level three-way merge.\n *\n * - If source is an ancestor of target (fast-forward), moves target ref to source.\n * - If target is an ancestor of source (reverse FF), moves target ref to source.\n * - Otherwise, performs a true 3-way merge at the tree level.\n *\n * Throws `GitMergeConflictError` if the merge has unresolvable conflicts.\n */\nexport async function threeWayMerge(\n\trepo: Repo,\n\tsourceRef: string,\n\ttargetRef: string,\n\topts?: MergeOpts,\n): Promise<MergeResult> {\n\tconst [sourceOid, targetOid] = await Promise.all([\n\t\tgit.resolveRef({ ...repo, ref: sourceRef }),\n\t\tgit.resolveRef({ ...repo, ref: targetRef }),\n\t]);\n\n\tif (sourceOid === targetOid) {\n\t\treturn { commitOid: sourceOid };\n\t}\n\n\t// Fast-forward: source is ahead of target\n\tconst isSourceAhead = await git.isDescendent({\n\t\t...repo,\n\t\toid: sourceOid,\n\t\tancestor: targetOid,\n\t});\n\tif (isSourceAhead) {\n\t\tawait git.writeRef({\n\t\t\t...repo,\n\t\t\tref: targetRef,\n\t\t\tvalue: sourceOid,\n\t\t\tforce: true,\n\t\t});\n\t\treturn { commitOid: sourceOid };\n\t}\n\n\t// Reverse fast-forward: target is ahead of source\n\tconst isTargetAhead = await git.isDescendent({\n\t\t...repo,\n\t\toid: targetOid,\n\t\tancestor: sourceOid,\n\t});\n\tif (isTargetAhead) {\n\t\tawait git.writeRef({\n\t\t\t...repo,\n\t\t\tref: targetRef,\n\t\t\tvalue: sourceOid,\n\t\t\tforce: true,\n\t\t});\n\t\treturn { commitOid: sourceOid };\n\t}\n\n\t// True 3-way merge\n\tconst baseOids = await git.findMergeBase({\n\t\t...repo,\n\t\toids: [sourceOid, targetOid],\n\t});\n\n\tif (!baseOids || baseOids.length === 0) {\n\t\tthrow new Error(\"No merge base found between source and target\");\n\t}\n\n\tconst baseOid = baseOids[0];\n\n\t// Read commit objects to get their trees\n\tconst [baseCommit, sourceCommit, targetCommit] = await Promise.all([\n\t\tgit.readCommit({ ...repo, oid: baseOid }),\n\t\tgit.readCommit({ ...repo, oid: sourceOid }),\n\t\tgit.readCommit({ ...repo, oid: targetOid }),\n\t]);\n\n\tconst baseTreeOid = baseCommit.commit.tree;\n\tconst sourceTreeOid = sourceCommit.commit.tree;\n\tconst targetTreeOid = targetCommit.commit.tree;\n\n\t// Flatten all three trees\n\tconst [baseMap, sourceMap, targetMap] = await Promise.all([\n\t\tflattenTreeDeep(repo, baseTreeOid, \"\"),\n\t\tflattenTreeDeep(repo, sourceTreeOid, \"\"),\n\t\tflattenTreeDeep(repo, targetTreeOid, \"\"),\n\t]);\n\n\t// Collect all unique paths\n\tconst allPaths = new Set([\n\t\t...baseMap.keys(),\n\t\t...sourceMap.keys(),\n\t\t...targetMap.keys(),\n\t]);\n\n\tconst resultEntries = new Map<string, { oid: string; mode: string }>();\n\tconst conflictingPaths: string[] = [];\n\n\tfor (const filepath of allPaths) {\n\t\tconst baseOid = baseMap.get(filepath);\n\t\tconst sourceOid = sourceMap.get(filepath);\n\t\tconst targetOid = targetMap.get(filepath);\n\n\t\tconst _baseExists = baseOid !== undefined;\n\t\tconst sourceExists = sourceOid !== undefined;\n\t\tconst targetExists = targetOid !== undefined;\n\n\t\tif (sourceExists && !targetExists) {\n\t\t\t// Added in source, not in target — take source\n\t\t\tresultEntries.set(filepath, { oid: sourceOid!, mode: \"100644\" });\n\t\t} else if (!sourceExists && targetExists) {\n\t\t\t// Deleted in source, exists in target — keep target\n\t\t\tresultEntries.set(filepath, { oid: targetOid!, mode: \"100644\" });\n\t\t} else if (sourceExists && targetExists) {\n\t\t\t// Exists in both — check if both changed from base\n\t\t\tconst sourceChanged = sourceOid !== baseOid;\n\t\t\tconst targetChanged = targetOid !== baseOid;\n\n\t\t\tif (!sourceChanged && !targetChanged) {\n\t\t\t\t// Neither changed — keep as-is\n\t\t\t\tresultEntries.set(filepath, { oid: targetOid!, mode: \"100644\" });\n\t\t\t} else if (!sourceChanged) {\n\t\t\t\t// Only target changed — take target\n\t\t\t\tresultEntries.set(filepath, { oid: targetOid!, mode: \"100644\" });\n\t\t\t} else if (!targetChanged) {\n\t\t\t\t// Only source changed — take source\n\t\t\t\tresultEntries.set(filepath, { oid: sourceOid!, mode: \"100644\" });\n\t\t\t} else {\n\t\t\t\t// Both changed — attempt content merge\n\t\t\t\tconst [baseContent, sourceContent, targetContent] = await Promise.all([\n\t\t\t\t\treadBlob(repo, baseOid!),\n\t\t\t\t\treadBlob(repo, sourceOid!),\n\t\t\t\t\treadBlob(repo, targetOid!),\n\t\t\t\t]);\n\n\t\t\t\tif (\n\t\t\t\t\tbaseContent.length === sourceContent.length &&\n\t\t\t\t\tbaseContent.every((b, i) => b === sourceContent[i])\n\t\t\t\t) {\n\t\t\t\t\t// Source is identical to base — take target\n\t\t\t\t\tresultEntries.set(filepath, { oid: targetOid!, mode: \"100644\" });\n\t\t\t\t} else if (\n\t\t\t\t\tbaseContent.length === targetContent.length &&\n\t\t\t\t\tbaseContent.every((b, i) => b === targetContent[i])\n\t\t\t\t) {\n\t\t\t\t\t// Target is identical to base — take source\n\t\t\t\t\tresultEntries.set(filepath, { oid: sourceOid!, mode: \"100644\" });\n\t\t\t\t} else if (\n\t\t\t\t\tsourceContent.length === targetContent.length &&\n\t\t\t\t\tsourceContent.every((b, i) => b === targetContent[i])\n\t\t\t\t) {\n\t\t\t\t\t// Source and target are identical — keep either\n\t\t\t\t\tresultEntries.set(filepath, { oid: sourceOid!, mode: \"100644\" });\n\t\t\t\t} else {\n\t\t\t\t\t// Both truly changed — attempt line-level merge\n\t\t\t\t\tconst merged = mergeContents(\n\t\t\t\t\t\tbaseContent,\n\t\t\t\t\t\tsourceContent,\n\t\t\t\t\t\ttargetContent,\n\t\t\t\t\t);\n\n\t\t\t\t\t// Check for conflict markers\n\t\t\t\t\tconst mergedText = new TextDecoder().decode(merged);\n\t\t\t\t\tif (\n\t\t\t\t\t\tmergedText.includes(\"<<<<<<< ours\") ||\n\t\t\t\t\t\tmergedText.includes(\">>>>>>> theirs\")\n\t\t\t\t\t) {\n\t\t\t\t\t\tconflictingPaths.push(filepath);\n\t\t\t\t\t\t// Still take the merged result with conflict markers\n\t\t\t\t\t\t// so the caller can see what happened\n\t\t\t\t\t\tconst mergedOid = await writeBlob(repo, merged);\n\t\t\t\t\t\tresultEntries.set(filepath, { oid: mergedOid, mode: \"100644\" });\n\t\t\t\t\t} else {\n\t\t\t\t\t\tconst mergedOid = await writeBlob(repo, merged);\n\t\t\t\t\t\tresultEntries.set(filepath, { oid: mergedOid, mode: \"100644\" });\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\t// Deleted in both — no-op\n\t\t}\n\t}\n\n\tif (conflictingPaths.length > 0) {\n\t\tthrow new GitMergeConflictError(conflictingPaths);\n\t}\n\n\t// Build the result tree\n\tconst resultTreeOid = await writeTreeFromFlat(repo, resultEntries);\n\n\t// Create the merge commit\n\tconst author = opts?.authorName\n\t\t? {\n\t\t\t\tname: opts.authorName,\n\t\t\t\temail: opts.authorEmail || \"git-edge@local\",\n\t\t\t\ttimestamp: Math.floor(Date.now() / 1000),\n\t\t\t\ttimezoneOffset: 0,\n\t\t\t}\n\t\t: defaultAuthor();\n\n\tconst message = opts?.message || `Merge ${sourceRef} into ${targetRef}`;\n\n\tconst mergeCommitOid = await git.commit({\n\t\t...repo,\n\t\tmessage,\n\t\ttree: resultTreeOid,\n\t\tparent: [targetOid, sourceOid],\n\t\tauthor,\n\t\tcommitter: author,\n\t});\n\n\t// Update the target ref\n\tawait git.writeRef({\n\t\t...repo,\n\t\tref: targetRef,\n\t\tvalue: mergeCommitOid,\n\t\tforce: true,\n\t});\n\n\treturn { commitOid: mergeCommitOid };\n}\n","/**\n * Repository management utilities.\n *\n * Edge-compatible helpers for isomorphic-git repos: bare repo init,\n * packfile-only disk usage, and isomorphic-git cache management.\n *\n * No node: imports. The `fs` parameter is caller-provided.\n */\n\nimport type { FsClient } from \"isomorphic-git\";\nimport git from \"isomorphic-git\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** The repo fs+gitdir shape used by isomorphic-git. */\nexport interface Repo {\n\tfs: FsClient;\n\tgitdir: string;\n\tcache?: object;\n}\n\n// ---------------------------------------------------------------------------\n// isomorphic-git cache management\n// ---------------------------------------------------------------------------\n\n/**\n * Per-repo isomorphic-git packfile cache.\n *\n * isomorphic-git re-parses a packfile index from scratch on every\n * readTree/log/readObject call unless callers share a `cache` object\n * across calls. Without this, operations that touch many objects\n * (e.g. walking commit history) pay that parse cost hundreds of times.\n * Objects are content-addressed/immutable so a long-lived per-repo\n * cache is safe.\n */\nconst repoCaches = new Map<string, object>();\n\nexport function getRepoCache(ownerKey: string, repoName: string): object {\n\tconst key = `${ownerKey}/${repoName}`;\n\tlet cache = repoCaches.get(key);\n\tif (!cache) {\n\t\tcache = {};\n\t\trepoCaches.set(key, cache);\n\t}\n\treturn cache;\n}\n\nexport function invalidateRepoCache(ownerKey: string, repoName: string): void {\n\trepoCaches.delete(`${ownerKey}/${repoName}`);\n}\n\n// ---------------------------------------------------------------------------\n// Repo init\n// ---------------------------------------------------------------------------\n\n/**\n * Initialize a new bare git repository.\n *\n * Accepts any fs implementation — works with node:fs, S3-backed fs,\n * or any isomorphic-git-compatible filesystem.\n *\n * Returns the gitdir path.\n */\nexport async function initBareRepo(\n\trepo: Repo,\n\tdefaultBranch: string = \"main\",\n): Promise<void> {\n\tawait git.init({\n\t\t...repo,\n\t\tdir: repo.gitdir,\n\t\tdefaultBranch,\n\t\tbare: true,\n\t});\n}\n\n// ---------------------------------------------------------------------------\n// Disk usage\n// ---------------------------------------------------------------------------\n\n/**\n * Estimate the disk usage of a bare repo's git objects.\n *\n * This walks the loose object directories and reads `.pack` file sizes\n * from the `objects/` directory. For S3-backed repos, this may be\n * expensive — prefer tracking size at write time if possible.\n *\n * The caller must provide a `stat` function that works with their fs.\n * For node:fs this is `fs.stat`; for edge runtimes it may be a HEAD\n * request to the S3 object.\n */\nexport async function estimateRepoSize(\n\trepo: Repo,\n\tstat: (path: string) => Promise<{ size: number }>,\n\tlist: (path: string) => Promise<string[]>,\n): Promise<number> {\n\tlet total = 0;\n\n\tasync function walk(dir: string): Promise<void> {\n\t\tlet entries: string[];\n\t\ttry {\n\t\t\tentries = await list(dir);\n\t\t} catch {\n\t\t\treturn;\n\t\t}\n\n\t\tconst sizes = await Promise.all(\n\t\t\tentries.map(async (name) => {\n\t\t\t\tconst fullPath = `${dir}/${name}`;\n\t\t\t\ttry {\n\t\t\t\t\tconst s = await stat(fullPath);\n\t\t\t\t\treturn s.size;\n\t\t\t\t} catch {\n\t\t\t\t\treturn 0;\n\t\t\t\t}\n\t\t\t}),\n\t\t);\n\n\t\tfor (const size of sizes) total += size;\n\t}\n\n\tawait walk(`${repo.gitdir}/objects`);\n\treturn total;\n}\n"],"mappings":"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","names":["baseOid","sourceOid","targetOid","git"]}
1
+ {"version":3,"sources":["../src/cache.ts","../src/errors.ts","../src/merge.ts","../src/repo.ts"],"sourcesContent":["/**\n * Generalized parsed-object LRU cache for isomorphic-git.\n *\n * Stores JS values directly — avoids JSON.parse on every hot-path read.\n * Configurable maxSize (bytes estimate via JSON.stringify.length) and TTL.\n *\n * Edge-compatible: no node: imports.\n */\nimport { LRUCache } from \"lru-cache\";\n\nexport interface ParsedObjectCacheOptions {\n\t/** Maximum estimated size in bytes (default: 256 MB). */\n\tmaxSize?: number;\n\t/** Time-to-live in milliseconds (default: 1 hour). */\n\tttl?: number;\n}\n\nexport interface ParsedObjectStore {\n\tget<T extends object>(key: string): T | null;\n\tset<T extends object>(key: string, value: T): void;\n\tdelete(key: string): void;\n\t/** Delete all keys matching a prefix. */\n\tinvalidatePrefix(prefix: string): void;\n}\n\n/**\n * Create a parsed-object cache instance.\n *\n * `key` convention: `\"${oid}:${format}\"` or any caller-chosen string.\n * `sizeCalculation` uses `JSON.stringify(v).length` as a byte estimate.\n */\nexport function createParsedObjectCache(\n\toptions?: ParsedObjectCacheOptions,\n): ParsedObjectStore {\n\tconst maxSize = options?.maxSize ?? 256 * 1024 * 1024;\n\tconst ttl = options?.ttl ?? 3600 * 1000;\n\n\tconst cache = new LRUCache<string, object>({\n\t\tmaxSize,\n\t\tsizeCalculation: (v) => JSON.stringify(v).length,\n\t\tttl,\n\t});\n\n\treturn {\n\t\tget<T extends object>(key: string): T | null {\n\t\t\treturn (cache.get(key) as T) ?? null;\n\t\t},\n\t\tset<T extends object>(key: string, value: T): void {\n\t\t\tcache.set(key, value);\n\t\t},\n\t\tdelete(key: string): void {\n\t\t\tcache.delete(key);\n\t\t},\n\t\tinvalidatePrefix(prefix: string): void {\n\t\t\tfor (const k of cache.keys()) {\n\t\t\t\tif (k.startsWith(prefix)) cache.delete(k);\n\t\t\t}\n\t\t},\n\t};\n}\n","/**\n * Error types for git-edge.\n *\n * Edge-compatible: no node: imports.\n */\n\n/** Base class for all git-edge errors. */\nexport class GitEdgeError extends Error {\n\tconstructor(message: string) {\n\t\tsuper(message);\n\t\tthis.name = \"GitEdgeError\";\n\t\tError.captureStackTrace?.(this, this.constructor);\n\t}\n}\n\n/** Thrown when a three-way merge has unresolvable conflicts. */\nexport class GitMergeConflictError extends GitEdgeError {\n\treadonly conflictingPaths: string[];\n\n\tconstructor(conflictingPaths: string[]) {\n\t\tsuper(\n\t\t\t`Merge conflict in ${conflictingPaths.length} file(s): ${conflictingPaths.join(\", \")}`,\n\t\t);\n\t\tthis.name = \"GitMergeConflictError\";\n\t\tthis.conflictingPaths = conflictingPaths;\n\t}\n}\n","/**\n * Object-level three-way merge for isomorphic-git.\n *\n * Merges two commits by walking their trees directly — no worktree, no disk.\n * Returns the merged commit OID, or throws MergeConflictError with the list of\n * conflicting file paths.\n *\n * Edge-compatible: no node: imports, uses only isomorphic-git object APIs.\n */\n\nimport type { Repo } from \"git-fs-s3/ops\";\nimport git from \"isomorphic-git\";\nimport { GitMergeConflictError } from \"./errors.js\";\n\nexport type { Repo };\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** Options for threeWayMerge. */\nexport interface MergeOpts {\n\t/** Merge commit message (default: \"Merge <source> into <target>\"). */\n\tmessage?: string;\n\t/** Author name (default: \"Git Edge\"). */\n\tauthorName?: string;\n\t/** Author email (default: \"git-edge@local\"). */\n\tauthorEmail?: string;\n}\n\n/** Result of a successful merge. */\nexport interface MergeResult {\n\tcommitOid: string;\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction defaultAuthor() {\n\treturn {\n\t\tname: \"Git Edge\",\n\t\temail: \"git-edge@local\",\n\t\ttimestamp: Math.floor(Date.now() / 1000),\n\t\ttimezoneOffset: 0,\n\t};\n}\n\n/** Recursively flatten a tree into path → oid entries. */\nasync function flattenTreeDeep(\n\trepo: Repo,\n\ttreeOid: string,\n\tprefix: string,\n): Promise<Map<string, string>> {\n\tconst map = new Map<string, string>();\n\tconst entries = (await git.readTree({ ...repo, oid: treeOid })).tree;\n\tawait Promise.all(\n\t\tentries.map(async (entry) => {\n\t\t\tconst fullPath = prefix ? `${prefix}/${entry.path}` : entry.path;\n\t\t\tif (entry.type === \"tree\") {\n\t\t\t\tconst sub = await flattenTreeDeep(repo, entry.oid, fullPath);\n\t\t\t\tfor (const [k, v] of sub) map.set(k, v);\n\t\t\t} else {\n\t\t\t\tmap.set(fullPath, entry.oid);\n\t\t\t}\n\t\t}),\n\t);\n\treturn map;\n}\n\n/** Read a blob and return it as a Uint8Array. */\nasync function readBlob(repo: Repo, oid: string): Promise<Uint8Array> {\n\tconst { blob } = await git.readBlob({ ...repo, oid });\n\treturn blob;\n}\n\n/** Write a blob into the object store. */\nasync function writeBlob(repo: Repo, content: Uint8Array): Promise<string> {\n\treturn git.writeBlob({ ...repo, blob: content });\n}\n\n/** Write a tree object from a flat path→oid map. */\nasync function writeTreeObject(\n\trepo: Repo,\n\tentries: Array<{\n\t\tpath: string;\n\t\toid: string;\n\t\tmode: string;\n\t\ttype: \"blob\" | \"tree\";\n\t}>,\n): Promise<string> {\n\treturn git.writeObject({\n\t\t...repo,\n\t\ttype: \"tree\",\n\t\tobject: entries.map((e) => ({\n\t\t\tpath: e.path,\n\t\t\toid: e.oid,\n\t\t\tmode: e.mode,\n\t\t\ttype: e.type,\n\t\t})),\n\t\tformat: \"parsed\",\n\t});\n}\n\n/**\n * Write a tree from a flat path→{oid,mode} map by recursively building\n * intermediate trees for subdirectories.\n */\nasync function writeTreeFromFlat(\n\trepo: Repo,\n\tflat: Map<string, { oid: string; mode: string }>,\n): Promise<string> {\n\t// Group entries by first path segment\n\tconst root = new Map<string, { oid: string; mode: string }>();\n\tconst children = new Map<\n\t\tstring,\n\t\tMap<string, { oid: string; mode: string }>\n\t>();\n\n\tfor (const [p, v] of flat) {\n\t\tconst slash = p.indexOf(\"/\");\n\t\tif (slash === -1) {\n\t\t\troot.set(p, v);\n\t\t} else {\n\t\t\tconst dir = p.slice(0, slash);\n\t\t\tconst rest = p.slice(slash + 1);\n\t\t\tif (!children.has(dir)) children.set(dir, new Map());\n\t\t\tchildren.get(dir)?.set(rest, v);\n\t\t}\n\t}\n\n\tconst treeEntries: Array<{\n\t\tpath: string;\n\t\toid: string;\n\t\tmode: string;\n\t\ttype: \"blob\" | \"tree\";\n\t}> = [];\n\n\tfor (const [name, { oid, mode }] of root) {\n\t\ttreeEntries.push({ path: name, oid, mode, type: \"blob\" });\n\t}\n\n\tfor (const [dirName, subMap] of children) {\n\t\tconst subOid = await writeTreeFromFlat(repo, subMap);\n\t\ttreeEntries.push({\n\t\t\tpath: dirName,\n\t\t\toid: subOid,\n\t\t\tmode: \"040000\",\n\t\t\ttype: \"tree\",\n\t\t});\n\t}\n\n\treturn writeTreeObject(repo, treeEntries);\n}\n\n/**\n * Simple byte-level 3-way merge on two Uint8Array contents.\n *\n * This is a line-level merge using a basic diff algorithm.\n * For production quality, callers should use the `diff` library —\n * this is the fallback for edge environments where `diff` is not available.\n */\nfunction mergeContents(\n\tbase: Uint8Array,\n\tours: Uint8Array,\n\ttheirs: Uint8Array,\n): Uint8Array {\n\tconst baseLines = decodeLines(base);\n\tconst oursLines = decodeLines(ours);\n\tconst theirsLines = decodeLines(theirs);\n\n\t// If either side is identical to base, take the other\n\tif (arraysEqual(baseLines, oursLines)) return theirs;\n\tif (arraysEqual(baseLines, theirsLines)) return ours;\n\tif (arraysEqual(oursLines, theirsLines)) return ours;\n\n\t// Simple line-by-line merge: walk all three in lockstep\n\tconst result: string[] = [];\n\tlet bi = 0,\n\t\toi = 0,\n\t\tti = 0;\n\n\twhile (\n\t\tbi < baseLines.length ||\n\t\toi < oursLines.length ||\n\t\tti < theirsLines.length\n\t) {\n\t\tconst b = bi < baseLines.length ? baseLines[bi] : undefined;\n\t\tconst o = oi < oursLines.length ? oursLines[oi] : undefined;\n\t\tconst t = ti < theirsLines.length ? theirsLines[ti] : undefined;\n\n\t\tif (b === o && b === t) {\n\t\t\t// All three agree\n\t\t\tresult.push(b ?? \"\");\n\t\t\tbi++;\n\t\t\toi++;\n\t\t\tti++;\n\t\t} else if (b === o) {\n\t\t\t// Only theirs changed — including the case where theirs ran out\n\t\t\t// of lines here (t undefined, i.e. a deletion): `?? \"\"` matches\n\t\t\t// what pushing `t` directly already did once joined below\n\t\t\t// (Array.prototype.join renders an undefined element as \"\"),\n\t\t\t// just without relying on `!` to silence the type checker\n\t\t\t// about it.\n\t\t\tresult.push(t ?? \"\");\n\t\t\tbi++;\n\t\t\toi++;\n\t\t\tti++;\n\t\t} else if (b === t) {\n\t\t\t// Only ours changed — symmetric to the theirs case above.\n\t\t\tresult.push(o ?? \"\");\n\t\t\tbi++;\n\t\t\toi++;\n\t\t\tti++;\n\t\t} else {\n\t\t\t// Both changed — conflict: take ours + theirs separated by\n\t\t\t// conflict markers. o or t can individually be undefined here\n\t\t\t// too (one side ran out of lines while the other still\n\t\t\t// conflicts with base) — same `?? \"\"` reasoning as above.\n\t\t\tresult.push(`<<<<<<< ours`);\n\t\t\tresult.push(o ?? \"\");\n\t\t\tresult.push(`=======`);\n\t\t\tresult.push(t ?? \"\");\n\t\t\tresult.push(`>>>>>>> theirs`);\n\t\t\tbi++;\n\t\t\toi++;\n\t\t\tti++;\n\t\t}\n\t}\n\n\treturn encodeLines(result);\n}\n\nfunction decodeLines(data: Uint8Array): string[] {\n\tconst text = new TextDecoder().decode(data);\n\tif (text === \"\") return [];\n\treturn text.split(\"\\n\");\n}\n\nfunction encodeLines(lines: string[]): Uint8Array {\n\treturn new TextEncoder().encode(lines.join(\"\\n\"));\n}\n\nfunction arraysEqual(a: string[], b: string[]): boolean {\n\tif (a.length !== b.length) return false;\n\tfor (let i = 0; i < a.length; i++) {\n\t\tif (a[i] !== b[i]) return false;\n\t}\n\treturn true;\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Perform an object-level three-way merge.\n *\n * - If source is an ancestor of target (fast-forward), moves target ref to source.\n * - If target is an ancestor of source (reverse FF), moves target ref to source.\n * - Otherwise, performs a true 3-way merge at the tree level.\n *\n * Throws `GitMergeConflictError` if the merge has unresolvable conflicts.\n */\nexport async function threeWayMerge(\n\trepo: Repo,\n\tsourceRef: string,\n\ttargetRef: string,\n\topts?: MergeOpts,\n): Promise<MergeResult> {\n\tconst [sourceOid, targetOid] = await Promise.all([\n\t\tgit.resolveRef({ ...repo, ref: sourceRef }),\n\t\tgit.resolveRef({ ...repo, ref: targetRef }),\n\t]);\n\n\tif (sourceOid === targetOid) {\n\t\treturn { commitOid: sourceOid };\n\t}\n\n\t// Fast-forward: source is ahead of target\n\tconst isSourceAhead = await git.isDescendent({\n\t\t...repo,\n\t\toid: sourceOid,\n\t\tancestor: targetOid,\n\t});\n\tif (isSourceAhead) {\n\t\tawait git.writeRef({\n\t\t\t...repo,\n\t\t\tref: targetRef,\n\t\t\tvalue: sourceOid,\n\t\t\tforce: true,\n\t\t});\n\t\treturn { commitOid: sourceOid };\n\t}\n\n\t// Reverse fast-forward: target is ahead of source\n\tconst isTargetAhead = await git.isDescendent({\n\t\t...repo,\n\t\toid: targetOid,\n\t\tancestor: sourceOid,\n\t});\n\tif (isTargetAhead) {\n\t\tawait git.writeRef({\n\t\t\t...repo,\n\t\t\tref: targetRef,\n\t\t\tvalue: sourceOid,\n\t\t\tforce: true,\n\t\t});\n\t\treturn { commitOid: sourceOid };\n\t}\n\n\t// True 3-way merge\n\tconst baseOids = await git.findMergeBase({\n\t\t...repo,\n\t\toids: [sourceOid, targetOid],\n\t});\n\n\tif (!baseOids || baseOids.length === 0) {\n\t\tthrow new Error(\"No merge base found between source and target\");\n\t}\n\n\tconst baseOid = baseOids[0];\n\n\t// Read commit objects to get their trees\n\tconst [baseCommit, sourceCommit, targetCommit] = await Promise.all([\n\t\tgit.readCommit({ ...repo, oid: baseOid }),\n\t\tgit.readCommit({ ...repo, oid: sourceOid }),\n\t\tgit.readCommit({ ...repo, oid: targetOid }),\n\t]);\n\n\tconst baseTreeOid = baseCommit.commit.tree;\n\tconst sourceTreeOid = sourceCommit.commit.tree;\n\tconst targetTreeOid = targetCommit.commit.tree;\n\n\t// Flatten all three trees\n\tconst [baseMap, sourceMap, targetMap] = await Promise.all([\n\t\tflattenTreeDeep(repo, baseTreeOid, \"\"),\n\t\tflattenTreeDeep(repo, sourceTreeOid, \"\"),\n\t\tflattenTreeDeep(repo, targetTreeOid, \"\"),\n\t]);\n\n\t// Collect all unique paths\n\tconst allPaths = new Set([\n\t\t...baseMap.keys(),\n\t\t...sourceMap.keys(),\n\t\t...targetMap.keys(),\n\t]);\n\n\tconst resultEntries = new Map<string, { oid: string; mode: string }>();\n\tconst conflictingPaths: string[] = [];\n\n\tfor (const filepath of allPaths) {\n\t\tconst baseOid = baseMap.get(filepath);\n\t\tconst sourceOid = sourceMap.get(filepath);\n\t\tconst targetOid = targetMap.get(filepath);\n\n\t\tif (sourceOid !== undefined && targetOid === undefined) {\n\t\t\t// Added in source, not in target — take source\n\t\t\tresultEntries.set(filepath, { oid: sourceOid, mode: \"100644\" });\n\t\t} else if (sourceOid === undefined && targetOid !== undefined) {\n\t\t\t// Deleted in source, exists in target — keep target\n\t\t\tresultEntries.set(filepath, { oid: targetOid, mode: \"100644\" });\n\t\t} else if (sourceOid !== undefined && targetOid !== undefined) {\n\t\t\t// Exists in both — check if both changed from base. A path with\n\t\t\t// no base entry (both sides independently added it — no common\n\t\t\t// ancestor content to diff against) is its own case below,\n\t\t\t// compared directly against each other instead of a base blob\n\t\t\t// that doesn't exist.\n\t\t\tconst sourceChanged = sourceOid !== baseOid;\n\t\t\tconst targetChanged = targetOid !== baseOid;\n\n\t\t\tif (!sourceChanged && !targetChanged) {\n\t\t\t\t// Neither changed — keep as-is\n\t\t\t\tresultEntries.set(filepath, { oid: targetOid, mode: \"100644\" });\n\t\t\t} else if (!sourceChanged) {\n\t\t\t\t// Only target changed — take target\n\t\t\t\tresultEntries.set(filepath, { oid: targetOid, mode: \"100644\" });\n\t\t\t} else if (!targetChanged) {\n\t\t\t\t// Only source changed — take source\n\t\t\t\tresultEntries.set(filepath, { oid: sourceOid, mode: \"100644\" });\n\t\t\t} else if (baseOid === undefined) {\n\t\t\t\t// Both sides independently added this path. Same oid means\n\t\t\t\t// identical content on both sides — no real conflict. Different\n\t\t\t\t// oids is a genuine conflict with no common ancestor to 3-way\n\t\t\t\t// merge against, so run the line-level merge against an empty\n\t\t\t\t// \"base\" — mergeContents already treats an exhausted side as\n\t\t\t\t// producing full conflict markers around whatever content the\n\t\t\t\t// other side has.\n\t\t\t\tif (sourceOid === targetOid) {\n\t\t\t\t\tresultEntries.set(filepath, { oid: sourceOid, mode: \"100644\" });\n\t\t\t\t} else {\n\t\t\t\t\tconst [sourceContent, targetContent] = await Promise.all([\n\t\t\t\t\t\treadBlob(repo, sourceOid),\n\t\t\t\t\t\treadBlob(repo, targetOid),\n\t\t\t\t\t]);\n\t\t\t\t\tconst merged = mergeContents(\n\t\t\t\t\t\tnew Uint8Array(),\n\t\t\t\t\t\tsourceContent,\n\t\t\t\t\t\ttargetContent,\n\t\t\t\t\t);\n\t\t\t\t\tconflictingPaths.push(filepath);\n\t\t\t\t\tconst mergedOid = await writeBlob(repo, merged);\n\t\t\t\t\tresultEntries.set(filepath, { oid: mergedOid, mode: \"100644\" });\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Both changed — attempt content merge\n\t\t\t\tconst [baseContent, sourceContent, targetContent] = await Promise.all([\n\t\t\t\t\treadBlob(repo, baseOid),\n\t\t\t\t\treadBlob(repo, sourceOid),\n\t\t\t\t\treadBlob(repo, targetOid),\n\t\t\t\t]);\n\n\t\t\t\tif (\n\t\t\t\t\tbaseContent.length === sourceContent.length &&\n\t\t\t\t\tbaseContent.every((b, i) => b === sourceContent[i])\n\t\t\t\t) {\n\t\t\t\t\t// Source is identical to base — take target\n\t\t\t\t\tresultEntries.set(filepath, { oid: targetOid, mode: \"100644\" });\n\t\t\t\t} else if (\n\t\t\t\t\tbaseContent.length === targetContent.length &&\n\t\t\t\t\tbaseContent.every((b, i) => b === targetContent[i])\n\t\t\t\t) {\n\t\t\t\t\t// Target is identical to base — take source\n\t\t\t\t\tresultEntries.set(filepath, { oid: sourceOid, mode: \"100644\" });\n\t\t\t\t} else if (\n\t\t\t\t\tsourceContent.length === targetContent.length &&\n\t\t\t\t\tsourceContent.every((b, i) => b === targetContent[i])\n\t\t\t\t) {\n\t\t\t\t\t// Source and target are identical — keep either\n\t\t\t\t\tresultEntries.set(filepath, { oid: sourceOid, mode: \"100644\" });\n\t\t\t\t} else {\n\t\t\t\t\t// Both truly changed — attempt line-level merge\n\t\t\t\t\tconst merged = mergeContents(\n\t\t\t\t\t\tbaseContent,\n\t\t\t\t\t\tsourceContent,\n\t\t\t\t\t\ttargetContent,\n\t\t\t\t\t);\n\n\t\t\t\t\t// Check for conflict markers\n\t\t\t\t\tconst mergedText = new TextDecoder().decode(merged);\n\t\t\t\t\tif (\n\t\t\t\t\t\tmergedText.includes(\"<<<<<<< ours\") ||\n\t\t\t\t\t\tmergedText.includes(\">>>>>>> theirs\")\n\t\t\t\t\t) {\n\t\t\t\t\t\tconflictingPaths.push(filepath);\n\t\t\t\t\t\t// Still take the merged result with conflict markers\n\t\t\t\t\t\t// so the caller can see what happened\n\t\t\t\t\t\tconst mergedOid = await writeBlob(repo, merged);\n\t\t\t\t\t\tresultEntries.set(filepath, { oid: mergedOid, mode: \"100644\" });\n\t\t\t\t\t} else {\n\t\t\t\t\t\tconst mergedOid = await writeBlob(repo, merged);\n\t\t\t\t\t\tresultEntries.set(filepath, { oid: mergedOid, mode: \"100644\" });\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\t// Deleted in both — no-op\n\t\t}\n\t}\n\n\tif (conflictingPaths.length > 0) {\n\t\tthrow new GitMergeConflictError(conflictingPaths);\n\t}\n\n\t// Build the result tree\n\tconst resultTreeOid = await writeTreeFromFlat(repo, resultEntries);\n\n\t// Create the merge commit\n\tconst author = opts?.authorName\n\t\t? {\n\t\t\t\tname: opts.authorName,\n\t\t\t\temail: opts.authorEmail || \"git-edge@local\",\n\t\t\t\ttimestamp: Math.floor(Date.now() / 1000),\n\t\t\t\ttimezoneOffset: 0,\n\t\t\t}\n\t\t: defaultAuthor();\n\n\tconst message = opts?.message || `Merge ${sourceRef} into ${targetRef}`;\n\n\tconst mergeCommitOid = await git.commit({\n\t\t...repo,\n\t\tmessage,\n\t\ttree: resultTreeOid,\n\t\tparent: [targetOid, sourceOid],\n\t\tauthor,\n\t\tcommitter: author,\n\t});\n\n\t// Update the target ref\n\tawait git.writeRef({\n\t\t...repo,\n\t\tref: targetRef,\n\t\tvalue: mergeCommitOid,\n\t\tforce: true,\n\t});\n\n\treturn { commitOid: mergeCommitOid };\n}\n","/**\n * isomorphic-git per-repo parse cache management.\n *\n * No node: imports.\n */\n\n// ---------------------------------------------------------------------------\n// isomorphic-git cache management\n// ---------------------------------------------------------------------------\n\n/**\n * Per-repo isomorphic-git packfile cache.\n *\n * isomorphic-git re-parses a packfile index from scratch on every\n * readTree/log/readObject call unless callers share a `cache` object\n * across calls. Without this, operations that touch many objects\n * (e.g. walking commit history) pay that parse cost hundreds of times.\n * Objects are content-addressed/immutable so a long-lived per-repo\n * cache is safe.\n */\nconst repoCaches = new Map<string, object>();\n\nexport function getRepoCache(ownerKey: string, repoName: string): object {\n\tconst key = `${ownerKey}/${repoName}`;\n\tlet cache = repoCaches.get(key);\n\tif (!cache) {\n\t\tcache = {};\n\t\trepoCaches.set(key, cache);\n\t}\n\treturn cache;\n}\n\nexport function invalidateRepoCache(ownerKey: string, repoName: string): void {\n\trepoCaches.delete(`${ownerKey}/${repoName}`);\n}\n"],"mappings":";AAQA,SAAS,gBAAgB;AAuBlB,SAAS,wBACf,SACoB;AACpB,QAAM,UAAU,SAAS,WAAW,MAAM,OAAO;AACjD,QAAM,MAAM,SAAS,OAAO,OAAO;AAEnC,QAAM,QAAQ,IAAI,SAAyB;AAAA,IAC1C;AAAA,IACA,iBAAiB,CAAC,MAAM,KAAK,UAAU,CAAC,EAAE;AAAA,IAC1C;AAAA,EACD,CAAC;AAED,SAAO;AAAA,IACN,IAAsB,KAAuB;AAC5C,aAAQ,MAAM,IAAI,GAAG,KAAW;AAAA,IACjC;AAAA,IACA,IAAsB,KAAa,OAAgB;AAClD,YAAM,IAAI,KAAK,KAAK;AAAA,IACrB;AAAA,IACA,OAAO,KAAmB;AACzB,YAAM,OAAO,GAAG;AAAA,IACjB;AAAA,IACA,iBAAiB,QAAsB;AACtC,iBAAW,KAAK,MAAM,KAAK,GAAG;AAC7B,YAAI,EAAE,WAAW,MAAM,EAAG,OAAM,OAAO,CAAC;AAAA,MACzC;AAAA,IACD;AAAA,EACD;AACD;;;ACpDO,IAAM,eAAN,cAA2B,MAAM;AAAA,EACvC,YAAY,SAAiB;AAC5B,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,UAAM,oBAAoB,MAAM,KAAK,WAAW;AAAA,EACjD;AACD;AAGO,IAAM,wBAAN,cAAoC,aAAa;AAAA,EAC9C;AAAA,EAET,YAAY,kBAA4B;AACvC;AAAA,MACC,qBAAqB,iBAAiB,MAAM,aAAa,iBAAiB,KAAK,IAAI,CAAC;AAAA,IACrF;AACA,SAAK,OAAO;AACZ,SAAK,mBAAmB;AAAA,EACzB;AACD;;;ACfA,OAAO,SAAS;AA4BhB,SAAS,gBAAgB;AACxB,SAAO;AAAA,IACN,MAAM;AAAA,IACN,OAAO;AAAA,IACP,WAAW,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;AAAA,IACvC,gBAAgB;AAAA,EACjB;AACD;AAGA,eAAe,gBACd,MACA,SACA,QAC+B;AAC/B,QAAM,MAAM,oBAAI,IAAoB;AACpC,QAAM,WAAW,MAAM,IAAI,SAAS,EAAE,GAAG,MAAM,KAAK,QAAQ,CAAC,GAAG;AAChE,QAAM,QAAQ;AAAA,IACb,QAAQ,IAAI,OAAO,UAAU;AAC5B,YAAM,WAAW,SAAS,GAAG,MAAM,IAAI,MAAM,IAAI,KAAK,MAAM;AAC5D,UAAI,MAAM,SAAS,QAAQ;AAC1B,cAAM,MAAM,MAAM,gBAAgB,MAAM,MAAM,KAAK,QAAQ;AAC3D,mBAAW,CAAC,GAAG,CAAC,KAAK,IAAK,KAAI,IAAI,GAAG,CAAC;AAAA,MACvC,OAAO;AACN,YAAI,IAAI,UAAU,MAAM,GAAG;AAAA,MAC5B;AAAA,IACD,CAAC;AAAA,EACF;AACA,SAAO;AACR;AAGA,eAAe,SAAS,MAAY,KAAkC;AACrE,QAAM,EAAE,KAAK,IAAI,MAAM,IAAI,SAAS,EAAE,GAAG,MAAM,IAAI,CAAC;AACpD,SAAO;AACR;AAGA,eAAe,UAAU,MAAY,SAAsC;AAC1E,SAAO,IAAI,UAAU,EAAE,GAAG,MAAM,MAAM,QAAQ,CAAC;AAChD;AAGA,eAAe,gBACd,MACA,SAMkB;AAClB,SAAO,IAAI,YAAY;AAAA,IACtB,GAAG;AAAA,IACH,MAAM;AAAA,IACN,QAAQ,QAAQ,IAAI,CAAC,OAAO;AAAA,MAC3B,MAAM,EAAE;AAAA,MACR,KAAK,EAAE;AAAA,MACP,MAAM,EAAE;AAAA,MACR,MAAM,EAAE;AAAA,IACT,EAAE;AAAA,IACF,QAAQ;AAAA,EACT,CAAC;AACF;AAMA,eAAe,kBACd,MACA,MACkB;AAElB,QAAM,OAAO,oBAAI,IAA2C;AAC5D,QAAM,WAAW,oBAAI,IAGnB;AAEF,aAAW,CAAC,GAAG,CAAC,KAAK,MAAM;AAC1B,UAAM,QAAQ,EAAE,QAAQ,GAAG;AAC3B,QAAI,UAAU,IAAI;AACjB,WAAK,IAAI,GAAG,CAAC;AAAA,IACd,OAAO;AACN,YAAM,MAAM,EAAE,MAAM,GAAG,KAAK;AAC5B,YAAM,OAAO,EAAE,MAAM,QAAQ,CAAC;AAC9B,UAAI,CAAC,SAAS,IAAI,GAAG,EAAG,UAAS,IAAI,KAAK,oBAAI,IAAI,CAAC;AACnD,eAAS,IAAI,GAAG,GAAG,IAAI,MAAM,CAAC;AAAA,IAC/B;AAAA,EACD;AAEA,QAAM,cAKD,CAAC;AAEN,aAAW,CAAC,MAAM,EAAE,KAAK,KAAK,CAAC,KAAK,MAAM;AACzC,gBAAY,KAAK,EAAE,MAAM,MAAM,KAAK,MAAM,MAAM,OAAO,CAAC;AAAA,EACzD;AAEA,aAAW,CAAC,SAAS,MAAM,KAAK,UAAU;AACzC,UAAM,SAAS,MAAM,kBAAkB,MAAM,MAAM;AACnD,gBAAY,KAAK;AAAA,MAChB,MAAM;AAAA,MACN,KAAK;AAAA,MACL,MAAM;AAAA,MACN,MAAM;AAAA,IACP,CAAC;AAAA,EACF;AAEA,SAAO,gBAAgB,MAAM,WAAW;AACzC;AASA,SAAS,cACR,MACA,MACA,QACa;AACb,QAAM,YAAY,YAAY,IAAI;AAClC,QAAM,YAAY,YAAY,IAAI;AAClC,QAAM,cAAc,YAAY,MAAM;AAGtC,MAAI,YAAY,WAAW,SAAS,EAAG,QAAO;AAC9C,MAAI,YAAY,WAAW,WAAW,EAAG,QAAO;AAChD,MAAI,YAAY,WAAW,WAAW,EAAG,QAAO;AAGhD,QAAM,SAAmB,CAAC;AAC1B,MAAI,KAAK,GACR,KAAK,GACL,KAAK;AAEN,SACC,KAAK,UAAU,UACf,KAAK,UAAU,UACf,KAAK,YAAY,QAChB;AACD,UAAM,IAAI,KAAK,UAAU,SAAS,UAAU,EAAE,IAAI;AAClD,UAAM,IAAI,KAAK,UAAU,SAAS,UAAU,EAAE,IAAI;AAClD,UAAM,IAAI,KAAK,YAAY,SAAS,YAAY,EAAE,IAAI;AAEtD,QAAI,MAAM,KAAK,MAAM,GAAG;AAEvB,aAAO,KAAK,KAAK,EAAE;AACnB;AACA;AACA;AAAA,IACD,WAAW,MAAM,GAAG;AAOnB,aAAO,KAAK,KAAK,EAAE;AACnB;AACA;AACA;AAAA,IACD,WAAW,MAAM,GAAG;AAEnB,aAAO,KAAK,KAAK,EAAE;AACnB;AACA;AACA;AAAA,IACD,OAAO;AAKN,aAAO,KAAK,cAAc;AAC1B,aAAO,KAAK,KAAK,EAAE;AACnB,aAAO,KAAK,SAAS;AACrB,aAAO,KAAK,KAAK,EAAE;AACnB,aAAO,KAAK,gBAAgB;AAC5B;AACA;AACA;AAAA,IACD;AAAA,EACD;AAEA,SAAO,YAAY,MAAM;AAC1B;AAEA,SAAS,YAAY,MAA4B;AAChD,QAAM,OAAO,IAAI,YAAY,EAAE,OAAO,IAAI;AAC1C,MAAI,SAAS,GAAI,QAAO,CAAC;AACzB,SAAO,KAAK,MAAM,IAAI;AACvB;AAEA,SAAS,YAAY,OAA6B;AACjD,SAAO,IAAI,YAAY,EAAE,OAAO,MAAM,KAAK,IAAI,CAAC;AACjD;AAEA,SAAS,YAAY,GAAa,GAAsB;AACvD,MAAI,EAAE,WAAW,EAAE,OAAQ,QAAO;AAClC,WAAS,IAAI,GAAG,IAAI,EAAE,QAAQ,KAAK;AAClC,QAAI,EAAE,CAAC,MAAM,EAAE,CAAC,EAAG,QAAO;AAAA,EAC3B;AACA,SAAO;AACR;AAeA,eAAsB,cACrB,MACA,WACA,WACA,MACuB;AACvB,QAAM,CAAC,WAAW,SAAS,IAAI,MAAM,QAAQ,IAAI;AAAA,IAChD,IAAI,WAAW,EAAE,GAAG,MAAM,KAAK,UAAU,CAAC;AAAA,IAC1C,IAAI,WAAW,EAAE,GAAG,MAAM,KAAK,UAAU,CAAC;AAAA,EAC3C,CAAC;AAED,MAAI,cAAc,WAAW;AAC5B,WAAO,EAAE,WAAW,UAAU;AAAA,EAC/B;AAGA,QAAM,gBAAgB,MAAM,IAAI,aAAa;AAAA,IAC5C,GAAG;AAAA,IACH,KAAK;AAAA,IACL,UAAU;AAAA,EACX,CAAC;AACD,MAAI,eAAe;AAClB,UAAM,IAAI,SAAS;AAAA,MAClB,GAAG;AAAA,MACH,KAAK;AAAA,MACL,OAAO;AAAA,MACP,OAAO;AAAA,IACR,CAAC;AACD,WAAO,EAAE,WAAW,UAAU;AAAA,EAC/B;AAGA,QAAM,gBAAgB,MAAM,IAAI,aAAa;AAAA,IAC5C,GAAG;AAAA,IACH,KAAK;AAAA,IACL,UAAU;AAAA,EACX,CAAC;AACD,MAAI,eAAe;AAClB,UAAM,IAAI,SAAS;AAAA,MAClB,GAAG;AAAA,MACH,KAAK;AAAA,MACL,OAAO;AAAA,MACP,OAAO;AAAA,IACR,CAAC;AACD,WAAO,EAAE,WAAW,UAAU;AAAA,EAC/B;AAGA,QAAM,WAAW,MAAM,IAAI,cAAc;AAAA,IACxC,GAAG;AAAA,IACH,MAAM,CAAC,WAAW,SAAS;AAAA,EAC5B,CAAC;AAED,MAAI,CAAC,YAAY,SAAS,WAAW,GAAG;AACvC,UAAM,IAAI,MAAM,+CAA+C;AAAA,EAChE;AAEA,QAAM,UAAU,SAAS,CAAC;AAG1B,QAAM,CAAC,YAAY,cAAc,YAAY,IAAI,MAAM,QAAQ,IAAI;AAAA,IAClE,IAAI,WAAW,EAAE,GAAG,MAAM,KAAK,QAAQ,CAAC;AAAA,IACxC,IAAI,WAAW,EAAE,GAAG,MAAM,KAAK,UAAU,CAAC;AAAA,IAC1C,IAAI,WAAW,EAAE,GAAG,MAAM,KAAK,UAAU,CAAC;AAAA,EAC3C,CAAC;AAED,QAAM,cAAc,WAAW,OAAO;AACtC,QAAM,gBAAgB,aAAa,OAAO;AAC1C,QAAM,gBAAgB,aAAa,OAAO;AAG1C,QAAM,CAAC,SAAS,WAAW,SAAS,IAAI,MAAM,QAAQ,IAAI;AAAA,IACzD,gBAAgB,MAAM,aAAa,EAAE;AAAA,IACrC,gBAAgB,MAAM,eAAe,EAAE;AAAA,IACvC,gBAAgB,MAAM,eAAe,EAAE;AAAA,EACxC,CAAC;AAGD,QAAM,WAAW,oBAAI,IAAI;AAAA,IACxB,GAAG,QAAQ,KAAK;AAAA,IAChB,GAAG,UAAU,KAAK;AAAA,IAClB,GAAG,UAAU,KAAK;AAAA,EACnB,CAAC;AAED,QAAM,gBAAgB,oBAAI,IAA2C;AACrE,QAAM,mBAA6B,CAAC;AAEpC,aAAW,YAAY,UAAU;AAChC,UAAMA,WAAU,QAAQ,IAAI,QAAQ;AACpC,UAAMC,aAAY,UAAU,IAAI,QAAQ;AACxC,UAAMC,aAAY,UAAU,IAAI,QAAQ;AAExC,QAAID,eAAc,UAAaC,eAAc,QAAW;AAEvD,oBAAc,IAAI,UAAU,EAAE,KAAKD,YAAW,MAAM,SAAS,CAAC;AAAA,IAC/D,WAAWA,eAAc,UAAaC,eAAc,QAAW;AAE9D,oBAAc,IAAI,UAAU,EAAE,KAAKA,YAAW,MAAM,SAAS,CAAC;AAAA,IAC/D,WAAWD,eAAc,UAAaC,eAAc,QAAW;AAM9D,YAAM,gBAAgBD,eAAcD;AACpC,YAAM,gBAAgBE,eAAcF;AAEpC,UAAI,CAAC,iBAAiB,CAAC,eAAe;AAErC,sBAAc,IAAI,UAAU,EAAE,KAAKE,YAAW,MAAM,SAAS,CAAC;AAAA,MAC/D,WAAW,CAAC,eAAe;AAE1B,sBAAc,IAAI,UAAU,EAAE,KAAKA,YAAW,MAAM,SAAS,CAAC;AAAA,MAC/D,WAAW,CAAC,eAAe;AAE1B,sBAAc,IAAI,UAAU,EAAE,KAAKD,YAAW,MAAM,SAAS,CAAC;AAAA,MAC/D,WAAWD,aAAY,QAAW;AAQjC,YAAIC,eAAcC,YAAW;AAC5B,wBAAc,IAAI,UAAU,EAAE,KAAKD,YAAW,MAAM,SAAS,CAAC;AAAA,QAC/D,OAAO;AACN,gBAAM,CAAC,eAAe,aAAa,IAAI,MAAM,QAAQ,IAAI;AAAA,YACxD,SAAS,MAAMA,UAAS;AAAA,YACxB,SAAS,MAAMC,UAAS;AAAA,UACzB,CAAC;AACD,gBAAM,SAAS;AAAA,YACd,IAAI,WAAW;AAAA,YACf;AAAA,YACA;AAAA,UACD;AACA,2BAAiB,KAAK,QAAQ;AAC9B,gBAAM,YAAY,MAAM,UAAU,MAAM,MAAM;AAC9C,wBAAc,IAAI,UAAU,EAAE,KAAK,WAAW,MAAM,SAAS,CAAC;AAAA,QAC/D;AAAA,MACD,OAAO;AAEN,cAAM,CAAC,aAAa,eAAe,aAAa,IAAI,MAAM,QAAQ,IAAI;AAAA,UACrE,SAAS,MAAMF,QAAO;AAAA,UACtB,SAAS,MAAMC,UAAS;AAAA,UACxB,SAAS,MAAMC,UAAS;AAAA,QACzB,CAAC;AAED,YACC,YAAY,WAAW,cAAc,UACrC,YAAY,MAAM,CAAC,GAAG,MAAM,MAAM,cAAc,CAAC,CAAC,GACjD;AAED,wBAAc,IAAI,UAAU,EAAE,KAAKA,YAAW,MAAM,SAAS,CAAC;AAAA,QAC/D,WACC,YAAY,WAAW,cAAc,UACrC,YAAY,MAAM,CAAC,GAAG,MAAM,MAAM,cAAc,CAAC,CAAC,GACjD;AAED,wBAAc,IAAI,UAAU,EAAE,KAAKD,YAAW,MAAM,SAAS,CAAC;AAAA,QAC/D,WACC,cAAc,WAAW,cAAc,UACvC,cAAc,MAAM,CAAC,GAAG,MAAM,MAAM,cAAc,CAAC,CAAC,GACnD;AAED,wBAAc,IAAI,UAAU,EAAE,KAAKA,YAAW,MAAM,SAAS,CAAC;AAAA,QAC/D,OAAO;AAEN,gBAAM,SAAS;AAAA,YACd;AAAA,YACA;AAAA,YACA;AAAA,UACD;AAGA,gBAAM,aAAa,IAAI,YAAY,EAAE,OAAO,MAAM;AAClD,cACC,WAAW,SAAS,cAAc,KAClC,WAAW,SAAS,gBAAgB,GACnC;AACD,6BAAiB,KAAK,QAAQ;AAG9B,kBAAM,YAAY,MAAM,UAAU,MAAM,MAAM;AAC9C,0BAAc,IAAI,UAAU,EAAE,KAAK,WAAW,MAAM,SAAS,CAAC;AAAA,UAC/D,OAAO;AACN,kBAAM,YAAY,MAAM,UAAU,MAAM,MAAM;AAC9C,0BAAc,IAAI,UAAU,EAAE,KAAK,WAAW,MAAM,SAAS,CAAC;AAAA,UAC/D;AAAA,QACD;AAAA,MACD;AAAA,IACD,OAAO;AAAA,IAEP;AAAA,EACD;AAEA,MAAI,iBAAiB,SAAS,GAAG;AAChC,UAAM,IAAI,sBAAsB,gBAAgB;AAAA,EACjD;AAGA,QAAM,gBAAgB,MAAM,kBAAkB,MAAM,aAAa;AAGjE,QAAM,SAAS,MAAM,aAClB;AAAA,IACA,MAAM,KAAK;AAAA,IACX,OAAO,KAAK,eAAe;AAAA,IAC3B,WAAW,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;AAAA,IACvC,gBAAgB;AAAA,EACjB,IACC,cAAc;AAEjB,QAAM,UAAU,MAAM,WAAW,SAAS,SAAS,SAAS,SAAS;AAErE,QAAM,iBAAiB,MAAM,IAAI,OAAO;AAAA,IACvC,GAAG;AAAA,IACH;AAAA,IACA,MAAM;AAAA,IACN,QAAQ,CAAC,WAAW,SAAS;AAAA,IAC7B;AAAA,IACA,WAAW;AAAA,EACZ,CAAC;AAGD,QAAM,IAAI,SAAS;AAAA,IAClB,GAAG;AAAA,IACH,KAAK;AAAA,IACL,OAAO;AAAA,IACP,OAAO;AAAA,EACR,CAAC;AAED,SAAO,EAAE,WAAW,eAAe;AACpC;;;AC7dA,IAAM,aAAa,oBAAI,IAAoB;AAEpC,SAAS,aAAa,UAAkB,UAA0B;AACxE,QAAM,MAAM,GAAG,QAAQ,IAAI,QAAQ;AACnC,MAAI,QAAQ,WAAW,IAAI,GAAG;AAC9B,MAAI,CAAC,OAAO;AACX,YAAQ,CAAC;AACT,eAAW,IAAI,KAAK,KAAK;AAAA,EAC1B;AACA,SAAO;AACR;AAEO,SAAS,oBAAoB,UAAkB,UAAwB;AAC7E,aAAW,OAAO,GAAG,QAAQ,IAAI,QAAQ,EAAE;AAC5C;","names":["baseOid","sourceOid","targetOid"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "git-edge",
3
- "version": "0.1.2",
3
+ "version": "0.2.0",
4
4
  "description": "High-level edge-compatible git operations — parsed-object cache, object-level three-way merge, and repo management utilities. Composes with git-fs-s3.",
5
5
  "type": "module",
6
6
  "exports": {