Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

g1t.sh
273 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] = &[
The balance shows what was spent this month and in total4 "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",
The balance shows what was spent this month and in total16 "pulls",
17 "issues",
18 "oauth",
19 "assets",
20 "avatars",
21 "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",
The balance shows what was spent this month and in total29 "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",
The balance shows what was spent this month and in total35 "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",
The balance shows what was spent this month and in total51];
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
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9'60/// The GitHub accounts whose actions workflows use most. A workflow's
61/// `uses: actions/setup-node@v4` means GitHub's `actions`, so nobody may
62/// register these on g1t and be mistaken for them. They are not reserved
63/// as [`is_reserved_name`] is: an account that already has one keeps it,
64/// mentions and invitations still find it, and staff can give one, as an
65/// alias, to the account that proves it owns the name on GitHub.
66const ACTION_OWNERS: &[&str] = &[
67 "actions", "actions-ecosystem", "actions-rs", "amannn", "anchore", "appleboy", "aquasecurity",
68 "arduino", "astral-sh", "aws-actions", "azure", "browser-actions", "bufbuild", "cachix",
69 "changesets", "chromaui", "cloudflare", "codecov", "conda-incubator", "coursier", "crazy-max",
70 "cypress-io", "dawidd6", "denoland", "dependabot", "dessant", "determinatesystems",
71 "devcontainers", "digitalocean", "docker", "dorny", "dtolnay", "endbug", "erlef",
72 "firebaseextended", "fluxcd", "getsentry", "github", "golangci", "google-github-actions",
73 "googleapis", "goreleaser", "gradle", "gradle-update", "hadolint", "hashicorp",
74 "haskell-actions", "hecrj", "helm", "ilammy", "jamesives", "jetbrains", "julia-actions",
75 "kubernetes-sigs", "lukka", "marocchino", "microsoft", "mikepenz", "mozilla-actions", "msys2",
76 "mxschmitt", "ncipollo", "netlify", "nick-fields", "nwtgck", "octokit", "ossf", "oven-sh",
77 "peaceiris", "peter-evans", "pnpm", "pre-commit", "pypa", "r-lib", "release-drafter",
78 "renovatebot", "reviewdog", "rhysd", "ruby", "rust-lang", "sbt", "shivammathur", "sigstore",
79 "slackapi", "slsa-framework", "snyk", "softprops", "sonarsource", "stefanzweifel",
80 "step-security", "styfle", "subosito", "supabase", "super-linter", "superfly", "svenstaro",
81 "swatinem", "taiki-e", "tj-actions", "treosh", "vercel", "webfactory",
82];
83
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent84/// Whether `value`, whatever its case, is a name nobody can register or
85/// rename a workspace to: a route, or g1t's own.
86pub fn is_reserved_name(value: &str) -> bool {
Merge branch 'worktree-agent-a8385d293d42c913a'87 is_route_name(value) || OWN.iter().any(|own| own.eq_ignore_ascii_case(value.trim()))
88}
89
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9'90/// Whether `value`, whatever its case, names one of the GitHub accounts
91/// whose actions workflows use most: see [`ACTION_OWNERS`].
92pub fn is_action_owner_name(value: &str) -> bool {
93 ACTION_OWNERS.iter().any(|owner| owner.eq_ignore_ascii_case(value.trim()))
94}
95
96/// Whether a person or workspace may take `value` as its name now, by
97/// signing up, creating a workspace or renaming one: a valid namespace
98/// that no well-known GitHub action owner goes by.
99pub fn is_claimable_name(value: &str) -> bool {
100 is_valid_namespace(value) && !is_action_owner_name(value)
101}
102
Merge branch 'worktree-agent-a8385d293d42c913a'103/// Whether `value`, whatever its case, is one of the site's own routes.
104pub 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-agent105 let value = value.trim();
Merge branch 'worktree-agent-a8385d293d42c913a'106 ROUTES.iter().any(|route| route.eq_ignore_ascii_case(value))
107}
108
109/// Whether `value` has a namespace's shape: letters, digits and single
110/// hyphens, not starting or ending with a hyphen, at most 39 characters.
111/// Reserved names have it too; see [`is_valid_namespace`].
112pub fn is_namespace_shaped(value: &str) -> bool {
113 let bytes = value.as_bytes();
114 !bytes.is_empty()
115 && bytes.len() <= 39
116 && bytes
117 .iter()
118 .all(|byte| byte.is_ascii_lowercase() || byte.is_ascii_digit() || *byte == b'-')
119 && !value.starts_with('-')
120 && !value.ends_with('-')
121 && !value.contains("--")
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent122}
123
Merge branch 'worktree-agent-a8385d293d42c913a'124/// A workspace alias as staff typed it, trimmed and lowercased, if it can
125/// be one: shaped like a namespace and not one of the site's routes, which
126/// would always answer first. Reserved names such as `g1t` can be; whether
127/// a person or workspace already has it is identity's to check.
128pub fn aliasable_name(value: &str) -> Option<String> {
129 let value = value.trim().to_lowercase();
130 (is_namespace_shaped(&value) && !is_route_name(&value)).then_some(value)
131}
132
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent133/// A username or workspace slug as someone typed it, trimmed and
134/// lowercased, if it can be registered: what signing up, signing up with
135/// GitHub and creating a workspace each take. None when it is malformed or
136/// reserved.
137pub fn claimable_namespace(value: &str) -> Option<String> {
138 let value = value.trim().to_lowercase();
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9'139 is_claimable_name(&value).then_some(value)
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent140}
141
One kind of access token; presence and status; usernames keep their case; the tour is a miniature of the real app; icons for password managers142/// A username as someone typed it, if it can be registered: the name
143/// everything finds them by (`canonical`, lowercased: URLs, lookups,
144/// mentions, git) and the name as they wrote it (`display`, trimmed, its
145/// case kept). Letters of either case, digits and single hyphens, not
146/// starting or ending with a hyphen, 1 to 39 characters, never reserved
147/// in any case. ASCII only, so no other letter lowercases into one.
148pub fn claimable_username(value: &str) -> Option<Username> {
149 let display = value.trim();
150 if !display.is_ascii() {
151 return None;
152 }
153 let canonical = display.to_ascii_lowercase();
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9'154 is_claimable_name(&canonical).then(|| Username { canonical, display: display.to_owned() })
One kind of access token; presence and status; usernames keep their case; the tour is a miniature of the real app; icons for password managers155}
156
157/// A username in its two forms: see [`claimable_username`].
158#[derive(Clone, Debug, PartialEq, Eq)]
159pub struct Username {
160 /// Lowercased: what it is stored, found, linked and mentioned by.
161 pub canonical: String,
162 /// As its owner chose to write it: what it is shown as.
163 pub display: String,
164}
165
166impl Username {
167 /// The display form, when it differs from the canonical one: what is
168 /// kept beside it (`users.display_username`); none means the same.
169 pub fn display_if_cased(&self) -> Option<&str> {
170 (self.display != self.canonical).then_some(self.display.as_str())
171 }
172}
173
The balance shows what was spent this month and in total174/// Namespaces follow GitHub's rules: letters, digits and single hyphens,
175/// not starting or ending with a hyphen, at most 39 characters.
176pub fn is_valid_namespace(value: &str) -> bool {
Merge branch 'worktree-agent-a8385d293d42c913a'177 is_namespace_shaped(value) && !is_reserved_name(value)
The balance shows what was spent this month and in total178}
179
180pub fn is_valid_repo_name(value: &str) -> bool {
181 !value.is_empty()
182 && value.len() <= 100
183 && value.bytes().all(|byte| {
184 byte.is_ascii_lowercase() || byte.is_ascii_digit() || matches!(byte, b'.' | b'_' | b'-')
185 })
186 && !value.starts_with('.')
187 && !value.ends_with(".git")
188 // `/<workspace>/-/…` holds the workspace's own pages.
189 && value != "-"
190}
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent191
192#[cfg(test)]
193mod tests {
194 use super::*;
195
196 #[test]
197 fn g1ts_own_names_are_reserved_in_any_case() {
198 for name in ["g1t", "G1T", "g1t-agent", "G1T-Agent", " g1t "] {
199 assert!(is_reserved_name(name), "{name}");
200 assert_eq!(claimable_namespace(name), None, "{name}");
201 }
202 assert!(!is_valid_namespace("g1t"));
203 assert!(!is_valid_namespace("g1t-agent"));
Merge account deletion: soft delete for 30 days, staff restore and purge, ghost for what remains (identity 0037)204 assert!(!is_valid_namespace("ghost"));
205 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-agent206 }
207
208 #[test]
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9'209 fn github_action_owners_cannot_be_claimed_but_stay_findable() {
210 for name in ["actions", "Actions", " docker ", "aws-actions", "Swatinem"] {
211 assert!(is_action_owner_name(name), "{name}");
212 assert_eq!(claimable_namespace(name), None, "{name}");
213 assert_eq!(claimable_username(name), None, "{name}");
214 }
215 // An account that has one already is still a valid name to find,
216 // mention and invite, can be given one as an alias, and is not
217 // protected the way g1t's own names are.
218 assert!(is_valid_namespace("actions"));
219 assert!(!is_reserved_name("actions"));
220 assert_eq!(aliasable_name("Actions").as_deref(), Some("actions"));
221 assert!(!is_action_owner_name("actions-team"));
222 for owner in ACTION_OWNERS {
223 assert!(is_namespace_shaped(owner) && *owner == owner.to_ascii_lowercase(), "{owner}");
224 }
225 }
226
227 #[test]
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent228 fn names_near_g1ts_are_anyones() {
229 assert!(!is_reserved_name("g1t-agents"));
230 assert_eq!(claimable_namespace("G1T-Bot").as_deref(), Some("g1t-bot"));
231 assert_eq!(claimable_namespace(" Ana ").as_deref(), Some("ana"));
232 assert_eq!(claimable_namespace("an--a"), None);
233 }
Merge branch 'worktree-agent-a8385d293d42c913a'234
235 #[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 managers236 fn usernames_keep_their_case_and_are_found_without_it() {
237 let name = claimable_username(" Octo-Cat ").unwrap();
238 assert_eq!(name.canonical, "octo-cat");
239 assert_eq!(name.display, "Octo-Cat");
240 assert_eq!(name.display_if_cased(), Some("Octo-Cat"));
241 let plain = claimable_username("ana").unwrap();
242 assert_eq!(plain.display_if_cased(), None);
243 assert_eq!(claimable_username("A").unwrap().canonical, "a");
244 assert_eq!(claimable_username(&"Z".repeat(39)).unwrap().display.len(), 39);
245 }
246
247 #[test]
248 fn usernames_follow_the_common_rules_in_any_case() {
249 let long = "a".repeat(40);
250 for bad in ["", " ", "-Ana", "Ana-", "An--a", "An_a", "Ana.B", "Ana B", long.as_str()] {
251 assert_eq!(claimable_username(bad), None, "{bad:?}");
252 }
253 // Reserved whatever the case: routes and g1t's own.
254 for reserved in ["G1T", "G1t-Agent", "Ghost", "Settings", "API", "U"] {
255 assert_eq!(claimable_username(reserved), None, "{reserved}");
256 }
257 // A letter that lowercases into ASCII is not one: the Kelvin sign is not K.
258 assert_eq!(claimable_username("\u{212A}elvin"), None);
259 assert_eq!(claimable_username("Ana\u{0301}"), None);
260 }
261
262 #[test]
Merge branch 'worktree-agent-a8385d293d42c913a'263 fn g1t_can_be_an_alias_but_a_route_cannot() {
264 assert_eq!(aliasable_name(" G1T ").as_deref(), Some("g1t"));
265 assert_eq!(aliasable_name("acme-corp").as_deref(), Some("acme-corp"));
266 for name in ["settings", "api", "login", "Explore", "-acme", "ac--me", "acme_inc", "", "a.b"] {
267 assert_eq!(aliasable_name(name), None, "{name}");
268 }
269 assert_eq!(aliasable_name(&"a".repeat(40)), None);
270 // Still nobody's to register.
271 assert!(is_reserved_name("g1t") && !is_route_name("g1t"));
272 }
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent273}

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