Skip to content
217 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.

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] = &[
Get started on mission control4 "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",
Get started on mission control16 "pulls",
17 "issues",
18 "oauth",
19 "assets",
Workspace names and icons, and a component kit for every control20 "avatars",
Get started on mission control21 "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",
Get started on mission control29 "help",
30 "support",
31 "status",
32 "blog",
33 "verify",
Merge email confirmation gate: a code and a link, nothing until confirmed (identity 0036)34 "confirm-email",
Get started on mission control35 "forgot",
36 "reset",
37 "device",
38 "workspaces",
39 "u",
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look40 // g1t.sh/invite/<code>: invite links; and the waitlist.
41 "invite",
42 "invites",
43 "waitlist",
Merge workspace invitations: nobody joins a workspace without saying yes, people are found by username, your own invites can bring someone in, and nobody is left without a workspace (identity 0040)44 // g1t.sh/invitations: the workspace invitations waiting for an answer.
45 "invitations",
Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member46 // g1t.sh/v2/: the container registry; `-` paths hold the others.
47 "v2",
Inbox: a bell in the top bar opens it beside the page, and /inbox holds it all48 // g1t.sh/inbox, and the name it might also go by.
49 "inbox",
50 "notifications",
Get started on mission control51];
52
Merge account deletion: soft delete for 30 days, staff restore and purge, ghost for what remains (identity 0037)53/// g1t itself, the name its agent once went by, and `ghost`, who wrote what
54/// a deleted account wrote (`account_deletion::GHOST_USERNAME`): everything
55/// g1t does is shown as `g1t`, so nobody else may be called any of these.
56/// Not routes: staff may point one at a workspace as an alias (identity's
57/// `aliases.rs`).
58const OWN: &[&str] = &["g1t", "g1t-agent", "ghost"];
Merge branch 'worktree-agent-a8385d293d42c913a'59
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent60/// Whether `value`, whatever its case, is a name nobody can register or
61/// rename a workspace to: a route, or g1t's own.
62pub fn is_reserved_name(value: &str) -> bool {
Merge branch 'worktree-agent-a8385d293d42c913a'63 is_route_name(value) || OWN.iter().any(|own| own.eq_ignore_ascii_case(value.trim()))
64}
65
66/// Whether `value`, whatever its case, is one of the site's own routes.
67pub 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-agent68 let value = value.trim();
Merge branch 'worktree-agent-a8385d293d42c913a'69 ROUTES.iter().any(|route| route.eq_ignore_ascii_case(value))
70}
71
72/// Whether `value` has a namespace's shape: letters, digits and single
73/// hyphens, not starting or ending with a hyphen, at most 39 characters.
74/// Reserved names have it too; see [`is_valid_namespace`].
75pub fn is_namespace_shaped(value: &str) -> bool {
76 let bytes = value.as_bytes();
77 !bytes.is_empty()
78 && bytes.len() <= 39
79 && bytes
80 .iter()
81 .all(|byte| byte.is_ascii_lowercase() || byte.is_ascii_digit() || *byte == b'-')
82 && !value.starts_with('-')
83 && !value.ends_with('-')
84 && !value.contains("--")
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent85}
86
Merge branch 'worktree-agent-a8385d293d42c913a'87/// A workspace alias as staff typed it, trimmed and lowercased, if it can
88/// be one: shaped like a namespace and not one of the site's routes, which
89/// would always answer first. Reserved names such as `g1t` can be; whether
90/// a person or workspace already has it is identity's to check.
91pub fn aliasable_name(value: &str) -> Option<String> {
92 let value = value.trim().to_lowercase();
93 (is_namespace_shaped(&value) && !is_route_name(&value)).then_some(value)
94}
95
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent96/// A username or workspace slug as someone typed it, trimmed and
97/// lowercased, if it can be registered: what signing up, signing up with
98/// GitHub and creating a workspace each take. None when it is malformed or
99/// reserved.
100pub fn claimable_namespace(value: &str) -> Option<String> {
101 let value = value.trim().to_lowercase();
102 is_valid_namespace(&value).then_some(value)
103}
104
One kind of access token; presence and status; usernames keep their case; the tour is a miniature of the real app; icons for password managers105/// A username as someone typed it, if it can be registered: the name
106/// everything finds them by (`canonical`, lowercased: URLs, lookups,
107/// mentions, git) and the name as they wrote it (`display`, trimmed, its
108/// case kept). Letters of either case, digits and single hyphens, not
109/// starting or ending with a hyphen, 1 to 39 characters, never reserved
110/// in any case. ASCII only, so no other letter lowercases into one.
111pub fn claimable_username(value: &str) -> Option<Username> {
112 let display = value.trim();
113 if !display.is_ascii() {
114 return None;
115 }
116 let canonical = display.to_ascii_lowercase();
117 is_valid_namespace(&canonical).then(|| Username { canonical, display: display.to_owned() })
118}
119
120/// A username in its two forms: see [`claimable_username`].
121#[derive(Clone, Debug, PartialEq, Eq)]
122pub struct Username {
123 /// Lowercased: what it is stored, found, linked and mentioned by.
124 pub canonical: String,
125 /// As its owner chose to write it: what it is shown as.
126 pub display: String,
127}
128
129impl Username {
130 /// The display form, when it differs from the canonical one: what is
131 /// kept beside it (`users.display_username`); none means the same.
132 pub fn display_if_cased(&self) -> Option<&str> {
133 (self.display != self.canonical).then_some(self.display.as_str())
134 }
135}
136
Get started on mission control137/// Namespaces follow GitHub's rules: letters, digits and single hyphens,
138/// not starting or ending with a hyphen, at most 39 characters.
139pub fn is_valid_namespace(value: &str) -> bool {
Merge branch 'worktree-agent-a8385d293d42c913a'140 is_namespace_shaped(value) && !is_reserved_name(value)
Get started on mission control141}
142
143pub fn is_valid_repo_name(value: &str) -> bool {
144 !value.is_empty()
145 && value.len() <= 100
146 && value.bytes().all(|byte| {
147 byte.is_ascii_lowercase() || byte.is_ascii_digit() || matches!(byte, b'.' | b'_' | b'-')
148 })
149 && !value.starts_with('.')
150 && !value.ends_with(".git")
151 // `/<workspace>/-/…` holds the workspace's own pages.
152 && value != "-"
153}
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent154
155#[cfg(test)]
156mod tests {
157 use super::*;
158
159 #[test]
160 fn g1ts_own_names_are_reserved_in_any_case() {
161 for name in ["g1t", "G1T", "g1t-agent", "G1T-Agent", " g1t "] {
162 assert!(is_reserved_name(name), "{name}");
163 assert_eq!(claimable_namespace(name), None, "{name}");
164 }
165 assert!(!is_valid_namespace("g1t"));
166 assert!(!is_valid_namespace("g1t-agent"));
Merge account deletion: soft delete for 30 days, staff restore and purge, ghost for what remains (identity 0037)167 assert!(!is_valid_namespace("ghost"));
168 assert_eq!(claimable_namespace(" Ghost "), None);
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent169 }
170
171 #[test]
172 fn names_near_g1ts_are_anyones() {
173 assert!(!is_reserved_name("g1t-agents"));
174 assert_eq!(claimable_namespace("G1T-Bot").as_deref(), Some("g1t-bot"));
175 assert_eq!(claimable_namespace(" Ana ").as_deref(), Some("ana"));
176 assert_eq!(claimable_namespace("an--a"), None);
177 }
Merge branch 'worktree-agent-a8385d293d42c913a'178
179 #[test]
One kind of access token; presence and status; usernames keep their case; the tour is a miniature of the real app; icons for password managers180 fn usernames_keep_their_case_and_are_found_without_it() {
181 let name = claimable_username(" Octo-Cat ").unwrap();
182 assert_eq!(name.canonical, "octo-cat");
183 assert_eq!(name.display, "Octo-Cat");
184 assert_eq!(name.display_if_cased(), Some("Octo-Cat"));
185 let plain = claimable_username("ana").unwrap();
186 assert_eq!(plain.display_if_cased(), None);
187 assert_eq!(claimable_username("A").unwrap().canonical, "a");
188 assert_eq!(claimable_username(&"Z".repeat(39)).unwrap().display.len(), 39);
189 }
190
191 #[test]
192 fn usernames_follow_the_common_rules_in_any_case() {
193 let long = "a".repeat(40);
194 for bad in ["", " ", "-Ana", "Ana-", "An--a", "An_a", "Ana.B", "Ana B", long.as_str()] {
195 assert_eq!(claimable_username(bad), None, "{bad:?}");
196 }
197 // Reserved whatever the case: routes and g1t's own.
198 for reserved in ["G1T", "G1t-Agent", "Ghost", "Settings", "API", "U"] {
199 assert_eq!(claimable_username(reserved), None, "{reserved}");
200 }
201 // A letter that lowercases into ASCII is not one: the Kelvin sign is not K.
202 assert_eq!(claimable_username("\u{212A}elvin"), None);
203 assert_eq!(claimable_username("Ana\u{0301}"), None);
204 }
205
206 #[test]
Merge branch 'worktree-agent-a8385d293d42c913a'207 fn g1t_can_be_an_alias_but_a_route_cannot() {
208 assert_eq!(aliasable_name(" G1T ").as_deref(), Some("g1t"));
209 assert_eq!(aliasable_name("acme-corp").as_deref(), Some("acme-corp"));
210 for name in ["settings", "api", "login", "Explore", "-acme", "ac--me", "acme_inc", "", "a.b"] {
211 assert_eq!(aliasable_name(name), None, "{name}");
212 }
213 assert_eq!(aliasable_name(&"a".repeat(40)), None);
214 // Still nobody's to register.
215 assert!(is_reserved_name("g1t") && !is_route_name("g1t"));
216 }
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent217}

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