Repository navigation
Expand file tree
/
Copy pathpullRequestStack.ts
More file actions
85 lines (81 loc) · 3.24 KB
/
Copy pathpullRequestStack.ts
File metadata and controls
85 lines (81 loc) · 3.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
import type { ChangeRequestTerminology } from "@t3tools/client-runtime/source-control-clients";
import type { PullRequestRef, PullRequestStack, ThreadPullRequestLink } from "@t3tools/contracts";
export function pullRequestStackMerge(
stack: PullRequestStack,
number: number,
terminology: ChangeRequestTerminology,
) {
const position = stack.layers.findIndex((layer) => layer.number === number);
const layers = stack.layers.slice(0, position + 1).filter((layer) => layer.state !== "merged");
const heads = layers.flatMap((layer) =>
layer.headSha ? [{ number: layer.number, headSha: layer.headSha }] : [],
);
const reason =
position < 0
? `This ${terminology.singular} is no longer in the stack. Refresh it.`
: stack.layers[position]?.state !== "open" || layers.length === 0
? `There are no open layers to merge through this ${terminology.singular}.`
: layers.some((layer) => layer.state !== "open")
? "Reopen the closed layers before merging this stack."
: layers.some((layer) => layer.isDraft)
? "Mark every layer being merged ready for review."
: heads.length !== layers.length
? "Refresh the stack to load its latest revisions before merging."
: null;
return { position: position + 1, layers, heads, reason };
}
/** Saved membership supplies navigation while offline, never revisions for a merge. */
export function savedPullRequestStack(
links: readonly ThreadPullRequestLink[],
reference: PullRequestRef,
): PullRequestStack | null {
const matches = links.filter(
(link) =>
link.host.toLowerCase() === reference.host?.toLowerCase() &&
link.repository.toLowerCase() === reference.repository.toLowerCase(),
);
const candidates = matches.filter(
(link) =>
link.number === reference.number ||
link.stack?.layers.some((layer) => layer.number === reference.number),
);
const newest = [...candidates].sort(
(a, b) =>
Date.parse(b.snapshot?.syncedAt ?? b.linkedAt) -
Date.parse(a.snapshot?.syncedAt ?? a.linkedAt),
)[0]?.stack;
if (!newest?.layers.some((layer) => layer.number === reference.number)) return null;
return {
...newest,
layers: newest.layers.map((layer) => {
const snapshot = [...matches]
.filter((link) => link.number === layer.number)
.sort(
(a, b) =>
Date.parse(b.snapshot?.syncedAt ?? b.linkedAt) -
Date.parse(a.snapshot?.syncedAt ?? a.linkedAt),
)[0]?.snapshot;
return {
number: layer.number,
headBranch: layer.headBranch,
state: layer.state,
...(snapshot ? { title: snapshot.title, isDraft: snapshot.isDraft } : {}),
};
}),
};
}
/** The list summary describes the layer it was opened from, so another layer's route drops it. */
export function pullRequestLayerParams<Params extends { readonly initialSummary?: unknown }>(
params: Params,
number: number,
) {
const { initialSummary: _, ...layer } = params;
return { ...layer, number };
}
export function singlePullRequestMergeAllowed(input: {
readonly supportsStackActions: boolean;
readonly hasStack: boolean;
readonly isFresh: boolean;
}) {
return !input.supportsStackActions || (!input.hasStack && input.isFresh);
}