Skip to content
85 linesCodeBlameRaw

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.

The sidebar asks billing whether it is on, not for the account, so no page waits on the card processor1/**
2 * A repository moved: transferred to another workspace, or renamed within
3 * its own. Its projects go with it.
4 *
5 * A project lives in the workspace its repository is in, so each of the
6 * repository's projects under a stale path's workspace moves to the
7 * workspace the repository is in now, keeping its id (deployments,
8 * secrets and the rest are keyed by it) and its slug. Projects elsewhere
Projects no longer depend on each other: the dependsOn relation, its settings page (old links redirect), the overview card, a pull request's Affects panel and preview stacks, reference variables and agents' notes on what a project uses are gone; the table is dropped in a later deploy9 * that build from the repository only learn its new path.
The sidebar asks billing whether it is on, not for the account, so no page waits on the card processor10 *
11 * Renamed, the repository's own project (its primary one, named after it)
12 * takes the new name as its slug, so its pages at `/<workspace>/<name>`
13 * follow, unless the workspace already has a project of that slug; it
14 * keeps its old slug then. Its display name follows when it was still the
15 * repository's old name.
16 *
17 * Each statement changes nothing once the rows have moved, so a repeated
18 * or late delivery is harmless.
19 */
20
21import type { Statement } from "./rename";
22
23/** A name as an address: lowercase letters, digits, `-`, `_` and `.`. */
24export function slugOf(name: string): string {
25 return name
26 .trim()
27 .toLowerCase()
28 .replace(/[^a-z0-9._-]+/g, "-")
29 .replace(/^[-.]+|[-.]+$/g, "")
30 .slice(0, 100);
31}
32
33/** `stale` and `current` are `namespace/name` paths. */
34export function moveStatements(stale: string[], current: string, repoId: string): Statement[] {
35 const [workspace, name] = current.split("/") as [string, string];
36 const moves = [...new Set(stale.map((path) => path.split("/")[0]!))]
37 .filter((from) => from !== workspace)
38 .map((from) => ({
39 // A project of the same slug already in the destination keeps it; the
40 // moving one is given a free slug afterwards (see `strandedQuery`).
41 sql: "UPDATE OR IGNORE projects SET workspace = ?, repo_namespace = ? WHERE repo_id = ? AND workspace = ?",
42 params: [workspace, workspace, repoId, from],
43 }));
44 const renames = [...new Set(stale.map((path) => path.split("/")[1]!))]
45 .filter((old) => old !== name)
46 .flatMap((old) => {
47 const statements: Statement[] = [];
48 const [from, to] = [slugOf(old), slugOf(name)];
49 if (from && to && from !== to) {
50 // Ignored when the workspace already has a project called `to`.
51 statements.push({
52 sql: "UPDATE OR IGNORE projects SET slug = ? WHERE repo_id = ? AND is_primary = 1 AND workspace = ? AND slug = ?",
53 params: [to, repoId, workspace, from],
54 });
55 }
56 statements.push({
57 sql: "UPDATE projects SET name = ? WHERE repo_id = ? AND is_primary = 1 AND name = ?",
58 params: [name, repoId, old],
59 });
60 return statements;
61 });
62 return [
63 ...moves,
64 ...renames,
65 {
66 sql: "UPDATE projects SET repo_namespace = ?, repo_name = ? WHERE repo_id = ?",
67 params: [workspace, name, repoId],
68 },
69 ];
70}
71
72/**
73 * The repository's projects still under a stale workspace after the moves:
74 * those whose slug the destination already had. Null for a rename within
75 * the workspace, which moves no project between workspaces.
76 */
77export function strandedQuery(stale: string[], current: string, repoId: string): Statement | null {
78 const workspace = current.split("/")[0]!;
79 const from = [...new Set(stale.map((path) => path.split("/")[0]!))].filter((ns) => ns !== workspace);
80 if (from.length === 0) return null;
81 return {
82 sql: `SELECT * FROM projects WHERE repo_id = ? AND workspace IN (${from.map(() => "?").join(", ")})`,
83 params: [repoId, ...from],
84 };
85}

This file's history is long; its oldest lines are credited to the oldest commit read.