Repository navigation
Expand file tree
/
Copy pathpullRequestDiffPages.ts
More file actions
113 lines (107 loc) · 4.24 KB
/
Copy pathpullRequestDiffPages.ts
File metadata and controls
113 lines (107 loc) · 4.24 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
import type { ChangeRequestTerminology } from "@t3tools/client-runtime/source-control-clients";
import type { PullRequestDiffResult } from "@t3tools/contracts";
import { buildReviewParsedDiff, type ReviewParsedDiff } from "../review/reviewModel";
export interface PullRequestDiffPage {
readonly cursor: string | null;
readonly result: PullRequestDiffResult;
}
interface CachedPullRequestDiffPages {
readonly pages: readonly PullRequestDiffPage[];
readonly cache: WeakMap<PullRequestDiffResult, ReviewParsedDiff>;
readonly parsed: ReviewParsedDiff;
}
// Keep only the latest combination per scoped first page; query results own its lifetime.
const parsedSnapshots = new WeakMap<
PullRequestDiffResult,
Map<string, CachedPullRequestDiffPages>
>();
export function getCachedPullRequestDiffPages(
pages: readonly PullRequestDiffPage[],
scope: string,
terminology: ChangeRequestTerminology,
): ReviewParsedDiff {
const first = pages[0];
if (!first) return { kind: "empty" };
const key = JSON.stringify([scope, terminology.singular, first.cursor]);
let snapshots = parsedSnapshots.get(first.result);
if (!snapshots) {
snapshots = new Map();
parsedSnapshots.set(first.result, snapshots);
}
const previous = snapshots.get(key);
if (
previous?.pages.length === pages.length &&
pages.every((page, index) => {
const cached = previous.pages[index];
return cached?.cursor === page.cursor && cached.result === page.result;
})
)
return previous.parsed;
const cache = previous?.cache ?? new WeakMap<PullRequestDiffResult, ReviewParsedDiff>();
const parsed = combinePullRequestDiffPages(pages, cache, scope, terminology);
snapshots.set(key, { pages, cache, parsed });
return parsed;
}
/** A changed page invalidates later cursors, which belong to the previous patch. */
export function retainPullRequestDiffPage(
pages: readonly PullRequestDiffPage[],
cursor: string | null,
result: PullRequestDiffResult,
): readonly PullRequestDiffPage[] {
const index = pages.findIndex((page) => page.cursor === cursor);
if (index === -1) return [...pages, { cursor, result }];
const previous = pages[index]?.result;
if (
previous === result ||
(previous?.patch === result.patch &&
previous.truncated === result.truncated &&
previous.nextCursor === result.nextCursor &&
JSON.stringify(previous.omittedFileStats ?? []) ===
JSON.stringify(result.omittedFileStats ?? []))
)
return pages;
return [...pages.slice(0, index), { cursor, result }];
}
export function combinePullRequestDiffPages(
pages: readonly PullRequestDiffPage[],
cache: WeakMap<PullRequestDiffResult, ReviewParsedDiff>,
scope: string,
terminology: ChangeRequestTerminology,
): ReviewParsedDiff {
const parsed = pages.map((page) => {
const cached = cache.get(page.result);
if (cached) return cached;
let value = buildReviewParsedDiff(page.result.patch, `${scope}:${page.cursor ?? "first"}`);
if (value.kind === "files" && page.result.omittedFileStats?.length) {
const omittedFileStats = new Map(
page.result.omittedFileStats.map((stat) => [stat.path, stat]),
);
const files = value.files.map((file) => {
const stat = omittedFileStats.get(file.path);
return stat ? { ...file, additions: stat.additions, deletions: stat.deletions } : file;
});
value = {
...value,
files,
additions: files.reduce((total, file) => total + file.additions, 0),
deletions: files.reduce((total, file) => total + file.deletions, 0),
};
}
cache.set(page.result, value);
return value;
});
const files = parsed.flatMap((page) => (page.kind === "files" ? page.files : []));
if (files.length === 0) return parsed[0] ?? { kind: "empty" };
return {
kind: "files",
files,
fileCount: files.length,
additions: files.reduce((total, file) => total + file.additions, 0),
deletions: files.reduce((total, file) => total + file.deletions, 0),
notice:
pages.some((page) => page.result.truncated) ||
parsed.some((page) => page.kind === "raw" || (page.kind === "files" && page.notice !== null))
? `Some file contents are unavailable. Open the ${terminology.singular} on its provider to see the full changes.`
: null,
};
}