@lumy-pack/line-lore 0.0.4 → 0.0.6
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 +71 -0
- package/dist/cli.mjs +204 -45
- package/dist/core/ancestry/ancestry.d.ts +1 -0
- package/dist/core/pr-lookup/pr-lookup.d.ts +7 -1
- package/dist/git/executor.d.ts +1 -0
- package/dist/git/health.d.ts +4 -1
- package/dist/git/index.d.ts +2 -2
- package/dist/index.cjs +200 -43
- package/dist/index.mjs +200 -42
- package/dist/types/cache.d.ts +11 -0
- package/dist/types/git.d.ts +6 -0
- package/dist/types/index.d.ts +2 -2
- package/dist/types/trace.d.ts +2 -0
- package/dist/version.d.ts +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -317,6 +317,51 @@ async function gitExec(args, options) {
|
|
|
317
317
|
LineLoreErrorCode.GIT_COMMAND_FAILED
|
|
318
318
|
);
|
|
319
319
|
}
|
|
320
|
+
async function gitPipe(producerArgs, consumerArgs, options) {
|
|
321
|
+
const { cwd, timeout } = options ?? {};
|
|
322
|
+
const pipeArgs = [...producerArgs, "|", ...consumerArgs];
|
|
323
|
+
try {
|
|
324
|
+
const result = await (0, import_execa.execa)("git", producerArgs, {
|
|
325
|
+
cwd,
|
|
326
|
+
timeout,
|
|
327
|
+
reject: false
|
|
328
|
+
}).pipe("git", consumerArgs, { cwd, timeout, reject: false });
|
|
329
|
+
const exitCode = result.exitCode ?? 0;
|
|
330
|
+
if (exitCode !== 0) {
|
|
331
|
+
throw new LineLoreError(
|
|
332
|
+
LineLoreErrorCode.GIT_COMMAND_FAILED,
|
|
333
|
+
`git pipe failed with exit code ${exitCode}: ${result.stderr}`,
|
|
334
|
+
{
|
|
335
|
+
command: "git",
|
|
336
|
+
args: pipeArgs,
|
|
337
|
+
exitCode,
|
|
338
|
+
stderr: result.stderr,
|
|
339
|
+
cwd
|
|
340
|
+
}
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
return {
|
|
344
|
+
stdout: result.stdout,
|
|
345
|
+
stderr: result.stderr,
|
|
346
|
+
exitCode
|
|
347
|
+
};
|
|
348
|
+
} catch (error) {
|
|
349
|
+
if (error instanceof LineLoreError) throw error;
|
|
350
|
+
const isTimeout = error instanceof Error && "isTerminated" in error && error.timedOut === true;
|
|
351
|
+
if (isTimeout) {
|
|
352
|
+
throw new LineLoreError(
|
|
353
|
+
LineLoreErrorCode.GIT_TIMEOUT,
|
|
354
|
+
`git pipe timed out after ${timeout}ms`,
|
|
355
|
+
{ command: "git", args: pipeArgs, timeout, cwd }
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
throw new LineLoreError(
|
|
359
|
+
LineLoreErrorCode.GIT_COMMAND_FAILED,
|
|
360
|
+
`git pipe failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
361
|
+
{ command: "git", args: pipeArgs, cwd }
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
320
365
|
async function shellExec(command, args, options) {
|
|
321
366
|
return execCommand(
|
|
322
367
|
command,
|
|
@@ -338,14 +383,22 @@ var init_executor = __esm({
|
|
|
338
383
|
// src/core/ancestry/ancestry.ts
|
|
339
384
|
async function findMergeCommit(commitSha, options) {
|
|
340
385
|
const ref = options?.ref ?? "HEAD";
|
|
386
|
+
const budget = options?.timeout ?? DEFAULT_ANCESTRY_TIMEOUT;
|
|
387
|
+
const startTime = Date.now();
|
|
341
388
|
const firstParentResult = await findMergeCommitWithArgs(
|
|
342
389
|
commitSha,
|
|
343
390
|
ref,
|
|
344
391
|
["--first-parent"],
|
|
345
|
-
options
|
|
392
|
+
{ ...options, timeout: budget }
|
|
346
393
|
);
|
|
347
394
|
if (firstParentResult) return firstParentResult;
|
|
348
|
-
|
|
395
|
+
const elapsed = Date.now() - startTime;
|
|
396
|
+
const remaining = budget - elapsed;
|
|
397
|
+
if (remaining <= 0) return null;
|
|
398
|
+
return findMergeCommitWithArgs(commitSha, ref, [], {
|
|
399
|
+
...options,
|
|
400
|
+
timeout: remaining
|
|
401
|
+
});
|
|
349
402
|
}
|
|
350
403
|
async function findMergeCommitWithArgs(commitSha, ref, extraArgs, options) {
|
|
351
404
|
try {
|
|
@@ -393,15 +446,18 @@ function extractPRFromMergeMessage(subject) {
|
|
|
393
446
|
if (squashMatch) return parseInt(squashMatch[1], 10);
|
|
394
447
|
const glMatch = /!(\d+)\s*$/.exec(subject);
|
|
395
448
|
if (glMatch) return parseInt(glMatch[1], 10);
|
|
449
|
+
const adoMatch = /Merged PR (\d+):/.exec(subject);
|
|
450
|
+
if (adoMatch) return parseInt(adoMatch[1], 10);
|
|
396
451
|
return null;
|
|
397
452
|
}
|
|
398
|
-
var import_common_utils9;
|
|
453
|
+
var import_common_utils9, DEFAULT_ANCESTRY_TIMEOUT;
|
|
399
454
|
var init_ancestry = __esm({
|
|
400
455
|
"src/core/ancestry/ancestry.ts"() {
|
|
401
456
|
"use strict";
|
|
402
457
|
init_cjs_shims();
|
|
403
458
|
import_common_utils9 = require("@winglet/common-utils");
|
|
404
459
|
init_executor();
|
|
460
|
+
DEFAULT_ANCESTRY_TIMEOUT = 3e4;
|
|
405
461
|
}
|
|
406
462
|
});
|
|
407
463
|
|
|
@@ -437,14 +493,10 @@ async function computePatchId(commitSha, options) {
|
|
|
437
493
|
const cached = await cache.get(commitSha);
|
|
438
494
|
if (cached) return cached;
|
|
439
495
|
try {
|
|
440
|
-
const
|
|
441
|
-
|
|
442
|
-
"
|
|
443
|
-
|
|
444
|
-
"-c",
|
|
445
|
-
`git -C "${cwd}" diff "${commitSha}^..${commitSha}" | git patch-id --stable`
|
|
446
|
-
],
|
|
447
|
-
{ timeout: options?.timeout }
|
|
496
|
+
const result = await gitPipe(
|
|
497
|
+
["diff", `${commitSha}^..${commitSha}`],
|
|
498
|
+
["patch-id", "--stable"],
|
|
499
|
+
{ cwd: options?.cwd, timeout: options?.timeout }
|
|
448
500
|
);
|
|
449
501
|
const patchId = result.stdout.trim().split(/\s+/)[0];
|
|
450
502
|
if (!patchId) return null;
|
|
@@ -460,15 +512,18 @@ async function findPatchIdMatch(commitSha, options) {
|
|
|
460
512
|
const targetPatchId = await computePatchId(commitSha, options);
|
|
461
513
|
if (!targetPatchId) return null;
|
|
462
514
|
try {
|
|
463
|
-
const
|
|
464
|
-
["log",
|
|
465
|
-
|
|
515
|
+
const result = await gitPipe(
|
|
516
|
+
["log", `-${scanDepth}`, "-p", ref],
|
|
517
|
+
["patch-id", "--stable"],
|
|
518
|
+
{ cwd: options?.cwd, timeout: options?.timeout ?? 6e4 }
|
|
466
519
|
);
|
|
467
|
-
const
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
const
|
|
471
|
-
if (
|
|
520
|
+
const lines = (0, import_common_utils10.filter)(result.stdout.trim().split("\n"), import_common_utils10.isTruthy);
|
|
521
|
+
const cache = getCache(options?.repoId, options?.noCache);
|
|
522
|
+
for (const line of lines) {
|
|
523
|
+
const [patchId, candidateSha] = line.split(/\s+/);
|
|
524
|
+
if (!patchId || !candidateSha) continue;
|
|
525
|
+
await cache.set(candidateSha, patchId);
|
|
526
|
+
if (candidateSha !== commitSha && patchId === targetPatchId) {
|
|
472
527
|
return { matchedSha: candidateSha, patchId: targetPatchId };
|
|
473
528
|
}
|
|
474
529
|
}
|
|
@@ -525,10 +580,40 @@ function getCache2(repoId, noCache) {
|
|
|
525
580
|
}
|
|
526
581
|
return cache;
|
|
527
582
|
}
|
|
528
|
-
|
|
529
|
-
|
|
583
|
+
function toCachedPR(pr) {
|
|
584
|
+
return {
|
|
585
|
+
number: pr.number,
|
|
586
|
+
title: pr.title,
|
|
587
|
+
author: pr.author,
|
|
588
|
+
url: pr.url,
|
|
589
|
+
mergeCommit: pr.mergeCommit,
|
|
590
|
+
baseBranch: pr.baseBranch,
|
|
591
|
+
mergedAt: pr.mergedAt ? new Date(pr.mergedAt).getTime() : void 0
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
function fromCachedPR(cached) {
|
|
595
|
+
let mergedAt;
|
|
596
|
+
if (cached.mergedAt != null) {
|
|
597
|
+
mergedAt = typeof cached.mergedAt === "number" ? new Date(cached.mergedAt).toISOString() : String(cached.mergedAt);
|
|
598
|
+
}
|
|
599
|
+
return {
|
|
600
|
+
number: cached.number,
|
|
601
|
+
title: cached.title,
|
|
602
|
+
author: cached.author,
|
|
603
|
+
url: cached.url,
|
|
604
|
+
mergeCommit: cached.mergeCommit,
|
|
605
|
+
baseBranch: cached.baseBranch,
|
|
606
|
+
mergedAt
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
async function lookupPR(commitSha, adapter, options, _recursionDepth = 0) {
|
|
610
|
+
const cache = getCache2(
|
|
611
|
+
options?.repoId,
|
|
612
|
+
options?.cacheOnly ? false : options?.noCache
|
|
613
|
+
);
|
|
530
614
|
const cached = await cache.get(commitSha);
|
|
531
|
-
if (cached) return cached;
|
|
615
|
+
if (cached) return fromCachedPR(cached);
|
|
616
|
+
if (options?.cacheOnly) return null;
|
|
532
617
|
let mergeBasedPR = null;
|
|
533
618
|
const mergeResult = await findMergeCommit(commitSha, options);
|
|
534
619
|
if (mergeResult) {
|
|
@@ -551,39 +636,46 @@ async function lookupPR(commitSha, adapter, options) {
|
|
|
551
636
|
};
|
|
552
637
|
}
|
|
553
638
|
if (!options?.deep || mergeBasedPR.mergedAt) {
|
|
554
|
-
await cache.set(commitSha, mergeBasedPR);
|
|
639
|
+
await cache.set(commitSha, toCachedPR(mergeBasedPR));
|
|
555
640
|
return mergeBasedPR;
|
|
556
641
|
}
|
|
557
642
|
}
|
|
558
643
|
}
|
|
559
|
-
const patchIdMatch = await findPatchIdMatch(commitSha, {
|
|
560
|
-
...options,
|
|
561
|
-
scanDepth: options?.deep ? DEEP_SCAN_DEPTH : void 0
|
|
562
|
-
});
|
|
563
|
-
if (patchIdMatch) {
|
|
564
|
-
const result = await lookupPR(patchIdMatch.matchedSha, adapter, options);
|
|
565
|
-
if (result) {
|
|
566
|
-
await cache.set(commitSha, result);
|
|
567
|
-
return result;
|
|
568
|
-
}
|
|
569
|
-
}
|
|
570
644
|
if (mergeBasedPR) {
|
|
571
|
-
await cache.set(commitSha, mergeBasedPR);
|
|
645
|
+
await cache.set(commitSha, toCachedPR(mergeBasedPR));
|
|
572
646
|
return mergeBasedPR;
|
|
573
647
|
}
|
|
574
648
|
if (adapter) {
|
|
575
649
|
const prInfo = await adapter.getPRForCommit(commitSha);
|
|
576
650
|
if (prInfo?.mergedAt) {
|
|
577
|
-
await cache.set(commitSha, prInfo);
|
|
651
|
+
await cache.set(commitSha, toCachedPR(prInfo));
|
|
578
652
|
return prInfo;
|
|
579
653
|
}
|
|
580
654
|
}
|
|
655
|
+
if (!options?.skipPatchIdScan && _recursionDepth < MAX_RECURSION_DEPTH) {
|
|
656
|
+
const patchIdMatch = await findPatchIdMatch(commitSha, {
|
|
657
|
+
...options,
|
|
658
|
+
scanDepth: options?.deep ? DEEP_SCAN_DEPTH : void 0
|
|
659
|
+
});
|
|
660
|
+
if (patchIdMatch) {
|
|
661
|
+
const result = await lookupPR(
|
|
662
|
+
patchIdMatch.matchedSha,
|
|
663
|
+
adapter,
|
|
664
|
+
options,
|
|
665
|
+
_recursionDepth + 1
|
|
666
|
+
);
|
|
667
|
+
if (result) {
|
|
668
|
+
await cache.set(commitSha, toCachedPR(result));
|
|
669
|
+
return result;
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
}
|
|
581
673
|
return null;
|
|
582
674
|
}
|
|
583
675
|
function resetPRCache() {
|
|
584
676
|
cacheRegistry2.clear();
|
|
585
677
|
}
|
|
586
|
-
var cacheRegistry2, DEEP_SCAN_DEPTH;
|
|
678
|
+
var cacheRegistry2, DEEP_SCAN_DEPTH, MAX_RECURSION_DEPTH;
|
|
587
679
|
var init_pr_lookup = __esm({
|
|
588
680
|
"src/core/pr-lookup/pr-lookup.ts"() {
|
|
589
681
|
"use strict";
|
|
@@ -593,6 +685,7 @@ var init_pr_lookup = __esm({
|
|
|
593
685
|
init_patch_id2();
|
|
594
686
|
cacheRegistry2 = /* @__PURE__ */ new Map();
|
|
595
687
|
DEEP_SCAN_DEPTH = 2e3;
|
|
688
|
+
MAX_RECURSION_DEPTH = 2;
|
|
596
689
|
}
|
|
597
690
|
});
|
|
598
691
|
|
|
@@ -861,6 +954,27 @@ function isVersionAtLeast(version, minVersion) {
|
|
|
861
954
|
}
|
|
862
955
|
return true;
|
|
863
956
|
}
|
|
957
|
+
async function checkCloneStatus(options) {
|
|
958
|
+
let partialClone = false;
|
|
959
|
+
let shallow = false;
|
|
960
|
+
try {
|
|
961
|
+
const shallowResult = await gitExec(
|
|
962
|
+
["rev-parse", "--is-shallow-repository"],
|
|
963
|
+
{ cwd: options?.cwd }
|
|
964
|
+
);
|
|
965
|
+
shallow = shallowResult.stdout.trim() === "true";
|
|
966
|
+
} catch {
|
|
967
|
+
}
|
|
968
|
+
try {
|
|
969
|
+
const partialResult = await gitExec(
|
|
970
|
+
["config", "--get", "extensions.partialclone"],
|
|
971
|
+
{ cwd: options?.cwd }
|
|
972
|
+
);
|
|
973
|
+
partialClone = partialResult.stdout.trim().length > 0;
|
|
974
|
+
} catch {
|
|
975
|
+
}
|
|
976
|
+
return { partialClone, shallow };
|
|
977
|
+
}
|
|
864
978
|
async function checkGitHealth(options) {
|
|
865
979
|
const hints = [];
|
|
866
980
|
let gitVersion = "0.0.0";
|
|
@@ -887,7 +1001,18 @@ async function checkGitHealth(options) {
|
|
|
887
1001
|
`Upgrade git to ${BLOOM_FILTER_MIN_VERSION.join(".")}+ for bloom filter support (current: ${gitVersion}).`
|
|
888
1002
|
);
|
|
889
1003
|
}
|
|
890
|
-
|
|
1004
|
+
const cloneStatus = await checkCloneStatus({ cwd: options?.cwd });
|
|
1005
|
+
if (cloneStatus.partialClone) {
|
|
1006
|
+
hints.push(
|
|
1007
|
+
"Partial clone detected. Patch-ID scan (Strategy 4) will be skipped to avoid blob downloads."
|
|
1008
|
+
);
|
|
1009
|
+
}
|
|
1010
|
+
if (cloneStatus.shallow) {
|
|
1011
|
+
hints.push(
|
|
1012
|
+
"Shallow repository detected. Ancestry-path results may be incomplete."
|
|
1013
|
+
);
|
|
1014
|
+
}
|
|
1015
|
+
return { commitGraph, bloomFilter, gitVersion, hints, ...cloneStatus };
|
|
891
1016
|
}
|
|
892
1017
|
|
|
893
1018
|
// src/platform/index.ts
|
|
@@ -2111,7 +2236,7 @@ async function runBlameAndAuth(adapter, options, execOptions) {
|
|
|
2111
2236
|
}
|
|
2112
2237
|
return { analyzed: blameResult.value, operatingLevel, warnings };
|
|
2113
2238
|
}
|
|
2114
|
-
async function processEntry(entry, featureFlags, adapter, options, execOptions, repoId) {
|
|
2239
|
+
async function processEntry(entry, featureFlags, adapter, options, execOptions, repoId, skipPatchIdScan) {
|
|
2115
2240
|
const nodes = [];
|
|
2116
2241
|
const commitNode = {
|
|
2117
2242
|
type: entry.isCosmetic ? "cosmetic_commit" : "original_commit",
|
|
@@ -2142,8 +2267,10 @@ async function processEntry(entry, featureFlags, adapter, options, execOptions,
|
|
|
2142
2267
|
const prInfo = await lookupPR(targetSha, adapter, {
|
|
2143
2268
|
...execOptions,
|
|
2144
2269
|
noCache: options.noCache,
|
|
2270
|
+
cacheOnly: options.cacheOnly,
|
|
2145
2271
|
deep: featureFlags.deepTrace,
|
|
2146
|
-
repoId
|
|
2272
|
+
repoId,
|
|
2273
|
+
skipPatchIdScan
|
|
2147
2274
|
});
|
|
2148
2275
|
if (prInfo) {
|
|
2149
2276
|
nodes.push({
|
|
@@ -2160,11 +2287,19 @@ async function processEntry(entry, featureFlags, adapter, options, execOptions,
|
|
|
2160
2287
|
}
|
|
2161
2288
|
return nodes;
|
|
2162
2289
|
}
|
|
2163
|
-
async function buildTraceNodes(analyzed, featureFlags, adapter, options, execOptions, repoId) {
|
|
2290
|
+
async function buildTraceNodes(analyzed, featureFlags, adapter, options, execOptions, repoId, skipPatchIdScan) {
|
|
2164
2291
|
const results = await Promise.allSettled(
|
|
2165
2292
|
(0, import_common_utils11.map)(
|
|
2166
2293
|
analyzed,
|
|
2167
|
-
(entry) => processEntry(
|
|
2294
|
+
(entry) => processEntry(
|
|
2295
|
+
entry,
|
|
2296
|
+
featureFlags,
|
|
2297
|
+
adapter,
|
|
2298
|
+
options,
|
|
2299
|
+
execOptions,
|
|
2300
|
+
repoId,
|
|
2301
|
+
skipPatchIdScan
|
|
2302
|
+
)
|
|
2168
2303
|
)
|
|
2169
2304
|
);
|
|
2170
2305
|
return results.flatMap((r) => r.status === "fulfilled" ? r.value : []);
|
|
@@ -2195,14 +2330,36 @@ async function trace(options) {
|
|
|
2195
2330
|
);
|
|
2196
2331
|
const operatingLevel = blameAuth.operatingLevel || platform.operatingLevel;
|
|
2197
2332
|
const warnings = [...platform.warnings, ...blameAuth.warnings];
|
|
2333
|
+
if (options.cacheOnly && options.noCache) {
|
|
2334
|
+
warnings.push(
|
|
2335
|
+
"Both cacheOnly and noCache are set. cacheOnly takes precedence \u2014 cache reads are enabled."
|
|
2336
|
+
);
|
|
2337
|
+
}
|
|
2198
2338
|
const featureFlags = computeFeatureFlags(operatingLevel, options);
|
|
2339
|
+
let cloneStatus = { partialClone: false, shallow: false };
|
|
2340
|
+
try {
|
|
2341
|
+
const result = await checkCloneStatus({ cwd: options.cwd });
|
|
2342
|
+
if (result) cloneStatus = result;
|
|
2343
|
+
} catch {
|
|
2344
|
+
}
|
|
2345
|
+
if (cloneStatus.partialClone) {
|
|
2346
|
+
warnings.push(
|
|
2347
|
+
"Partial clone detected. Patch-ID scan (Strategy 4) will be skipped to avoid blob downloads."
|
|
2348
|
+
);
|
|
2349
|
+
}
|
|
2350
|
+
if (cloneStatus.shallow) {
|
|
2351
|
+
warnings.push(
|
|
2352
|
+
"Shallow repository detected. Ancestry-path results may be incomplete."
|
|
2353
|
+
);
|
|
2354
|
+
}
|
|
2199
2355
|
const nodes = await buildTraceNodes(
|
|
2200
2356
|
blameAuth.analyzed,
|
|
2201
2357
|
featureFlags,
|
|
2202
2358
|
platform.adapter,
|
|
2203
2359
|
options,
|
|
2204
2360
|
execOptions,
|
|
2205
|
-
repoId
|
|
2361
|
+
repoId,
|
|
2362
|
+
cloneStatus.partialClone || void 0
|
|
2206
2363
|
);
|
|
2207
2364
|
return { nodes, operatingLevel, featureFlags, warnings };
|
|
2208
2365
|
}
|