git-fs-s3 0.4.0 → 0.5.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.
Files changed (62) hide show
  1. package/README.md +23 -17
  2. package/dist/{chunk-Z4656PGE.js → chunk-2JP4MS6H.js} +2 -2
  3. package/dist/{chunk-YQFNY6PG.js → chunk-FIFWOMPM.js} +1 -1
  4. package/dist/chunk-FIFWOMPM.js.map +1 -0
  5. package/dist/http.js +3 -3
  6. package/dist/http.js.map +1 -1
  7. package/dist/index.js +2 -3
  8. package/dist/index.js.map +1 -1
  9. package/dist/ops.js +2 -2
  10. package/dist/types/cache.d.ts +81 -0
  11. package/dist/types/edge-utils.d.ts +43 -0
  12. package/dist/types/errors.d.ts +16 -0
  13. package/dist/types/git-errors.d.ts +70 -0
  14. package/dist/types/git-fs.d.ts +60 -0
  15. package/dist/types/git-layout.d.ts +52 -0
  16. package/dist/types/http/index.d.ts +7 -0
  17. package/dist/types/http/info-refs.d.ts +27 -0
  18. package/dist/types/http/pack-reuse.d.ts +26 -0
  19. package/dist/types/http/pkt-line.d.ts +14 -0
  20. package/dist/types/http/reachability.d.ts +36 -0
  21. package/dist/types/http/receive-pack.d.ts +54 -0
  22. package/dist/types/http/repack.d.ts +24 -0
  23. package/dist/types/http/types.d.ts +40 -0
  24. package/dist/types/http/upload-pack.d.ts +21 -0
  25. package/dist/types/index.d.ts +11 -0
  26. package/dist/types/ops/branch.d.ts +38 -0
  27. package/dist/types/ops/commit.d.ts +45 -0
  28. package/dist/types/ops/diff.d.ts +24 -0
  29. package/dist/types/ops/file-history.d.ts +41 -0
  30. package/dist/types/ops/history.d.ts +85 -0
  31. package/dist/types/ops/index.d.ts +9 -0
  32. package/dist/types/ops/last-commit.d.ts +23 -0
  33. package/dist/types/ops/merge.d.ts +28 -0
  34. package/dist/types/ops/tree.d.ts +19 -0
  35. package/dist/{types-BHoHOaQt.d.cts → types/ops/types.d.ts} +7 -9
  36. package/dist/types/pack-files.d.ts +28 -0
  37. package/dist/types/path.d.ts +9 -0
  38. package/dist/types/refs.d.ts +54 -0
  39. package/dist/types/retry.d.ts +52 -0
  40. package/dist/types/stores/memory.d.ts +16 -0
  41. package/dist/{s3.d.ts → types/stores/s3.d.ts} +4 -7
  42. package/dist/{types-dbHzZ4ed.d.ts → types/types.d.ts} +11 -22
  43. package/package.json +18 -22
  44. package/dist/chunk-YQFNY6PG.js.map +0 -1
  45. package/dist/http.cjs +0 -1063
  46. package/dist/http.cjs.map +0 -1
  47. package/dist/http.d.cts +0 -215
  48. package/dist/http.d.ts +0 -215
  49. package/dist/index.cjs +0 -944
  50. package/dist/index.cjs.map +0 -1
  51. package/dist/index.d.cts +0 -479
  52. package/dist/index.d.ts +0 -479
  53. package/dist/ops.cjs +0 -1060
  54. package/dist/ops.cjs.map +0 -1
  55. package/dist/ops.d.cts +0 -302
  56. package/dist/ops.d.ts +0 -302
  57. package/dist/s3.cjs +0 -123
  58. package/dist/s3.cjs.map +0 -1
  59. package/dist/s3.d.cts +0 -36
  60. package/dist/types-BHoHOaQt.d.ts +0 -53
  61. package/dist/types-dbHzZ4ed.d.cts +0 -131
  62. /package/dist/{chunk-Z4656PGE.js.map → chunk-2JP4MS6H.js.map} +0 -0
