git-fs-s3 0.8.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -3
- package/dist/http.js +5 -3
- package/dist/http.js.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/ops.js +265 -232
- package/dist/ops.js.map +1 -1
- package/dist/types/errors.d.ts +0 -1
- package/dist/types/http/index.d.ts +2 -2
- package/dist/types/http/pack-reuse.d.ts +2 -1
- package/dist/types/ops/diff.d.ts +22 -5
- package/dist/types/ops/file-history.d.ts +4 -18
- package/dist/types/ops/history.d.ts +30 -43
- package/dist/types/ops/index.d.ts +5 -4
- package/dist/types/ops/last-commit.d.ts +2 -8
- package/dist/types/ops/submodules.d.ts +13 -0
- package/dist/types/ops/tree.d.ts +1 -1
- package/dist/types/ops/types.d.ts +10 -16
- package/package.json +2 -2
package/dist/ops.js
CHANGED
|
@@ -16,13 +16,10 @@ import {
|
|
|
16
16
|
GitObjectNotFoundError,
|
|
17
17
|
GitPathNotFoundError,
|
|
18
18
|
GitRefNotFoundError,
|
|
19
|
-
decodeUtf8,
|
|
20
|
-
hasNullByte,
|
|
21
19
|
inflate,
|
|
22
20
|
isSafeTreePath,
|
|
23
21
|
qualifyBranchRef,
|
|
24
|
-
readBlobContent
|
|
25
|
-
toBase64
|
|
22
|
+
readBlobContent
|
|
26
23
|
} from "./chunk-PASVUPKR.js";
|
|
27
24
|
|
|
28
25
|
// src/ops/commit.ts
|
|
@@ -332,36 +329,49 @@ function resultKeyPrefixes(gitdir) {
|
|
|
332
329
|
return [
|
|
333
330
|
`commitlog:${gitdir}:`,
|
|
334
331
|
`tree:${gitdir}:`,
|
|
335
|
-
`commits:${gitdir}:`,
|
|
336
332
|
`last-commits:${gitdir}:`,
|
|
337
333
|
`file-history:${gitdir}:`
|
|
338
334
|
];
|
|
339
335
|
}
|
|
340
336
|
|
|
341
337
|
// src/ops/history.ts
|
|
338
|
+
var HISTORY_WALK_DEPTH = 400;
|
|
339
|
+
function summarizeCommit(commit) {
|
|
340
|
+
return {
|
|
341
|
+
sha: commit.oid,
|
|
342
|
+
message: commit.commit.message.trim(),
|
|
343
|
+
authorName: commit.commit.author.name,
|
|
344
|
+
authorEmail: commit.commit.author.email,
|
|
345
|
+
createdAt: new Date(commit.commit.author.timestamp * 1e3).toISOString()
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
var isNotFound = (err) => err?.code === "NotFoundError";
|
|
342
349
|
function wrapMissingObject(promise, context) {
|
|
343
350
|
return promise.catch((err) => {
|
|
344
|
-
if (err
|
|
351
|
+
if (isNotFound(err)) {
|
|
345
352
|
throw new GitObjectNotFoundError(
|
|
346
|
-
`Git data for ${context} is missing from storage
|
|
353
|
+
`Git data for ${context} is missing from storage`
|
|
347
354
|
);
|
|
348
355
|
}
|
|
349
356
|
throw err;
|
|
350
357
|
});
|
|
351
358
|
}
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
359
|
+
async function resolveRefOid(repo, ref) {
|
|
360
|
+
try {
|
|
361
|
+
return await git3.resolveRef({ ...repo, ref: qualifyBranchRef(ref) });
|
|
362
|
+
} catch (err) {
|
|
363
|
+
if (isNotFound(err))
|
|
356
364
|
throw new GitRefNotFoundError(`Ref ${ref} does not exist`);
|
|
357
|
-
}
|
|
358
365
|
throw err;
|
|
359
|
-
}
|
|
360
|
-
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
async function resolveCommit(repo, ref) {
|
|
369
|
+
const oid = await resolveRefOid(repo, ref);
|
|
370
|
+
const { commit } = await wrapMissingObject(
|
|
361
371
|
git3.readCommit({ ...repo, oid }),
|
|
362
372
|
`${repo.gitdir} commit ${oid}`
|
|
363
373
|
);
|
|
364
|
-
return { oid, commit
|
|
374
|
+
return { oid, commit };
|
|
365
375
|
}
|
|
366
376
|
async function getBlob(repo, sha) {
|
|
367
377
|
const { blob } = await wrapMissingObject(
|
|
@@ -371,9 +381,7 @@ async function getBlob(repo, sha) {
|
|
|
371
381
|
return blob;
|
|
372
382
|
}
|
|
373
383
|
async function getFileContent(repo, filePath, ref, hooks) {
|
|
374
|
-
if (hooks?.prefetch)
|
|
375
|
-
await runStep(hooks, "prefetch", hooks.prefetch);
|
|
376
|
-
}
|
|
384
|
+
if (hooks?.prefetch) await runStep(hooks, "prefetch", hooks.prefetch);
|
|
377
385
|
const { commit } = await resolveCommit(repo, ref);
|
|
378
386
|
const context = `${repo.gitdir}@${ref}:${filePath}`;
|
|
379
387
|
const entry = await wrapMissingObject(
|
|
@@ -396,39 +404,28 @@ async function getCommit(repo, sha) {
|
|
|
396
404
|
);
|
|
397
405
|
return { oid: result.oid, commit: result.commit, payload: result.payload };
|
|
398
406
|
}
|
|
399
|
-
function isFullyWalked(commits) {
|
|
400
|
-
const last = commits[commits.length - 1];
|
|
401
|
-
return !!last && last.commit.parent.length === 0;
|
|
402
|
-
}
|
|
403
407
|
async function getCommitLog(repo, options, hooks) {
|
|
404
408
|
const { ref } = options;
|
|
405
409
|
const depth = options.depth ?? 50;
|
|
406
|
-
let headSha;
|
|
407
|
-
if (
|
|
408
|
-
headSha = options.knownHeadSha;
|
|
409
|
-
} else {
|
|
410
|
+
let headSha = options.knownHeadSha;
|
|
411
|
+
if (headSha === void 0) {
|
|
410
412
|
try {
|
|
411
|
-
headSha = await
|
|
413
|
+
headSha = await resolveRefOid(repo, ref);
|
|
412
414
|
} catch (err) {
|
|
413
|
-
if (
|
|
415
|
+
if (err instanceof GitRefNotFoundError) return [];
|
|
414
416
|
throw err;
|
|
415
417
|
}
|
|
416
418
|
}
|
|
417
419
|
const cacheKey = `commitlog:${repo.gitdir}:${headSha}`;
|
|
418
420
|
const cached = hooks?.resultCache?.get(cacheKey);
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
);
|
|
421
|
+
const fullyWalked = cached?.at(-1)?.commit.parent.length === 0;
|
|
422
|
+
if (cached && (cached.length >= depth || fullyWalked)) {
|
|
423
|
+
hooks?.onNote?.(`getCommitLog: cache hit ${cacheKey} (depth=${depth})`);
|
|
423
424
|
return cached.slice(0, depth);
|
|
424
425
|
}
|
|
425
|
-
hooks?.onNote?.(
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
if (hooks?.prefetch && depth >= (hooks.prefetchMinDepth ?? 5)) {
|
|
429
|
-
await runStep(hooks, "prefetch", hooks.prefetch);
|
|
430
|
-
}
|
|
431
|
-
const commits = await wrapMissingObject(
|
|
426
|
+
hooks?.onNote?.(`getCommitLog: cache miss ${cacheKey} (depth=${depth})`);
|
|
427
|
+
if (hooks?.prefetch) await runStep(hooks, "prefetch", hooks.prefetch);
|
|
428
|
+
const log = await wrapMissingObject(
|
|
432
429
|
runStep(
|
|
433
430
|
hooks,
|
|
434
431
|
`git.log ${ref} depth=${depth}`,
|
|
@@ -436,25 +433,16 @@ async function getCommitLog(repo, options, hooks) {
|
|
|
436
433
|
),
|
|
437
434
|
`${repo.gitdir}@${ref} history`
|
|
438
435
|
);
|
|
439
|
-
const result =
|
|
440
|
-
oid
|
|
441
|
-
commit
|
|
442
|
-
payload:
|
|
436
|
+
const result = log.map(({ oid, commit, payload }) => ({
|
|
437
|
+
oid,
|
|
438
|
+
commit,
|
|
439
|
+
payload: payload ?? ""
|
|
443
440
|
}));
|
|
444
441
|
if (!cached || result.length > cached.length) {
|
|
445
442
|
hooks?.resultCache?.set(cacheKey, result);
|
|
446
443
|
}
|
|
447
444
|
return result;
|
|
448
445
|
}
|
|
449
|
-
async function getFileFromRef(repo, filePath, ref, hooks) {
|
|
450
|
-
const bytes = await getFileContent(repo, filePath, ref, hooks);
|
|
451
|
-
const isBinary = hasNullByte(bytes);
|
|
452
|
-
return {
|
|
453
|
-
content: isBinary ? toBase64(bytes) : decodeUtf8(bytes),
|
|
454
|
-
size: bytes.length,
|
|
455
|
-
isBinary
|
|
456
|
-
};
|
|
457
|
-
}
|
|
458
446
|
async function getTreeFromRef(repo, options, hooks) {
|
|
459
447
|
const { ref } = options;
|
|
460
448
|
const treePath = options.treePath ?? "";
|
|
@@ -462,227 +450,189 @@ async function getTreeFromRef(repo, options, hooks) {
|
|
|
462
450
|
const cacheKey = `tree:${repo.gitdir}:${headSha}:${treePath}`;
|
|
463
451
|
const cached = hooks?.resultCache?.get(cacheKey);
|
|
464
452
|
if (cached) {
|
|
465
|
-
hooks?.onNote?.(`getTreeFromRef:
|
|
453
|
+
hooks?.onNote?.(`getTreeFromRef: cache hit ${cacheKey}`);
|
|
466
454
|
return cached;
|
|
467
455
|
}
|
|
468
|
-
hooks?.onNote?.(`getTreeFromRef:
|
|
469
|
-
if (hooks?.prefetch)
|
|
470
|
-
await runStep(hooks, "prefetch", hooks.prefetch);
|
|
471
|
-
}
|
|
456
|
+
hooks?.onNote?.(`getTreeFromRef: cache miss ${cacheKey}`);
|
|
457
|
+
if (hooks?.prefetch) await runStep(hooks, "prefetch", hooks.prefetch);
|
|
472
458
|
const context = `${repo.gitdir}@${ref}:${treePath || "/"}`;
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
const entry = await wrapMissingObject(
|
|
485
|
-
runStep(
|
|
486
|
-
hooks,
|
|
487
|
-
`findTreeEntry ${treePath}`,
|
|
488
|
-
() => findTreeEntry(repo, commit.tree, treePath)
|
|
489
|
-
),
|
|
490
|
-
context
|
|
491
|
-
);
|
|
492
|
-
if (!entry) {
|
|
493
|
-
throw new GitPathNotFoundError(
|
|
494
|
-
`Path "${treePath}" does not exist at ${ref}`
|
|
495
|
-
);
|
|
496
|
-
}
|
|
497
|
-
result = entry.type !== "tree" ? [] : await wrapMissingObject(
|
|
498
|
-
runStep(
|
|
499
|
-
hooks,
|
|
500
|
-
`listTreeEntries ${treePath}`,
|
|
501
|
-
() => listTreeEntries(repo, entry.oid, entry.path)
|
|
502
|
-
),
|
|
503
|
-
context
|
|
459
|
+
const dir = treePath ? await wrapMissingObject(
|
|
460
|
+
runStep(
|
|
461
|
+
hooks,
|
|
462
|
+
`findTreeEntry ${treePath}`,
|
|
463
|
+
() => findTreeEntry(repo, commit.tree, treePath)
|
|
464
|
+
),
|
|
465
|
+
context
|
|
466
|
+
) : { path: "", type: "tree", oid: commit.tree };
|
|
467
|
+
if (!dir) {
|
|
468
|
+
throw new GitPathNotFoundError(
|
|
469
|
+
`Path "${treePath}" does not exist at ${ref}`
|
|
504
470
|
);
|
|
505
471
|
}
|
|
472
|
+
const result = dir.type === "tree" ? await wrapMissingObject(
|
|
473
|
+
runStep(
|
|
474
|
+
hooks,
|
|
475
|
+
`listTreeEntries ${treePath || "/"}`,
|
|
476
|
+
() => listTreeEntries(repo, dir.oid, dir.path)
|
|
477
|
+
),
|
|
478
|
+
context
|
|
479
|
+
) : [];
|
|
506
480
|
hooks?.resultCache?.set(cacheKey, result);
|
|
507
481
|
return result;
|
|
508
482
|
}
|
|
509
483
|
async function getCommitHistory(repo, options, hooks) {
|
|
510
484
|
const limit = options.limit ?? 50;
|
|
511
485
|
const skip = options.skip ?? 0;
|
|
512
|
-
const
|
|
513
|
-
const cacheKey = headSha ? `commits:${repo.gitdir}:${headSha}:${limit}:${skip}` : null;
|
|
514
|
-
if (cacheKey) {
|
|
515
|
-
const cached = hooks?.resultCache?.get(cacheKey);
|
|
516
|
-
if (cached) {
|
|
517
|
-
hooks?.onNote?.(`getCommitHistory: result-cache HIT for ${cacheKey}`);
|
|
518
|
-
return cached;
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
hooks?.onNote?.(
|
|
522
|
-
`getCommitHistory: result-cache MISS for ${cacheKey ?? "(no head)"}`
|
|
523
|
-
);
|
|
524
|
-
if (!headSha) return [];
|
|
525
|
-
const all = await getCommitLog(
|
|
486
|
+
const log = await getCommitLog(
|
|
526
487
|
repo,
|
|
527
|
-
{ ref: options.ref, depth: limit + skip
|
|
488
|
+
{ ref: options.ref, depth: limit + skip },
|
|
528
489
|
hooks
|
|
529
490
|
);
|
|
530
|
-
|
|
531
|
-
if (cacheKey) hooks?.resultCache?.set(cacheKey, result);
|
|
532
|
-
return result;
|
|
491
|
+
return log.slice(skip, skip + limit);
|
|
533
492
|
}
|
|
534
493
|
|
|
535
494
|
// src/ops/diff.ts
|
|
536
495
|
var MAX_DIFF_TEXT_BYTES = 1024 * 1024;
|
|
537
|
-
function detectBlobContent(blob) {
|
|
538
|
-
return readBlobContent(blob);
|
|
539
|
-
}
|
|
540
496
|
function countContentLines(content) {
|
|
541
497
|
if (content.length === 0) return 0;
|
|
542
498
|
const lines = content.split("\n");
|
|
543
|
-
if (lines
|
|
499
|
+
if (lines.at(-1) === "") lines.pop();
|
|
544
500
|
return lines.length;
|
|
545
501
|
}
|
|
546
|
-
function
|
|
547
|
-
const
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
params.before,
|
|
553
|
-
params.after,
|
|
502
|
+
function unifiedPatch(path, before, after) {
|
|
503
|
+
const body = createTwoFilesPatch(
|
|
504
|
+
before ? `a/${path}` : "/dev/null",
|
|
505
|
+
after ? `b/${path}` : "/dev/null",
|
|
506
|
+
before?.text ?? "",
|
|
507
|
+
after?.text ?? "",
|
|
554
508
|
"",
|
|
555
509
|
"",
|
|
556
510
|
{ context: 3 }
|
|
557
511
|
).replace(/^=+\n/, "");
|
|
558
|
-
return `diff --git a/${
|
|
559
|
-
${
|
|
512
|
+
return `diff --git a/${path} b/${path}
|
|
513
|
+
${body}`;
|
|
560
514
|
}
|
|
561
|
-
function
|
|
562
|
-
return {
|
|
563
|
-
files,
|
|
564
|
-
totalAdditions: files.reduce((sum, f) => sum + f.additions, 0),
|
|
565
|
-
totalDeletions: files.reduce((sum, f) => sum + f.deletions, 0),
|
|
566
|
-
totalFiles: files.length
|
|
567
|
-
};
|
|
568
|
-
}
|
|
569
|
-
function buildDiffFile(path, status, before, after) {
|
|
515
|
+
function buildDiffFile(path, before, after) {
|
|
570
516
|
const kind = before?.isBinary || after?.isBinary ? "binary" : Math.max(before?.bytes.length ?? 0, after?.bytes.length ?? 0) > MAX_DIFF_TEXT_BYTES ? "too-large" : "text";
|
|
571
517
|
const isText = kind === "text";
|
|
572
518
|
return {
|
|
573
519
|
path,
|
|
574
|
-
status,
|
|
520
|
+
status: !before ? "added" : !after ? "deleted" : "modified",
|
|
575
521
|
kind,
|
|
576
522
|
additions: isText && after ? countContentLines(after.text) : 0,
|
|
577
523
|
deletions: isText && before ? countContentLines(before.text) : 0,
|
|
578
|
-
patch: isText ?
|
|
579
|
-
path,
|
|
580
|
-
before: before?.text ?? "",
|
|
581
|
-
after: after?.text ?? "",
|
|
582
|
-
oldPath: before ? void 0 : "/dev/null",
|
|
583
|
-
newPath: after ? void 0 : "/dev/null"
|
|
584
|
-
}) : "",
|
|
524
|
+
patch: isText ? unifiedPatch(path, before, after) : "",
|
|
585
525
|
oldSize: before?.bytes.length,
|
|
586
526
|
newSize: after?.bytes.length
|
|
587
527
|
};
|
|
588
528
|
}
|
|
589
|
-
async function
|
|
529
|
+
async function readDiffEntry(entry) {
|
|
530
|
+
return entry ? {
|
|
531
|
+
type: await entry.type(),
|
|
532
|
+
oid: await entry.oid(),
|
|
533
|
+
mode: await entry.mode()
|
|
534
|
+
} : void 0;
|
|
535
|
+
}
|
|
536
|
+
function buildSubmoduleDiff(filepath, before, after) {
|
|
537
|
+
if (before?.type === after?.type && before?.oid === after?.oid)
|
|
538
|
+
return void 0;
|
|
539
|
+
const line = (entry) => entry ? `${entry.type === "commit" ? "Subproject commit" : entry.type === "tree" ? "Directory" : "File"} ${entry.oid}
|
|
540
|
+
` : "";
|
|
541
|
+
return {
|
|
542
|
+
path: filepath,
|
|
543
|
+
status: !before ? "added" : !after ? "deleted" : "modified",
|
|
544
|
+
kind: "submodule",
|
|
545
|
+
additions: 0,
|
|
546
|
+
deletions: 0,
|
|
547
|
+
oldEntry: before,
|
|
548
|
+
newEntry: after,
|
|
549
|
+
patch: createTwoFilesPatch(
|
|
550
|
+
before ? `a/${filepath}` : "/dev/null",
|
|
551
|
+
after ? `b/${filepath}` : "/dev/null",
|
|
552
|
+
line(before),
|
|
553
|
+
line(after),
|
|
554
|
+
"",
|
|
555
|
+
"",
|
|
556
|
+
{ context: 3 }
|
|
557
|
+
)
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
async function diffTrees(repo, oldTreeOid, newTreeOid) {
|
|
561
|
+
const readSide = async (entry) => {
|
|
562
|
+
if (!entry || await entry.type() !== "blob") return null;
|
|
563
|
+
const { blob } = await git4.readBlob({ ...repo, oid: await entry.oid() });
|
|
564
|
+
return readBlobContent(blob);
|
|
565
|
+
};
|
|
566
|
+
const trees = oldTreeOid ? [git4.TREE({ ref: oldTreeOid }), git4.TREE({ ref: newTreeOid })] : [git4.TREE({ ref: newTreeOid })];
|
|
590
567
|
const changes = await git4.walk({
|
|
591
568
|
...repo,
|
|
592
|
-
trees
|
|
593
|
-
map: async (filepath,
|
|
594
|
-
const [
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
const { blob } = await git4.readBlob({ ...repo, oid: oidA2 });
|
|
599
|
-
const before = detectBlobContent(blob);
|
|
600
|
-
return buildDiffFile(filepath, "deleted", before, null);
|
|
601
|
-
}
|
|
602
|
-
if (!typeA && typeB) {
|
|
603
|
-
const oidB2 = B ? await B.oid() : "";
|
|
604
|
-
const { blob } = await git4.readBlob({ ...repo, oid: oidB2 });
|
|
605
|
-
const after = detectBlobContent(blob);
|
|
606
|
-
return buildDiffFile(filepath, "added", null, after);
|
|
607
|
-
}
|
|
608
|
-
const [oidA, oidB] = await Promise.all([
|
|
609
|
-
A ? A.oid() : Promise.resolve(""),
|
|
610
|
-
B ? B.oid() : Promise.resolve("")
|
|
569
|
+
trees,
|
|
570
|
+
map: async (filepath, entries) => {
|
|
571
|
+
const [oldEntry, newEntry] = oldTreeOid ? [entries[0] ?? null, entries[1] ?? null] : [null, entries[0] ?? null];
|
|
572
|
+
const [oldType, newType] = await Promise.all([
|
|
573
|
+
oldEntry?.type(),
|
|
574
|
+
newEntry?.type()
|
|
611
575
|
]);
|
|
612
|
-
if (
|
|
613
|
-
const [
|
|
614
|
-
|
|
615
|
-
|
|
576
|
+
if (oldType === "commit" || newType === "commit") {
|
|
577
|
+
const [before2, after2] = await Promise.all([
|
|
578
|
+
readDiffEntry(oldEntry),
|
|
579
|
+
readDiffEntry(newEntry)
|
|
580
|
+
]);
|
|
581
|
+
return buildSubmoduleDiff(filepath, before2, after2);
|
|
582
|
+
}
|
|
583
|
+
if (oldType !== "blob" && newType !== "blob") return void 0;
|
|
584
|
+
if (oldEntry && newEntry && oldType === newType) {
|
|
585
|
+
const [oldOid, newOid] = await Promise.all([
|
|
586
|
+
oldEntry.oid(),
|
|
587
|
+
newEntry.oid()
|
|
616
588
|
]);
|
|
617
|
-
|
|
618
|
-
const after = detectBlobContent(blobB);
|
|
619
|
-
return buildDiffFile(filepath, "modified", before, after);
|
|
589
|
+
if (oldOid === newOid) return void 0;
|
|
620
590
|
}
|
|
621
|
-
|
|
591
|
+
const [before, after] = await Promise.all([
|
|
592
|
+
readSide(oldEntry),
|
|
593
|
+
readSide(newEntry)
|
|
594
|
+
]);
|
|
595
|
+
return before || after ? buildDiffFile(filepath, before, after) : void 0;
|
|
622
596
|
}
|
|
623
597
|
});
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
598
|
+
const files = (changes ?? []).filter((file) => !!file);
|
|
599
|
+
return {
|
|
600
|
+
files,
|
|
601
|
+
totalAdditions: files.reduce((sum, f) => sum + f.additions, 0),
|
|
602
|
+
totalDeletions: files.reduce((sum, f) => sum + f.deletions, 0),
|
|
603
|
+
totalFiles: files.length
|
|
604
|
+
};
|
|
627
605
|
}
|
|
628
606
|
async function getCommitDiff(repo, commitSha) {
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
while (stack.length) {
|
|
638
|
-
const { treeOid, prefix } = stack.pop();
|
|
639
|
-
const { tree } = await git4.readTree({ ...repo, oid: treeOid });
|
|
640
|
-
for (const entry of tree) {
|
|
641
|
-
const full = prefix ? `${prefix}/${entry.path}` : entry.path;
|
|
642
|
-
if (entry.type === "tree") {
|
|
643
|
-
stack.push({ treeOid: entry.oid, prefix: full });
|
|
644
|
-
} else if (entry.type === "blob") {
|
|
645
|
-
entries.push({ path: full, oid: entry.oid });
|
|
646
|
-
}
|
|
647
|
-
}
|
|
648
|
-
}
|
|
649
|
-
const files2 = await Promise.all(
|
|
650
|
-
entries.map(async ({ path, oid }) => {
|
|
651
|
-
const { blob } = await git4.readBlob({ ...repo, oid });
|
|
652
|
-
const after = detectBlobContent(blob);
|
|
653
|
-
return buildDiffFile(path, "added", null, after);
|
|
654
|
-
})
|
|
655
|
-
);
|
|
656
|
-
return summarizeDiff(files2);
|
|
657
|
-
}
|
|
658
|
-
const files = await walkTreeDiff(repo, parent, commitSha);
|
|
659
|
-
return summarizeDiff(files);
|
|
660
|
-
} catch (error) {
|
|
661
|
-
throw new Error(`Failed to get commit diff: ${error}`);
|
|
662
|
-
}
|
|
607
|
+
const { commit } = await getCommit(repo, commitSha);
|
|
608
|
+
const parent = commit.parent[0];
|
|
609
|
+
const parentTree = parent ? (await getCommit(repo, parent)).commit.tree : null;
|
|
610
|
+
return {
|
|
611
|
+
...await diffTrees(repo, parentTree, commit.tree),
|
|
612
|
+
oldRef: parent,
|
|
613
|
+
newRef: commitSha
|
|
614
|
+
};
|
|
663
615
|
}
|
|
664
616
|
async function getDiffBetweenRefs(repo, baseRef, compareRef) {
|
|
665
|
-
const [
|
|
666
|
-
|
|
667
|
-
|
|
617
|
+
const [base, compare] = await Promise.all([
|
|
618
|
+
resolveCommit(repo, baseRef),
|
|
619
|
+
resolveCommit(repo, compareRef)
|
|
668
620
|
]);
|
|
669
|
-
const
|
|
670
|
-
|
|
621
|
+
const [mergeBase] = await git4.findMergeBase({
|
|
622
|
+
...repo,
|
|
623
|
+
oids: [base.oid, compare.oid]
|
|
624
|
+
});
|
|
625
|
+
const baseTree = mergeBase === void 0 ? null : mergeBase === base.oid ? base.commit.tree : (await getCommit(repo, mergeBase)).commit.tree;
|
|
626
|
+
return {
|
|
627
|
+
...await diffTrees(repo, baseTree, compare.commit.tree),
|
|
628
|
+
oldRef: mergeBase,
|
|
629
|
+
newRef: compare.oid
|
|
630
|
+
};
|
|
671
631
|
}
|
|
672
632
|
|
|
673
633
|
// src/ops/file-history.ts
|
|
674
|
-
var HISTORY_WALK_DEPTH = 400;
|
|
675
634
|
var BANNER_WALK_DEPTH = 60;
|
|
676
635
|
var PREFETCH_WINDOW = 24;
|
|
677
|
-
function toEntry(commit) {
|
|
678
|
-
return {
|
|
679
|
-
sha: commit.oid,
|
|
680
|
-
message: commit.commit.message.trim(),
|
|
681
|
-
authorName: commit.commit.author.name,
|
|
682
|
-
authorEmail: commit.commit.author.email,
|
|
683
|
-
createdAt: new Date(commit.commit.author.timestamp * 1e3).toISOString()
|
|
684
|
-
};
|
|
685
|
-
}
|
|
686
636
|
async function getFileHistory(repo, options, hooks) {
|
|
687
637
|
const limit = options.limit ?? 30;
|
|
688
638
|
const maxDepth = options.maxDepth ?? HISTORY_WALK_DEPTH;
|
|
@@ -707,7 +657,7 @@ async function getFileHistory(repo, options, hooks) {
|
|
|
707
657
|
const cachedOid = oidByCommitTree.get(commitTreeOid);
|
|
708
658
|
if (cachedOid !== void 0) return cachedOid;
|
|
709
659
|
const entry = await findTreeEntry(repo, commitTreeOid, options.filePath);
|
|
710
|
-
const oid = entry
|
|
660
|
+
const oid = entry && entry.type !== "tree" ? entry.oid : null;
|
|
711
661
|
oidByCommitTree.set(commitTreeOid, oid);
|
|
712
662
|
return oid;
|
|
713
663
|
}
|
|
@@ -733,7 +683,7 @@ async function getFileHistory(repo, options, hooks) {
|
|
|
733
683
|
parentCommit ? resolveOid(parentCommit.commit.tree) : Promise.resolve(null)
|
|
734
684
|
]);
|
|
735
685
|
if (oid !== parentOid) {
|
|
736
|
-
entries.push(
|
|
686
|
+
entries.push(summarizeCommit(commit));
|
|
737
687
|
if (entries.length >= limit) {
|
|
738
688
|
truncated = i < commits.length - 1;
|
|
739
689
|
break outer;
|
|
@@ -747,20 +697,10 @@ async function getFileHistory(repo, options, hooks) {
|
|
|
747
697
|
}
|
|
748
698
|
|
|
749
699
|
// src/ops/last-commit.ts
|
|
750
|
-
var HISTORY_WALK_DEPTH2 = 400;
|
|
751
700
|
var PREFETCH_WINDOW2 = 24;
|
|
752
|
-
function toLastCommitInfo(commit) {
|
|
753
|
-
return {
|
|
754
|
-
sha: commit.oid,
|
|
755
|
-
message: commit.commit.message.trim(),
|
|
756
|
-
authorName: commit.commit.author.name,
|
|
757
|
-
authorEmail: commit.commit.author.email,
|
|
758
|
-
createdAt: new Date(commit.commit.author.timestamp * 1e3).toISOString()
|
|
759
|
-
};
|
|
760
|
-
}
|
|
761
701
|
async function getLastCommitsForTree(repo, options, hooks) {
|
|
762
702
|
const treePath = options.treePath ?? "";
|
|
763
|
-
const depth = options.depth ??
|
|
703
|
+
const depth = options.depth ?? HISTORY_WALK_DEPTH;
|
|
764
704
|
const commits = await runStep(
|
|
765
705
|
hooks,
|
|
766
706
|
`getCommitLog depth=${depth}`,
|
|
@@ -840,7 +780,7 @@ async function getLastCommitsForTree(repo, options, hooks) {
|
|
|
840
780
|
);
|
|
841
781
|
for (const name of remaining) {
|
|
842
782
|
if (childByName.get(name) !== parentChildByName.get(name)) {
|
|
843
|
-
result[name] =
|
|
783
|
+
result[name] = summarizeCommit(commit);
|
|
844
784
|
}
|
|
845
785
|
}
|
|
846
786
|
for (const name of Object.keys(result)) {
|
|
@@ -912,6 +852,97 @@ async function fastForwardMerge(repo, sourceBranch, targetBranch) {
|
|
|
912
852
|
});
|
|
913
853
|
return { success: true, commitSha: sourceOid };
|
|
914
854
|
}
|
|
855
|
+
|
|
856
|
+
// src/ops/submodules.ts
|
|
857
|
+
function parseValue(raw) {
|
|
858
|
+
let value = "";
|
|
859
|
+
let spaces = "";
|
|
860
|
+
let quoted = false;
|
|
861
|
+
const escapes = {
|
|
862
|
+
n: "\n",
|
|
863
|
+
t: " ",
|
|
864
|
+
b: "\b",
|
|
865
|
+
"\\": "\\",
|
|
866
|
+
'"': '"'
|
|
867
|
+
};
|
|
868
|
+
for (let i = 0; i < raw.length; i++) {
|
|
869
|
+
const c = raw.charAt(i);
|
|
870
|
+
if (c === "\\") {
|
|
871
|
+
const escaped = escapes[raw.charAt(++i)];
|
|
872
|
+
if (escaped === void 0) return null;
|
|
873
|
+
value += spaces + escaped;
|
|
874
|
+
spaces = "";
|
|
875
|
+
} else if (c === '"') {
|
|
876
|
+
value += spaces;
|
|
877
|
+
spaces = "";
|
|
878
|
+
quoted = !quoted;
|
|
879
|
+
} else if (!quoted && (c === "#" || c === ";")) break;
|
|
880
|
+
else if (!quoted && /\s/.test(c)) {
|
|
881
|
+
if (value) spaces += c;
|
|
882
|
+
} else {
|
|
883
|
+
value += spaces + c;
|
|
884
|
+
spaces = "";
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
return quoted ? null : value;
|
|
888
|
+
}
|
|
889
|
+
function parseGitmodules(content) {
|
|
890
|
+
const sections = /* @__PURE__ */ new Map();
|
|
891
|
+
let current;
|
|
892
|
+
const lines = [];
|
|
893
|
+
let pending = "";
|
|
894
|
+
for (const line of content.replace(/\r\n/g, "\n").split("\n")) {
|
|
895
|
+
const slashes = /\\+$/.exec(line)?.[0].length ?? 0;
|
|
896
|
+
if (slashes % 2 === 1) pending += line.slice(0, -1);
|
|
897
|
+
else {
|
|
898
|
+
lines.push(pending + line);
|
|
899
|
+
pending = "";
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
for (const line of lines) {
|
|
903
|
+
const trimmed = line.trim();
|
|
904
|
+
if (!trimmed || /^[#;]/.test(trimmed)) continue;
|
|
905
|
+
if (trimmed.startsWith("[")) {
|
|
906
|
+
const match = /^\[submodule\s+"((?:[^"\\]|\\["\\])*)"\]\s*(?:[#;].*)?$/i.exec(
|
|
907
|
+
trimmed
|
|
908
|
+
);
|
|
909
|
+
current = void 0;
|
|
910
|
+
if (!match?.[1]) continue;
|
|
911
|
+
const name = match[1].replace(/\\(["\\])/g, "$1");
|
|
912
|
+
current = sections.get(name) ?? { name };
|
|
913
|
+
sections.set(name, current);
|
|
914
|
+
continue;
|
|
915
|
+
}
|
|
916
|
+
if (!current) continue;
|
|
917
|
+
const field = /^(path|url)\s*=\s*(.*)$/i.exec(trimmed);
|
|
918
|
+
if (!field?.[1] || field[2] === void 0) continue;
|
|
919
|
+
const key = field[1].toLowerCase();
|
|
920
|
+
const value = parseValue(field[2]);
|
|
921
|
+
if (value === null) delete current[key];
|
|
922
|
+
else current[key] = value;
|
|
923
|
+
}
|
|
924
|
+
const valid = [...sections.values()].filter(
|
|
925
|
+
(s) => !!s.name && !!s.path && !!s.url && ![...s.path + s.url].some(
|
|
926
|
+
(c) => c.charCodeAt(0) < 32 || c.charCodeAt(0) === 127
|
|
927
|
+
) && !s.path.startsWith("/") && !s.path.includes("\\") && s.path.split("/").every(
|
|
928
|
+
(p) => p !== "" && p !== "." && p !== ".." && p.toLowerCase() !== ".git"
|
|
929
|
+
)
|
|
930
|
+
);
|
|
931
|
+
const counts = /* @__PURE__ */ new Map();
|
|
932
|
+
for (const s of valid) counts.set(s.path, (counts.get(s.path) ?? 0) + 1);
|
|
933
|
+
return valid.filter((s) => counts.get(s.path) === 1);
|
|
934
|
+
}
|
|
935
|
+
async function getSubmodules(repo, ref, hooks) {
|
|
936
|
+
let bytes;
|
|
937
|
+
try {
|
|
938
|
+
bytes = await getFileContent(repo, ".gitmodules", ref, hooks);
|
|
939
|
+
} catch (error) {
|
|
940
|
+
if (error instanceof GitPathNotFoundError) return [];
|
|
941
|
+
throw error;
|
|
942
|
+
}
|
|
943
|
+
if (bytes.length > 1024 * 1024) return [];
|
|
944
|
+
return parseGitmodules(new TextDecoder().decode(bytes));
|
|
945
|
+
}
|
|
915
946
|
export {
|
|
916
947
|
BANNER_WALK_DEPTH,
|
|
917
948
|
HISTORY_WALK_DEPTH,
|
|
@@ -934,15 +965,17 @@ export {
|
|
|
934
965
|
getCommitLog,
|
|
935
966
|
getDiffBetweenRefs,
|
|
936
967
|
getFileContent,
|
|
937
|
-
getFileFromRef,
|
|
938
968
|
getFileHistory,
|
|
939
969
|
getLastCommitsForTree,
|
|
970
|
+
getSubmodules,
|
|
940
971
|
getTreeFromRef,
|
|
941
972
|
listBranches,
|
|
942
973
|
listTreeEntries,
|
|
974
|
+
parseGitmodules,
|
|
943
975
|
resolveCommit,
|
|
944
976
|
resolveLooseRefFast,
|
|
945
977
|
resultKeyPrefixes,
|
|
978
|
+
summarizeCommit,
|
|
946
979
|
upsertTree,
|
|
947
980
|
withPackedWrites,
|
|
948
981
|
writeCommitToBare
|