~james/core

core/web/src/lib/api/pullRoute.ts -rw-r--r-- 3.4 KiB
452f17b7 — james chore(import): snapshot james.dad.town/core at f70c71c7696a 15 hours ago
                                                                                
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
// the pull page's url grammar:
//
//   /pulls/<rkey>/latest              diff of the newest version
//   /pulls/<rkey>/1                   diff of version 1
//   /pulls/<rkey>/1/c1..c2            that diff narrowed to a commit range
//   /pulls/<rkey>/0..1                interdiff between two versions
//   /pulls/<rkey>/0..1/<change-id>    that interdiff narrowed to one change

export interface Revspec {
	base: string;
	head: string;
	// a pull version's source repo, when it isn't the target repo
	repo?: string;
}

// parses `<base>..<head>`, where either side may be missing
export const parseRange = (input: string): Revspec | null => {
	const spec = input.trim();
	if (spec === "") return { base: "", head: "" };
	// `...` is a symmetric difference in git, and two ranges are not a range
	if (spec.includes("...") || spec.split("..").length > 2) return null;
	if (!spec.includes("..")) return { base: "", head: spec };

	const [base, head] = spec.split("..", 2).map((part) => part.trim());
	if (base === "" && head === "") return null;
	return { base, head };
};

export interface PullDiffView {
	mode: "diff";
	version: number;
	base: string;
	head: string;
	repo?: string;
}

export interface PullInterdiffView {
	mode: "interdiff";
	oldVersion: number;
	version: number;
	from: Revspec; // oldVersion {base, head}
	to: Revspec; // version {base, head}
	// "" means all changes
	changeId: string;
}

export type PullView = PullDiffView | PullInterdiffView;

export const versionSegment = (view: PullView): string =>
	view.mode === "diff" ? `${view.version}` : `${view.oldVersion}..${view.version}`;

// a bad version sends you to the newest one, a bad range only drops itself
export type PullRouteRedirect = { redirect: "latest" | "version" };

export const isRedirect = <T extends object>(
	result: T | PullRouteRedirect
): result is PullRouteRedirect => "redirect" in result;

const index = (raw: string, count: number): number | null => {
	if (!/^\d+$/.test(raw)) return null;
	const value = Number(raw);
	return value < count ? value : null;
};

// `/<v1>..<v2>[/<change-id>]`
export const parseInterdiffRoute = (
	versions: readonly Revspec[],
	versionParam: string,
	rangeParam?: string
): PullInterdiffView | PullRouteRedirect => {
	const range = parseRange(versionParam);
	if (!range) return { redirect: "latest" };
	const version1 = index(range.base, versions.length);
	const version2 = index(range.head, versions.length);
	if (version1 === null || version2 === null) return { redirect: "latest" };
	const changeId = rangeParam === "all" ? "" : (rangeParam ?? "");
	return {
		mode: "interdiff",
		oldVersion: version1,
		version: version2,
		from: versions[version1],
		to: versions[version2],
		changeId
	};
};

// `/<version>[/<base>..<head>]`
export const parseDiffRoute = (
	versions: readonly Revspec[],
	versionParam: string,
	rangeParam?: string
): PullDiffView | PullRouteRedirect => {
	const version =
		versionParam === "latest"
			? Math.max(versions.length - 1, 0)
			: index(versionParam, versions.length);
	if (version === null) return { redirect: "latest" };

	const spec = parseRange(rangeParam ?? "");
	if (!spec) return { redirect: "version" };

	const isDiffBase = spec.base === "" || spec.base === "base";
	const isDiffHead = spec.head === "" || spec.head === "head";
	return {
		mode: "diff",
		version,
		base: isDiffBase ? (versions[version]?.base ?? "") : spec.base,
		head: isDiffHead ? (versions[version]?.head ?? "") : spec.head,
		repo: versions[version]?.repo
	};
};