@lumy-pack/line-lore 0.0.7 → 0.0.9
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 +228 -68
- package/dist/core/ancestry/ancestry.d.ts +18 -0
- package/dist/core/ancestry/index.d.ts +1 -1
- package/dist/core/blame/blame.d.ts +11 -3
- package/dist/core/blame/index.d.ts +1 -1
- package/dist/core/index.d.ts +1 -1
- package/dist/index.cjs +214 -65
- package/dist/index.d.ts +1 -1
- package/dist/index.mjs +214 -65
- package/dist/types/blame.d.ts +12 -0
- package/dist/types/git.d.ts +5 -0
- package/dist/types/index.d.ts +3 -3
- package/dist/types/stage.d.ts +4 -0
- package/dist/types/trace.d.ts +3 -0
- package/dist/version.d.ts +1 -1
- package/package.json +1 -1
|
@@ -5,6 +5,11 @@ export interface AncestryResult {
|
|
|
5
5
|
subject: string;
|
|
6
6
|
}
|
|
7
7
|
export declare const DEFAULT_ANCESTRY_TIMEOUT = 30000;
|
|
8
|
+
/**
|
|
9
|
+
* @deprecated Use {@link findMergeCommits} (plural) instead.
|
|
10
|
+
* Returns only the first verified merge commit. The plural version returns
|
|
11
|
+
* multiple candidates from both first-parent and full ancestry paths.
|
|
12
|
+
*/
|
|
8
13
|
export declare function findMergeCommit(commitSha: string, options?: GitExecOptions & {
|
|
9
14
|
ref?: string;
|
|
10
15
|
}): Promise<AncestryResult | null>;
|
|
@@ -19,6 +24,19 @@ export declare function findMergeCommit(commitSha: string, options?: GitExecOpti
|
|
|
19
24
|
* Returns false on git command failure (fail-skip policy).
|
|
20
25
|
*/
|
|
21
26
|
export declare function verifyMergeIntroducesCommit(targetSha: string, mergeResult: AncestryResult, options?: GitExecOptions): Promise<boolean>;
|
|
27
|
+
/**
|
|
28
|
+
* Multi-candidate merge commit search.
|
|
29
|
+
* Returns up to MAX_CANDIDATES verified merge commits from both first-parent
|
|
30
|
+
* and full ancestry paths, deduplicated by mergeCommitSha and ordered with
|
|
31
|
+
* first-parent results first.
|
|
32
|
+
*
|
|
33
|
+
* Unlike `findMergeCommit` (singular) which returns only the first verified candidate,
|
|
34
|
+
* this function enables callers to iterate through multiple candidates when the
|
|
35
|
+
* first one doesn't yield a PR (e.g., bulk merge with non-standard message).
|
|
36
|
+
*/
|
|
37
|
+
export declare function findMergeCommits(commitSha: string, options?: GitExecOptions & {
|
|
38
|
+
ref?: string;
|
|
39
|
+
}): Promise<AncestryResult[]>;
|
|
22
40
|
/** Retrieve the subject line of a single commit. Returns null on git failure. */
|
|
23
41
|
export declare function getCommitSubject(sha: string, options?: GitExecOptions): Promise<string | null>;
|
|
24
42
|
export declare function extractPRFromMergeMessage(subject: string, platform?: string): number | null;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { extractPRFromMergeMessage, findMergeCommit, getCommitSubject, verifyMergeIntroducesCommit, } from './ancestry.js';
|
|
1
|
+
export { extractPRFromMergeMessage, findMergeCommit, findMergeCommits, getCommitSubject, verifyMergeIntroducesCommit, } from './ancestry.js';
|
|
2
2
|
export type { AncestryResult } from './ancestry.js';
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
-
import type { BlameResult, BlameStageResult, GitExecOptions, LineRange } from '../../types/index.js';
|
|
2
|
-
export declare function executeBlame(file: string, lineRange: LineRange, options?:
|
|
3
|
-
|
|
1
|
+
import type { BlameExecOptions, BlameResult, BlameStageResult, DualBlameResult, GitExecOptions, LineRange } from '../../types/index.js';
|
|
2
|
+
export declare function executeBlame(file: string, lineRange: LineRange, options?: BlameExecOptions): Promise<BlameResult[]>;
|
|
3
|
+
/**
|
|
4
|
+
* Run dual blame for origin mode: origin blame (-C -C -M) and change blame (-w)
|
|
5
|
+
* in parallel. For change mode, only runs the single blame.
|
|
6
|
+
*
|
|
7
|
+
* Assumes both blames on the same -L range produce results in the same
|
|
8
|
+
* finalLine order (guaranteed by git blame line-ordered output).
|
|
9
|
+
*/
|
|
10
|
+
export declare function executeDualBlame(file: string, lineRange: LineRange, options?: BlameExecOptions): Promise<DualBlameResult>;
|
|
11
|
+
export declare function analyzeBlameResults(results: BlameResult[], filePath: string, options?: GitExecOptions, changeResults?: BlameResult[]): Promise<BlameStageResult[]>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { analyzeBlameResults, executeBlame } from './blame.js';
|
|
1
|
+
export { analyzeBlameResults, executeBlame, executeDualBlame } from './blame.js';
|
|
2
2
|
export { getCosmeticDiff, isCosmeticDiff } from './detection/index.js';
|
|
3
3
|
export type { CosmeticCheckResult } from './detection/index.js';
|
|
4
4
|
export { parsePorcelainOutput } from './parsing/index.js';
|
package/dist/core/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { analyzeBlameResults, executeBlame, isCosmeticDiff, parsePorcelainOutput, } from './blame/index.js';
|
|
2
2
|
export { compareSymbolMaps, computeContentHash, computeExactHash, computeStructuralHash, extractSymbols, findContainingSymbol, findMatchAcrossFiles, traceByAst, } from './ast-diff/index.js';
|
|
3
|
-
export { extractPRFromMergeMessage, findMergeCommit, } from './ancestry/index.js';
|
|
3
|
+
export { extractPRFromMergeMessage, findMergeCommit, findMergeCommits, } from './ancestry/index.js';
|
|
4
4
|
export type { AncestryResult } from './ancestry/index.js';
|
|
5
5
|
export { computePatchId, findPatchIdMatch, resetPatchIdCache, } from './patch-id/index.js';
|
|
6
6
|
export type { PatchIdResult } from './patch-id/index.js';
|
package/dist/index.cjs
CHANGED
|
@@ -381,25 +381,6 @@ var init_executor = __esm({
|
|
|
381
381
|
});
|
|
382
382
|
|
|
383
383
|
// src/core/ancestry/ancestry.ts
|
|
384
|
-
async function findMergeCommit(commitSha, options) {
|
|
385
|
-
const ref = options?.ref ?? "HEAD";
|
|
386
|
-
const budget = options?.timeout ?? DEFAULT_ANCESTRY_TIMEOUT;
|
|
387
|
-
const startTime = Date.now();
|
|
388
|
-
const firstParentResult = await findMergeCommitWithArgs(
|
|
389
|
-
commitSha,
|
|
390
|
-
ref,
|
|
391
|
-
["--first-parent"],
|
|
392
|
-
{ ...options, timeout: budget }
|
|
393
|
-
);
|
|
394
|
-
if (firstParentResult) return firstParentResult;
|
|
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
384
|
async function verifyMergeIntroducesCommit(targetSha, mergeResult, options) {
|
|
404
385
|
if (mergeResult.parentShas.length < 2) return true;
|
|
405
386
|
const firstParent = mergeResult.parentShas[0];
|
|
@@ -429,7 +410,7 @@ async function isAncestor(commitA, commitB, options) {
|
|
|
429
410
|
return null;
|
|
430
411
|
}
|
|
431
412
|
}
|
|
432
|
-
async function
|
|
413
|
+
async function findMergeCommitsWithArgs(commitSha, ref, extraArgs, options) {
|
|
433
414
|
try {
|
|
434
415
|
const result = await gitExec(
|
|
435
416
|
[
|
|
@@ -445,28 +426,29 @@ async function findMergeCommitWithArgs(commitSha, ref, extraArgs, options) {
|
|
|
445
426
|
{ cwd: options?.cwd, timeout: options?.timeout }
|
|
446
427
|
);
|
|
447
428
|
const lines = (0, import_common_utils9.filter)(result.stdout.trim().split("\n"), import_common_utils9.isTruthy);
|
|
448
|
-
if (lines.length === 0) return
|
|
429
|
+
if (lines.length === 0) return [];
|
|
430
|
+
const verifiedCandidates = [];
|
|
449
431
|
const candidateCount = Math.min(lines.length, MAX_CANDIDATES);
|
|
450
|
-
let
|
|
432
|
+
let attemptedCount = 0;
|
|
451
433
|
for (let i = 0; i < candidateCount; i++) {
|
|
452
434
|
const candidate = parseMergeLogLine(lines[i]);
|
|
453
435
|
if (!candidate) continue;
|
|
454
|
-
|
|
436
|
+
attemptedCount++;
|
|
455
437
|
const verified = await verifyMergeIntroducesCommit(
|
|
456
438
|
commitSha,
|
|
457
439
|
candidate,
|
|
458
440
|
options
|
|
459
441
|
);
|
|
460
|
-
if (verified)
|
|
442
|
+
if (verified) verifiedCandidates.push(candidate);
|
|
461
443
|
}
|
|
462
|
-
if (
|
|
444
|
+
if (attemptedCount > 0 && verifiedCandidates.length === 0 && options?.warnings) {
|
|
463
445
|
options.warnings.push(
|
|
464
|
-
`ancestry: all ${
|
|
446
|
+
`ancestry: all ${attemptedCount} merge candidate(s) failed verification for ${commitSha.slice(0, 8)}`
|
|
465
447
|
);
|
|
466
448
|
}
|
|
467
|
-
return
|
|
449
|
+
return verifiedCandidates;
|
|
468
450
|
} catch {
|
|
469
|
-
return
|
|
451
|
+
return [];
|
|
470
452
|
}
|
|
471
453
|
}
|
|
472
454
|
function parseMergeLogLine(line) {
|
|
@@ -486,6 +468,38 @@ function parseMergeLogLine(line) {
|
|
|
486
468
|
const subject = parts.slice(subjectStart).join(" ");
|
|
487
469
|
return { mergeCommitSha, parentShas, subject };
|
|
488
470
|
}
|
|
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
|
+
}
|
|
489
503
|
async function getCommitSubject(sha, options) {
|
|
490
504
|
try {
|
|
491
505
|
const result = await gitExec(["log", "-1", "--format=%s", sha], {
|
|
@@ -689,16 +703,17 @@ async function lookupPR(commitSha, adapter, options, _recursionDepth = 0) {
|
|
|
689
703
|
}
|
|
690
704
|
}
|
|
691
705
|
let mergeBasedPR = null;
|
|
692
|
-
const
|
|
693
|
-
|
|
706
|
+
const mergeCandidates = await findMergeCommits(commitSha, options);
|
|
707
|
+
const hasAncestryMerges = mergeCandidates.length > 0;
|
|
708
|
+
for (const candidate of mergeCandidates) {
|
|
694
709
|
const prNumber = extractPRFromMergeMessage(
|
|
695
|
-
|
|
710
|
+
candidate.subject,
|
|
696
711
|
options?.platform
|
|
697
712
|
);
|
|
698
713
|
if (prNumber) {
|
|
699
714
|
if (adapter) {
|
|
700
715
|
const prInfo = await adapter.getPRForCommit(
|
|
701
|
-
|
|
716
|
+
candidate.mergeCommitSha,
|
|
702
717
|
prSelectOptions
|
|
703
718
|
);
|
|
704
719
|
if (prInfo?.mergedAt) {
|
|
@@ -708,23 +723,32 @@ async function lookupPR(commitSha, adapter, options, _recursionDepth = 0) {
|
|
|
708
723
|
if (!mergeBasedPR) {
|
|
709
724
|
mergeBasedPR = {
|
|
710
725
|
number: prNumber,
|
|
711
|
-
title:
|
|
726
|
+
title: candidate.subject,
|
|
712
727
|
author: "",
|
|
713
728
|
url: "",
|
|
714
|
-
mergeCommit:
|
|
729
|
+
mergeCommit: candidate.mergeCommitSha,
|
|
715
730
|
baseBranch: "",
|
|
716
731
|
resolvedVia: "ancestry"
|
|
717
732
|
};
|
|
718
733
|
}
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
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;
|
|
722
744
|
}
|
|
723
745
|
}
|
|
724
746
|
}
|
|
725
747
|
if (mergeBasedPR) {
|
|
726
|
-
|
|
727
|
-
|
|
748
|
+
if (!options?.deep || mergeBasedPR.mergedAt) {
|
|
749
|
+
await cache.set(commitSha, toCachedPR(mergeBasedPR));
|
|
750
|
+
return mergeBasedPR;
|
|
751
|
+
}
|
|
728
752
|
}
|
|
729
753
|
const commitSubject = await getCommitSubject(commitSha, options);
|
|
730
754
|
if (commitSubject) {
|
|
@@ -746,7 +770,7 @@ async function lookupPR(commitSha, adapter, options, _recursionDepth = 0) {
|
|
|
746
770
|
return subjectPR;
|
|
747
771
|
}
|
|
748
772
|
}
|
|
749
|
-
if (!options?.skipPatchIdScan && _recursionDepth < MAX_RECURSION_DEPTH) {
|
|
773
|
+
if (!options?.skipPatchIdScan && _recursionDepth < MAX_RECURSION_DEPTH && (!hasAncestryMerges || options?.deep)) {
|
|
750
774
|
const patchIdMatch = await findPatchIdMatch(commitSha, {
|
|
751
775
|
...options,
|
|
752
776
|
scanDepth: options?.deep ? DEEP_SCAN_DEPTH : void 0
|
|
@@ -764,6 +788,10 @@ async function lookupPR(commitSha, adapter, options, _recursionDepth = 0) {
|
|
|
764
788
|
}
|
|
765
789
|
}
|
|
766
790
|
}
|
|
791
|
+
if (mergeBasedPR) {
|
|
792
|
+
await cache.set(commitSha, toCachedPR(mergeBasedPR));
|
|
793
|
+
return mergeBasedPR;
|
|
794
|
+
}
|
|
767
795
|
return null;
|
|
768
796
|
}
|
|
769
797
|
function resetPRCache() {
|
|
@@ -2083,6 +2111,7 @@ function parsePorcelainOutput(output) {
|
|
|
2083
2111
|
}
|
|
2084
2112
|
let commitHash = headerMatch[1];
|
|
2085
2113
|
const originalLine = parseInt(headerMatch[2], 10);
|
|
2114
|
+
const finalLine = parseInt(headerMatch[3], 10) || 0;
|
|
2086
2115
|
const isBoundary = commitHash.startsWith("^");
|
|
2087
2116
|
if (isBoundary) {
|
|
2088
2117
|
commitHash = commitHash.slice(1).padStart(40, "0");
|
|
@@ -2126,6 +2155,7 @@ function parsePorcelainOutput(output) {
|
|
|
2126
2155
|
authorEmail: cleanEmail,
|
|
2127
2156
|
date,
|
|
2128
2157
|
lineContent,
|
|
2158
|
+
finalLine,
|
|
2129
2159
|
originalFile,
|
|
2130
2160
|
originalLine: originalFile ? originalLine : void 0
|
|
2131
2161
|
});
|
|
@@ -2136,14 +2166,112 @@ function parsePorcelainOutput(output) {
|
|
|
2136
2166
|
// src/core/blame/blame.ts
|
|
2137
2167
|
async function executeBlame(file, lineRange, options) {
|
|
2138
2168
|
const lineSpec = `${lineRange.start},${lineRange.end}`;
|
|
2139
|
-
const
|
|
2140
|
-
|
|
2141
|
-
options
|
|
2142
|
-
);
|
|
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);
|
|
2143
2171
|
return parsePorcelainOutput(result.stdout);
|
|
2144
2172
|
}
|
|
2145
|
-
async function
|
|
2146
|
-
|
|
2173
|
+
async function executeDualBlame(file, lineRange, options) {
|
|
2174
|
+
if (options?.mode === "change") {
|
|
2175
|
+
const results = await executeBlame(file, lineRange, options);
|
|
2176
|
+
return { blame: results, changeBlame: [] };
|
|
2177
|
+
}
|
|
2178
|
+
const [originResult, changeResult] = await Promise.allSettled([
|
|
2179
|
+
executeBlame(file, lineRange, options),
|
|
2180
|
+
executeBlame(file, lineRange, { ...options, mode: "change" })
|
|
2181
|
+
]);
|
|
2182
|
+
const blame = originResult.status === "fulfilled" ? originResult.value : [];
|
|
2183
|
+
const changeBlame = changeResult.status === "fulfilled" ? changeResult.value : [];
|
|
2184
|
+
if (originResult.status === "rejected") {
|
|
2185
|
+
throw originResult.reason;
|
|
2186
|
+
}
|
|
2187
|
+
return { blame, changeBlame };
|
|
2188
|
+
}
|
|
2189
|
+
async function verifyRename(originalFile, currentFile, options) {
|
|
2190
|
+
try {
|
|
2191
|
+
const result = await gitExec(
|
|
2192
|
+
[
|
|
2193
|
+
"log",
|
|
2194
|
+
"--diff-filter=R",
|
|
2195
|
+
"--find-renames",
|
|
2196
|
+
"--format=%H",
|
|
2197
|
+
"--",
|
|
2198
|
+
originalFile,
|
|
2199
|
+
currentFile
|
|
2200
|
+
],
|
|
2201
|
+
options
|
|
2202
|
+
);
|
|
2203
|
+
return result.stdout.trim().length > 0;
|
|
2204
|
+
} catch {
|
|
2205
|
+
return false;
|
|
2206
|
+
}
|
|
2207
|
+
}
|
|
2208
|
+
function crossValidateBlame(originResults, changeResults) {
|
|
2209
|
+
const changeMap = /* @__PURE__ */ new Map();
|
|
2210
|
+
for (const r of changeResults) {
|
|
2211
|
+
changeMap.set(r.finalLine, r);
|
|
2212
|
+
}
|
|
2213
|
+
const validated = [];
|
|
2214
|
+
const crossValidatedFlags = [];
|
|
2215
|
+
const changeFallbackFlags = [];
|
|
2216
|
+
const renameChecks = [];
|
|
2217
|
+
for (let i = 0; i < originResults.length; i++) {
|
|
2218
|
+
const origin = originResults[i];
|
|
2219
|
+
const change = changeMap.get(origin.finalLine);
|
|
2220
|
+
if (!change) {
|
|
2221
|
+
validated.push(origin);
|
|
2222
|
+
crossValidatedFlags.push(false);
|
|
2223
|
+
changeFallbackFlags.push(false);
|
|
2224
|
+
continue;
|
|
2225
|
+
}
|
|
2226
|
+
if (origin.commitHash === change.commitHash) {
|
|
2227
|
+
validated.push(origin);
|
|
2228
|
+
crossValidatedFlags.push(true);
|
|
2229
|
+
changeFallbackFlags.push(false);
|
|
2230
|
+
continue;
|
|
2231
|
+
}
|
|
2232
|
+
if (origin.originalFile) {
|
|
2233
|
+
renameChecks.push({
|
|
2234
|
+
originalFile: origin.originalFile,
|
|
2235
|
+
lineIndex: i
|
|
2236
|
+
});
|
|
2237
|
+
}
|
|
2238
|
+
validated.push(change);
|
|
2239
|
+
crossValidatedFlags.push(true);
|
|
2240
|
+
changeFallbackFlags.push(true);
|
|
2241
|
+
}
|
|
2242
|
+
return { validated, renameChecks, crossValidatedFlags, changeFallbackFlags };
|
|
2243
|
+
}
|
|
2244
|
+
async function analyzeBlameResults(results, filePath, options, changeResults) {
|
|
2245
|
+
let effectiveResults = results;
|
|
2246
|
+
let crossValidatedFlags;
|
|
2247
|
+
let changeFallbackFlagsResult;
|
|
2248
|
+
if (changeResults && changeResults.length > 0) {
|
|
2249
|
+
const { validated, renameChecks, crossValidatedFlags: cvFlags, changeFallbackFlags } = crossValidateBlame(results, changeResults);
|
|
2250
|
+
if (renameChecks.length > 0) {
|
|
2251
|
+
const pendingChecks = /* @__PURE__ */ new Map();
|
|
2252
|
+
for (const check of renameChecks) {
|
|
2253
|
+
const cacheKey = `${check.originalFile}:${filePath}`;
|
|
2254
|
+
if (!pendingChecks.has(cacheKey)) {
|
|
2255
|
+
pendingChecks.set(cacheKey, verifyRename(check.originalFile, filePath, options));
|
|
2256
|
+
}
|
|
2257
|
+
}
|
|
2258
|
+
const renameResults = /* @__PURE__ */ new Map();
|
|
2259
|
+
for (const [key, promise] of pendingChecks) {
|
|
2260
|
+
renameResults.set(key, await promise);
|
|
2261
|
+
}
|
|
2262
|
+
for (const check of renameChecks) {
|
|
2263
|
+
const cacheKey = `${check.originalFile}:${filePath}`;
|
|
2264
|
+
if (renameResults.get(cacheKey)) {
|
|
2265
|
+
validated[check.lineIndex] = results[check.lineIndex];
|
|
2266
|
+
changeFallbackFlags[check.lineIndex] = false;
|
|
2267
|
+
}
|
|
2268
|
+
}
|
|
2269
|
+
}
|
|
2270
|
+
effectiveResults = validated;
|
|
2271
|
+
crossValidatedFlags = cvFlags;
|
|
2272
|
+
changeFallbackFlagsResult = changeFallbackFlags;
|
|
2273
|
+
}
|
|
2274
|
+
const uniqueShas = [...new Set((0, import_common_utils7.map)(effectiveResults, (r) => r.commitHash))];
|
|
2147
2275
|
const cosmeticMap = /* @__PURE__ */ new Map();
|
|
2148
2276
|
const zeroSha = "0".repeat(40);
|
|
2149
2277
|
const tasks = [];
|
|
@@ -2152,7 +2280,7 @@ async function analyzeBlameResults(results, filePath, options) {
|
|
|
2152
2280
|
tasks.push(
|
|
2153
2281
|
(async () => {
|
|
2154
2282
|
try {
|
|
2155
|
-
const blameResult =
|
|
2283
|
+
const blameResult = effectiveResults.find((r) => r.commitHash === sha);
|
|
2156
2284
|
if (!blameResult) return;
|
|
2157
2285
|
const file = blameResult.originalFile ?? filePath;
|
|
2158
2286
|
const diff = await getCosmeticDiff(sha, file, options);
|
|
@@ -2164,12 +2292,14 @@ async function analyzeBlameResults(results, filePath, options) {
|
|
|
2164
2292
|
);
|
|
2165
2293
|
});
|
|
2166
2294
|
await Promise.all(tasks);
|
|
2167
|
-
return (0, import_common_utils7.map)(
|
|
2295
|
+
return (0, import_common_utils7.map)(effectiveResults, (blame, i) => {
|
|
2168
2296
|
const cosmetic = cosmeticMap.get(blame.commitHash);
|
|
2169
2297
|
return {
|
|
2170
2298
|
blame,
|
|
2171
2299
|
isCosmetic: cosmetic?.isCosmetic ?? false,
|
|
2172
|
-
cosmeticReason: cosmetic?.reason
|
|
2300
|
+
cosmeticReason: cosmetic?.reason,
|
|
2301
|
+
crossValidated: crossValidatedFlags?.[i],
|
|
2302
|
+
usedChangeFallback: changeFallbackFlagsResult?.[i]
|
|
2173
2303
|
};
|
|
2174
2304
|
});
|
|
2175
2305
|
}
|
|
@@ -2350,8 +2480,11 @@ async function runBlameAndAuth(adapter, options, execOptions) {
|
|
|
2350
2480
|
const lineRange = parseLineRange(
|
|
2351
2481
|
options.endLine ? `${options.line},${options.endLine}` : `${options.line}`
|
|
2352
2482
|
);
|
|
2353
|
-
const blameChain =
|
|
2354
|
-
|
|
2483
|
+
const blameChain = executeDualBlame(options.file, lineRange, {
|
|
2484
|
+
...execOptions,
|
|
2485
|
+
mode: options.mode
|
|
2486
|
+
}).then(
|
|
2487
|
+
({ blame, changeBlame }) => analyzeBlameResults(blame, options.file, execOptions, changeBlame.length > 0 ? changeBlame : void 0)
|
|
2355
2488
|
);
|
|
2356
2489
|
const [authResult, blameResult] = await Promise.allSettled([
|
|
2357
2490
|
adapter ? adapter.checkAuth() : Promise.resolve({ authenticated: false }),
|
|
@@ -2373,12 +2506,24 @@ async function runBlameAndAuth(adapter, options, execOptions) {
|
|
|
2373
2506
|
}
|
|
2374
2507
|
return { analyzed: blameResult.value, operatingLevel, warnings };
|
|
2375
2508
|
}
|
|
2376
|
-
|
|
2509
|
+
function resolveTraceMode(mode) {
|
|
2510
|
+
return mode ?? "origin";
|
|
2511
|
+
}
|
|
2512
|
+
function deduplicatedLookupPR(sha, adapter, options, inflight) {
|
|
2513
|
+
const existing = inflight.get(sha);
|
|
2514
|
+
if (existing) return existing;
|
|
2515
|
+
const promise = lookupPR(sha, adapter, options);
|
|
2516
|
+
inflight.set(sha, promise);
|
|
2517
|
+
promise.finally(() => inflight.delete(sha));
|
|
2518
|
+
return promise;
|
|
2519
|
+
}
|
|
2520
|
+
async function processEntry(entry, featureFlags, adapter, options, execOptions, repoId, inflightPR, skipPatchIdScan, preferredBase) {
|
|
2377
2521
|
const nodes = [];
|
|
2522
|
+
const traceMode = resolveTraceMode(options.mode);
|
|
2378
2523
|
const commitNode = {
|
|
2379
2524
|
type: entry.isCosmetic ? "cosmetic_commit" : "original_commit",
|
|
2380
2525
|
sha: entry.blame.commitHash,
|
|
2381
|
-
trackingMethod: "blame-CMw",
|
|
2526
|
+
trackingMethod: traceMode === "change" || entry.usedChangeFallback ? "blame" : "blame-CMw",
|
|
2382
2527
|
confidence: "exact",
|
|
2383
2528
|
note: entry.cosmeticReason ? `Cosmetic change: ${entry.cosmeticReason}` : void 0
|
|
2384
2529
|
};
|
|
@@ -2400,17 +2545,18 @@ async function processEntry(entry, featureFlags, adapter, options, execOptions,
|
|
|
2400
2545
|
}
|
|
2401
2546
|
}
|
|
2402
2547
|
const targetSha = nodes[nodes.length - 1].sha;
|
|
2548
|
+
const prLookupOptions = {
|
|
2549
|
+
...execOptions,
|
|
2550
|
+
noCache: options.noCache,
|
|
2551
|
+
cacheOnly: options.cacheOnly,
|
|
2552
|
+
deep: featureFlags.deepTrace,
|
|
2553
|
+
repoId,
|
|
2554
|
+
skipPatchIdScan,
|
|
2555
|
+
preferredBase,
|
|
2556
|
+
platform: adapter?.platform
|
|
2557
|
+
};
|
|
2403
2558
|
if (targetSha) {
|
|
2404
|
-
const prInfo = await
|
|
2405
|
-
...execOptions,
|
|
2406
|
-
noCache: options.noCache,
|
|
2407
|
-
cacheOnly: options.cacheOnly,
|
|
2408
|
-
deep: featureFlags.deepTrace,
|
|
2409
|
-
repoId,
|
|
2410
|
-
skipPatchIdScan,
|
|
2411
|
-
preferredBase,
|
|
2412
|
-
platform: adapter?.platform
|
|
2413
|
-
});
|
|
2559
|
+
const prInfo = await deduplicatedLookupPR(targetSha, adapter, prLookupOptions, inflightPR);
|
|
2414
2560
|
if (prInfo) {
|
|
2415
2561
|
nodes.push({
|
|
2416
2562
|
type: "pull_request",
|
|
@@ -2427,6 +2573,7 @@ async function processEntry(entry, featureFlags, adapter, options, execOptions,
|
|
|
2427
2573
|
return nodes;
|
|
2428
2574
|
}
|
|
2429
2575
|
async function buildTraceNodes(analyzed, featureFlags, adapter, options, execOptions, repoId, skipPatchIdScan, preferredBase) {
|
|
2576
|
+
const inflightPR = /* @__PURE__ */ new Map();
|
|
2430
2577
|
const results = await Promise.allSettled(
|
|
2431
2578
|
(0, import_common_utils11.map)(
|
|
2432
2579
|
analyzed,
|
|
@@ -2437,6 +2584,7 @@ async function buildTraceNodes(analyzed, featureFlags, adapter, options, execOpt
|
|
|
2437
2584
|
options,
|
|
2438
2585
|
execOptions,
|
|
2439
2586
|
repoId,
|
|
2587
|
+
inflightPR,
|
|
2440
2588
|
skipPatchIdScan,
|
|
2441
2589
|
preferredBase
|
|
2442
2590
|
)
|
|
@@ -2446,6 +2594,7 @@ async function buildTraceNodes(analyzed, featureFlags, adapter, options, execOpt
|
|
|
2446
2594
|
}
|
|
2447
2595
|
var legacyCacheCleaned = false;
|
|
2448
2596
|
async function trace(options) {
|
|
2597
|
+
const mode = resolveTraceMode(options.mode);
|
|
2449
2598
|
const { file, cwd } = await resolveFileContext(options.file, options.cwd);
|
|
2450
2599
|
const warnings = [];
|
|
2451
2600
|
const execOptions = { cwd, warnings };
|
|
@@ -2467,7 +2616,7 @@ async function trace(options) {
|
|
|
2467
2616
|
}
|
|
2468
2617
|
const blameAuth = await runBlameAndAuth(
|
|
2469
2618
|
platform.adapter,
|
|
2470
|
-
{ ...options, file, cwd },
|
|
2619
|
+
{ ...options, mode, file, cwd },
|
|
2471
2620
|
execOptions
|
|
2472
2621
|
);
|
|
2473
2622
|
const operatingLevel = blameAuth.operatingLevel || platform.operatingLevel;
|
|
@@ -2510,7 +2659,7 @@ async function trace(options) {
|
|
|
2510
2659
|
blameAuth.analyzed,
|
|
2511
2660
|
featureFlags,
|
|
2512
2661
|
platform.adapter,
|
|
2513
|
-
{ ...options, file, cwd },
|
|
2662
|
+
{ ...options, mode, file, cwd },
|
|
2514
2663
|
execOptions,
|
|
2515
2664
|
repoId,
|
|
2516
2665
|
cloneStatus.partialClone || void 0,
|
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';
|