merge-steward 0.5.2 → 0.5.4
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/dist/github/shell-git.d.ts +2 -2
- package/dist/github/shell-git.js +4 -6
- package/dist/interfaces.d.ts +2 -2
- package/dist/reconciler.js +10 -21
- package/dist/watch/DetailView.js +3 -2
- package/dist/watch/QueueListView.js +15 -9
- package/dist/watch/StatusBar.js +8 -3
- package/dist/watch/format.d.ts +6 -0
- package/dist/watch/format.js +51 -0
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { GitOperations, SpeculativeBranchBuilder } from "../interfaces.ts";
|
|
2
|
-
import type { MergeResult
|
|
2
|
+
import type { MergeResult } from "../types.ts";
|
|
3
3
|
export declare class ShellGitOperations implements GitOperations, SpeculativeBranchBuilder {
|
|
4
4
|
private readonly clonePath;
|
|
5
5
|
private readonly repoFullName;
|
|
@@ -9,7 +9,7 @@ export declare class ShellGitOperations implements GitOperations, SpeculativeBra
|
|
|
9
9
|
fetch(remote?: string): Promise<void>;
|
|
10
10
|
headSha(branch: string): Promise<string>;
|
|
11
11
|
isAncestor(ancestor: string, descendant: string): Promise<boolean>;
|
|
12
|
-
|
|
12
|
+
mergeBaseInto(branch: string, base: string): Promise<MergeResult>;
|
|
13
13
|
push(branch: string, force?: boolean): Promise<void>;
|
|
14
14
|
buildSpeculative(prBranch: string, baseBranch: string, specName: string): Promise<MergeResult>;
|
|
15
15
|
deleteSpeculative(specName: string): Promise<void>;
|
package/dist/github/shell-git.js
CHANGED
|
@@ -41,24 +41,22 @@ export class ShellGitOperations {
|
|
|
41
41
|
return false;
|
|
42
42
|
throw new Error(`git merge-base --is-ancestor failed: ${result.stderr || result.stdout}`);
|
|
43
43
|
}
|
|
44
|
-
async
|
|
44
|
+
async mergeBaseInto(branch, base) {
|
|
45
45
|
const remoteBranchRef = `refs/remotes/origin/${branch}`;
|
|
46
46
|
const remoteBranchExists = await this.git(["show-ref", "--verify", remoteBranchRef], { allowNonZero: true });
|
|
47
47
|
if (remoteBranchExists.exitCode === 0) {
|
|
48
|
-
// Always start from the freshly fetched remote branch tip so stale local branches
|
|
49
|
-
// cannot reintroduce old commits into queue processing.
|
|
50
48
|
await this.git(["checkout", "-B", branch, `origin/${branch}`]);
|
|
51
49
|
}
|
|
52
50
|
else {
|
|
53
51
|
await this.git(["checkout", branch]);
|
|
54
52
|
}
|
|
55
|
-
const result = await this.git(["
|
|
53
|
+
const result = await this.git(["merge", "--no-ff", "--no-edit", base], { allowNonZero: true });
|
|
56
54
|
if (result.exitCode !== 0) {
|
|
57
|
-
await this.git(["
|
|
55
|
+
await this.git(["merge", "--abort"], { allowNonZero: true });
|
|
58
56
|
return { success: false, conflictFiles: parseConflicts(result.stderr) };
|
|
59
57
|
}
|
|
60
58
|
const newSha = await this.headSha("HEAD");
|
|
61
|
-
return { success: true,
|
|
59
|
+
return { success: true, sha: newSha };
|
|
62
60
|
}
|
|
63
61
|
async push(branch, force = false) {
|
|
64
62
|
const args = ["push"];
|
package/dist/interfaces.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { CIStatus, CheckResult, IncidentRecord, MergeResult, PRStatus, QueueEntry
|
|
1
|
+
import type { CIStatus, CheckResult, IncidentRecord, MergeResult, PRStatus, QueueEntry } from "./types.ts";
|
|
2
2
|
/**
|
|
3
3
|
* Git operations needed by the reconciler. The sim (GitSim) implements
|
|
4
4
|
* additional methods for test harness setup, but the reconciler only uses these.
|
|
@@ -7,7 +7,7 @@ export interface GitOperations {
|
|
|
7
7
|
fetch(remote?: string): Promise<void>;
|
|
8
8
|
headSha(branch: string): Promise<string>;
|
|
9
9
|
isAncestor(ancestor: string, descendant: string): Promise<boolean>;
|
|
10
|
-
|
|
10
|
+
mergeBaseInto(branch: string, base: string): Promise<MergeResult>;
|
|
11
11
|
push(branch: string, force?: boolean): Promise<void>;
|
|
12
12
|
}
|
|
13
13
|
/**
|
package/dist/reconciler.js
CHANGED
|
@@ -71,7 +71,7 @@ export async function reconcile(ctx) {
|
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
73
|
}
|
|
74
|
-
// ─── Head entry: fetch + gate +
|
|
74
|
+
// ─── Head entry: fetch + gate + branch refresh ──────────────────
|
|
75
75
|
async function prepareHead(ctx, entry) {
|
|
76
76
|
emit(ctx, entry, "fetch_started");
|
|
77
77
|
await ctx.git.fetch();
|
|
@@ -116,7 +116,7 @@ async function prepareHead(ctx, entry) {
|
|
|
116
116
|
emit(ctx, entry, "retry_gated", { baseSha, detail: "base unchanged since last conflict" });
|
|
117
117
|
return;
|
|
118
118
|
}
|
|
119
|
-
await
|
|
119
|
+
await performBranchRefresh(ctx, entry, baseSha);
|
|
120
120
|
}
|
|
121
121
|
function describeMainBroken(failingChecks, pendingChecks) {
|
|
122
122
|
const parts = [];
|
|
@@ -135,9 +135,9 @@ function summarizeCheckNames(checks, limit = 3) {
|
|
|
135
135
|
}
|
|
136
136
|
return `${names.slice(0, limit).join(", ")} +${names.length - limit} more`;
|
|
137
137
|
}
|
|
138
|
-
async function
|
|
138
|
+
async function performBranchRefresh(ctx, entry, baseSha) {
|
|
139
139
|
emit(ctx, entry, "rebase_started", { baseSha });
|
|
140
|
-
const result = await ctx.git.
|
|
140
|
+
const result = await ctx.git.mergeBaseInto(entry.branch, ref(ctx, ctx.baseBranch));
|
|
141
141
|
if (!result.success) {
|
|
142
142
|
emit(ctx, entry, "rebase_conflict", { baseSha, conflictFiles: result.conflictFiles });
|
|
143
143
|
if (isBudgetExhausted(entry)) {
|
|
@@ -153,28 +153,17 @@ async function performRebase(ctx, entry, baseSha) {
|
|
|
153
153
|
}
|
|
154
154
|
return;
|
|
155
155
|
}
|
|
156
|
-
const headSha = result.
|
|
156
|
+
const headSha = result.sha ?? entry.headSha;
|
|
157
157
|
await ctx.git.fetch();
|
|
158
158
|
const latestRemoteHead = await ctx.git.headSha(ref(ctx, entry.branch));
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
? `candidate diverged from remote head: expected ${entry.headSha.slice(0, 8)}, ` +
|
|
163
|
-
`latest ${latestRemoteHead.slice(0, 8)}, candidate ${headSha.slice(0, 8)}`
|
|
164
|
-
: `remote advanced during rebase: expected ${entry.headSha.slice(0, 8)}, ` +
|
|
165
|
-
`latest ${latestRemoteHead.slice(0, 8)}, candidate ${headSha.slice(0, 8)}`;
|
|
159
|
+
if (latestRemoteHead !== entry.headSha) {
|
|
160
|
+
const detail = `remote advanced during refresh: expected ${entry.headSha.slice(0, 8)}, ` +
|
|
161
|
+
`latest ${latestRemoteHead.slice(0, 8)}, candidate ${headSha.slice(0, 8)}`;
|
|
166
162
|
emit(ctx, entry, "branch_mismatch", { detail });
|
|
167
|
-
|
|
168
|
-
ctx.store.updateHead(entry.id, latestRemoteHead);
|
|
169
|
-
}
|
|
170
|
-
else {
|
|
171
|
-
ctx.store.transition(entry.id, "queued", {
|
|
172
|
-
...CLEAN_CI,
|
|
173
|
-
}, `stale local branch diverged from ${latestRemoteHead.slice(0, 8)}`);
|
|
174
|
-
}
|
|
163
|
+
ctx.store.updateHead(entry.id, latestRemoteHead);
|
|
175
164
|
return;
|
|
176
165
|
}
|
|
177
|
-
await ctx.git.push(entry.branch,
|
|
166
|
+
await ctx.git.push(entry.branch, false);
|
|
178
167
|
emit(ctx, entry, "rebase_succeeded", { baseSha });
|
|
179
168
|
// Build speculative branch for downstream entries.
|
|
180
169
|
let specBranch = null;
|
package/dist/watch/DetailView.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Box, Text } from "ink";
|
|
3
|
-
import { formatEntryEvent, progressBar, relativeTime, shortSha, statusColor, summarizeQueueBlock } from "./format.js";
|
|
3
|
+
import { formatEntryEvent, humanStatus, nextStepLabel, progressBar, queueProgress, relativeTime, shortSha, statusColor, summarizeQueueBlock } from "./format.js";
|
|
4
4
|
import { EntryStateGraph } from "./EntryStateGraph.js";
|
|
5
5
|
import { ExternalRepairObservation } from "./ExternalRepairObservation.js";
|
|
6
6
|
import { buildEntryStateGraph, buildExternalRepairObservations } from "./state-visualization.js";
|
|
@@ -17,5 +17,6 @@ export function DetailView({ detail, isHead, activeIndex, activeCount, headPrNum
|
|
|
17
17
|
headPrNumber,
|
|
18
18
|
queueBlock,
|
|
19
19
|
});
|
|
20
|
-
|
|
20
|
+
const pipeline = queueProgress(entry.status);
|
|
21
|
+
return (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsxs(Box, { gap: 2, children: [_jsxs(Text, { bold: true, children: ["#", entry.prNumber] }), entry.issueKey ? _jsx(Text, { children: entry.issueKey }) : null, _jsx(Text, { color: statusColor(entry.status), children: humanStatus(entry.status) }), _jsxs(Text, { dimColor: true, children: ["pos ", entry.position] }), _jsxs(Text, { dimColor: true, children: ["generation ", entry.generation] }), _jsxs(Text, { dimColor: true, children: ["retry ", entry.retryAttempts, "/", entry.maxRetries] })] }), _jsx(Text, { children: entry.branch }), _jsxs(Box, { gap: 2, children: [_jsxs(Text, { dimColor: true, children: ["head ", shortSha(entry.headSha)] }), _jsxs(Text, { dimColor: true, children: ["base ", shortSha(entry.baseSha)] }), entry.issueKey && _jsx(Text, { dimColor: true, children: entry.issueKey })] }), _jsxs(Box, { gap: 1, marginTop: 1, children: [_jsx(Text, { dimColor: true, children: "progress" }), _jsx(Text, { children: progressBar(pipeline.current, pipeline.total, 12) }), _jsx(Text, { dimColor: true, children: nextStepLabel(entry.status) })] }), isHead && queueBlock && (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsxs(Text, { color: "yellow", children: ["Queue paused: ", summarizeQueueBlock(queueBlock) ?? "main branch is unhealthy", "."] }), _jsxs(Text, { dimColor: true, children: [queueBlock.baseBranch, queueBlock.baseSha ? ` @ ${shortSha(queueBlock.baseSha)}` : ""] }), _jsxs(Text, { dimColor: true, children: ["Head PR #", queueBlock.headPrNumber ?? entry.prNumber, " will resume automatically once main recovers."] })] })), entry.maxRetries > 0 && (_jsxs(Box, { gap: 1, marginTop: 1, children: [_jsx(Text, { dimColor: true, children: "retry" }), _jsx(Text, { children: progressBar(entry.retryAttempts, entry.maxRetries, 10) }), _jsxs(Text, { dimColor: true, children: [entry.retryAttempts, "/", entry.maxRetries] })] })), _jsx(EntryStateGraph, { main: graph.main, exits: graph.exits }), _jsx(ExternalRepairObservation, { observations: observations }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { bold: true, children: "Incidents" }), incidents.length === 0 ? (_jsx(Text, { dimColor: true, children: "No incidents." })) : (incidents.map((incident) => (_jsxs(Box, { gap: 1, children: [_jsx(Text, { dimColor: true, children: relativeTime(incident.at).padStart(4, " ") }), _jsx(Text, { color: "red", children: incident.failureClass }), _jsx(Text, { dimColor: true, children: incident.outcome })] }, incident.id))))] }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { bold: true, children: "Events" }), events.length === 0 ? (_jsx(Text, { dimColor: true, children: "No events yet." })) : (events.slice(-16).map((event) => (_jsxs(Box, { gap: 1, children: [_jsx(Text, { dimColor: true, children: relativeTime(event.at).padStart(4, " ") }), _jsx(Text, { children: formatEntryEvent(event) })] }, event.id ?? `${event.entryId}-${event.at}`))))] })] }));
|
|
21
22
|
}
|
|
@@ -1,23 +1,29 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useMemo } from "react";
|
|
3
3
|
import { Box, Text, useStdout } from "ink";
|
|
4
|
-
import { formatEventSummary, relativeTime, statusColor, summarizeQueueBlock, truncate } from "./format.js";
|
|
4
|
+
import { formatEventSummary, humanStatus, nextStepLabel, progressBar, queueProgress, relativeTime, statusColor, summarizeQueueBlock, truncate } from "./format.js";
|
|
5
|
+
const ENTRY_ROW_HEIGHT = 2;
|
|
5
6
|
const CHROME_ROWS = 13;
|
|
6
|
-
function QueueRow({ entry, selected,
|
|
7
|
+
function QueueRow({ entry, selected, infoWidth, isHead, queueBlock, }) {
|
|
7
8
|
const retryText = `${entry.retryAttempts}/${entry.maxRetries}`;
|
|
8
|
-
const ciText = entry.ciRetries > 0 ? `
|
|
9
|
+
const ciText = entry.ciRetries > 0 ? `CI retries ${entry.ciRetries}` : null;
|
|
9
10
|
const blockedOnMain = isHead && queueBlock?.reason === "main_broken" && queueBlock.headPrNumber === entry.prNumber;
|
|
10
|
-
const renderedStatus = blockedOnMain ? "blocked
|
|
11
|
+
const renderedStatus = blockedOnMain ? "blocked by broken main" : humanStatus(entry.status);
|
|
11
12
|
const renderedColor = blockedOnMain ? "red" : statusColor(entry.status);
|
|
12
|
-
|
|
13
|
+
const progress = queueProgress(entry.status);
|
|
14
|
+
const branchLabel = truncate(entry.branch, Math.max(12, infoWidth - 34));
|
|
15
|
+
const nextStep = blockedOnMain
|
|
16
|
+
? summarizeQueueBlock(queueBlock) ?? "waiting for main to recover"
|
|
17
|
+
: nextStepLabel(entry.status);
|
|
18
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { color: selected ? "cyan" : "gray", children: selected ? "›" : " " }), _jsx(Text, { color: blockedOnMain ? "red" : isHead ? "green" : "gray", children: blockedOnMain ? "!" : isHead ? "#" : " " }), _jsx(Text, { bold: true, children: ` #${entry.prNumber}` }), entry.issueKey ? _jsx(Text, { children: ` ${entry.issueKey}` }) : null, _jsx(Text, { dimColor: true, children: ` pos ${entry.position}` }), _jsx(Text, { dimColor: true, children: ` ${relativeTime(entry.updatedAt)}` }), _jsx(Text, { children: ` ` }), _jsx(Text, { color: renderedColor, children: renderedStatus })] }), _jsxs(Box, { paddingLeft: 2, gap: 1, children: [_jsx(Text, { dimColor: true, children: progressBar(progress.current, progress.total, 8) }), _jsx(Text, { dimColor: true, children: branchLabel }), _jsx(Text, { dimColor: true, children: "|" }), _jsx(Text, { dimColor: true, children: nextStep }), _jsx(Text, { dimColor: true, children: ` | retry ${retryText}` }), ciText ? (_jsxs(_Fragment, { children: [_jsx(Text, { dimColor: true, children: "|" }), _jsx(Text, { dimColor: true, children: ciText })] })) : null] })] }));
|
|
13
19
|
}
|
|
14
20
|
export function QueueListView({ entries, selectedEntryId, recentEvents, headEntryId, queueBlock, }) {
|
|
15
21
|
const { stdout } = useStdout();
|
|
16
22
|
const rows = stdout?.rows ?? 24;
|
|
17
23
|
const cols = stdout?.columns ?? 100;
|
|
18
|
-
const
|
|
19
|
-
const eventRows = Math.min(8, Math.max(4, rows - entries.length - CHROME_ROWS));
|
|
24
|
+
const infoWidth = Math.max(32, cols - 4);
|
|
25
|
+
const eventRows = Math.min(8, Math.max(4, rows - (entries.length * ENTRY_ROW_HEIGHT) - CHROME_ROWS));
|
|
20
26
|
const displayedEvents = useMemo(() => recentEvents.slice(-eventRows), [eventRows, recentEvents]);
|
|
21
27
|
const queueBlockLabel = summarizeQueueBlock(queueBlock);
|
|
22
|
-
return (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [queueBlock && (_jsxs(Box, { marginBottom: 1, flexDirection: "column", children: [_jsxs(Text, { color: "yellow", children: ["Queue paused: ", queueBlockLabel ?? "main is unhealthy", queueBlock.baseSha ? ` at ${truncate(queueBlock.baseSha, 10)}` : "", "."] }), _jsxs(Text, { dimColor: true, children: ["Head PR #", queueBlock.headPrNumber ?? "?", " will resume automatically once main recovers."] })] })),
|
|
28
|
+
return (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [queueBlock && (_jsxs(Box, { marginBottom: 1, flexDirection: "column", children: [_jsxs(Text, { color: "yellow", children: ["Queue paused: ", queueBlockLabel ?? "main is unhealthy", queueBlock.baseSha ? ` at ${truncate(queueBlock.baseSha, 10)}` : "", "."] }), _jsxs(Text, { dimColor: true, children: ["Head PR #", queueBlock.headPrNumber ?? "?", " will resume automatically once main recovers."] })] })), entries.length === 0 ? (_jsx(Text, { dimColor: true, children: "No queue entries in this filter." })) : (entries.map((entry) => (_jsx(QueueRow, { entry: entry, selected: entry.id === selectedEntryId, infoWidth: infoWidth, isHead: entry.id === headEntryId, queueBlock: queueBlock }, entry.id)))), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { bold: true, children: "Recent Events" }), displayedEvents.length === 0 ? (_jsx(Text, { dimColor: true, children: "No queue events yet." })) : (displayedEvents.map((event) => (_jsxs(Box, { gap: 1, children: [_jsx(Text, { dimColor: true, children: relativeTime(event.at).padStart(4, " ") }), _jsx(Text, { children: formatEventSummary(event) })] }, event.id ?? `${event.entryId}-${event.at}`))))] })] }));
|
|
23
29
|
}
|
package/dist/watch/StatusBar.js
CHANGED
|
@@ -17,14 +17,19 @@ export function StatusBar({ snapshot, connected, filter, lastSnapshotReceivedAt,
|
|
|
17
17
|
const avgWaitMs = activeEntries.length > 0
|
|
18
18
|
? activeEntries.reduce((sum, e) => sum + (Date.now() - new Date(e.enqueuedAt).getTime()), 0) / activeEntries.length
|
|
19
19
|
: 0;
|
|
20
|
+
const queueHealth = queueBlockLabel
|
|
21
|
+
? `paused on broken main`
|
|
22
|
+
: summary.headPrNumber !== null
|
|
23
|
+
? `head #${summary.headPrNumber} active`
|
|
24
|
+
: "queue idle";
|
|
20
25
|
const leftParts = [
|
|
21
26
|
snapshot.repoFullName,
|
|
22
27
|
`base:${snapshot.baseBranch}`,
|
|
23
28
|
`${summary.total} entries ${summary.active} active`,
|
|
24
|
-
|
|
25
|
-
queueBlockLabel
|
|
29
|
+
queueHealth,
|
|
30
|
+
queueBlockLabel ?? null,
|
|
26
31
|
avgWaitMs > 0 ? `wait ~${formatDuration(avgWaitMs)}` : null,
|
|
27
|
-
`tick ${runtimeLabel(runtime)} ${relativeTime(runtime.lastTickCompletedAt ?? runtime.lastTickStartedAt)}`,
|
|
32
|
+
`last tick ${runtimeLabel(runtime)} ${relativeTime(runtime.lastTickCompletedAt ?? runtime.lastTickStartedAt)}`,
|
|
28
33
|
filter,
|
|
29
34
|
].filter(Boolean).join(" | ");
|
|
30
35
|
const availableLeft = Math.max(1, width - 28);
|
package/dist/watch/format.d.ts
CHANGED
|
@@ -2,6 +2,12 @@ import type { CheckResult, QueueBlockState, QueueEntryStatus, QueueEventRecord,
|
|
|
2
2
|
export declare function shortSha(value: string | null | undefined): string;
|
|
3
3
|
export declare function relativeTime(iso: string | null | undefined): string;
|
|
4
4
|
export declare function statusColor(status: QueueEntryStatus): "yellow" | "cyan" | "green" | "red" | "gray";
|
|
5
|
+
export declare function humanStatus(status: QueueEntryStatus): string;
|
|
6
|
+
export declare function queueProgress(status: QueueEntryStatus): {
|
|
7
|
+
current: number;
|
|
8
|
+
total: number;
|
|
9
|
+
};
|
|
10
|
+
export declare function nextStepLabel(status: QueueEntryStatus): string;
|
|
5
11
|
export declare function runtimeLabel(runtime: QueueRuntimeStatus): string;
|
|
6
12
|
export declare function formatEventSummary(event: QueueEventSummary): string;
|
|
7
13
|
export declare function formatEntryEvent(event: QueueEventRecord): string;
|
package/dist/watch/format.js
CHANGED
|
@@ -38,6 +38,57 @@ export function statusColor(status) {
|
|
|
38
38
|
return "gray";
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
|
+
export function humanStatus(status) {
|
|
42
|
+
switch (status) {
|
|
43
|
+
case "queued":
|
|
44
|
+
return "queued";
|
|
45
|
+
case "preparing_head":
|
|
46
|
+
return "refreshing branch";
|
|
47
|
+
case "validating":
|
|
48
|
+
return "running CI";
|
|
49
|
+
case "merging":
|
|
50
|
+
return "merging to main";
|
|
51
|
+
case "merged":
|
|
52
|
+
return "merged";
|
|
53
|
+
case "evicted":
|
|
54
|
+
return "removed from queue";
|
|
55
|
+
case "dequeued":
|
|
56
|
+
return "dequeued";
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
export function queueProgress(status) {
|
|
60
|
+
switch (status) {
|
|
61
|
+
case "queued":
|
|
62
|
+
return { current: 1, total: 4 };
|
|
63
|
+
case "preparing_head":
|
|
64
|
+
return { current: 2, total: 4 };
|
|
65
|
+
case "validating":
|
|
66
|
+
return { current: 3, total: 4 };
|
|
67
|
+
case "merging":
|
|
68
|
+
case "merged":
|
|
69
|
+
case "evicted":
|
|
70
|
+
case "dequeued":
|
|
71
|
+
return { current: 4, total: 4 };
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
export function nextStepLabel(status) {
|
|
75
|
+
switch (status) {
|
|
76
|
+
case "queued":
|
|
77
|
+
return "waiting for head-of-line turn";
|
|
78
|
+
case "preparing_head":
|
|
79
|
+
return "rebasing onto latest main";
|
|
80
|
+
case "validating":
|
|
81
|
+
return "waiting for CI result";
|
|
82
|
+
case "merging":
|
|
83
|
+
return "final GitHub merge";
|
|
84
|
+
case "merged":
|
|
85
|
+
return "landed on main";
|
|
86
|
+
case "evicted":
|
|
87
|
+
return "needs external repair";
|
|
88
|
+
case "dequeued":
|
|
89
|
+
return "removed manually";
|
|
90
|
+
}
|
|
91
|
+
}
|
|
41
92
|
export function runtimeLabel(runtime) {
|
|
42
93
|
if (runtime.tickInProgress) {
|
|
43
94
|
return "running";
|