package/dist/ops.cjs DELETED
@@ -1,1060 +0,0 @@
1
- "use strict";
2
- var __create = Object.create;
3
- var __defProp = Object.defineProperty;
4
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
- var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __getProtoOf = Object.getPrototypeOf;
7
- var __hasOwnProp = Object.prototype.hasOwnProperty;
8
- var __export = (target, all) => {
9
- for (var name in all)
10
- __defProp(target, name, { get: all[name], enumerable: true });
11
- };
12
- var __copyProps = (to, from, except, desc) => {
13
- if (from && typeof from === "object" || typeof from === "function") {
14
- for (let key of __getOwnPropNames(from))
15
- if (!__hasOwnProp.call(to, key) && key !== except)
16
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
- }
18
- return to;
19
- };
20
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
- // If the importer is in node compatibility mode or this is not an ESM
22
- // file that has been converted to a CommonJS file using a Babel-
23
- // compatible transform (i.e. "__esModule" has not been set), then set
24
- // "default" to the CommonJS "module.exports" for node compatibility.
25
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
- mod
27
- ));
28
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
-
30
- // src/ops/index.ts
31
- var ops_exports = {};
32
- __export(ops_exports, {
33
- BANNER_WALK_DEPTH: () => BANNER_WALK_DEPTH,
34
- HISTORY_WALK_DEPTH: () => HISTORY_WALK_DEPTH,
35
- analyzeMerge: () => analyzeMerge,
36
- assertBranchExists: () => assertBranchExists,
37
- assertSafeBranchName: () => assertSafeBranchName,
38
- authorNow: () => authorNow,
39
- commitFilesToBare: () => commitFilesToBare,
40
- createBranchFrom: () => createBranchFrom,
41
- deleteBranchByName: () => deleteBranchByName,
42
- deleteFileFromBare: () => deleteFileFromBare,
43
- deleteFromTree: () => deleteFromTree,
44
- fastForwardMerge: () => fastForwardMerge,
45
- findTreeEntry: () => findTreeEntry,
46
- getBlob: () => getBlob,
47
- getCommit: () => getCommit,
48
- getCommitDiff: () => getCommitDiff,
49
- getCommitHistory: () => getCommitHistory,
50
- getCommitLog: () => getCommitLog,
51
- getDiffBetweenRefs: () => getDiffBetweenRefs,
52
- getFileContent: () => getFileContent,
53
- getFileFromRef: () => getFileFromRef,
54
- getFileHistory: () => getFileHistory,
55
- getLastCommitsForTree: () => getLastCommitsForTree,
56
- getTreeFromRef: () => getTreeFromRef,
57
- listBranches: () => listBranches,
58
- listTreeEntries: () => listTreeEntries,
59
- resolveCommit: () => resolveCommit,
60
- resolveLooseRefFast: () => resolveLooseRefFast,
61
- resultKeyPrefixes: () => resultKeyPrefixes,
62
- upsertTree: () => upsertTree,
63
- writeCommitToBare: () => writeCommitToBare
64
- });
65
- module.exports = __toCommonJS(ops_exports);
66
-
67
- // src/ops/branch.ts
68
- var import_isomorphic_git = __toESM(require("isomorphic-git"), 1);
69
-
70
- // src/git-errors.ts
71
- var GitError = class extends Error {
72
- statusCode;
73
- retryable;
74
- constructor(message, statusCode = 500, retryable = false) {
75
- super(message);
76
- this.name = this.constructor.name;
77
- this.statusCode = statusCode;
78
- this.retryable = retryable;
79
- Error.captureStackTrace?.(this, this.constructor);
80
- }
81
- toJSON() {
82
- return {
83
- error: this.name,
84
- message: this.message,
85
- statusCode: this.statusCode,
86
- retryable: this.retryable
87
- };
88
- }
89
- };
90
- var GitPathNotFoundError = class extends GitError {
91
- constructor(message) {
92
- super(message, 404, false);
93
- }
94
- };
95
- var GitObjectNotFoundError = class extends GitError {
96
- constructor(message) {
97
- super(message, 404, false);
98
- }
99
- };
100
- var GitInvalidRequestError = class extends GitError {
101
- constructor(message) {
102
- super(message, 400, false);
103
- }
104
- };
105
-
106
- // src/refs.ts
107
- var BAD_REF_COMPONENT = (
108
- // biome-ignore lint/suspicious/noControlCharactersInRegex: control chars are exactly what git's own ref-name rules reject — this needs to match the same range.
109
- /(^|[/.])([/.]|$)|^@$|@\{|[\x00-\x20\x7f~^:?*[\\]|\.lock(\/|$)/
110
- );
111
- var FULL_SHA_RE = /^[0-9a-f]{40}$/i;
112
- function isSafeBranchName(name) {
113
- if (!name || name.startsWith("refs/") || name === "HEAD") return false;
114
- if (FULL_SHA_RE.test(name)) return false;
115
- return !BAD_REF_COMPONENT.test(name);
116
- }
117
- function qualifyBranchRef(ref) {
118
- if (ref.startsWith("refs/") || ref === "HEAD" || FULL_SHA_RE.test(ref)) {
119
- return ref;
120
- }
121
- return `refs/heads/${ref}`;
122
- }
123
-
124
- // src/ops/branch.ts
125
- function assertSafeBranchName(name) {
126
- if (!isSafeBranchName(name)) {
127
- throw new GitInvalidRequestError(`Invalid branch name: ${name}`);
128
- }
129
- }
130
- var FULL_SHA_RE2 = /^[0-9a-f]{40}$/i;
131
- function hasRecursiveFileLister(fs) {
132
- return typeof fs === "object" && fs !== null && "listFilesRecursively" in fs && typeof fs.listFilesRecursively === "function";
133
- }
134
- function getPromisesFs(fs) {
135
- const promises = fs.promises;
136
- return promises ?? null;
137
- }
138
- function decodeFileContent(content) {
139
- return typeof content === "string" ? content : new TextDecoder().decode(content);
140
- }
141
- async function listLooseBranches(repo) {
142
- return listLooseRefNames(repo, "refs/heads");
143
- }
144
- async function listLooseRefNames(repo, dir) {
145
- if (!hasRecursiveFileLister(repo.fs)) return null;
146
- const names = await repo.fs.listFilesRecursively(`${repo.gitdir}/${dir}`);
147
- return names.filter(isSafeBranchName);
148
- }
149
- async function readPackedBranches(repo) {
150
- if (!hasRecursiveFileLister(repo.fs)) return /* @__PURE__ */ new Map();
151
- const promisesFs = getPromisesFs(repo.fs);
152
- if (!promisesFs) return /* @__PURE__ */ new Map();
153
- try {
154
- const content = await promisesFs.readFile(
155
- `${repo.gitdir}/packed-refs`,
156
- "utf8"
157
- );
158
- const refs = /* @__PURE__ */ new Map();
159
- for (const line of decodeFileContent(content).split("\n")) {
160
- const match = /^([0-9a-f]{40}) refs\/heads\/(.+)$/i.exec(line);
161
- if (match?.[1] && match[2] && isSafeBranchName(match[2])) {
162
- refs.set(match[2], match[1]);
163
- }
164
- }
165
- return refs;
166
- } catch {
167
- return /* @__PURE__ */ new Map();
168
- }
169
- }
170
- async function getCurrentLooseBranch(repo) {
171
- if (!hasRecursiveFileLister(repo.fs)) return null;
172
- const promisesFs = getPromisesFs(repo.fs);
173
- if (!promisesFs) return null;
174
- try {
175
- const content = await promisesFs.readFile(`${repo.gitdir}/HEAD`, "utf8");
176
- const head = decodeFileContent(content).trim();
177
- const match = /^ref: refs\/heads\/(.+)$/.exec(head);
178
- return match?.[1] && isSafeBranchName(match[1]) ? match[1] : null;
179
- } catch {
180
- return null;
181
- }
182
- }
183
- async function resolveLooseRefFast(repo, ref) {
184
- const promisesFs = getPromisesFs(repo.fs);
185
- if (promisesFs) {
186
- try {
187
- const content = await promisesFs.readFile(
188
- `${repo.gitdir}/${ref}`,
189
- "utf8"
190
- );
191
- const oid = decodeFileContent(content).trim();
192
- if (FULL_SHA_RE2.test(oid)) return oid;
193
- } catch {
194
- }
195
- }
196
- return import_isomorphic_git.default.resolveRef({ ...repo, ref });
197
- }
198
- async function listBranches(repo) {
199
- try {
200
- const [looseBranches, looseCurrentBranch, packedBranches] = await Promise.all([
201
- listLooseBranches(repo),
202
- getCurrentLooseBranch(repo),
203
- readPackedBranches(repo)
204
- ]);
205
- const branches = looseBranches === null ? await import_isomorphic_git.default.listBranches(repo) : [.../* @__PURE__ */ new Set([...looseBranches, ...packedBranches.keys()])].sort();
206
- const currentBranch = looseBranches === null ? await import_isomorphic_git.default.currentBranch({ ...repo, fullname: false }).catch(() => null) : looseCurrentBranch;
207
- return Promise.all(
208
- branches.map(async (branch) => {
209
- const packedCommit = packedBranches.get(branch);
210
- return {
211
- name: branch,
212
- commit: packedCommit && !looseBranches?.includes(branch) ? packedCommit : await resolveLooseRefFast(repo, `refs/heads/${branch}`),
213
- isDefault: branch === currentBranch
214
- };
215
- })
216
- );
217
- } catch (err) {
218
- if (err.code === "NotFoundError") return [];
219
- throw err;
220
- }
221
- }
222
- async function createBranchFrom(repo, name, startPoint = "main") {
223
- assertSafeBranchName(name);
224
- assertSafeBranchName(startPoint);
225
- const object = await resolveLooseRefFast(repo, `refs/heads/${startPoint}`);
226
- await import_isomorphic_git.default.branch({ ...repo, ref: name, checkout: false, object });
227
- }
228
- async function deleteBranchByName(repo, name) {
229
- assertSafeBranchName(name);
230
- await import_isomorphic_git.default.deleteBranch({ ...repo, ref: name });
231
- }
232
- async function assertBranchExists(repo, name) {
233
- assertSafeBranchName(name);
234
- await resolveLooseRefFast(repo, `refs/heads/${name}`);
235
- }
236
-
237
- // src/ops/commit.ts
238
- var import_isomorphic_git3 = __toESM(require("isomorphic-git"), 1);
239
-
240
- // src/ops/tree.ts
241
- var import_isomorphic_git2 = __toESM(require("isomorphic-git"), 1);
242
- var joinPath = (prefix, name) => prefix ? `${prefix.replace(/\/+$/, "")}/${name}` : name;
243
- async function upsertTree(repo, treeOid, entries) {
244
- const existing = treeOid ? (await import_isomorphic_git2.default.readTree({ ...repo, oid: treeOid })).tree : [];
245
- const byName = new Map(existing.map((e) => [e.path, e]));
246
- const direct = /* @__PURE__ */ new Map();
247
- const nested = /* @__PURE__ */ new Map();
248
- for (const [filePath, blobOid] of entries) {
249
- const slash = filePath.indexOf("/");
250
- if (slash === -1) {
251
- direct.set(filePath, blobOid);
252
- } else {
253
- const dir = filePath.slice(0, slash);
254
- const rest = filePath.slice(slash + 1);
255
- if (!nested.has(dir)) nested.set(dir, /* @__PURE__ */ new Map());
256
- nested.get(dir)?.set(rest, blobOid);
257
- }
258
- }
259
- for (const [name, blobOid] of direct) {
260
- byName.set(name, {
261
- mode: "100644",
262
- path: name,
263
- oid: blobOid,
264
- type: "blob"
265
- });
266
- }
267
- const nestedResults = await Promise.all(
268
- Array.from(nested, async ([dir, subEntries]) => {
269
- const entry = byName.get(dir);
270
- const subtreeOid = entry?.type === "tree" ? entry.oid : void 0;
271
- const newOid = await upsertTree(repo, subtreeOid, subEntries);
272
- return [dir, newOid];
273
- })
274
- );
275
- for (const [dir, newOid] of nestedResults) {
276
- byName.set(dir, { mode: "040000", path: dir, oid: newOid, type: "tree" });
277
- }
278
- return import_isomorphic_git2.default.writeTree({ ...repo, tree: Array.from(byName.values()) });
279
- }
280
- async function deleteFromTree(repo, treeOid, filePath) {
281
- const existing = (await import_isomorphic_git2.default.readTree({ ...repo, oid: treeOid })).tree;
282
- const byName = new Map(existing.map((e) => [e.path, e]));
283
- const slash = filePath.indexOf("/");
284
- if (slash === -1) {
285
- byName.delete(filePath);
286
- } else {
287
- const dir = filePath.slice(0, slash);
288
- const rest = filePath.slice(slash + 1);
289
- const entry = byName.get(dir);
290
- if (entry?.type === "tree") {
291
- const newOid = await deleteFromTree(repo, entry.oid, rest);
292
- byName.set(dir, { ...entry, oid: newOid });
293
- }
294
- }
295
- return import_isomorphic_git2.default.writeTree({ ...repo, tree: Array.from(byName.values()) });
296
- }
297
- async function findTreeEntry(repo, rootTreeOid, treePath) {
298
- if (!treePath) {
299
- return { path: "", mode: "040000", type: "tree", oid: rootTreeOid };
300
- }
301
- const parts = treePath.split("/").filter(Boolean);
302
- let currentTreeOid = rootTreeOid;
303
- let currentPath = "";
304
- for (const [index, part] of parts.entries()) {
305
- const tree = await import_isomorphic_git2.default.readTree({ ...repo, oid: currentTreeOid });
306
- const entry = tree.tree.find((candidate) => candidate.path === part);
307
- if (!entry) return null;
308
- currentPath = currentPath ? joinPath(currentPath, entry.path) : entry.path;
309
- if (index === parts.length - 1) {
310
- return {
311
- path: currentPath,
312
- mode: entry.mode,
313
- type: entry.type,
314
- oid: entry.oid
315
- };
316
- }
317
- if (entry.type !== "tree") return null;
318
- currentTreeOid = entry.oid;
319
- }
320
- return null;
321
- }
322
- async function listTreeEntries(repo, treeOid, prefix = "") {
323
- const tree = await import_isomorphic_git2.default.readTree({ ...repo, oid: treeOid });
324
- return tree.tree.map((entry) => ({
325
- path: prefix ? joinPath(prefix, entry.path) : entry.path,
326
- mode: entry.mode,
327
- type: entry.type,
328
- oid: entry.oid
329
- }));
330
- }
331
-
332
- // src/ops/commit.ts
333
- function authorNow(name, email) {
334
- return {
335
- name,
336
- email,
337
- timestamp: Math.floor(Date.now() / 1e3),
338
- timezoneOffset: 0
339
- };
340
- }
341
- async function writeCommitToBare(repo, options) {
342
- assertSafeBranchName(options.branch);
343
- let parentOid;
344
- let parentTreeOid;
345
- try {
346
- parentOid = await import_isomorphic_git3.default.resolveRef({
347
- ...repo,
348
- ref: `refs/heads/${options.branch}`
349
- });
350
- const { commit } = await import_isomorphic_git3.default.readCommit({ ...repo, oid: parentOid });
351
- parentTreeOid = commit.tree;
352
- } catch (err) {
353
- if (err?.code !== "NotFoundError") {
354
- throw err;
355
- }
356
- }
357
- const treeOid = await options.buildTree(parentTreeOid);
358
- const commitOid = await import_isomorphic_git3.default.writeCommit({
359
- ...repo,
360
- commit: {
361
- message: options.message,
362
- tree: treeOid,
363
- parent: parentOid ? [parentOid] : [],
364
- author: options.author,
365
- committer: options.author
366
- }
367
- });
368
- await import_isomorphic_git3.default.writeRef({
369
- ...repo,
370
- ref: `refs/heads/${options.branch}`,
371
- value: commitOid,
372
- force: true
373
- });
374
- return commitOid;
375
- }
376
- function commitFilesToBare(repo, options) {
377
- return writeCommitToBare(repo, {
378
- branch: options.branch,
379
- message: options.message,
380
- author: options.author,
381
- buildTree: async (parentTreeOid) => {
382
- const blobs = /* @__PURE__ */ new Map();
383
- await Promise.all(
384
- options.files.map(async (file) => {
385
- const content = typeof file.content === "string" ? new TextEncoder().encode(file.content) : file.content;
386
- const oid = await import_isomorphic_git3.default.writeBlob({ ...repo, blob: content });
387
- blobs.set(file.path, oid);
388
- })
389
- );
390
- return upsertTree(repo, parentTreeOid, blobs);
391
- }
392
- });
393
- }
394
- function deleteFileFromBare(repo, options) {
395
- return writeCommitToBare(repo, {
396
- branch: options.branch,
397
- message: options.message,
398
- author: options.author,
399
- buildTree: async (parentTreeOid) => {
400
- if (!parentTreeOid) {
401
- throw new Error(`Branch ${options.branch} is empty`);
402
- }
403
- return deleteFromTree(repo, parentTreeOid, options.filePath);
404
- }
405
- });
406
- }
407
-
408
- // src/ops/diff.ts
409
- var import_diff = require("diff");
410
- var import_isomorphic_git5 = __toESM(require("isomorphic-git"), 1);
411
-
412
- // src/edge-utils.ts
413
- var textEncoder = new TextEncoder();
414
- var textDecoder = new TextDecoder();
415
- function decodeUtf8(data) {
416
- return textDecoder.decode(data);
417
- }
418
- function toBase64(data) {
419
- let binary = "";
420
- for (let i = 0; i < data.length; i++)
421
- binary += String.fromCharCode(data[i]);
422
- return btoa(binary);
423
- }
424
- function hasNullByte(data) {
425
- return data.includes(0);
426
- }
427
- function readBlobContent(blob) {
428
- const isBinary = hasNullByte(blob);
429
- return {
430
- isBinary,
431
- text: isBinary ? "" : decodeUtf8(blob),
432
- bytes: blob
433
- };
434
- }
435
-
436
- // src/ops/history.ts
437
- var import_isomorphic_git4 = __toESM(require("isomorphic-git"), 1);
438
-
439
- // src/ops/types.ts
440
- var runStep = (hooks, label, fn) => hooks?.step ? hooks.step(label, fn) : fn();
441
- function resultKeyPrefixes(gitdir) {
442
- return [
443
- `commitlog:${gitdir}:`,
444
- `tree:${gitdir}:`,
445
- `commits:${gitdir}:`,
446
- `last-commits:${gitdir}:`,
447
- `file-history:${gitdir}:`
448
- ];
449
- }
450
-
451
- // src/ops/history.ts
452
- function wrapMissingObject(promise, context) {
453
- return promise.catch((err) => {
454
- if (err?.code === "NotFoundError") {
455
- throw new GitObjectNotFoundError(
456
- `Git data for ${context} is missing from storage. The repository may need to be re-pushed to repair it.`
457
- );
458
- }
459
- throw err;
460
- });
461
- }
462
- var isNotFound = (err) => err?.code === "NotFoundError";
463
- async function resolveCommit(repo, ref) {
464
- const oid = await import_isomorphic_git4.default.resolveRef({ ...repo, ref: qualifyBranchRef(ref) });
465
- const result = await wrapMissingObject(
466
- import_isomorphic_git4.default.readCommit({ ...repo, oid }),
467
- `${repo.gitdir} commit ${oid}`
468
- );
469
- return { oid, commit: result.commit };
470
- }
471
- async function getBlob(repo, sha) {
472
- const { blob } = await wrapMissingObject(
473
- import_isomorphic_git4.default.readBlob({ ...repo, oid: sha }),
474
- `${repo.gitdir} blob ${sha}`
475
- );
476
- return blob;
477
- }
478
- async function getFileContent(repo, filePath, ref = "main", hooks) {
479
- if (hooks?.prefetch) {
480
- await runStep(hooks, "prefetch", hooks.prefetch);
481
- }
482
- const { commit } = await resolveCommit(repo, ref);
483
- const context = `${repo.gitdir}@${ref}:${filePath}`;
484
- const entry = await wrapMissingObject(
485
- findTreeEntry(repo, commit.tree, filePath),
486
- context
487
- );
488
- if (entry?.type !== "blob") {
489
- throw new GitPathNotFoundError(`File not found: ${filePath}`);
490
- }
491
- const { blob } = await wrapMissingObject(
492
- import_isomorphic_git4.default.readBlob({ ...repo, oid: entry.oid }),
493
- context
494
- );
495
- return blob;
496
- }
497
- async function getCommit(repo, sha) {
498
- const result = await wrapMissingObject(
499
- import_isomorphic_git4.default.readCommit({ ...repo, oid: sha }),
500
- `${repo.gitdir} commit ${sha}`
501
- );
502
- return { oid: result.oid, commit: result.commit, payload: result.payload };
503
- }
504
- function isFullyWalked(commits) {
505
- const last = commits[commits.length - 1];
506
- return !!last && last.commit.parent.length === 0;
507
- }
508
- async function getCommitLog(repo, options = {}, hooks) {
509
- const ref = options.ref ?? "main";
510
- const depth = options.depth ?? 50;
511
- let headSha;
512
- if (options.knownHeadSha) {
513
- headSha = options.knownHeadSha;
514
- } else {
515
- try {
516
- headSha = await import_isomorphic_git4.default.resolveRef({ ...repo, ref: qualifyBranchRef(ref) });
517
- } catch (err) {
518
- if (isNotFound(err)) return [];
519
- throw err;
520
- }
521
- }
522
- const cacheKey = `commitlog:${repo.gitdir}:${headSha}`;
523
- const cached = hooks?.resultCache?.get(cacheKey);
524
- if (cached && (cached.length >= depth || isFullyWalked(cached))) {
525
- hooks?.onNote?.(
526
- `getCommitLog: result-cache HIT for ${cacheKey} (depth=${depth})`
527
- );
528
- return cached.slice(0, depth);
529
- }
530
- hooks?.onNote?.(
531
- `getCommitLog: result-cache MISS for ${cacheKey} (depth=${depth})`
532
- );
533
- if (hooks?.prefetch && depth >= (hooks.prefetchMinDepth ?? 5)) {
534
- await runStep(hooks, "prefetch", hooks.prefetch);
535
- }
536
- const commits = await wrapMissingObject(
537
- runStep(
538
- hooks,
539
- `git.log ${ref} depth=${depth}`,
540
- () => import_isomorphic_git4.default.log({ ...repo, ref: headSha, depth })
541
- ),
542
- `${repo.gitdir}@${ref} history`
543
- );
544
- const result = commits.map((commit) => ({
545
- oid: commit.oid,
546
- commit: commit.commit,
547
- payload: commit.payload || ""
548
- }));
549
- if (!cached || result.length > cached.length) {
550
- hooks?.resultCache?.set(cacheKey, result);
551
- }
552
- return result;
553
- }
554
- async function getFileFromRef(repo, filePath, ref, hooks) {
555
- const bytes = await getFileContent(repo, filePath, ref, hooks);
556
- const isBinary = hasNullByte(bytes);
557
- return {
558
- content: isBinary ? toBase64(bytes) : decodeUtf8(bytes),
559
- size: bytes.length,
560
- isBinary
561
- };
562
- }
563
- async function getTreeFromRef(repo, options = {}, hooks) {
564
- const ref = options.ref ?? "main";
565
- const treePath = options.treePath ?? "";
566
- let commit;
567
- let headSha;
568
- try {
569
- const resolved = await resolveCommit(repo, ref);
570
- commit = resolved.commit;
571
- headSha = resolved.oid;
572
- } catch (err) {
573
- if (isNotFound(err)) return [];
574
- throw err;
575
- }
576
- const cacheKey = `tree:${repo.gitdir}:${headSha}:${treePath}`;
577
- const cached = hooks?.resultCache?.get(cacheKey);
578
- if (cached) {
579
- hooks?.onNote?.(`getTreeFromRef: result-cache HIT for ${cacheKey}`);
580
- return cached;
581
- }
582
- hooks?.onNote?.(`getTreeFromRef: result-cache MISS for ${cacheKey}`);
583
- if (hooks?.prefetch) {
584
- await runStep(hooks, "prefetch", hooks.prefetch);
585
- }
586
- const context = `${repo.gitdir}@${ref}:${treePath || "/"}`;
587
- let result;
588
- if (!treePath) {
589
- result = await wrapMissingObject(
590
- runStep(
591
- hooks,
592
- "listTreeEntries (root)",
593
- () => listTreeEntries(repo, commit.tree)
594
- ),
595
- context
596
- );
597
- } else {
598
- const entry = await wrapMissingObject(
599
- runStep(
600
- hooks,
601
- `findTreeEntry ${treePath}`,
602
- () => findTreeEntry(repo, commit.tree, treePath)
603
- ),
604
- context
605
- );
606
- if (!entry) {
607
- throw new GitPathNotFoundError(
608
- `Path "${treePath}" does not exist at ${ref}`
609
- );
610
- }
611
- result = entry.type !== "tree" ? [] : await wrapMissingObject(
612
- runStep(
613
- hooks,
614
- `listTreeEntries ${treePath}`,
615
- () => listTreeEntries(repo, entry.oid, entry.path)
616
- ),
617
- context
618
- );
619
- }
620
- hooks?.resultCache?.set(cacheKey, result);
621
- return result;
622
- }
623
- async function getCommitHistory(repo, options, hooks) {
624
- const limit = options.limit ?? 50;
625
- const skip = options.skip ?? 0;
626
- const headSha = await import_isomorphic_git4.default.resolveRef({ ...repo, ref: qualifyBranchRef(options.ref) }).catch(() => null);
627
- const cacheKey = headSha ? `commits:${repo.gitdir}:${headSha}:${limit}:${skip}` : null;
628
- if (cacheKey) {
629
- const cached = hooks?.resultCache?.get(cacheKey);
630
- if (cached) {
631
- hooks?.onNote?.(`getCommitHistory: result-cache HIT for ${cacheKey}`);
632
- return cached;
633
- }
634
- }
635
- hooks?.onNote?.(
636
- `getCommitHistory: result-cache MISS for ${cacheKey ?? "(no head)"}`
637
- );
638
- if (!headSha) return [];
639
- const all = await getCommitLog(
640
- repo,
641
- { ref: options.ref, depth: limit + skip, knownHeadSha: headSha },
642
- hooks
643
- );
644
- const result = all.slice(skip, skip + limit);
645
- if (cacheKey) hooks?.resultCache?.set(cacheKey, result);
646
- return result;
647
- }
648
-
649
- // src/ops/diff.ts
650
- function detectBlobContent(blob) {
651
- return readBlobContent(blob);
652
- }
653
- function countContentLines(content) {
654
- if (content.length === 0) return 0;
655
- const lines = content.split("\n");
656
- if (lines[lines.length - 1] === "") lines.pop();
657
- return lines.length;
658
- }
659
- function createUnifiedPatch(params) {
660
- const oldPath = params.oldPath ?? `a/${params.path}`;
661
- const newPath = params.newPath ?? `b/${params.path}`;
662
- const patchBody = (0, import_diff.createTwoFilesPatch)(
663
- oldPath,
664
- newPath,
665
- params.before,
666
- params.after,
667
- "",
668
- "",
669
- { context: 3 }
670
- ).replace(/^=+\n/, "");
671
- return `diff --git a/${params.path} b/${params.path}
672
- ${patchBody}`;
673
- }
674
- function summarizeDiff(files) {
675
- return {
676
- files,
677
- totalAdditions: files.reduce((sum, f) => sum + f.additions, 0),
678
- totalDeletions: files.reduce((sum, f) => sum + f.deletions, 0),
679
- totalFiles: files.length
680
- };
681
- }
682
- function buildDiffFile(path, status, before, after) {
683
- const isBinary = !!(before?.isBinary || after?.isBinary);
684
- return {
685
- path,
686
- status,
687
- additions: !isBinary && after ? countContentLines(after.text) : 0,
688
- deletions: !isBinary && before ? countContentLines(before.text) : 0,
689
- patch: isBinary ? "" : createUnifiedPatch({
690
- path,
691
- before: before?.text ?? "",
692
- after: after?.text ?? "",
693
- oldPath: before ? void 0 : "/dev/null",
694
- newPath: after ? void 0 : "/dev/null"
695
- }),
696
- isBinary,
697
- oldContent: before && isBinary ? toBase64(before.bytes) : void 0,
698
- newContent: after && isBinary ? toBase64(after.bytes) : void 0,
699
- oldSize: before?.bytes.length,
700
- newSize: after?.bytes.length
701
- };
702
- }
703
- async function walkTreeDiff(repo, oldOid, newOid) {
704
- const changes = await import_isomorphic_git5.default.walk({
705
- ...repo,
706
- trees: [import_isomorphic_git5.default.TREE({ ref: oldOid }), import_isomorphic_git5.default.TREE({ ref: newOid })],
707
- map: async (filepath, [A, B]) => {
708
- const [typeA, typeB] = await Promise.all([A?.type(), B?.type()]);
709
- if (typeA === "tree" || typeB === "tree") return;
710
- if (typeA && !typeB) {
711
- const oidA2 = A ? await A.oid() : "";
712
- const { blob } = await import_isomorphic_git5.default.readBlob({ ...repo, oid: oidA2 });
713
- const before = detectBlobContent(blob);
714
- return buildDiffFile(filepath, "deleted", before, null);
715
- }
716
- if (!typeA && typeB) {
717
- const oidB2 = B ? await B.oid() : "";
718
- const { blob } = await import_isomorphic_git5.default.readBlob({ ...repo, oid: oidB2 });
719
- const after = detectBlobContent(blob);
720
- return buildDiffFile(filepath, "added", null, after);
721
- }
722
- const [oidA, oidB] = await Promise.all([
723
- A ? A.oid() : Promise.resolve(""),
724
- B ? B.oid() : Promise.resolve("")
725
- ]);
726
- if (oidA !== oidB) {
727
- const [{ blob: blobA }, { blob: blobB }] = await Promise.all([
728
- import_isomorphic_git5.default.readBlob({ ...repo, oid: oidA }),
729
- import_isomorphic_git5.default.readBlob({ ...repo, oid: oidB })
730
- ]);
731
- const before = detectBlobContent(blobA);
732
- const after = detectBlobContent(blobB);
733
- return buildDiffFile(filepath, "modified", before, after);
734
- }
735
- return null;
736
- }
737
- });
738
- return (changes ?? []).filter(
739
- (c) => c !== null && c !== void 0
740
- );
741
- }
742
- async function getCommitDiff(repo, commitSha) {
743
- try {
744
- const commit = await getCommit(repo, commitSha);
745
- const parent = commit.commit.parent[0];
746
- if (!parent) {
747
- const entries = [];
748
- const stack = [
749
- { treeOid: commit.commit.tree, prefix: "" }
750
- ];
751
- while (stack.length) {
752
- const { treeOid, prefix } = stack.pop();
753
- const { tree } = await import_isomorphic_git5.default.readTree({ ...repo, oid: treeOid });
754
- for (const entry of tree) {
755
- const full = prefix ? `${prefix}/${entry.path}` : entry.path;
756
- if (entry.type === "tree") {
757
- stack.push({ treeOid: entry.oid, prefix: full });
758
- } else if (entry.type === "blob") {
759
- entries.push({ path: full, oid: entry.oid });
760
- }
761
- }
762
- }
763
- const files2 = await Promise.all(
764
- entries.map(async ({ path, oid }) => {
765
- const { blob } = await import_isomorphic_git5.default.readBlob({ ...repo, oid });
766
- const after = detectBlobContent(blob);
767
- return buildDiffFile(path, "added", null, after);
768
- })
769
- );
770
- return summarizeDiff(files2);
771
- }
772
- const files = await walkTreeDiff(repo, parent, commitSha);
773
- return summarizeDiff(files);
774
- } catch (error) {
775
- throw new Error(`Failed to get commit diff: ${error}`);
776
- }
777
- }
778
- async function getDiffBetweenRefs(repo, baseRef, compareRef) {
779
- const [baseOid, compareOid] = await Promise.all([
780
- import_isomorphic_git5.default.resolveRef({ ...repo, ref: qualifyBranchRef(baseRef) }),
781
- import_isomorphic_git5.default.resolveRef({ ...repo, ref: qualifyBranchRef(compareRef) })
782
- ]);
783
- const files = await walkTreeDiff(repo, baseOid, compareOid);
784
- return summarizeDiff(files);
785
- }
786
-
787
- // src/ops/file-history.ts
788
- var HISTORY_WALK_DEPTH = 400;
789
- var BANNER_WALK_DEPTH = 60;
790
- var PREFETCH_WINDOW = 24;
791
- function toEntry(commit) {
792
- return {
793
- sha: commit.oid,
794
- message: commit.commit.message.trim(),
795
- authorName: commit.commit.author.name,
796
- authorEmail: commit.commit.author.email,
797
- createdAt: new Date(commit.commit.author.timestamp * 1e3).toISOString()
798
- };
799
- }
800
- async function getFileHistory(repo, options, hooks) {
801
- const limit = options.limit ?? 30;
802
- const maxDepth = options.maxDepth ?? HISTORY_WALK_DEPTH;
803
- const walkDepth = Math.max(maxDepth, limit);
804
- const commits = await runStep(
805
- hooks,
806
- `getCommitLog depth=${walkDepth}`,
807
- () => getCommitLog(repo, { ref: options.ref, depth: walkDepth }, hooks)
808
- );
809
- const head = commits[0];
810
- if (!head) return { entries: [], truncated: false };
811
- const cacheKey = `file-history:${repo.gitdir}:${head.oid}:${options.filePath}:${limit}:${maxDepth}`;
812
- const cached = hooks?.resultCache?.get(cacheKey);
813
- if (cached) {
814
- hooks?.onNote?.("getFileHistory: result-cache HIT, skipping history walk");
815
- return cached;
816
- }
817
- hooks?.onNote?.("getFileHistory: result-cache MISS, walking history");
818
- const byOid = new Map(commits.map((commit) => [commit.oid, commit]));
819
- const oidByCommitTree = /* @__PURE__ */ new Map();
820
- async function resolveOid(commitTreeOid) {
821
- const cachedOid = oidByCommitTree.get(commitTreeOid);
822
- if (cachedOid !== void 0) return cachedOid;
823
- const entry = await findTreeEntry(repo, commitTreeOid, options.filePath);
824
- const oid = entry?.type === "blob" ? entry.oid : null;
825
- oidByCommitTree.set(commitTreeOid, oid);
826
- return oid;
827
- }
828
- const entries = [];
829
- let truncated = false;
830
- outer: for (let windowStart = 0; windowStart < commits.length; windowStart += PREFETCH_WINDOW) {
831
- const windowEnd = Math.min(windowStart + PREFETCH_WINDOW, commits.length);
832
- const prefetchEnd = Math.min(windowEnd + 1, commits.length);
833
- await Promise.all(
834
- commits.slice(windowStart, prefetchEnd).map((commit) => resolveOid(commit.commit.tree))
835
- );
836
- for (let i = windowStart; i < windowEnd; i++) {
837
- const commit = commits[i];
838
- if (!commit) break outer;
839
- const parentSha = commit.commit.parent[0];
840
- const parentCommit = parentSha ? byOid.get(parentSha) : void 0;
841
- if (parentSha && !parentCommit) {
842
- truncated = true;
843
- break outer;
844
- }
845
- const [oid, parentOid] = await Promise.all([
846
- resolveOid(commit.commit.tree),
847
- parentCommit ? resolveOid(parentCommit.commit.tree) : Promise.resolve(null)
848
- ]);
849
- if (oid !== parentOid) {
850
- entries.push(toEntry(commit));
851
- if (entries.length >= limit) {
852
- truncated = i < commits.length - 1;
853
- break outer;
854
- }
855
- }
856
- }
857
- }
858
- const result = { entries, truncated };
859
- hooks?.resultCache?.set(cacheKey, result);
860
- return result;
861
- }
862
-
863
- // src/ops/last-commit.ts
864
- var HISTORY_WALK_DEPTH2 = 400;
865
- var PREFETCH_WINDOW2 = 24;
866
- function toLastCommitInfo(commit) {
867
- return {
868
- sha: commit.oid,
869
- message: commit.commit.message.trim(),
870
- authorName: commit.commit.author.name,
871
- authorEmail: commit.commit.author.email,
872
- createdAt: new Date(commit.commit.author.timestamp * 1e3).toISOString()
873
- };
874
- }
875
- async function getLastCommitsForTree(repo, options, hooks) {
876
- const treePath = options.treePath ?? "";
877
- const depth = options.depth ?? HISTORY_WALK_DEPTH2;
878
- const commits = await runStep(
879
- hooks,
880
- `getCommitLog depth=${depth}`,
881
- () => getCommitLog(repo, { ref: options.ref, depth }, hooks)
882
- );
883
- hooks?.onNote?.(`getLastCommitsForTree: ${commits.length} commits in log`);
884
- const head = commits[0];
885
- if (!head) return {};
886
- const cacheKey = `last-commits:${repo.gitdir}:${head.oid}:${treePath}`;
887
- const cachedResult = hooks?.resultCache?.get(cacheKey);
888
- if (cachedResult) {
889
- hooks?.onNote?.(
890
- "getLastCommitsForTree: result-cache HIT, skipping history walk"
891
- );
892
- return cachedResult;
893
- }
894
- hooks?.onNote?.("getLastCommitsForTree: result-cache MISS, walking history");
895
- const byOid = new Map(commits.map((commit) => [commit.oid, commit]));
896
- const dirOidByCommitTree = /* @__PURE__ */ new Map();
897
- const childrenByDirOid = /* @__PURE__ */ new Map();
898
- async function resolveDirOid(commitTreeOid) {
899
- const cached = dirOidByCommitTree.get(commitTreeOid);
900
- if (cached !== void 0) return cached;
901
- const entry = await findTreeEntry(repo, commitTreeOid, treePath);
902
- const dirOid = entry?.type === "tree" ? entry.oid : null;
903
- dirOidByCommitTree.set(commitTreeOid, dirOid);
904
- return dirOid;
905
- }
906
- async function resolveChildren(dirOid) {
907
- if (dirOid === null) return [];
908
- const cached = childrenByDirOid.get(dirOid);
909
- if (cached) return cached;
910
- const children = await listTreeEntries(repo, dirOid, treePath);
911
- childrenByDirOid.set(dirOid, children);
912
- return children;
913
- }
914
- const headDirOid = await resolveDirOid(head.commit.tree);
915
- if (headDirOid === null) return {};
916
- const headChildren = await resolveChildren(headDirOid);
917
- const remaining = new Set(headChildren.map((entry) => entry.path));
918
- const result = {};
919
- let commitsWalked = 0;
920
- let prefetchWindows = 0;
921
- const walkStart = performance.now();
922
- outer: for (let windowStart = 0; windowStart < commits.length && remaining.size > 0; windowStart += PREFETCH_WINDOW2) {
923
- const windowEnd = Math.min(windowStart + PREFETCH_WINDOW2, commits.length);
924
- const prefetchEnd = Math.min(windowEnd + 1, commits.length);
925
- prefetchWindows++;
926
- const dirOids = await Promise.all(
927
- commits.slice(windowStart, prefetchEnd).map((commit) => resolveDirOid(commit.commit.tree))
928
- );
929
- await Promise.all(
930
- [...new Set(dirOids)].map((dirOid) => resolveChildren(dirOid))
931
- );
932
- for (let i = windowStart; i < windowEnd; i++) {
933
- if (remaining.size === 0) break outer;
934
- const commit = commits[i];
935
- if (!commit) break outer;
936
- commitsWalked++;
937
- const parentSha = commit.commit.parent[0];
938
- const parentCommit = parentSha ? byOid.get(parentSha) : void 0;
939
- if (parentSha && !parentCommit) break outer;
940
- const [dirOid, parentDirOid] = await Promise.all([
941
- resolveDirOid(commit.commit.tree),
942
- parentCommit ? resolveDirOid(parentCommit.commit.tree) : Promise.resolve(null)
943
- ]);
944
- if (dirOid === parentDirOid) continue;
945
- const [children, parentChildren] = await Promise.all([
946
- resolveChildren(dirOid),
947
- resolveChildren(parentDirOid)
948
- ]);
949
- const childByName = new Map(
950
- children.map((entry) => [entry.path, entry.oid])
951
- );
952
- const parentChildByName = new Map(
953
- parentChildren.map((entry) => [entry.path, entry.oid])
954
- );
955
- for (const name of remaining) {
956
- if (childByName.get(name) !== parentChildByName.get(name)) {
957
- result[name] = toLastCommitInfo(commit);
958
- }
959
- }
960
- for (const name of Object.keys(result)) {
961
- remaining.delete(name);
962
- }
963
- }
964
- }
965
- hooks?.onNote?.(
966
- `getLastCommitsForTree: walked ${commitsWalked}/${commits.length} commits across ${prefetchWindows} prefetch windows in ${(performance.now() - walkStart).toFixed(1)}ms, ${dirOidByCommitTree.size} unique tree lookups, ${remaining.size} entries never resolved`
967
- );
968
- hooks?.resultCache?.set(cacheKey, result);
969
- return result;
970
- }
971
-
972
- // src/ops/merge.ts
973
- var import_isomorphic_git6 = __toESM(require("isomorphic-git"), 1);
974
- async function analyzeMerge(repo, sourceBranch, targetBranch) {
975
- assertSafeBranchName(sourceBranch);
976
- assertSafeBranchName(targetBranch);
977
- try {
978
- const [sourceOid, targetOid] = await Promise.all([
979
- import_isomorphic_git6.default.resolveRef({ ...repo, ref: qualifyBranchRef(sourceBranch) }),
980
- import_isomorphic_git6.default.resolveRef({ ...repo, ref: qualifyBranchRef(targetBranch) })
981
- ]);
982
- const isDescendant = await import_isomorphic_git6.default.isDescendent({
983
- ...repo,
984
- oid: sourceOid,
985
- ancestor: targetOid
986
- });
987
- return {
988
- canMerge: true,
989
- hasConflicts: false,
990
- conflictingFiles: [],
991
- fastForward: isDescendant
992
- };
993
- } catch (err) {
994
- if (err?.code !== "NotFoundError") {
995
- throw err;
996
- }
997
- return {
998
- canMerge: false,
999
- hasConflicts: true,
1000
- conflictingFiles: [],
1001
- fastForward: false
1002
- };
1003
- }
1004
- }
1005
- async function fastForwardMerge(repo, sourceBranch, targetBranch) {
1006
- assertSafeBranchName(sourceBranch);
1007
- assertSafeBranchName(targetBranch);
1008
- const [sourceOid, targetOid] = await Promise.all([
1009
- import_isomorphic_git6.default.resolveRef({ ...repo, ref: `refs/heads/${sourceBranch}` }),
1010
- import_isomorphic_git6.default.resolveRef({ ...repo, ref: `refs/heads/${targetBranch}` })
1011
- ]);
1012
- const isFF = await import_isomorphic_git6.default.isDescendent({
1013
- ...repo,
1014
- oid: sourceOid,
1015
- ancestor: targetOid
1016
- });
1017
- if (!isFF) return null;
1018
- await import_isomorphic_git6.default.writeRef({
1019
- ...repo,
1020
- ref: `refs/heads/${targetBranch}`,
1021
- value: sourceOid,
1022
- force: true
1023
- });
1024
- return { success: true, commitSha: sourceOid };
1025
- }
1026
- // Annotate the CommonJS export names for ESM import in node:
1027
- 0 && (module.exports = {
1028
- BANNER_WALK_DEPTH,
1029
- HISTORY_WALK_DEPTH,
1030
- analyzeMerge,
1031
- assertBranchExists,
1032
- assertSafeBranchName,
1033
- authorNow,
1034
- commitFilesToBare,
1035
- createBranchFrom,
1036
- deleteBranchByName,
1037
- deleteFileFromBare,
1038
- deleteFromTree,
1039
- fastForwardMerge,
1040
- findTreeEntry,
1041
- getBlob,
1042
- getCommit,
1043
- getCommitDiff,
1044
- getCommitHistory,
1045
- getCommitLog,
1046
- getDiffBetweenRefs,
1047
- getFileContent,
1048
- getFileFromRef,
1049
- getFileHistory,
1050
- getLastCommitsForTree,
1051
- getTreeFromRef,
1052
- listBranches,
1053
- listTreeEntries,
1054
- resolveCommit,
1055
- resolveLooseRefFast,
1056
- resultKeyPrefixes,
1057
- upsertTree,
1058
- writeCommitToBare
1059
- });
1060
- //# sourceMappingURL=ops.cjs.map