Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.
| Merge branch 'mirroring' into artifacts-mode | 1 | import assert from "node:assert/strict"; |
| 2 | import { test } from "node:test"; | |
| 3 | ||
| 4 | import type { HandbackPlan, RepoMirror } from "@g1t/contracts"; | |
| 5 | ||
| 6 | import { DEFAULT_MIRROR_SETTINGS, TAKE_OVER_AFTER_MINUTES, mirrorWritable } from "../../../../packages/contracts/src/mirrors.ts"; | |
| 7 | ||
| 8 | import { | |
| 9 | clampMinutes, | |
| 10 | decisionsFrom, | |
| 11 | handBackReady, | |
| 12 | mirrorBadge, | |
| 13 | mirrorBanner, | |
| 14 | mirrorReason, | |
| 15 | mirrorSettingsFrom, | |
| 16 | shortSha, | |
| 17 | TAKE_OVER_MINUTES, | |
| 18 | workflowReason, | |
| 19 | writable, | |
| 20 | } from "./mirror.ts"; | |
| 21 | ||
| 22 | const mirror = (state: RepoMirror["state"]): RepoMirror => ({ | |
| 23 | state, | |
| 24 | remote: "github.com/acme/web", | |
| 25 | url: "https://github.com/acme/web", | |
| 26 | since: "2026-10-08T10:00:00Z", | |
| 27 | }); | |
| 28 | const repo = (state: RepoMirror["state"] | null) => ({ namespace: "acme", name: "web", mirror: state ? mirror(state) : null }); | |
| 29 | const leader = (reachable: boolean) => ({ repoId: "r", role: "leader" as const, name: "github.com/acme/web", state: "standby" as const, reachable }); | |
| 30 | const follower = (name: string) => ({ repoId: "r", role: "follower" as const, name, state: "following" as const, reachable: true }); | |
| 31 | ||
| 32 | test("the copies agree with the contracts", () => { | |
| 33 | for (const state of [null, "standby", "ci", "takeover", "handing_back"] as const) { | |
| 34 | assert.equal(writable(state ? mirror(state) : null), mirrorWritable(state ? mirror(state) : null)); | |
| 35 | } | |
| 36 | assert.deepEqual([...TAKE_OVER_MINUTES], [...TAKE_OVER_AFTER_MINUTES]); | |
| 37 | }); | |
| 38 | ||
| 39 | test("a read-only mirror says why its buttons are off, and where to take over", () => { | |
| 40 | assert.equal( | |
| 41 | mirrorReason(repo("standby")), | |
| 42 | "acme/web is a mirror of github.com/acme/web. Work happens there until someone takes over in Settings → Mirroring.", | |
| 43 | ); | |
| 44 | assert.equal(mirrorReason(repo("ci")), mirrorReason(repo("standby"))); | |
| 45 | assert.equal(mirrorReason(repo("handing_back")), "acme/web is handing back to github.com/acme/web; it takes changes again when that is done."); | |
| 46 | assert.equal(mirrorReason(repo("takeover")), null); | |
| 47 | assert.equal(mirrorReason(repo(null)), null); | |
| 48 | assert.equal(mirrorReason(null), null); | |
| 49 | }); | |
| 50 | ||
| 51 | test("workflows run in CI failover and takeovers, not while standing by or handing back", () => { | |
| 52 | assert.equal(workflowReason(repo("ci")), null); | |
| 53 | assert.equal(workflowReason(repo("takeover")), null); | |
| 54 | assert.ok(workflowReason(repo("standby"))); | |
| 55 | assert.ok(workflowReason(repo("handing_back"))); | |
| 56 | assert.equal(workflowReason(repo(null)), null); | |
| 57 | }); | |
| 58 | ||
| 59 | test("the badge says what the repository is to its remotes", () => { | |
| 60 | assert.deepEqual(mirrorBadge(mirror("standby"), []), { label: "Mirror of github.com/acme/web", tone: "neutral", more: 0 }); | |
| 61 | assert.equal(mirrorBadge(mirror("ci"), [])?.tone, "info"); | |
| 62 | assert.equal(mirrorBadge(mirror("takeover"), [])?.label, "Taken over from github.com/acme/web"); | |
| 63 | assert.equal(mirrorBadge(mirror("handing_back"), [])?.label, "Handing back to github.com/acme/web"); | |
| 64 | assert.deepEqual(mirrorBadge(null, [follower("github.com/acme/web"), follower("git.example.com/web")]), { | |
| 65 | label: "Mirrored to github.com/acme/web", | |
| 66 | tone: "accent", | |
| 67 | more: 1, | |
| 68 | }); | |
| 69 | assert.equal(mirrorBadge(null, []), null); | |
| 70 | }); | |
| 71 | ||
| 72 | test("a mirror standing by whose remote answers needs no banner", () => { | |
| 73 | assert.equal(mirrorBanner(mirror("standby"), [leader(true)]), null); | |
| 74 | assert.equal(mirrorBanner(mirror("standby"), []), null); | |
| 75 | assert.equal(mirrorBanner(mirror("standby"), [leader(false)]), "unreachable"); | |
| 76 | assert.equal(mirrorBanner(mirror("ci"), [leader(false)]), "unreachable"); | |
| 77 | assert.equal(mirrorBanner(mirror("ci"), [leader(true)]), "ci"); | |
| 78 | assert.equal(mirrorBanner(mirror("takeover"), [leader(false)]), "takeover"); | |
| 79 | assert.equal(mirrorBanner(mirror("handing_back"), []), "handing_back"); | |
| 80 | assert.equal(mirrorBanner(null, [follower("x")]), null); | |
| 81 | }); | |
| 82 | ||
| 83 | test("hand-back decisions are read from their fields, and only known ones", () => { | |
| 84 | const form = new FormData(); | |
| 85 | form.set("intent", "hand-back"); | |
| 86 | form.set("decision:main", "keep_ours"); | |
| 87 | form.set("decision:release/1.x", "pull_request"); | |
| 88 | form.set("decision:dev", "delete_everything"); | |
| 89 | assert.deepEqual(decisionsFrom(form.entries()), { main: "keep_ours", "release/1.x": "pull_request" }); | |
| 90 | }); | |
| 91 | ||
| 92 | test("a hand-back is ready once the remote answers and every diverged branch has a decision", () => { | |
| 93 | const plan: HandbackPlan = { | |
| 94 | reachable: true, | |
| 95 | ready: false, | |
| 96 | refs: [ | |
| 97 | { ref: "main", base: "a", ours: "b", theirs: "c", action: "diverged", decision: null }, | |
| 98 | { ref: "dev", base: "a", ours: "b", theirs: "a", action: "push", decision: null }, | |
| 99 | ], | |
| 100 | }; | |
| 101 | assert.equal(handBackReady(plan, {}), false); | |
| 102 | assert.equal(handBackReady(plan, { main: "keep_theirs" }), true); | |
| 103 | assert.equal(handBackReady({ ...plan, reachable: false }, { main: "keep_theirs" }), false); | |
| 104 | }); | |
| 105 | ||
| 106 | test("settings come back from the form, keeping what it did not carry", () => { | |
| 107 | const leaderForm = new FormData(); | |
| 108 | leaderForm.set("kind", "leader"); | |
| 109 | leaderForm.set("notify", "inbox"); | |
| 110 | leaderForm.set("autoTakeOver", "on"); | |
| 111 | leaderForm.set("takeOverAfter", "2"); | |
| 112 | leaderForm.set("githubWorkflows", "on"); | |
| 113 | const current = { ...DEFAULT_MIRROR_SETTINGS, remotePushes: "overwrite" as const }; | |
| 114 | assert.deepEqual(mirrorSettingsFrom(leaderForm, current), { | |
| 115 | notify: "inbox", | |
| 116 | takeOverAfter: 5, | |
| 117 | handBack: "ask", | |
| 118 | keepCiWarm: false, | |
| 119 | githubWorkflows: true, | |
| 120 | holdDeploys: false, | |
| A mirror of a GitHub repository whose workflows g1t runs, in CI failover, during a takeover or with CI kept warm, now shows their results on GitHub: each status g1t sets on a commit, a workflow run's or a deploy's, goes to the commit there through g1t's GitHub App as a commit status named under g1t / (g1t / CI / push; g1t / deploy stays as it is), with its state, its description and a link back to g1t, once each time it changes, and commits made on g1t during a takeover are skipped. It is on by default and turned off with Report checks to GitHub in the mirror's settings, or report_checks through the API. The app needs the Commit statuses permission; until an installation has it, the mirror's settings say g1t can't report checks to GitHub and why, and the line goes once a status gets through. Statuses set by workflow runs, deploys and g1t's other parts are now published as status.created, as ones set through the API were, so webhooks hear them too. The mirroring, actions, GitHub, checks, webhooks and self-hosting guides say so. | 121 | reportChecks: true, |
| Merge branch 'mirroring' into artifacts-mode | 122 | remotePushes: "overwrite", |
| 123 | }); | |
| A mirror of a GitHub repository whose workflows g1t runs, in CI failover, during a takeover or with CI kept warm, now shows their results on GitHub: each status g1t sets on a commit, a workflow run's or a deploy's, goes to the commit there through g1t's GitHub App as a commit status named under g1t / (g1t / CI / push; g1t / deploy stays as it is), with its state, its description and a link back to g1t, once each time it changes, and commits made on g1t during a takeover are skipped. It is on by default and turned off with Report checks to GitHub in the mirror's settings, or report_checks through the API. The app needs the Commit statuses permission; until an installation has it, the mirror's settings say g1t can't report checks to GitHub and why, and the line goes once a status gets through. Statuses set by workflow runs, deploys and g1t's other parts are now published as status.created, as ones set through the API were, so webhooks hear them too. The mirroring, actions, GitHub, checks, webhooks and self-hosting guides say so. | 124 | const githubForm = new FormData(); |
| 125 | githubForm.set("kind", "leader"); | |
| 126 | githubForm.set("provider", "github"); | |
| 127 | assert.equal(mirrorSettingsFrom(githubForm, DEFAULT_MIRROR_SETTINGS).reportChecks, false, "a GitHub mirror's box left clear turns it off"); | |
| 128 | githubForm.set("reportChecks", "on"); | |
| 129 | assert.equal(mirrorSettingsFrom(githubForm, { ...DEFAULT_MIRROR_SETTINGS, reportChecks: false }).reportChecks, true); | |
| Merge branch 'mirroring' into artifacts-mode | 130 | const followerForm = new FormData(); |
| 131 | followerForm.set("kind", "follower"); | |
| 132 | followerForm.set("remotePushes", "overwrite"); | |
| 133 | assert.deepEqual(mirrorSettingsFrom(followerForm, DEFAULT_MIRROR_SETTINGS), { ...DEFAULT_MIRROR_SETTINGS, remotePushes: "overwrite" }); | |
| 134 | }); | |
| 135 | ||
| 136 | test("minutes and commits are shown within bounds", () => { | |
| 137 | assert.equal(clampMinutes(3), 5); | |
| 138 | assert.equal(clampMinutes(90.4), 90); | |
| 139 | assert.equal(clampMinutes(99999), 1440); | |
| 140 | assert.equal(clampMinutes(Number.NaN), 5); | |
| 141 | assert.equal(shortSha("0123456789abcdef"), "0123456"); | |
| 142 | assert.equal(shortSha(null), "—"); | |
| 143 | }); |
This file's history is long; its oldest lines are credited to the oldest commit read.