Skip to content

g1t/crates/contracts/src/names.rs

151 lines5,136 bytesCodeBlame

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 'worktree-agent-a8385d293d42c913a'1/// The site's own routes: first path segments that are never a workspace's,
2/// so nobody may register them, and no alias can be reached at them.
3const ROUTES: &[&str] = &[
Workspaces own repositories4 "api",
5 "mcp",
6 "login",
7 "logout",
8 "register",
9 "new",
10 "settings",
11 "search",
12 "admin",
13 "auth",
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look14 // g1t.sh/integrations/github/setup: where GitHub returns after an install.
15 "integrations",
Issues and pull requests replace intents and attempts16 "pulls",
17 "issues",
Workspaces own repositories18 "oauth",
19 "assets",
Workspace names and icons, and a component kit for every control20 "avatars",
Workspaces own repositories21 "docs",
22 "explore",
23 "about",
24 "pricing",
25 "terms",
26 "privacy",
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look27 "policies",
28 "security",
Workspaces own repositories29 "help",
30 "support",
31 "status",
32 "blog",
33 "verify",
34 "forgot",
35 "reset",
36 "device",
37 "workspaces",
38 "u",
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look39 // g1t.sh/invite/<code>: invite links; and the waitlist.
40 "invite",
41 "invites",
42 "waitlist",
Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member43 // g1t.sh/v2/: the container registry; `-` paths hold the others.
44 "v2",
Inbox: a bell in the top bar opens it beside the page, and /inbox holds it all45 // g1t.sh/inbox, and the name it might also go by.
46 "inbox",
47 "notifications",
API and MCP server, Rust identity service, registration, site redesign48];
49
Merge branch 'worktree-agent-a8385d293d42c913a'50/// g1t itself, and the name its agent once went by: everything g1t does is
51/// shown as `g1t`, so nobody else may be called either. Not routes: staff
52/// may point one at a workspace as an alias (identity's `aliases.rs`).
53const OWN: &[&str] = &["g1t", "g1t-agent"];
54
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent55/// Whether `value`, whatever its case, is a name nobody can register or
56/// rename a workspace to: a route, or g1t's own.
57pub fn is_reserved_name(value: &str) -> bool {
Merge branch 'worktree-agent-a8385d293d42c913a'58 is_route_name(value) || OWN.iter().any(|own| own.eq_ignore_ascii_case(value.trim()))
59}
60
61/// Whether `value`, whatever its case, is one of the site's own routes.
62pub fn is_route_name(value: &str) -> bool {
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent63 let value = value.trim();
Merge branch 'worktree-agent-a8385d293d42c913a'64 ROUTES.iter().any(|route| route.eq_ignore_ascii_case(value))
65}
66
67/// Whether `value` has a namespace's shape: letters, digits and single
68/// hyphens, not starting or ending with a hyphen, at most 39 characters.
69/// Reserved names have it too; see [`is_valid_namespace`].
70pub fn is_namespace_shaped(value: &str) -> bool {
71 let bytes = value.as_bytes();
72 !bytes.is_empty()
73 && bytes.len() <= 39
74 && bytes
75 .iter()
76 .all(|byte| byte.is_ascii_lowercase() || byte.is_ascii_digit() || *byte == b'-')
77 && !value.starts_with('-')
78 && !value.ends_with('-')
79 && !value.contains("--")
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent80}
81
Merge branch 'worktree-agent-a8385d293d42c913a'82/// A workspace alias as staff typed it, trimmed and lowercased, if it can
83/// be one: shaped like a namespace and not one of the site's routes, which
84/// would always answer first. Reserved names such as `g1t` can be; whether
85/// a person or workspace already has it is identity's to check.
86pub fn aliasable_name(value: &str) -> Option<String> {
87 let value = value.trim().to_lowercase();
88 (is_namespace_shaped(&value) && !is_route_name(&value)).then_some(value)
89}
90
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent91/// A username or workspace slug as someone typed it, trimmed and
92/// lowercased, if it can be registered: what signing up, signing up with
93/// GitHub and creating a workspace each take. None when it is malformed or
94/// reserved.
95pub fn claimable_namespace(value: &str) -> Option<String> {
96 let value = value.trim().to_lowercase();
97 is_valid_namespace(&value).then_some(value)
98}
99
API and MCP server, Rust identity service, registration, site redesign100/// Namespaces follow GitHub's rules: letters, digits and single hyphens,
101/// not starting or ending with a hyphen, at most 39 characters.
102pub fn is_valid_namespace(value: &str) -> bool {
Merge branch 'worktree-agent-a8385d293d42c913a'103 is_namespace_shaped(value) && !is_reserved_name(value)
API and MCP server, Rust identity service, registration, site redesign104}
105
106pub fn is_valid_repo_name(value: &str) -> bool {
107 !value.is_empty()
108 && value.len() <= 100
109 && value.bytes().all(|byte| {
110 byte.is_ascii_lowercase() || byte.is_ascii_digit() || matches!(byte, b'.' | b'_' | b'-')
111 })
112 && !value.starts_with('.')
113 && !value.ends_with(".git")
Agents as a team: lifecycle, merge queue, billing and a new shell114 // `/<workspace>/-/…` holds the workspace's own pages.
115 && value != "-"
API and MCP server, Rust identity service, registration, site redesign116}
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent117
118#[cfg(test)]
119mod tests {
120 use super::*;
121
122 #[test]
123 fn g1ts_own_names_are_reserved_in_any_case() {
124 for name in ["g1t", "G1T", "g1t-agent", "G1T-Agent", " g1t "] {
125 assert!(is_reserved_name(name), "{name}");
126 assert_eq!(claimable_namespace(name), None, "{name}");
127 }
128 assert!(!is_valid_namespace("g1t"));
129 assert!(!is_valid_namespace("g1t-agent"));
130 }
131
132 #[test]
133 fn names_near_g1ts_are_anyones() {
134 assert!(!is_reserved_name("g1t-agents"));
135 assert_eq!(claimable_namespace("G1T-Bot").as_deref(), Some("g1t-bot"));
136 assert_eq!(claimable_namespace(" Ana ").as_deref(), Some("ana"));
137 assert_eq!(claimable_namespace("an--a"), None);
138 }
Merge branch 'worktree-agent-a8385d293d42c913a'139
140 #[test]
141 fn g1t_can_be_an_alias_but_a_route_cannot() {
142 assert_eq!(aliasable_name(" G1T ").as_deref(), Some("g1t"));
143 assert_eq!(aliasable_name("acme-corp").as_deref(), Some("acme-corp"));
144 for name in ["settings", "api", "login", "Explore", "-acme", "ac--me", "acme_inc", "", "a.b"] {
145 assert_eq!(aliasable_name(name), None, "{name}");
146 }
147 assert_eq!(aliasable_name(&"a".repeat(40)), None);
148 // Still nobody's to register.
149 assert!(is_reserved_name("g1t") && !is_route_name("g1t"));
150 }
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent151}

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