@lumy-pack/line-lore 0.0.6 → 0.0.8
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 +44 -20
- package/dist/cli.mjs +306 -95
- package/dist/core/ancestry/ancestry.d.ts +32 -1
- package/dist/core/ancestry/index.d.ts +1 -1
- package/dist/core/blame/blame.d.ts +2 -2
- package/dist/core/index.d.ts +1 -1
- package/dist/core/pr-lookup/index.d.ts +1 -0
- package/dist/core/pr-lookup/pr-lookup.d.ts +18 -2
- package/dist/index.cjs +292 -92
- package/dist/index.d.ts +1 -1
- package/dist/index.mjs +292 -92
- package/dist/platform/github/github-adapter.d.ts +3 -1
- package/dist/platform/gitlab/gitlab-adapter.d.ts +3 -1
- package/dist/types/blame.d.ts +2 -0
- package/dist/types/cache.d.ts +2 -0
- package/dist/types/git.d.ts +7 -0
- package/dist/types/index.d.ts +2 -2
- package/dist/types/platform.d.ts +3 -1
- package/dist/types/trace.d.ts +3 -0
- package/dist/version.d.ts +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -381,26 +381,36 @@ var init_executor = __esm({
|
|
|
381
381
|
});
|
|
382
382
|
|
|
383
383
|
// src/core/ancestry/ancestry.ts
|
|
384
|
-
async function
|
|
385
|
-
|
|
386
|
-
const
|
|
387
|
-
const
|
|
388
|
-
const
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
384
|
+
async function verifyMergeIntroducesCommit(targetSha, mergeResult, options) {
|
|
385
|
+
if (mergeResult.parentShas.length < 2) return true;
|
|
386
|
+
const firstParent = mergeResult.parentShas[0];
|
|
387
|
+
const branchParents = mergeResult.parentShas.slice(1);
|
|
388
|
+
const onMainline = await isAncestor(targetSha, firstParent, options);
|
|
389
|
+
if (onMainline === null) return false;
|
|
390
|
+
if (onMainline) return false;
|
|
391
|
+
for (const branchParent of branchParents) {
|
|
392
|
+
const onBranch = await isAncestor(targetSha, branchParent, options);
|
|
393
|
+
if (onBranch === null) return false;
|
|
394
|
+
if (onBranch) return true;
|
|
395
|
+
}
|
|
396
|
+
return false;
|
|
397
|
+
}
|
|
398
|
+
async function isAncestor(commitA, commitB, options) {
|
|
399
|
+
try {
|
|
400
|
+
const result = await gitExec(
|
|
401
|
+
["merge-base", "--is-ancestor", commitA, commitB],
|
|
402
|
+
{
|
|
403
|
+
cwd: options?.cwd,
|
|
404
|
+
timeout: options?.timeout ?? 5e3,
|
|
405
|
+
allowExitCodes: [1]
|
|
406
|
+
}
|
|
407
|
+
);
|
|
408
|
+
return result.exitCode === 0;
|
|
409
|
+
} catch {
|
|
410
|
+
return null;
|
|
411
|
+
}
|
|
402
412
|
}
|
|
403
|
-
async function
|
|
413
|
+
async function findMergeCommitsWithArgs(commitSha, ref, extraArgs, options) {
|
|
404
414
|
try {
|
|
405
415
|
const result = await gitExec(
|
|
406
416
|
[
|
|
@@ -416,10 +426,29 @@ async function findMergeCommitWithArgs(commitSha, ref, extraArgs, options) {
|
|
|
416
426
|
{ cwd: options?.cwd, timeout: options?.timeout }
|
|
417
427
|
);
|
|
418
428
|
const lines = (0, import_common_utils9.filter)(result.stdout.trim().split("\n"), import_common_utils9.isTruthy);
|
|
419
|
-
if (lines.length === 0) return
|
|
420
|
-
|
|
429
|
+
if (lines.length === 0) return [];
|
|
430
|
+
const verifiedCandidates = [];
|
|
431
|
+
const candidateCount = Math.min(lines.length, MAX_CANDIDATES);
|
|
432
|
+
let attemptedCount = 0;
|
|
433
|
+
for (let i = 0; i < candidateCount; i++) {
|
|
434
|
+
const candidate = parseMergeLogLine(lines[i]);
|
|
435
|
+
if (!candidate) continue;
|
|
436
|
+
attemptedCount++;
|
|
437
|
+
const verified = await verifyMergeIntroducesCommit(
|
|
438
|
+
commitSha,
|
|
439
|
+
candidate,
|
|
440
|
+
options
|
|
441
|
+
);
|
|
442
|
+
if (verified) verifiedCandidates.push(candidate);
|
|
443
|
+
}
|
|
444
|
+
if (attemptedCount > 0 && verifiedCandidates.length === 0 && options?.warnings) {
|
|
445
|
+
options.warnings.push(
|
|
446
|
+
`ancestry: all ${attemptedCount} merge candidate(s) failed verification for ${commitSha.slice(0, 8)}`
|
|
447
|
+
);
|
|
448
|
+
}
|
|
449
|
+
return verifiedCandidates;
|
|
421
450
|
} catch {
|
|
422
|
-
return
|
|
451
|
+
return [];
|
|
423
452
|
}
|
|
424
453
|
}
|
|
425
454
|
function parseMergeLogLine(line) {
|
|
@@ -439,18 +468,64 @@ function parseMergeLogLine(line) {
|
|
|
439
468
|
const subject = parts.slice(subjectStart).join(" ");
|
|
440
469
|
return { mergeCommitSha, parentShas, subject };
|
|
441
470
|
}
|
|
442
|
-
function
|
|
471
|
+
async function findMergeCommits(commitSha, options) {
|
|
472
|
+
const ref = options?.ref ?? "HEAD";
|
|
473
|
+
const budget = options?.timeout ?? DEFAULT_ANCESTRY_TIMEOUT;
|
|
474
|
+
const startTime = Date.now();
|
|
475
|
+
const results = [];
|
|
476
|
+
const seen = /* @__PURE__ */ new Set();
|
|
477
|
+
const pushUnique = (candidates) => {
|
|
478
|
+
for (const candidate of candidates) {
|
|
479
|
+
if (seen.has(candidate.mergeCommitSha)) continue;
|
|
480
|
+
seen.add(candidate.mergeCommitSha);
|
|
481
|
+
results.push(candidate);
|
|
482
|
+
if (results.length >= MAX_CANDIDATES) break;
|
|
483
|
+
}
|
|
484
|
+
};
|
|
485
|
+
const firstParent = await findMergeCommitsWithArgs(
|
|
486
|
+
commitSha,
|
|
487
|
+
ref,
|
|
488
|
+
["--first-parent"],
|
|
489
|
+
{ ...options, timeout: budget }
|
|
490
|
+
);
|
|
491
|
+
pushUnique(firstParent);
|
|
492
|
+
const elapsed = Date.now() - startTime;
|
|
493
|
+
const remaining = budget - elapsed;
|
|
494
|
+
if (remaining > 0 && results.length < MAX_CANDIDATES) {
|
|
495
|
+
const full = await findMergeCommitsWithArgs(commitSha, ref, [], {
|
|
496
|
+
...options,
|
|
497
|
+
timeout: remaining
|
|
498
|
+
});
|
|
499
|
+
pushUnique(full);
|
|
500
|
+
}
|
|
501
|
+
return results;
|
|
502
|
+
}
|
|
503
|
+
async function getCommitSubject(sha, options) {
|
|
504
|
+
try {
|
|
505
|
+
const result = await gitExec(["log", "-1", "--format=%s", sha], {
|
|
506
|
+
cwd: options?.cwd,
|
|
507
|
+
timeout: options?.timeout ?? 5e3
|
|
508
|
+
});
|
|
509
|
+
const subject = result.stdout.trim();
|
|
510
|
+
return subject || null;
|
|
511
|
+
} catch {
|
|
512
|
+
return null;
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
function extractPRFromMergeMessage(subject, platform) {
|
|
443
516
|
const ghMatch = /Merge pull request #(\d+)/.exec(subject);
|
|
444
517
|
if (ghMatch) return parseInt(ghMatch[1], 10);
|
|
445
518
|
const squashMatch = /\(#(\d+)\)\s*$/.exec(subject);
|
|
446
519
|
if (squashMatch) return parseInt(squashMatch[1], 10);
|
|
447
|
-
|
|
448
|
-
|
|
520
|
+
if (!platform || platform === "gitlab" || platform === "gitlab-self-hosted") {
|
|
521
|
+
const glMatch = /See merge request\s+\S*!(\d+)\s*$/.exec(subject);
|
|
522
|
+
if (glMatch) return parseInt(glMatch[1], 10);
|
|
523
|
+
}
|
|
449
524
|
const adoMatch = /Merged PR (\d+):/.exec(subject);
|
|
450
525
|
if (adoMatch) return parseInt(adoMatch[1], 10);
|
|
451
526
|
return null;
|
|
452
527
|
}
|
|
453
|
-
var import_common_utils9, DEFAULT_ANCESTRY_TIMEOUT;
|
|
528
|
+
var import_common_utils9, DEFAULT_ANCESTRY_TIMEOUT, MAX_CANDIDATES;
|
|
454
529
|
var init_ancestry = __esm({
|
|
455
530
|
"src/core/ancestry/ancestry.ts"() {
|
|
456
531
|
"use strict";
|
|
@@ -458,6 +533,7 @@ var init_ancestry = __esm({
|
|
|
458
533
|
import_common_utils9 = require("@winglet/common-utils");
|
|
459
534
|
init_executor();
|
|
460
535
|
DEFAULT_ANCESTRY_TIMEOUT = 3e4;
|
|
536
|
+
MAX_CANDIDATES = 10;
|
|
461
537
|
}
|
|
462
538
|
});
|
|
463
539
|
|
|
@@ -588,7 +664,8 @@ function toCachedPR(pr) {
|
|
|
588
664
|
url: pr.url,
|
|
589
665
|
mergeCommit: pr.mergeCommit,
|
|
590
666
|
baseBranch: pr.baseBranch,
|
|
591
|
-
mergedAt: pr.mergedAt ? new Date(pr.mergedAt).getTime() : void 0
|
|
667
|
+
mergedAt: pr.mergedAt ? new Date(pr.mergedAt).getTime() : void 0,
|
|
668
|
+
resolvedVia: pr.resolvedVia
|
|
592
669
|
};
|
|
593
670
|
}
|
|
594
671
|
function fromCachedPR(cached) {
|
|
@@ -603,7 +680,9 @@ function fromCachedPR(cached) {
|
|
|
603
680
|
url: cached.url,
|
|
604
681
|
mergeCommit: cached.mergeCommit,
|
|
605
682
|
baseBranch: cached.baseBranch,
|
|
606
|
-
mergedAt
|
|
683
|
+
mergedAt,
|
|
684
|
+
// Preserve original resolvedVia; fallback to url heuristic for legacy cache entries
|
|
685
|
+
resolvedVia: cached.resolvedVia ?? (cached.url ? "api" : "message")
|
|
607
686
|
};
|
|
608
687
|
}
|
|
609
688
|
async function lookupPR(commitSha, adapter, options, _recursionDepth = 0) {
|
|
@@ -614,45 +693,84 @@ async function lookupPR(commitSha, adapter, options, _recursionDepth = 0) {
|
|
|
614
693
|
const cached = await cache.get(commitSha);
|
|
615
694
|
if (cached) return fromCachedPR(cached);
|
|
616
695
|
if (options?.cacheOnly) return null;
|
|
696
|
+
const prSelectOptions = options?.preferredBase ? { preferredBase: options.preferredBase } : void 0;
|
|
697
|
+
if (adapter) {
|
|
698
|
+
const directPR = await adapter.getPRForCommit(commitSha, prSelectOptions);
|
|
699
|
+
if (directPR?.mergedAt) {
|
|
700
|
+
const result = { ...directPR, resolvedVia: "api" };
|
|
701
|
+
await cache.set(commitSha, toCachedPR(result));
|
|
702
|
+
return result;
|
|
703
|
+
}
|
|
704
|
+
}
|
|
617
705
|
let mergeBasedPR = null;
|
|
618
|
-
const
|
|
619
|
-
|
|
620
|
-
|
|
706
|
+
const mergeCandidates = await findMergeCommits(commitSha, options);
|
|
707
|
+
const hasAncestryMerges = mergeCandidates.length > 0;
|
|
708
|
+
for (const candidate of mergeCandidates) {
|
|
709
|
+
const prNumber = extractPRFromMergeMessage(
|
|
710
|
+
candidate.subject,
|
|
711
|
+
options?.platform
|
|
712
|
+
);
|
|
621
713
|
if (prNumber) {
|
|
622
714
|
if (adapter) {
|
|
623
|
-
const prInfo = await adapter.getPRForCommit(
|
|
715
|
+
const prInfo = await adapter.getPRForCommit(
|
|
716
|
+
candidate.mergeCommitSha,
|
|
717
|
+
prSelectOptions
|
|
718
|
+
);
|
|
624
719
|
if (prInfo?.mergedAt) {
|
|
625
|
-
mergeBasedPR = prInfo;
|
|
720
|
+
mergeBasedPR = { ...prInfo, resolvedVia: "ancestry" };
|
|
626
721
|
}
|
|
627
722
|
}
|
|
628
723
|
if (!mergeBasedPR) {
|
|
629
724
|
mergeBasedPR = {
|
|
630
725
|
number: prNumber,
|
|
631
|
-
title:
|
|
726
|
+
title: candidate.subject,
|
|
632
727
|
author: "",
|
|
633
728
|
url: "",
|
|
634
|
-
mergeCommit:
|
|
635
|
-
baseBranch: ""
|
|
729
|
+
mergeCommit: candidate.mergeCommitSha,
|
|
730
|
+
baseBranch: "",
|
|
731
|
+
resolvedVia: "ancestry"
|
|
636
732
|
};
|
|
637
733
|
}
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
734
|
+
break;
|
|
735
|
+
}
|
|
736
|
+
if (adapter) {
|
|
737
|
+
const mergeCommitPR = await adapter.getPRForCommit(
|
|
738
|
+
candidate.mergeCommitSha,
|
|
739
|
+
prSelectOptions
|
|
740
|
+
);
|
|
741
|
+
if (mergeCommitPR?.mergedAt) {
|
|
742
|
+
mergeBasedPR = { ...mergeCommitPR, resolvedVia: "ancestry" };
|
|
743
|
+
break;
|
|
641
744
|
}
|
|
642
745
|
}
|
|
643
746
|
}
|
|
644
747
|
if (mergeBasedPR) {
|
|
645
|
-
|
|
646
|
-
|
|
748
|
+
if (!options?.deep || mergeBasedPR.mergedAt) {
|
|
749
|
+
await cache.set(commitSha, toCachedPR(mergeBasedPR));
|
|
750
|
+
return mergeBasedPR;
|
|
751
|
+
}
|
|
647
752
|
}
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
753
|
+
const commitSubject = await getCommitSubject(commitSha, options);
|
|
754
|
+
if (commitSubject) {
|
|
755
|
+
const directPrNumber = extractPRFromMergeMessage(
|
|
756
|
+
commitSubject,
|
|
757
|
+
options?.platform
|
|
758
|
+
);
|
|
759
|
+
if (directPrNumber) {
|
|
760
|
+
const subjectPR = {
|
|
761
|
+
number: directPrNumber,
|
|
762
|
+
title: commitSubject,
|
|
763
|
+
author: "",
|
|
764
|
+
url: "",
|
|
765
|
+
mergeCommit: commitSha,
|
|
766
|
+
baseBranch: "",
|
|
767
|
+
resolvedVia: "message"
|
|
768
|
+
};
|
|
769
|
+
await cache.set(commitSha, toCachedPR(subjectPR));
|
|
770
|
+
return subjectPR;
|
|
653
771
|
}
|
|
654
772
|
}
|
|
655
|
-
if (!options?.skipPatchIdScan && _recursionDepth < MAX_RECURSION_DEPTH) {
|
|
773
|
+
if (!options?.skipPatchIdScan && _recursionDepth < MAX_RECURSION_DEPTH && (!hasAncestryMerges || options?.deep)) {
|
|
656
774
|
const patchIdMatch = await findPatchIdMatch(commitSha, {
|
|
657
775
|
...options,
|
|
658
776
|
scanDepth: options?.deep ? DEEP_SCAN_DEPTH : void 0
|
|
@@ -670,6 +788,10 @@ async function lookupPR(commitSha, adapter, options, _recursionDepth = 0) {
|
|
|
670
788
|
}
|
|
671
789
|
}
|
|
672
790
|
}
|
|
791
|
+
if (mergeBasedPR) {
|
|
792
|
+
await cache.set(commitSha, toCachedPR(mergeBasedPR));
|
|
793
|
+
return mergeBasedPR;
|
|
794
|
+
}
|
|
673
795
|
return null;
|
|
674
796
|
}
|
|
675
797
|
function resetPRCache() {
|
|
@@ -721,6 +843,7 @@ init_errors();
|
|
|
721
843
|
// src/core/core.ts
|
|
722
844
|
init_cjs_shims();
|
|
723
845
|
var import_node_crypto2 = require("crypto");
|
|
846
|
+
var import_node_path2 = require("path");
|
|
724
847
|
var import_common_utils11 = require("@winglet/common-utils");
|
|
725
848
|
|
|
726
849
|
// src/ast/index.ts
|
|
@@ -1004,7 +1127,7 @@ async function checkGitHealth(options) {
|
|
|
1004
1127
|
const cloneStatus = await checkCloneStatus({ cwd: options?.cwd });
|
|
1005
1128
|
if (cloneStatus.partialClone) {
|
|
1006
1129
|
hints.push(
|
|
1007
|
-
"Partial clone detected. Patch-ID scan (Strategy
|
|
1130
|
+
"Partial clone detected. Patch-ID scan (Strategy 5) will be skipped to avoid blob downloads."
|
|
1008
1131
|
);
|
|
1009
1132
|
}
|
|
1010
1133
|
if (cloneStatus.shallow) {
|
|
@@ -1120,7 +1243,7 @@ var GitHubAdapter = class {
|
|
|
1120
1243
|
return { authenticated: false, hostname: this.hostname };
|
|
1121
1244
|
}
|
|
1122
1245
|
}
|
|
1123
|
-
async getPRForCommit(sha) {
|
|
1246
|
+
async getPRForCommit(sha, options) {
|
|
1124
1247
|
if (this.scheduler.isRateLimited()) return null;
|
|
1125
1248
|
try {
|
|
1126
1249
|
const result = await shellExec(
|
|
@@ -1137,18 +1260,20 @@ var GitHubAdapter = class {
|
|
|
1137
1260
|
);
|
|
1138
1261
|
const prs = JSON.parse(result.stdout);
|
|
1139
1262
|
if (!(0, import_common_utils3.isArray)(prs) || prs.length === 0) return null;
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1263
|
+
let data = prs[0];
|
|
1264
|
+
if (options?.preferredBase) {
|
|
1265
|
+
const preferred = prs.find(
|
|
1266
|
+
(pr) => pr.base === options.preferredBase
|
|
1267
|
+
);
|
|
1268
|
+
if (preferred) data = preferred;
|
|
1269
|
+
}
|
|
1145
1270
|
return {
|
|
1146
1271
|
number: data.number,
|
|
1147
1272
|
title: data.title ?? "",
|
|
1148
1273
|
author: data.user ?? "",
|
|
1149
1274
|
url: data.html_url ?? "",
|
|
1150
1275
|
mergeCommit: data.merge_commit_sha ?? sha,
|
|
1151
|
-
baseBranch: data.base ??
|
|
1276
|
+
baseBranch: data.base ?? "",
|
|
1152
1277
|
mergedAt: data.merged_at
|
|
1153
1278
|
};
|
|
1154
1279
|
} catch {
|
|
@@ -1330,7 +1455,7 @@ var GitLabAdapter = class {
|
|
|
1330
1455
|
return { authenticated: false, hostname: this.hostname };
|
|
1331
1456
|
}
|
|
1332
1457
|
}
|
|
1333
|
-
async getPRForCommit(sha) {
|
|
1458
|
+
async getPRForCommit(sha, options) {
|
|
1334
1459
|
if (this.scheduler.isRateLimited()) return null;
|
|
1335
1460
|
try {
|
|
1336
1461
|
const result = await shellExec(
|
|
@@ -1354,18 +1479,20 @@ var GitLabAdapter = class {
|
|
|
1354
1479
|
return aTime - bTime;
|
|
1355
1480
|
});
|
|
1356
1481
|
if (mergedMRs.length === 0) return null;
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1482
|
+
let mr = mergedMRs[0];
|
|
1483
|
+
if (options?.preferredBase) {
|
|
1484
|
+
const preferred = mergedMRs.find(
|
|
1485
|
+
(m) => m.target_branch === options.preferredBase
|
|
1486
|
+
);
|
|
1487
|
+
if (preferred) mr = preferred;
|
|
1488
|
+
}
|
|
1362
1489
|
return {
|
|
1363
1490
|
number: mr.iid,
|
|
1364
1491
|
title: mr.title ?? "",
|
|
1365
1492
|
author: mr.author?.username ?? "",
|
|
1366
1493
|
url: mr.web_url ?? "",
|
|
1367
1494
|
mergeCommit: mr.merge_commit_sha ?? sha,
|
|
1368
|
-
baseBranch: mr.target_branch ??
|
|
1495
|
+
baseBranch: mr.target_branch ?? "",
|
|
1369
1496
|
mergedAt: mr.merged_at
|
|
1370
1497
|
};
|
|
1371
1498
|
} catch {
|
|
@@ -1984,6 +2111,7 @@ function parsePorcelainOutput(output) {
|
|
|
1984
2111
|
}
|
|
1985
2112
|
let commitHash = headerMatch[1];
|
|
1986
2113
|
const originalLine = parseInt(headerMatch[2], 10);
|
|
2114
|
+
const finalLine = parseInt(headerMatch[3], 10) || 0;
|
|
1987
2115
|
const isBoundary = commitHash.startsWith("^");
|
|
1988
2116
|
if (isBoundary) {
|
|
1989
2117
|
commitHash = commitHash.slice(1).padStart(40, "0");
|
|
@@ -2027,6 +2155,7 @@ function parsePorcelainOutput(output) {
|
|
|
2027
2155
|
authorEmail: cleanEmail,
|
|
2028
2156
|
date,
|
|
2029
2157
|
lineContent,
|
|
2158
|
+
finalLine,
|
|
2030
2159
|
originalFile,
|
|
2031
2160
|
originalLine: originalFile ? originalLine : void 0
|
|
2032
2161
|
});
|
|
@@ -2037,10 +2166,8 @@ function parsePorcelainOutput(output) {
|
|
|
2037
2166
|
// src/core/blame/blame.ts
|
|
2038
2167
|
async function executeBlame(file, lineRange, options) {
|
|
2039
2168
|
const lineSpec = `${lineRange.start},${lineRange.end}`;
|
|
2040
|
-
const
|
|
2041
|
-
|
|
2042
|
-
options
|
|
2043
|
-
);
|
|
2169
|
+
const args = options?.mode === "change" ? ["blame", "-w", "--porcelain", "-L", lineSpec, file] : ["blame", "-w", "-C", "-C", "-M", "--porcelain", "-L", lineSpec, file];
|
|
2170
|
+
const result = await gitExec(args, options);
|
|
2044
2171
|
return parsePorcelainOutput(result.stdout);
|
|
2045
2172
|
}
|
|
2046
2173
|
async function analyzeBlameResults(results, filePath, options) {
|
|
@@ -2173,6 +2300,28 @@ async function traverse(adapter, type, number, depth, maxDepth, nodes, edges, vi
|
|
|
2173
2300
|
|
|
2174
2301
|
// src/core/core.ts
|
|
2175
2302
|
init_pr_lookup2();
|
|
2303
|
+
function resolvedViaToTrackingMethod(resolvedVia) {
|
|
2304
|
+
switch (resolvedVia) {
|
|
2305
|
+
case "api":
|
|
2306
|
+
return "api";
|
|
2307
|
+
case "ancestry":
|
|
2308
|
+
return "ancestry-path";
|
|
2309
|
+
case "message":
|
|
2310
|
+
return "message-parse";
|
|
2311
|
+
case "patch-id":
|
|
2312
|
+
return "patch-id";
|
|
2313
|
+
}
|
|
2314
|
+
}
|
|
2315
|
+
function resolvedViaToConfidence(resolvedVia) {
|
|
2316
|
+
switch (resolvedVia) {
|
|
2317
|
+
case "api":
|
|
2318
|
+
case "ancestry":
|
|
2319
|
+
return "exact";
|
|
2320
|
+
case "message":
|
|
2321
|
+
case "patch-id":
|
|
2322
|
+
return "heuristic";
|
|
2323
|
+
}
|
|
2324
|
+
}
|
|
2176
2325
|
function computeFeatureFlags(operatingLevel, options) {
|
|
2177
2326
|
return {
|
|
2178
2327
|
astDiff: isAstAvailable() && !options.noAst,
|
|
@@ -2190,6 +2339,22 @@ async function resolveRepoIdentity(cwd) {
|
|
|
2190
2339
|
return { host: "_local", owner: "_", repo: "_unknown" };
|
|
2191
2340
|
}
|
|
2192
2341
|
}
|
|
2342
|
+
async function resolveFileContext(file, cwd) {
|
|
2343
|
+
if (cwd || !(0, import_node_path2.isAbsolute)(file)) return { file, cwd };
|
|
2344
|
+
const fileDir = (0, import_node_path2.dirname)(file);
|
|
2345
|
+
try {
|
|
2346
|
+
const result = await gitExec(["rev-parse", "--show-toplevel"], {
|
|
2347
|
+
cwd: fileDir
|
|
2348
|
+
});
|
|
2349
|
+
const repoRoot = result.stdout.trim();
|
|
2350
|
+
return {
|
|
2351
|
+
file: (0, import_node_path2.relative)(repoRoot, file),
|
|
2352
|
+
cwd: repoRoot
|
|
2353
|
+
};
|
|
2354
|
+
} catch {
|
|
2355
|
+
return { file, cwd };
|
|
2356
|
+
}
|
|
2357
|
+
}
|
|
2193
2358
|
async function detectPlatform2(options) {
|
|
2194
2359
|
const warnings = [];
|
|
2195
2360
|
let adapter = null;
|
|
@@ -2213,9 +2378,10 @@ async function runBlameAndAuth(adapter, options, execOptions) {
|
|
|
2213
2378
|
const lineRange = parseLineRange(
|
|
2214
2379
|
options.endLine ? `${options.line},${options.endLine}` : `${options.line}`
|
|
2215
2380
|
);
|
|
2216
|
-
const blameChain = executeBlame(options.file, lineRange,
|
|
2217
|
-
|
|
2218
|
-
|
|
2381
|
+
const blameChain = executeBlame(options.file, lineRange, {
|
|
2382
|
+
...execOptions,
|
|
2383
|
+
mode: options.mode
|
|
2384
|
+
}).then((results) => analyzeBlameResults(results, options.file, execOptions));
|
|
2219
2385
|
const [authResult, blameResult] = await Promise.allSettled([
|
|
2220
2386
|
adapter ? adapter.checkAuth() : Promise.resolve({ authenticated: false }),
|
|
2221
2387
|
blameChain
|
|
@@ -2236,12 +2402,24 @@ async function runBlameAndAuth(adapter, options, execOptions) {
|
|
|
2236
2402
|
}
|
|
2237
2403
|
return { analyzed: blameResult.value, operatingLevel, warnings };
|
|
2238
2404
|
}
|
|
2239
|
-
|
|
2405
|
+
function resolveTraceMode(mode) {
|
|
2406
|
+
return mode ?? "origin";
|
|
2407
|
+
}
|
|
2408
|
+
function deduplicatedLookupPR(sha, adapter, options, inflight) {
|
|
2409
|
+
const existing = inflight.get(sha);
|
|
2410
|
+
if (existing) return existing;
|
|
2411
|
+
const promise = lookupPR(sha, adapter, options);
|
|
2412
|
+
inflight.set(sha, promise);
|
|
2413
|
+
promise.finally(() => inflight.delete(sha));
|
|
2414
|
+
return promise;
|
|
2415
|
+
}
|
|
2416
|
+
async function processEntry(entry, featureFlags, adapter, options, execOptions, repoId, inflightPR, skipPatchIdScan, preferredBase) {
|
|
2240
2417
|
const nodes = [];
|
|
2418
|
+
const traceMode = resolveTraceMode(options.mode);
|
|
2241
2419
|
const commitNode = {
|
|
2242
2420
|
type: entry.isCosmetic ? "cosmetic_commit" : "original_commit",
|
|
2243
2421
|
sha: entry.blame.commitHash,
|
|
2244
|
-
trackingMethod: "blame-CMw",
|
|
2422
|
+
trackingMethod: traceMode === "change" ? "blame" : "blame-CMw",
|
|
2245
2423
|
confidence: "exact",
|
|
2246
2424
|
note: entry.cosmeticReason ? `Cosmetic change: ${entry.cosmeticReason}` : void 0
|
|
2247
2425
|
};
|
|
@@ -2263,21 +2441,24 @@ async function processEntry(entry, featureFlags, adapter, options, execOptions,
|
|
|
2263
2441
|
}
|
|
2264
2442
|
}
|
|
2265
2443
|
const targetSha = nodes[nodes.length - 1].sha;
|
|
2444
|
+
const prLookupOptions = {
|
|
2445
|
+
...execOptions,
|
|
2446
|
+
noCache: options.noCache,
|
|
2447
|
+
cacheOnly: options.cacheOnly,
|
|
2448
|
+
deep: featureFlags.deepTrace,
|
|
2449
|
+
repoId,
|
|
2450
|
+
skipPatchIdScan,
|
|
2451
|
+
preferredBase,
|
|
2452
|
+
platform: adapter?.platform
|
|
2453
|
+
};
|
|
2266
2454
|
if (targetSha) {
|
|
2267
|
-
const prInfo = await
|
|
2268
|
-
...execOptions,
|
|
2269
|
-
noCache: options.noCache,
|
|
2270
|
-
cacheOnly: options.cacheOnly,
|
|
2271
|
-
deep: featureFlags.deepTrace,
|
|
2272
|
-
repoId,
|
|
2273
|
-
skipPatchIdScan
|
|
2274
|
-
});
|
|
2455
|
+
const prInfo = await deduplicatedLookupPR(targetSha, adapter, prLookupOptions, inflightPR);
|
|
2275
2456
|
if (prInfo) {
|
|
2276
2457
|
nodes.push({
|
|
2277
2458
|
type: "pull_request",
|
|
2278
2459
|
sha: prInfo.mergeCommit,
|
|
2279
|
-
trackingMethod: prInfo.
|
|
2280
|
-
confidence: prInfo.
|
|
2460
|
+
trackingMethod: resolvedViaToTrackingMethod(prInfo.resolvedVia),
|
|
2461
|
+
confidence: resolvedViaToConfidence(prInfo.resolvedVia),
|
|
2281
2462
|
prNumber: prInfo.number,
|
|
2282
2463
|
prUrl: prInfo.url || void 0,
|
|
2283
2464
|
prTitle: prInfo.title || void 0,
|
|
@@ -2287,7 +2468,8 @@ async function processEntry(entry, featureFlags, adapter, options, execOptions,
|
|
|
2287
2468
|
}
|
|
2288
2469
|
return nodes;
|
|
2289
2470
|
}
|
|
2290
|
-
async function buildTraceNodes(analyzed, featureFlags, adapter, options, execOptions, repoId, skipPatchIdScan) {
|
|
2471
|
+
async function buildTraceNodes(analyzed, featureFlags, adapter, options, execOptions, repoId, skipPatchIdScan, preferredBase) {
|
|
2472
|
+
const inflightPR = /* @__PURE__ */ new Map();
|
|
2291
2473
|
const results = await Promise.allSettled(
|
|
2292
2474
|
(0, import_common_utils11.map)(
|
|
2293
2475
|
analyzed,
|
|
@@ -2298,7 +2480,9 @@ async function buildTraceNodes(analyzed, featureFlags, adapter, options, execOpt
|
|
|
2298
2480
|
options,
|
|
2299
2481
|
execOptions,
|
|
2300
2482
|
repoId,
|
|
2301
|
-
|
|
2483
|
+
inflightPR,
|
|
2484
|
+
skipPatchIdScan,
|
|
2485
|
+
preferredBase
|
|
2302
2486
|
)
|
|
2303
2487
|
)
|
|
2304
2488
|
);
|
|
@@ -2306,13 +2490,16 @@ async function buildTraceNodes(analyzed, featureFlags, adapter, options, execOpt
|
|
|
2306
2490
|
}
|
|
2307
2491
|
var legacyCacheCleaned = false;
|
|
2308
2492
|
async function trace(options) {
|
|
2309
|
-
const
|
|
2493
|
+
const mode = resolveTraceMode(options.mode);
|
|
2494
|
+
const { file, cwd } = await resolveFileContext(options.file, options.cwd);
|
|
2495
|
+
const warnings = [];
|
|
2496
|
+
const execOptions = { cwd, warnings };
|
|
2310
2497
|
if (!legacyCacheCleaned) {
|
|
2311
2498
|
legacyCacheCleaned = true;
|
|
2312
2499
|
cleanupLegacyCache().catch(() => {
|
|
2313
2500
|
});
|
|
2314
2501
|
}
|
|
2315
|
-
const platform = await detectPlatform2(options);
|
|
2502
|
+
const platform = await detectPlatform2({ ...options, cwd });
|
|
2316
2503
|
let repoId;
|
|
2317
2504
|
if (platform.remote) {
|
|
2318
2505
|
repoId = {
|
|
@@ -2321,15 +2508,15 @@ async function trace(options) {
|
|
|
2321
2508
|
repo: platform.remote.repo
|
|
2322
2509
|
};
|
|
2323
2510
|
} else {
|
|
2324
|
-
repoId = await resolveRepoIdentity(
|
|
2511
|
+
repoId = await resolveRepoIdentity(cwd);
|
|
2325
2512
|
}
|
|
2326
2513
|
const blameAuth = await runBlameAndAuth(
|
|
2327
2514
|
platform.adapter,
|
|
2328
|
-
options,
|
|
2515
|
+
{ ...options, mode, file, cwd },
|
|
2329
2516
|
execOptions
|
|
2330
2517
|
);
|
|
2331
2518
|
const operatingLevel = blameAuth.operatingLevel || platform.operatingLevel;
|
|
2332
|
-
|
|
2519
|
+
warnings.push(...platform.warnings, ...blameAuth.warnings);
|
|
2333
2520
|
if (options.cacheOnly && options.noCache) {
|
|
2334
2521
|
warnings.push(
|
|
2335
2522
|
"Both cacheOnly and noCache are set. cacheOnly takes precedence \u2014 cache reads are enabled."
|
|
@@ -2338,13 +2525,13 @@ async function trace(options) {
|
|
|
2338
2525
|
const featureFlags = computeFeatureFlags(operatingLevel, options);
|
|
2339
2526
|
let cloneStatus = { partialClone: false, shallow: false };
|
|
2340
2527
|
try {
|
|
2341
|
-
const result = await checkCloneStatus({ cwd
|
|
2528
|
+
const result = await checkCloneStatus({ cwd });
|
|
2342
2529
|
if (result) cloneStatus = result;
|
|
2343
2530
|
} catch {
|
|
2344
2531
|
}
|
|
2345
2532
|
if (cloneStatus.partialClone) {
|
|
2346
2533
|
warnings.push(
|
|
2347
|
-
"Partial clone detected. Patch-ID scan (Strategy
|
|
2534
|
+
"Partial clone detected. Patch-ID scan (Strategy 5) will be skipped to avoid blob downloads."
|
|
2348
2535
|
);
|
|
2349
2536
|
}
|
|
2350
2537
|
if (cloneStatus.shallow) {
|
|
@@ -2352,14 +2539,27 @@ async function trace(options) {
|
|
|
2352
2539
|
"Shallow repository detected. Ancestry-path results may be incomplete."
|
|
2353
2540
|
);
|
|
2354
2541
|
}
|
|
2542
|
+
let preferredBase;
|
|
2543
|
+
try {
|
|
2544
|
+
const branchResult = await gitExec(
|
|
2545
|
+
["rev-parse", "--abbrev-ref", "HEAD"],
|
|
2546
|
+
execOptions
|
|
2547
|
+
);
|
|
2548
|
+
const branch = branchResult.stdout.trim();
|
|
2549
|
+
if (branch && branch !== "HEAD") {
|
|
2550
|
+
preferredBase = branch;
|
|
2551
|
+
}
|
|
2552
|
+
} catch {
|
|
2553
|
+
}
|
|
2355
2554
|
const nodes = await buildTraceNodes(
|
|
2356
2555
|
blameAuth.analyzed,
|
|
2357
2556
|
featureFlags,
|
|
2358
2557
|
platform.adapter,
|
|
2359
|
-
options,
|
|
2558
|
+
{ ...options, mode, file, cwd },
|
|
2360
2559
|
execOptions,
|
|
2361
2560
|
repoId,
|
|
2362
|
-
cloneStatus.partialClone || void 0
|
|
2561
|
+
cloneStatus.partialClone || void 0,
|
|
2562
|
+
preferredBase
|
|
2363
2563
|
);
|
|
2364
2564
|
return { nodes, operatingLevel, featureFlags, warnings };
|
|
2365
2565
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type { AstDiffStageResult, AstTraceResult, AuthStatus, BlameResult, BlameStageResult, CacheEntry, ChangeType, CommitInfo, ComparisonResult, Confidence, ContentHash, CosmeticReason, FeatureFlags, GraphOptions, GraphResult, HealthReport, IssueInfo, LineRange, NormalizedResponse, OperatingLevel, PlatformAdapter, PlatformType, PRInfo, RateLimitInfo, RemoteInfo, SymbolInfo, SymbolKind, TraceNode, TraceNodeType, TraceOptions, TraceResult, TrackingMethod, } from './types/index.js';
|
|
1
|
+
export type { AstDiffStageResult, AstTraceResult, AuthStatus, BlameResult, BlameStageResult, CacheEntry, ChangeType, CommitInfo, ComparisonResult, Confidence, ContentHash, CosmeticReason, FeatureFlags, GraphOptions, GraphResult, HealthReport, IssueInfo, LineRange, NormalizedResponse, OperatingLevel, PlatformAdapter, PlatformType, PRInfo, RateLimitInfo, RemoteInfo, SymbolInfo, SymbolKind, TraceNode, TraceNodeType, TraceMode, TraceOptions, TraceResult, TrackingMethod, } from './types/index.js';
|
|
2
2
|
export { LineLoreError, LineLoreErrorCode } from './errors.js';
|
|
3
3
|
export { clearCache, graph, health, trace } from './core/core.js';
|
|
4
4
|
export type { TraceFullResult } from './core/core.js';
|