@lumy-pack/line-lore 0.0.5 → 0.0.7
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 +331 -61
- package/dist/core/ancestry/ancestry.d.ts +15 -1
- package/dist/core/ancestry/index.d.ts +1 -1
- package/dist/core/pr-lookup/index.d.ts +1 -0
- package/dist/core/pr-lookup/pr-lookup.d.ts +23 -1
- package/dist/git/health.d.ts +4 -1
- package/dist/git/index.d.ts +1 -1
- package/dist/index.cjs +327 -59
- package/dist/index.mjs +327 -58
- package/dist/platform/github/github-adapter.d.ts +3 -1
- package/dist/platform/gitlab/gitlab-adapter.d.ts +3 -1
- package/dist/types/cache.d.ts +13 -0
- package/dist/types/git.d.ts +8 -0
- package/dist/types/index.d.ts +2 -2
- package/dist/types/platform.d.ts +3 -1
- package/dist/types/trace.d.ts +2 -0
- package/dist/version.d.ts +1 -1
- package/package.json +1 -1
package/dist/git/health.d.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import type { HealthReport } from '../types/index.js';
|
|
1
|
+
import type { CloneStatus, HealthReport } from '../types/index.js';
|
|
2
|
+
export declare function checkCloneStatus(options?: {
|
|
3
|
+
cwd?: string;
|
|
4
|
+
}): Promise<CloneStatus>;
|
|
2
5
|
export declare function checkGitHealth(options?: {
|
|
3
6
|
cwd?: string;
|
|
4
7
|
}): Promise<HealthReport>;
|
package/dist/git/index.d.ts
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -383,14 +383,51 @@ var init_executor = __esm({
|
|
|
383
383
|
// src/core/ancestry/ancestry.ts
|
|
384
384
|
async function findMergeCommit(commitSha, options) {
|
|
385
385
|
const ref = options?.ref ?? "HEAD";
|
|
386
|
+
const budget = options?.timeout ?? DEFAULT_ANCESTRY_TIMEOUT;
|
|
387
|
+
const startTime = Date.now();
|
|
386
388
|
const firstParentResult = await findMergeCommitWithArgs(
|
|
387
389
|
commitSha,
|
|
388
390
|
ref,
|
|
389
391
|
["--first-parent"],
|
|
390
|
-
options
|
|
392
|
+
{ ...options, timeout: budget }
|
|
391
393
|
);
|
|
392
394
|
if (firstParentResult) return firstParentResult;
|
|
393
|
-
|
|
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
|
+
});
|
|
402
|
+
}
|
|
403
|
+
async function verifyMergeIntroducesCommit(targetSha, mergeResult, options) {
|
|
404
|
+
if (mergeResult.parentShas.length < 2) return true;
|
|
405
|
+
const firstParent = mergeResult.parentShas[0];
|
|
406
|
+
const branchParents = mergeResult.parentShas.slice(1);
|
|
407
|
+
const onMainline = await isAncestor(targetSha, firstParent, options);
|
|
408
|
+
if (onMainline === null) return false;
|
|
409
|
+
if (onMainline) return false;
|
|
410
|
+
for (const branchParent of branchParents) {
|
|
411
|
+
const onBranch = await isAncestor(targetSha, branchParent, options);
|
|
412
|
+
if (onBranch === null) return false;
|
|
413
|
+
if (onBranch) return true;
|
|
414
|
+
}
|
|
415
|
+
return false;
|
|
416
|
+
}
|
|
417
|
+
async function isAncestor(commitA, commitB, options) {
|
|
418
|
+
try {
|
|
419
|
+
const result = await gitExec(
|
|
420
|
+
["merge-base", "--is-ancestor", commitA, commitB],
|
|
421
|
+
{
|
|
422
|
+
cwd: options?.cwd,
|
|
423
|
+
timeout: options?.timeout ?? 5e3,
|
|
424
|
+
allowExitCodes: [1]
|
|
425
|
+
}
|
|
426
|
+
);
|
|
427
|
+
return result.exitCode === 0;
|
|
428
|
+
} catch {
|
|
429
|
+
return null;
|
|
430
|
+
}
|
|
394
431
|
}
|
|
395
432
|
async function findMergeCommitWithArgs(commitSha, ref, extraArgs, options) {
|
|
396
433
|
try {
|
|
@@ -409,7 +446,25 @@ async function findMergeCommitWithArgs(commitSha, ref, extraArgs, options) {
|
|
|
409
446
|
);
|
|
410
447
|
const lines = (0, import_common_utils9.filter)(result.stdout.trim().split("\n"), import_common_utils9.isTruthy);
|
|
411
448
|
if (lines.length === 0) return null;
|
|
412
|
-
|
|
449
|
+
const candidateCount = Math.min(lines.length, MAX_CANDIDATES);
|
|
450
|
+
let verifiedCount = 0;
|
|
451
|
+
for (let i = 0; i < candidateCount; i++) {
|
|
452
|
+
const candidate = parseMergeLogLine(lines[i]);
|
|
453
|
+
if (!candidate) continue;
|
|
454
|
+
verifiedCount++;
|
|
455
|
+
const verified = await verifyMergeIntroducesCommit(
|
|
456
|
+
commitSha,
|
|
457
|
+
candidate,
|
|
458
|
+
options
|
|
459
|
+
);
|
|
460
|
+
if (verified) return candidate;
|
|
461
|
+
}
|
|
462
|
+
if (verifiedCount > 0 && options?.warnings) {
|
|
463
|
+
options.warnings.push(
|
|
464
|
+
`ancestry: all ${verifiedCount} merge candidate(s) failed verification for ${commitSha.slice(0, 8)}`
|
|
465
|
+
);
|
|
466
|
+
}
|
|
467
|
+
return null;
|
|
413
468
|
} catch {
|
|
414
469
|
return null;
|
|
415
470
|
}
|
|
@@ -431,22 +486,40 @@ function parseMergeLogLine(line) {
|
|
|
431
486
|
const subject = parts.slice(subjectStart).join(" ");
|
|
432
487
|
return { mergeCommitSha, parentShas, subject };
|
|
433
488
|
}
|
|
434
|
-
function
|
|
489
|
+
async function getCommitSubject(sha, options) {
|
|
490
|
+
try {
|
|
491
|
+
const result = await gitExec(["log", "-1", "--format=%s", sha], {
|
|
492
|
+
cwd: options?.cwd,
|
|
493
|
+
timeout: options?.timeout ?? 5e3
|
|
494
|
+
});
|
|
495
|
+
const subject = result.stdout.trim();
|
|
496
|
+
return subject || null;
|
|
497
|
+
} catch {
|
|
498
|
+
return null;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
function extractPRFromMergeMessage(subject, platform) {
|
|
435
502
|
const ghMatch = /Merge pull request #(\d+)/.exec(subject);
|
|
436
503
|
if (ghMatch) return parseInt(ghMatch[1], 10);
|
|
437
504
|
const squashMatch = /\(#(\d+)\)\s*$/.exec(subject);
|
|
438
505
|
if (squashMatch) return parseInt(squashMatch[1], 10);
|
|
439
|
-
|
|
440
|
-
|
|
506
|
+
if (!platform || platform === "gitlab" || platform === "gitlab-self-hosted") {
|
|
507
|
+
const glMatch = /See merge request\s+\S*!(\d+)\s*$/.exec(subject);
|
|
508
|
+
if (glMatch) return parseInt(glMatch[1], 10);
|
|
509
|
+
}
|
|
510
|
+
const adoMatch = /Merged PR (\d+):/.exec(subject);
|
|
511
|
+
if (adoMatch) return parseInt(adoMatch[1], 10);
|
|
441
512
|
return null;
|
|
442
513
|
}
|
|
443
|
-
var import_common_utils9;
|
|
514
|
+
var import_common_utils9, DEFAULT_ANCESTRY_TIMEOUT, MAX_CANDIDATES;
|
|
444
515
|
var init_ancestry = __esm({
|
|
445
516
|
"src/core/ancestry/ancestry.ts"() {
|
|
446
517
|
"use strict";
|
|
447
518
|
init_cjs_shims();
|
|
448
519
|
import_common_utils9 = require("@winglet/common-utils");
|
|
449
520
|
init_executor();
|
|
521
|
+
DEFAULT_ANCESTRY_TIMEOUT = 3e4;
|
|
522
|
+
MAX_CANDIDATES = 10;
|
|
450
523
|
}
|
|
451
524
|
});
|
|
452
525
|
|
|
@@ -569,19 +642,67 @@ function getCache2(repoId, noCache) {
|
|
|
569
642
|
}
|
|
570
643
|
return cache;
|
|
571
644
|
}
|
|
572
|
-
|
|
573
|
-
|
|
645
|
+
function toCachedPR(pr) {
|
|
646
|
+
return {
|
|
647
|
+
number: pr.number,
|
|
648
|
+
title: pr.title,
|
|
649
|
+
author: pr.author,
|
|
650
|
+
url: pr.url,
|
|
651
|
+
mergeCommit: pr.mergeCommit,
|
|
652
|
+
baseBranch: pr.baseBranch,
|
|
653
|
+
mergedAt: pr.mergedAt ? new Date(pr.mergedAt).getTime() : void 0,
|
|
654
|
+
resolvedVia: pr.resolvedVia
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
function fromCachedPR(cached) {
|
|
658
|
+
let mergedAt;
|
|
659
|
+
if (cached.mergedAt != null) {
|
|
660
|
+
mergedAt = typeof cached.mergedAt === "number" ? new Date(cached.mergedAt).toISOString() : String(cached.mergedAt);
|
|
661
|
+
}
|
|
662
|
+
return {
|
|
663
|
+
number: cached.number,
|
|
664
|
+
title: cached.title,
|
|
665
|
+
author: cached.author,
|
|
666
|
+
url: cached.url,
|
|
667
|
+
mergeCommit: cached.mergeCommit,
|
|
668
|
+
baseBranch: cached.baseBranch,
|
|
669
|
+
mergedAt,
|
|
670
|
+
// Preserve original resolvedVia; fallback to url heuristic for legacy cache entries
|
|
671
|
+
resolvedVia: cached.resolvedVia ?? (cached.url ? "api" : "message")
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
async function lookupPR(commitSha, adapter, options, _recursionDepth = 0) {
|
|
675
|
+
const cache = getCache2(
|
|
676
|
+
options?.repoId,
|
|
677
|
+
options?.cacheOnly ? false : options?.noCache
|
|
678
|
+
);
|
|
574
679
|
const cached = await cache.get(commitSha);
|
|
575
|
-
if (cached) return cached;
|
|
680
|
+
if (cached) return fromCachedPR(cached);
|
|
681
|
+
if (options?.cacheOnly) return null;
|
|
682
|
+
const prSelectOptions = options?.preferredBase ? { preferredBase: options.preferredBase } : void 0;
|
|
683
|
+
if (adapter) {
|
|
684
|
+
const directPR = await adapter.getPRForCommit(commitSha, prSelectOptions);
|
|
685
|
+
if (directPR?.mergedAt) {
|
|
686
|
+
const result = { ...directPR, resolvedVia: "api" };
|
|
687
|
+
await cache.set(commitSha, toCachedPR(result));
|
|
688
|
+
return result;
|
|
689
|
+
}
|
|
690
|
+
}
|
|
576
691
|
let mergeBasedPR = null;
|
|
577
692
|
const mergeResult = await findMergeCommit(commitSha, options);
|
|
578
693
|
if (mergeResult) {
|
|
579
|
-
const prNumber = extractPRFromMergeMessage(
|
|
694
|
+
const prNumber = extractPRFromMergeMessage(
|
|
695
|
+
mergeResult.subject,
|
|
696
|
+
options?.platform
|
|
697
|
+
);
|
|
580
698
|
if (prNumber) {
|
|
581
699
|
if (adapter) {
|
|
582
|
-
const prInfo = await adapter.getPRForCommit(
|
|
700
|
+
const prInfo = await adapter.getPRForCommit(
|
|
701
|
+
mergeResult.mergeCommitSha,
|
|
702
|
+
prSelectOptions
|
|
703
|
+
);
|
|
583
704
|
if (prInfo?.mergedAt) {
|
|
584
|
-
mergeBasedPR = prInfo;
|
|
705
|
+
mergeBasedPR = { ...prInfo, resolvedVia: "ancestry" };
|
|
585
706
|
}
|
|
586
707
|
}
|
|
587
708
|
if (!mergeBasedPR) {
|
|
@@ -591,35 +712,56 @@ async function lookupPR(commitSha, adapter, options) {
|
|
|
591
712
|
author: "",
|
|
592
713
|
url: "",
|
|
593
714
|
mergeCommit: mergeResult.mergeCommitSha,
|
|
594
|
-
baseBranch: ""
|
|
715
|
+
baseBranch: "",
|
|
716
|
+
resolvedVia: "ancestry"
|
|
595
717
|
};
|
|
596
718
|
}
|
|
597
719
|
if (!options?.deep || mergeBasedPR.mergedAt) {
|
|
598
|
-
await cache.set(commitSha, mergeBasedPR);
|
|
720
|
+
await cache.set(commitSha, toCachedPR(mergeBasedPR));
|
|
599
721
|
return mergeBasedPR;
|
|
600
722
|
}
|
|
601
723
|
}
|
|
602
724
|
}
|
|
603
725
|
if (mergeBasedPR) {
|
|
604
|
-
await cache.set(commitSha, mergeBasedPR);
|
|
726
|
+
await cache.set(commitSha, toCachedPR(mergeBasedPR));
|
|
605
727
|
return mergeBasedPR;
|
|
606
728
|
}
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
729
|
+
const commitSubject = await getCommitSubject(commitSha, options);
|
|
730
|
+
if (commitSubject) {
|
|
731
|
+
const directPrNumber = extractPRFromMergeMessage(
|
|
732
|
+
commitSubject,
|
|
733
|
+
options?.platform
|
|
734
|
+
);
|
|
735
|
+
if (directPrNumber) {
|
|
736
|
+
const subjectPR = {
|
|
737
|
+
number: directPrNumber,
|
|
738
|
+
title: commitSubject,
|
|
739
|
+
author: "",
|
|
740
|
+
url: "",
|
|
741
|
+
mergeCommit: commitSha,
|
|
742
|
+
baseBranch: "",
|
|
743
|
+
resolvedVia: "message"
|
|
744
|
+
};
|
|
745
|
+
await cache.set(commitSha, toCachedPR(subjectPR));
|
|
746
|
+
return subjectPR;
|
|
612
747
|
}
|
|
613
748
|
}
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
749
|
+
if (!options?.skipPatchIdScan && _recursionDepth < MAX_RECURSION_DEPTH) {
|
|
750
|
+
const patchIdMatch = await findPatchIdMatch(commitSha, {
|
|
751
|
+
...options,
|
|
752
|
+
scanDepth: options?.deep ? DEEP_SCAN_DEPTH : void 0
|
|
753
|
+
});
|
|
754
|
+
if (patchIdMatch) {
|
|
755
|
+
const result = await lookupPR(
|
|
756
|
+
patchIdMatch.matchedSha,
|
|
757
|
+
adapter,
|
|
758
|
+
options,
|
|
759
|
+
_recursionDepth + 1
|
|
760
|
+
);
|
|
761
|
+
if (result) {
|
|
762
|
+
await cache.set(commitSha, toCachedPR(result));
|
|
763
|
+
return result;
|
|
764
|
+
}
|
|
623
765
|
}
|
|
624
766
|
}
|
|
625
767
|
return null;
|
|
@@ -627,7 +769,7 @@ async function lookupPR(commitSha, adapter, options) {
|
|
|
627
769
|
function resetPRCache() {
|
|
628
770
|
cacheRegistry2.clear();
|
|
629
771
|
}
|
|
630
|
-
var cacheRegistry2, DEEP_SCAN_DEPTH;
|
|
772
|
+
var cacheRegistry2, DEEP_SCAN_DEPTH, MAX_RECURSION_DEPTH;
|
|
631
773
|
var init_pr_lookup = __esm({
|
|
632
774
|
"src/core/pr-lookup/pr-lookup.ts"() {
|
|
633
775
|
"use strict";
|
|
@@ -637,6 +779,7 @@ var init_pr_lookup = __esm({
|
|
|
637
779
|
init_patch_id2();
|
|
638
780
|
cacheRegistry2 = /* @__PURE__ */ new Map();
|
|
639
781
|
DEEP_SCAN_DEPTH = 2e3;
|
|
782
|
+
MAX_RECURSION_DEPTH = 2;
|
|
640
783
|
}
|
|
641
784
|
});
|
|
642
785
|
|
|
@@ -672,6 +815,7 @@ init_errors();
|
|
|
672
815
|
// src/core/core.ts
|
|
673
816
|
init_cjs_shims();
|
|
674
817
|
var import_node_crypto2 = require("crypto");
|
|
818
|
+
var import_node_path2 = require("path");
|
|
675
819
|
var import_common_utils11 = require("@winglet/common-utils");
|
|
676
820
|
|
|
677
821
|
// src/ast/index.ts
|
|
@@ -905,6 +1049,27 @@ function isVersionAtLeast(version, minVersion) {
|
|
|
905
1049
|
}
|
|
906
1050
|
return true;
|
|
907
1051
|
}
|
|
1052
|
+
async function checkCloneStatus(options) {
|
|
1053
|
+
let partialClone = false;
|
|
1054
|
+
let shallow = false;
|
|
1055
|
+
try {
|
|
1056
|
+
const shallowResult = await gitExec(
|
|
1057
|
+
["rev-parse", "--is-shallow-repository"],
|
|
1058
|
+
{ cwd: options?.cwd }
|
|
1059
|
+
);
|
|
1060
|
+
shallow = shallowResult.stdout.trim() === "true";
|
|
1061
|
+
} catch {
|
|
1062
|
+
}
|
|
1063
|
+
try {
|
|
1064
|
+
const partialResult = await gitExec(
|
|
1065
|
+
["config", "--get", "extensions.partialclone"],
|
|
1066
|
+
{ cwd: options?.cwd }
|
|
1067
|
+
);
|
|
1068
|
+
partialClone = partialResult.stdout.trim().length > 0;
|
|
1069
|
+
} catch {
|
|
1070
|
+
}
|
|
1071
|
+
return { partialClone, shallow };
|
|
1072
|
+
}
|
|
908
1073
|
async function checkGitHealth(options) {
|
|
909
1074
|
const hints = [];
|
|
910
1075
|
let gitVersion = "0.0.0";
|
|
@@ -931,7 +1096,18 @@ async function checkGitHealth(options) {
|
|
|
931
1096
|
`Upgrade git to ${BLOOM_FILTER_MIN_VERSION.join(".")}+ for bloom filter support (current: ${gitVersion}).`
|
|
932
1097
|
);
|
|
933
1098
|
}
|
|
934
|
-
|
|
1099
|
+
const cloneStatus = await checkCloneStatus({ cwd: options?.cwd });
|
|
1100
|
+
if (cloneStatus.partialClone) {
|
|
1101
|
+
hints.push(
|
|
1102
|
+
"Partial clone detected. Patch-ID scan (Strategy 5) will be skipped to avoid blob downloads."
|
|
1103
|
+
);
|
|
1104
|
+
}
|
|
1105
|
+
if (cloneStatus.shallow) {
|
|
1106
|
+
hints.push(
|
|
1107
|
+
"Shallow repository detected. Ancestry-path results may be incomplete."
|
|
1108
|
+
);
|
|
1109
|
+
}
|
|
1110
|
+
return { commitGraph, bloomFilter, gitVersion, hints, ...cloneStatus };
|
|
935
1111
|
}
|
|
936
1112
|
|
|
937
1113
|
// src/platform/index.ts
|
|
@@ -1039,7 +1215,7 @@ var GitHubAdapter = class {
|
|
|
1039
1215
|
return { authenticated: false, hostname: this.hostname };
|
|
1040
1216
|
}
|
|
1041
1217
|
}
|
|
1042
|
-
async getPRForCommit(sha) {
|
|
1218
|
+
async getPRForCommit(sha, options) {
|
|
1043
1219
|
if (this.scheduler.isRateLimited()) return null;
|
|
1044
1220
|
try {
|
|
1045
1221
|
const result = await shellExec(
|
|
@@ -1056,18 +1232,20 @@ var GitHubAdapter = class {
|
|
|
1056
1232
|
);
|
|
1057
1233
|
const prs = JSON.parse(result.stdout);
|
|
1058
1234
|
if (!(0, import_common_utils3.isArray)(prs) || prs.length === 0) return null;
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1235
|
+
let data = prs[0];
|
|
1236
|
+
if (options?.preferredBase) {
|
|
1237
|
+
const preferred = prs.find(
|
|
1238
|
+
(pr) => pr.base === options.preferredBase
|
|
1239
|
+
);
|
|
1240
|
+
if (preferred) data = preferred;
|
|
1241
|
+
}
|
|
1064
1242
|
return {
|
|
1065
1243
|
number: data.number,
|
|
1066
1244
|
title: data.title ?? "",
|
|
1067
1245
|
author: data.user ?? "",
|
|
1068
1246
|
url: data.html_url ?? "",
|
|
1069
1247
|
mergeCommit: data.merge_commit_sha ?? sha,
|
|
1070
|
-
baseBranch: data.base ??
|
|
1248
|
+
baseBranch: data.base ?? "",
|
|
1071
1249
|
mergedAt: data.merged_at
|
|
1072
1250
|
};
|
|
1073
1251
|
} catch {
|
|
@@ -1249,7 +1427,7 @@ var GitLabAdapter = class {
|
|
|
1249
1427
|
return { authenticated: false, hostname: this.hostname };
|
|
1250
1428
|
}
|
|
1251
1429
|
}
|
|
1252
|
-
async getPRForCommit(sha) {
|
|
1430
|
+
async getPRForCommit(sha, options) {
|
|
1253
1431
|
if (this.scheduler.isRateLimited()) return null;
|
|
1254
1432
|
try {
|
|
1255
1433
|
const result = await shellExec(
|
|
@@ -1273,18 +1451,20 @@ var GitLabAdapter = class {
|
|
|
1273
1451
|
return aTime - bTime;
|
|
1274
1452
|
});
|
|
1275
1453
|
if (mergedMRs.length === 0) return null;
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1454
|
+
let mr = mergedMRs[0];
|
|
1455
|
+
if (options?.preferredBase) {
|
|
1456
|
+
const preferred = mergedMRs.find(
|
|
1457
|
+
(m) => m.target_branch === options.preferredBase
|
|
1458
|
+
);
|
|
1459
|
+
if (preferred) mr = preferred;
|
|
1460
|
+
}
|
|
1281
1461
|
return {
|
|
1282
1462
|
number: mr.iid,
|
|
1283
1463
|
title: mr.title ?? "",
|
|
1284
1464
|
author: mr.author?.username ?? "",
|
|
1285
1465
|
url: mr.web_url ?? "",
|
|
1286
1466
|
mergeCommit: mr.merge_commit_sha ?? sha,
|
|
1287
|
-
baseBranch: mr.target_branch ??
|
|
1467
|
+
baseBranch: mr.target_branch ?? "",
|
|
1288
1468
|
mergedAt: mr.merged_at
|
|
1289
1469
|
};
|
|
1290
1470
|
} catch {
|
|
@@ -2092,6 +2272,28 @@ async function traverse(adapter, type, number, depth, maxDepth, nodes, edges, vi
|
|
|
2092
2272
|
|
|
2093
2273
|
// src/core/core.ts
|
|
2094
2274
|
init_pr_lookup2();
|
|
2275
|
+
function resolvedViaToTrackingMethod(resolvedVia) {
|
|
2276
|
+
switch (resolvedVia) {
|
|
2277
|
+
case "api":
|
|
2278
|
+
return "api";
|
|
2279
|
+
case "ancestry":
|
|
2280
|
+
return "ancestry-path";
|
|
2281
|
+
case "message":
|
|
2282
|
+
return "message-parse";
|
|
2283
|
+
case "patch-id":
|
|
2284
|
+
return "patch-id";
|
|
2285
|
+
}
|
|
2286
|
+
}
|
|
2287
|
+
function resolvedViaToConfidence(resolvedVia) {
|
|
2288
|
+
switch (resolvedVia) {
|
|
2289
|
+
case "api":
|
|
2290
|
+
case "ancestry":
|
|
2291
|
+
return "exact";
|
|
2292
|
+
case "message":
|
|
2293
|
+
case "patch-id":
|
|
2294
|
+
return "heuristic";
|
|
2295
|
+
}
|
|
2296
|
+
}
|
|
2095
2297
|
function computeFeatureFlags(operatingLevel, options) {
|
|
2096
2298
|
return {
|
|
2097
2299
|
astDiff: isAstAvailable() && !options.noAst,
|
|
@@ -2109,6 +2311,22 @@ async function resolveRepoIdentity(cwd) {
|
|
|
2109
2311
|
return { host: "_local", owner: "_", repo: "_unknown" };
|
|
2110
2312
|
}
|
|
2111
2313
|
}
|
|
2314
|
+
async function resolveFileContext(file, cwd) {
|
|
2315
|
+
if (cwd || !(0, import_node_path2.isAbsolute)(file)) return { file, cwd };
|
|
2316
|
+
const fileDir = (0, import_node_path2.dirname)(file);
|
|
2317
|
+
try {
|
|
2318
|
+
const result = await gitExec(["rev-parse", "--show-toplevel"], {
|
|
2319
|
+
cwd: fileDir
|
|
2320
|
+
});
|
|
2321
|
+
const repoRoot = result.stdout.trim();
|
|
2322
|
+
return {
|
|
2323
|
+
file: (0, import_node_path2.relative)(repoRoot, file),
|
|
2324
|
+
cwd: repoRoot
|
|
2325
|
+
};
|
|
2326
|
+
} catch {
|
|
2327
|
+
return { file, cwd };
|
|
2328
|
+
}
|
|
2329
|
+
}
|
|
2112
2330
|
async function detectPlatform2(options) {
|
|
2113
2331
|
const warnings = [];
|
|
2114
2332
|
let adapter = null;
|
|
@@ -2155,7 +2373,7 @@ async function runBlameAndAuth(adapter, options, execOptions) {
|
|
|
2155
2373
|
}
|
|
2156
2374
|
return { analyzed: blameResult.value, operatingLevel, warnings };
|
|
2157
2375
|
}
|
|
2158
|
-
async function processEntry(entry, featureFlags, adapter, options, execOptions, repoId) {
|
|
2376
|
+
async function processEntry(entry, featureFlags, adapter, options, execOptions, repoId, skipPatchIdScan, preferredBase) {
|
|
2159
2377
|
const nodes = [];
|
|
2160
2378
|
const commitNode = {
|
|
2161
2379
|
type: entry.isCosmetic ? "cosmetic_commit" : "original_commit",
|
|
@@ -2186,15 +2404,19 @@ async function processEntry(entry, featureFlags, adapter, options, execOptions,
|
|
|
2186
2404
|
const prInfo = await lookupPR(targetSha, adapter, {
|
|
2187
2405
|
...execOptions,
|
|
2188
2406
|
noCache: options.noCache,
|
|
2407
|
+
cacheOnly: options.cacheOnly,
|
|
2189
2408
|
deep: featureFlags.deepTrace,
|
|
2190
|
-
repoId
|
|
2409
|
+
repoId,
|
|
2410
|
+
skipPatchIdScan,
|
|
2411
|
+
preferredBase,
|
|
2412
|
+
platform: adapter?.platform
|
|
2191
2413
|
});
|
|
2192
2414
|
if (prInfo) {
|
|
2193
2415
|
nodes.push({
|
|
2194
2416
|
type: "pull_request",
|
|
2195
2417
|
sha: prInfo.mergeCommit,
|
|
2196
|
-
trackingMethod: prInfo.
|
|
2197
|
-
confidence: prInfo.
|
|
2418
|
+
trackingMethod: resolvedViaToTrackingMethod(prInfo.resolvedVia),
|
|
2419
|
+
confidence: resolvedViaToConfidence(prInfo.resolvedVia),
|
|
2198
2420
|
prNumber: prInfo.number,
|
|
2199
2421
|
prUrl: prInfo.url || void 0,
|
|
2200
2422
|
prTitle: prInfo.title || void 0,
|
|
@@ -2204,24 +2426,35 @@ async function processEntry(entry, featureFlags, adapter, options, execOptions,
|
|
|
2204
2426
|
}
|
|
2205
2427
|
return nodes;
|
|
2206
2428
|
}
|
|
2207
|
-
async function buildTraceNodes(analyzed, featureFlags, adapter, options, execOptions, repoId) {
|
|
2429
|
+
async function buildTraceNodes(analyzed, featureFlags, adapter, options, execOptions, repoId, skipPatchIdScan, preferredBase) {
|
|
2208
2430
|
const results = await Promise.allSettled(
|
|
2209
2431
|
(0, import_common_utils11.map)(
|
|
2210
2432
|
analyzed,
|
|
2211
|
-
(entry) => processEntry(
|
|
2433
|
+
(entry) => processEntry(
|
|
2434
|
+
entry,
|
|
2435
|
+
featureFlags,
|
|
2436
|
+
adapter,
|
|
2437
|
+
options,
|
|
2438
|
+
execOptions,
|
|
2439
|
+
repoId,
|
|
2440
|
+
skipPatchIdScan,
|
|
2441
|
+
preferredBase
|
|
2442
|
+
)
|
|
2212
2443
|
)
|
|
2213
2444
|
);
|
|
2214
2445
|
return results.flatMap((r) => r.status === "fulfilled" ? r.value : []);
|
|
2215
2446
|
}
|
|
2216
2447
|
var legacyCacheCleaned = false;
|
|
2217
2448
|
async function trace(options) {
|
|
2218
|
-
const
|
|
2449
|
+
const { file, cwd } = await resolveFileContext(options.file, options.cwd);
|
|
2450
|
+
const warnings = [];
|
|
2451
|
+
const execOptions = { cwd, warnings };
|
|
2219
2452
|
if (!legacyCacheCleaned) {
|
|
2220
2453
|
legacyCacheCleaned = true;
|
|
2221
2454
|
cleanupLegacyCache().catch(() => {
|
|
2222
2455
|
});
|
|
2223
2456
|
}
|
|
2224
|
-
const platform = await detectPlatform2(options);
|
|
2457
|
+
const platform = await detectPlatform2({ ...options, cwd });
|
|
2225
2458
|
let repoId;
|
|
2226
2459
|
if (platform.remote) {
|
|
2227
2460
|
repoId = {
|
|
@@ -2230,23 +2463,58 @@ async function trace(options) {
|
|
|
2230
2463
|
repo: platform.remote.repo
|
|
2231
2464
|
};
|
|
2232
2465
|
} else {
|
|
2233
|
-
repoId = await resolveRepoIdentity(
|
|
2466
|
+
repoId = await resolveRepoIdentity(cwd);
|
|
2234
2467
|
}
|
|
2235
2468
|
const blameAuth = await runBlameAndAuth(
|
|
2236
2469
|
platform.adapter,
|
|
2237
|
-
options,
|
|
2470
|
+
{ ...options, file, cwd },
|
|
2238
2471
|
execOptions
|
|
2239
2472
|
);
|
|
2240
2473
|
const operatingLevel = blameAuth.operatingLevel || platform.operatingLevel;
|
|
2241
|
-
|
|
2474
|
+
warnings.push(...platform.warnings, ...blameAuth.warnings);
|
|
2475
|
+
if (options.cacheOnly && options.noCache) {
|
|
2476
|
+
warnings.push(
|
|
2477
|
+
"Both cacheOnly and noCache are set. cacheOnly takes precedence \u2014 cache reads are enabled."
|
|
2478
|
+
);
|
|
2479
|
+
}
|
|
2242
2480
|
const featureFlags = computeFeatureFlags(operatingLevel, options);
|
|
2481
|
+
let cloneStatus = { partialClone: false, shallow: false };
|
|
2482
|
+
try {
|
|
2483
|
+
const result = await checkCloneStatus({ cwd });
|
|
2484
|
+
if (result) cloneStatus = result;
|
|
2485
|
+
} catch {
|
|
2486
|
+
}
|
|
2487
|
+
if (cloneStatus.partialClone) {
|
|
2488
|
+
warnings.push(
|
|
2489
|
+
"Partial clone detected. Patch-ID scan (Strategy 5) will be skipped to avoid blob downloads."
|
|
2490
|
+
);
|
|
2491
|
+
}
|
|
2492
|
+
if (cloneStatus.shallow) {
|
|
2493
|
+
warnings.push(
|
|
2494
|
+
"Shallow repository detected. Ancestry-path results may be incomplete."
|
|
2495
|
+
);
|
|
2496
|
+
}
|
|
2497
|
+
let preferredBase;
|
|
2498
|
+
try {
|
|
2499
|
+
const branchResult = await gitExec(
|
|
2500
|
+
["rev-parse", "--abbrev-ref", "HEAD"],
|
|
2501
|
+
execOptions
|
|
2502
|
+
);
|
|
2503
|
+
const branch = branchResult.stdout.trim();
|
|
2504
|
+
if (branch && branch !== "HEAD") {
|
|
2505
|
+
preferredBase = branch;
|
|
2506
|
+
}
|
|
2507
|
+
} catch {
|
|
2508
|
+
}
|
|
2243
2509
|
const nodes = await buildTraceNodes(
|
|
2244
2510
|
blameAuth.analyzed,
|
|
2245
2511
|
featureFlags,
|
|
2246
2512
|
platform.adapter,
|
|
2247
|
-
options,
|
|
2513
|
+
{ ...options, file, cwd },
|
|
2248
2514
|
execOptions,
|
|
2249
|
-
repoId
|
|
2515
|
+
repoId,
|
|
2516
|
+
cloneStatus.partialClone || void 0,
|
|
2517
|
+
preferredBase
|
|
2250
2518
|
);
|
|
2251
2519
|
return { nodes, operatingLevel, featureFlags, warnings };
|
|
2252
2520
|
}
|