Skip to content
1,529 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.

Fast pages, required checks on the branch, self-hosted runners, honest incidents1//! REST: each route maps an HTTP request onto one operation.
2
3use serde_json::{Map, Value};
4
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb975use crate::about::AboutOp;
The artifacts service is services/artifacts, the Worker g1t-artifacts, bound as ARTIFACTS by the API, the site and the agents; its live rooms move to it with a Durable Object transfer from g1t-docs-service, and its database, bucket, indexes and queue keep their names. The git store's binding and settings are GITSTORE, its ops scripts gitstore-*, and workflow run artifacts keep their compatible API under run_artifacts modules. The deploy tool puts a Worker that has never deployed before the Workers in its stage that bind to it, and the deploy guide gives the cutover runbook.6use crate::run_artifacts::ArtifactsOp;
Merge GitHub pull request #8 (claude/cool-newton-7nm8r9): Docker deployments on g1t.page behind a switch that stays off until Cloudflare's container APIs are checked live, workflows a workspace manages in its repositories, tarball and zipball downloads, refs/pull/* kept to g1t alone, framework detection for g1t.page builds, and a workflow's event payload matching GitHub's.7use crate::archives::ArchiveOp;
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r98use crate::customize::CustomizeOp;
Merge branch 'worktree-agent-a5a5de74d8863d554' into worktree-agent-a16631325aecf58ca9use crate::deploy_keys::DeployKeysOp;
Merge branch 'mirroring' into artifacts-mode10use crate::mirrors::MirrorsOp;
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb9711use crate::deployments::DeploymentsOp;
Merge packages: roles, Actions access, source label, soft delete, API12use crate::packages::PackagesOp;
Merge main into Artifacts Phase 213use crate::folios::FoliosOp;
Merge branch 'worktree-agent-a3abfcce648e87dca'14use crate::protection::ProtectionOp;
API and MCP for a workspace's personal access token rules, members' tokens and approvals15use crate::token_policy::TokenOp;
Fast pages, required checks on the branch, self-hosted runners, honest incidents16use crate::operations::Op;
Merge checks: statuses and check runs on every commit17use crate::checks::ChecksOp;
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge18use crate::rules::RulesOp;
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar19use crate::security::SecurityOp;
Fast pages, required checks on the branch, self-hosted runners, honest incidents20
21pub struct Route {
22 pub method: &'static str,
23 /// Segments starting with `:` are parameters.
24 pub path: &'static str,
25 pub op: Op,
26 /// Query parameters the route reads, as `(name in the URL, input name)`.
27 pub query: &'static [(&'static str, &'static str)],
28}
29
30const fn route(
31 method: &'static str,
32 path: &'static str,
33 op: Op,
34 query: &'static [(&'static str, &'static str)],
35) -> Route {
36 Route {
37 method,
38 path,
39 op,
40 query,
41 }
42}
43
44pub const ROUTES: &[Route] = &[
45 route("GET", "/user", Op::Whoami, &[]),
46 route("POST", "/workspaces", Op::CreateWorkspace, &[]),
Merge branch 'worktree-agent-ad7c6d88d93adc817'47 route("GET", "/workspaces/:workspace", Op::GetWorkspace, &[]),
Fast pages, required checks on the branch, self-hosted runners, honest incidents48 route("DELETE", "/workspaces/:workspace", Op::DeleteWorkspace, &[]),
49 route("GET", "/user/emails", Op::ListEmails, &[]),
50 route("POST", "/user/emails", Op::AddEmail, &[]),
Merge email confirmation gate: a code and a link, nothing until confirmed (identity 0036)51 route("POST", "/user/emails/confirm", Op::ConfirmEmail, &[]),
Fast pages, required checks on the branch, self-hosted runners, honest incidents52 route("DELETE", "/user/emails/:email", Op::RemoveEmail, &[]),
53 route("PATCH", "/user/email-settings", Op::UpdateEmailSettings, &[]),
54 route("GET", "/user/invites", Op::ListInvites, &[]),
55 route("POST", "/user/invites", Op::CreateInvite, &[]),
56 route("DELETE", "/user/invites/:id", Op::RevokeInvite, &[]),
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)57 // Invitations to workspaces waiting for your answer.
58 route("GET", "/user/invitations", Op::ListInvitations, &[]),
59 route("POST", "/user/invitations/:id/accept", Op::AcceptInvitation, &[]),
60 route("POST", "/user/invitations/:id/decline", Op::DeclineInvitation, &[]),
Fast pages, required checks on the branch, self-hosted runners, honest incidents61 route("GET", "/workspaces/:workspace/invitations", Op::ListWorkspaceInvites, &[]),
API and MCP for a workspace's personal access token rules, members' tokens and approvals62 // A workspace's rules for personal access tokens, and its members' tokens.
63 route("GET", "/workspaces/:workspace/personal-access-token-policy", Op::Tokens(TokenOp::GetTokenPolicy), &[]),
64 route("PATCH", "/workspaces/:workspace/personal-access-token-policy", Op::Tokens(TokenOp::SetTokenPolicy), &[]),
One kind of access token; presence and status; usernames keep their case; the tour is a miniature of the real app; icons for password managers65 route("GET", "/workspaces/:workspace/personal-access-tokens", Op::Tokens(TokenOp::ListMemberTokens), &[]),
API and MCP for a workspace's personal access token rules, members' tokens and approvals66 route("POST", "/workspaces/:workspace/personal-access-tokens/:id", Op::Tokens(TokenOp::RevokeMemberToken), &[]),
67 route("GET", "/workspaces/:workspace/personal-access-token-requests", Op::Tokens(TokenOp::ListTokenRequests), &[]),
68 route("POST", "/workspaces/:workspace/personal-access-token-requests/:id", Op::Tokens(TokenOp::ReviewTokenRequest), &[]),
Fast pages, required checks on the branch, self-hosted runners, honest incidents69 route("POST", "/workspaces/:workspace/invitations", Op::InviteMember, &[]),
70 route("DELETE", "/workspaces/:workspace/invitations/:id", Op::RevokeWorkspaceInvite, &[]),
71 // Who has access. GitHub's addresses, but for adding someone, which
72 // takes an email address as well as a username.
73 route("GET", "/repos/:owner/:name/collaborators", Op::ListCollaborators, &[]),
74 route("POST", "/repos/:owner/:name/collaborators", Op::AddCollaborator, &[]),
75 route("PATCH", "/repos/:owner/:name/collaborators/:username", Op::UpdateCollaborator, &[]),
76 route("DELETE", "/repos/:owner/:name/collaborators/:username", Op::RemoveCollaborator, &[]),
77 route(
78 "GET",
79 "/repos/:owner/:name/collaborators/:username/permission",
80 Op::GetCollaboratorPermission,
81 &[],
82 ),
83 route("GET", "/repos/:owner/:name/invitations", Op::ListRepoInvitations, &[]),
84 route("DELETE", "/repos/:owner/:name/invitations/:id", Op::RevokeRepoInvitation, &[]),
85 route("GET", "/user/repository_invitations", Op::ListMyRepoInvitations, &[]),
Merge branch 'worktree-agent-a5a5de74d8863d554' into worktree-agent-a16631325aecf58ca86 // Deploy keys, at GitHub's addresses.
87 route("GET", "/repos/:owner/:name/keys", Op::DeployKeys(DeployKeysOp::ListDeployKeys), &[]),
88 route("POST", "/repos/:owner/:name/keys", Op::DeployKeys(DeployKeysOp::CreateDeployKey), &[]),
89 route("GET", "/repos/:owner/:name/keys/:id", Op::DeployKeys(DeployKeysOp::GetDeployKey), &[]),
90 route("DELETE", "/repos/:owner/:name/keys/:id", Op::DeployKeys(DeployKeysOp::DeleteDeployKey), &[]),
Merge branch 'mirroring' into artifacts-mode91 // Mirroring: a repository's remotes, takeovers and hand-backs.
92 route("GET", "/repos/:owner/:name/mirror", Op::Mirrors(MirrorsOp::GetMirror), &[]),
93 route("GET", "/repos/:owner/:name/mirror/hand-back", Op::Mirrors(MirrorsOp::GetHandBackPlan), &[]),
94 route("POST", "/repos/:owner/:name/mirror/take-over", Op::Mirrors(MirrorsOp::TakeOver), &[]),
95 route("POST", "/repos/:owner/:name/mirror/ci", Op::Mirrors(MirrorsOp::SetCiFailover), &[]),
96 route("POST", "/repos/:owner/:name/mirror/hand-back", Op::Mirrors(MirrorsOp::HandBack), &[]),
97 route("POST", "/repos/:owner/:name/mirror/move-to-g1t", Op::Mirrors(MirrorsOp::MoveToG1t), &[]),
98 route("POST", "/repos/:owner/:name/mirror/sync", Op::Mirrors(MirrorsOp::SyncMirror), &[]),
99 route("POST", "/repos/:owner/:name/mirror/remotes", Op::Mirrors(MirrorsOp::AddRemote), &[]),
100 route("PATCH", "/repos/:owner/:name/mirror/remotes/:id", Op::Mirrors(MirrorsOp::UpdateRemote), &[]),
101 route("DELETE", "/repos/:owner/:name/mirror/remotes/:id", Op::Mirrors(MirrorsOp::RemoveRemote), &[]),
API: notifications over REST and MCP, with notifications scopes102 // Your notifications: threads, marking them, and what you subscribe
103 // to and watch. GitHub's addresses, with g1t's saved and snoozed.
104 route("GET", "/notifications", Op::ListNotifications, &[("all", "all"), ("participating", "participating"), ("view", "view"), ("reason", "reason"), ("severity", "severity"), ("since", "since"), ("before", "before"), ("cursor", "cursor"), ("per_page", "per_page")]),
105 route("PUT", "/notifications", Op::MarkNotificationsRead, &[]),
106 route("GET", "/notifications/threads/:id", Op::GetNotificationThread, &[]),
107 route("PATCH", "/notifications/threads/:id", Op::MarkThreadRead, &[]),
108 route("DELETE", "/notifications/threads/:id", Op::MarkThreadDone, &[]),
109 route("PUT", "/notifications/threads/:id/saved", Op::SaveThread, &[]),
110 route("DELETE", "/notifications/threads/:id/saved", Op::SaveThread, &[]),
111 route("PUT", "/notifications/threads/:id/snooze", Op::SnoozeThread, &[]),
112 route("DELETE", "/notifications/threads/:id/snooze", Op::SnoozeThread, &[]),
113 route("GET", "/notifications/threads/:id/subscription", Op::GetThreadSubscription, &[]),
114 route("PUT", "/notifications/threads/:id/subscription", Op::SetThreadSubscription, &[]),
115 route("DELETE", "/notifications/threads/:id/subscription", Op::DeleteThreadSubscription, &[]),
116 route("GET", "/repos/:owner/:name/notifications", Op::ListNotifications, &[("all", "all"), ("participating", "participating"), ("view", "view"), ("reason", "reason"), ("severity", "severity"), ("since", "since"), ("before", "before"), ("cursor", "cursor"), ("per_page", "per_page")]),
117 route("PUT", "/repos/:owner/:name/notifications", Op::MarkNotificationsRead, &[]),
118 route("GET", "/repos/:owner/:name/subscription", Op::GetRepoSubscription, &[]),
119 route("PUT", "/repos/:owner/:name/subscription", Op::SetRepoSubscription, &[]),
120 route("DELETE", "/repos/:owner/:name/subscription", Op::DeleteRepoSubscription, &[]),
121 route("GET", "/repos/:owner/:name/issues/:number/subscription", Op::GetThreadSubscription, &[]),
122 route("PUT", "/repos/:owner/:name/issues/:number/subscription", Op::SetThreadSubscription, &[]),
123 route("DELETE", "/repos/:owner/:name/issues/:number/subscription", Op::DeleteThreadSubscription, &[]),
124 route("GET", "/user/subscriptions", Op::ListWatchedRepos, &[]),
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97125 // Stars: yours, and who starred a repository.
126 route("GET", "/user/starred", Op::About(AboutOp::ListStarred), &[]),
127 route("GET", "/user/starred/:owner/:name", Op::About(AboutOp::CheckStarred), &[]),
128 route("PUT", "/user/starred/:owner/:name", Op::About(AboutOp::Star), &[]),
129 route("DELETE", "/user/starred/:owner/:name", Op::About(AboutOp::Unstar), &[]),
130 route("GET", "/repos/:owner/:name/stargazers", Op::About(AboutOp::ListStargazers), &[("page", "page")]),
131 // What the default branch says about a repository, kept by commit.
132 route("GET", "/repos/:owner/:name/languages", Op::About(AboutOp::GetLanguages), &[]),
133 route("GET", "/repos/:owner/:name/contributors", Op::About(AboutOp::ListContributors), &[]),
134 route("GET", "/repos/:owner/:name/license", Op::About(AboutOp::GetLicense), &[]),
135 // Releases: `latest` and `tags/…` before an id.
136 route("GET", "/repos/:owner/:name/releases", Op::About(AboutOp::ListReleases), &[]),
137 route("POST", "/repos/:owner/:name/releases", Op::About(AboutOp::CreateRelease), &[]),
138 route("GET", "/repos/:owner/:name/releases/latest", Op::About(AboutOp::GetLatestRelease), &[]),
139 route("GET", "/repos/:owner/:name/releases/tags/:tag", Op::About(AboutOp::GetReleaseByTag), &[]),
140 route("GET", "/repos/:owner/:name/releases/:id", Op::About(AboutOp::GetRelease), &[]),
141 route("PATCH", "/repos/:owner/:name/releases/:id", Op::About(AboutOp::UpdateRelease), &[]),
142 route("DELETE", "/repos/:owner/:name/releases/:id", Op::About(AboutOp::DeleteRelease), &[]),
API: pinned projects over REST and MCP143 // Your pinned projects in a workspace, in your order.
144 route("GET", "/user/pinned_projects/:workspace", Op::ListPinnedProjects, &[]),
145 route("PUT", "/user/pinned_projects/:workspace", Op::ReorderPinnedProjects, &[]),
146 route("PUT", "/user/pinned_projects/:workspace/:project", Op::PinProject, &[]),
147 route("DELETE", "/user/pinned_projects/:workspace/:project", Op::UnpinProject, &[]),
Fast pages, required checks on the branch, self-hosted runners, honest incidents148 route("PATCH", "/user/repository_invitations/:id", Op::AcceptRepoInvitation, &[]),
149 route("DELETE", "/user/repository_invitations/:id", Op::DeclineRepoInvitation, &[]),
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily150 route("PATCH", "/workspaces/:workspace", Op::UpdateWorkspace, &[]),
Merge main (membership, two-factor, GitHub repo roles) into tokens151 // Members and owners: GitHub's organization members, by username.
152 route("GET", "/workspaces/:workspace/members", Op::ListMembers, &[]),
153 route("PATCH", "/workspaces/:workspace/members/:username", Op::UpdateMember, &[]),
154 route("DELETE", "/workspaces/:workspace/members/:username", Op::RemoveMember, &[]),
155 route("POST", "/workspaces/:workspace/transfer_ownership", Op::TransferOwnership, &[]),
156 route("DELETE", "/user/memberships/:workspace", Op::LeaveWorkspace, &[]),
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97157 // A workspace's projects: what each is, where it runs, its links.
158 route("GET", "/workspaces/:workspace/projects", Op::ListProjects, &[]),
159 route("GET", "/workspaces/:workspace/projects/:project", Op::GetProject, &[]),
160 route("PATCH", "/workspaces/:workspace/projects/:project", Op::UpdateProject, &[]),
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily161 route("PUT", "/workspaces/:workspace/base_permission", Op::SetBasePermission, &[]),
Fast pages, required checks on the branch, self-hosted runners, honest incidents162 route(
163 "GET",
164 "/workspaces/:workspace/outside_collaborators",
165 Op::ListOutsideCollaborators,
166 &[],
167 ),
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar168 // Teams: a workspace's groups of members, with roles on repositories.
169 route("GET", "/workspaces/:workspace/teams", Op::ListTeams, &[("q", "query")]),
170 route("POST", "/workspaces/:workspace/teams", Op::CreateTeam, &[]),
171 route("GET", "/workspaces/:workspace/teams/:team", Op::GetTeam, &[]),
172 route("PATCH", "/workspaces/:workspace/teams/:team", Op::UpdateTeam, &[]),
173 route("DELETE", "/workspaces/:workspace/teams/:team", Op::DeleteTeam, &[]),
174 route(
175 "GET",
176 "/workspaces/:workspace/teams/:team/members",
177 Op::ListTeamMembers,
178 &[("include_child_teams", "include_child_teams")],
179 ),
180 route("PUT", "/workspaces/:workspace/teams/:team/members/:username", Op::SetTeamMember, &[]),
181 route("DELETE", "/workspaces/:workspace/teams/:team/members/:username", Op::RemoveTeamMember, &[]),
182 route("GET", "/workspaces/:workspace/teams/:team/teams", Op::ListChildTeams, &[]),
183 route("GET", "/workspaces/:workspace/teams/:team/repos", Op::ListTeamRepos, &[]),
184 route("PUT", "/workspaces/:workspace/teams/:team/repos/:repo", Op::SetTeamRepo, &[]),
185 route("DELETE", "/workspaces/:workspace/teams/:team/repos/:repo", Op::RemoveTeamRepo, &[]),
186 route(
187 "PUT",
188 "/workspaces/:workspace/teams/:team/review_assignment",
189 Op::SetTeamReviewAssignment,
190 &[],
191 ),
192 route("GET", "/workspaces/:workspace/members/:username/teams", Op::ListUserTeams, &[]),
Usage, Billing settings and prepaid AI credit; fixes from the UX audit193 // A workspace's billing: usage, budget, AI credit and invoices.
194 route(
195 "GET",
196 "/workspaces/:workspace/usage",
197 Op::GetUsage,
198 &[("from", "from"), ("until", "until"), ("products", "products"), ("projects", "projects"), ("group_by", "group_by")],
199 ),
200 route("GET", "/workspaces/:workspace/budget", Op::GetBudget, &[]),
201 route("PUT", "/workspaces/:workspace/budget", Op::SetBudget, &[]),
202 route("GET", "/workspaces/:workspace/ai_credit", Op::GetAiCredit, &[]),
203 route("POST", "/workspaces/:workspace/ai_credit/checkout", Op::BuyAiCredit, &[]),
204 route("GET", "/workspaces/:workspace/invoices", Op::ListInvoices, &[]),
205 route("GET", "/workspaces/:workspace/billing_details", Op::GetBillingDetails, &[]),
Merge the AI Gateway: Anthropic's Messages API on a workspace's tokens206 // The AI Gateway's log of a workspace's requests.
207 route("GET", "/workspaces/:workspace/gateway/requests", Op::ListGatewayRequests, &[("limit", "limit"), ("before", "before")]),
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar208 // Code owners: the CODEOWNERS file, checked.
209 route("GET", "/repos/:owner/:name/codeowners/errors", Op::GetCodeownersErrors, &[("ref", "ref")]),
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily210 // Security alerts: secrets and vulnerable dependencies.
211 route(
212 "GET",
213 "/repos/:owner/:name/security/alerts",
214 Op::ListSecurityAlerts,
215 &[("state", "state"), ("kind", "kind")],
216 ),
217 route("POST", "/repos/:owner/:name/security/alerts/:id/dismiss", Op::DismissSecurityAlert, &[]),
218 route("POST", "/repos/:owner/:name/security/alerts/:id/reopen", Op::ReopenSecurityAlert, &[]),
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar219 // The security suite: secret scanning, code scanning, vulnerability
220 // alerts and the supply chain, at the common addresses.
221 route("GET", "/repos/:owner/:name/secret-scanning/alerts", Op::Security(SecurityOp::ListSecretAlerts), &[("state", "state"), ("secret_type", "secret_type"), ("validity", "validity"), ("bypassed", "bypassed")]),
222 route("GET", "/workspaces/:workspace/secret-scanning/alerts", Op::Security(SecurityOp::ListSecretAlerts), &[("state", "state"), ("secret_type", "secret_type"), ("validity", "validity"), ("bypassed", "bypassed")]),
223 route("GET", "/repos/:owner/:name/secret-scanning/alerts/:id", Op::Security(SecurityOp::GetSecretAlert), &[]),
224 route("PATCH", "/repos/:owner/:name/secret-scanning/alerts/:id", Op::Security(SecurityOp::UpdateSecretAlert), &[]),
225 route("GET", "/repos/:owner/:name/secret-scanning/alerts/:id/locations", Op::Security(SecurityOp::ListSecretLocations), &[]),
226 route("POST", "/repos/:owner/:name/secret-scanning/alerts/:id/bypass", Op::Security(SecurityOp::BypassPushProtection), &[]),
227 route("POST", "/repos/:owner/:name/secret-scanning/alerts/:id/validity", Op::Security(SecurityOp::CheckSecretValidity), &[]),
228 route("GET", "/workspaces/:workspace/secret-scanning/bypass-requests", Op::Security(SecurityOp::ListBypassRequests), &[("state", "state"), ("repo", "repo")]),
229 route("PATCH", "/workspaces/:workspace/secret-scanning/bypass-requests/:id", Op::Security(SecurityOp::ReviewBypassRequest), &[]),
230 route("POST", "/repos/:owner/:name/secret-scanning/custom-patterns/dry-run", Op::Security(SecurityOp::DryRunCustomPattern), &[]),
231 route("POST", "/workspaces/:workspace/secret-scanning/custom-patterns/dry-run", Op::Security(SecurityOp::DryRunCustomPattern), &[]),
232 route("GET", "/repos/:owner/:name/secret-scanning/custom-patterns", Op::Security(SecurityOp::ListCustomPatterns), &[]),
233 route("GET", "/workspaces/:workspace/secret-scanning/custom-patterns", Op::Security(SecurityOp::ListCustomPatterns), &[]),
234 route("POST", "/repos/:owner/:name/secret-scanning/custom-patterns", Op::Security(SecurityOp::CreateCustomPattern), &[]),
235 route("POST", "/workspaces/:workspace/secret-scanning/custom-patterns", Op::Security(SecurityOp::CreateCustomPattern), &[]),
236 route("PATCH", "/repos/:owner/:name/secret-scanning/custom-patterns/:id", Op::Security(SecurityOp::UpdateCustomPattern), &[]),
237 route("PATCH", "/workspaces/:workspace/secret-scanning/custom-patterns/:id", Op::Security(SecurityOp::UpdateCustomPattern), &[]),
238 route("DELETE", "/repos/:owner/:name/secret-scanning/custom-patterns/:id", Op::Security(SecurityOp::DeleteCustomPattern), &[]),
239 route("DELETE", "/workspaces/:workspace/secret-scanning/custom-patterns/:id", Op::Security(SecurityOp::DeleteCustomPattern), &[]),
240 route("GET", "/repos/:owner/:name/code-scanning/alerts", Op::Security(SecurityOp::ListCodeAlerts), &[("state", "state"), ("severity", "severity"), ("tool", "tool"), ("rule_id", "rule_id")]),
241 route("GET", "/workspaces/:workspace/code-scanning/alerts", Op::Security(SecurityOp::ListCodeAlerts), &[("state", "state"), ("severity", "severity"), ("tool", "tool"), ("rule_id", "rule_id")]),
242 route("GET", "/repos/:owner/:name/code-scanning/alerts/:number", Op::Security(SecurityOp::GetCodeAlert), &[]),
243 route("PATCH", "/repos/:owner/:name/code-scanning/alerts/:number", Op::Security(SecurityOp::UpdateCodeAlert), &[]),
244 route("GET", "/repos/:owner/:name/code-scanning/analyses", Op::Security(SecurityOp::ListAnalyses), &[]),
245 route("POST", "/repos/:owner/:name/code-scanning/sarifs", Op::Security(SecurityOp::UploadSarif), &[]),
246 route("GET", "/repos/:owner/:name/code-scanning/sarifs/:id", Op::Security(SecurityOp::GetSarifUpload), &[]),
247 route("GET", "/repos/:owner/:name/vulnerability-alerts", Op::Security(SecurityOp::ListVulnerabilityAlerts), &[("state", "state"), ("severity", "severity"), ("ecosystem", "ecosystem"), ("package", "package")]),
248 route("GET", "/workspaces/:workspace/vulnerability-alerts", Op::Security(SecurityOp::ListVulnerabilityAlerts), &[("state", "state"), ("severity", "severity"), ("ecosystem", "ecosystem"), ("package", "package")]),
249 route("GET", "/repos/:owner/:name/vulnerability-alerts/:id", Op::Security(SecurityOp::GetVulnerabilityAlert), &[]),
250 route("PATCH", "/repos/:owner/:name/vulnerability-alerts/:id", Op::Security(SecurityOp::UpdateVulnerabilityAlert), &[]),
251 route("POST", "/repos/:owner/:name/security/alerts/:id/fix", Op::Security(SecurityOp::FixAlert), &[]),
252 route("GET", "/repos/:owner/:name/dependency-graph", Op::Security(SecurityOp::GetDependencyGraph), &[]),
253 route("GET", "/repos/:owner/:name/dependency-graph/sbom", Op::Security(SecurityOp::GetSbom), &[]),
254 route("GET", "/repos/:owner/:name/dependency-graph/compare/:basehead", Op::Security(SecurityOp::CompareDependencies), &[]),
255 route("GET", "/repos/:owner/:name/security/settings", Op::Security(SecurityOp::GetSettings), &[]),
256 route("PATCH", "/repos/:owner/:name/security/settings", Op::Security(SecurityOp::UpdateSettings), &[]),
257 route("GET", "/workspaces/:workspace/security/settings", Op::Security(SecurityOp::GetWorkspaceSettings), &[]),
258 route("PATCH", "/workspaces/:workspace/security/settings", Op::Security(SecurityOp::UpdateWorkspaceSettings), &[]),
259 route("GET", "/workspaces/:workspace/security/overview", Op::Security(SecurityOp::GetOverview), &[("days", "days")]),
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9260 // Reporting: a repository's advisories, and the vulnerability reports sent to its maintainers.
261 route("GET", "/repos/:owner/:name/security/advisories", Op::Security(SecurityOp::ListAdvisories), &[("state", "state")]),
262 route("POST", "/repos/:owner/:name/security/advisories", Op::Security(SecurityOp::CreateAdvisory), &[]),
263 route("GET", "/repos/:owner/:name/security/advisories/:id", Op::Security(SecurityOp::GetAdvisory), &[]),
264 route("PATCH", "/repos/:owner/:name/security/advisories/:id", Op::Security(SecurityOp::UpdateAdvisory), &[]),
265 route("POST", "/repos/:owner/:name/security/advisories/:id/state", Op::Security(SecurityOp::SetAdvisoryState), &[]),
266 route("GET", "/repos/:owner/:name/security/reports", Op::Security(SecurityOp::ListReports), &[("state", "state")]),
267 route("POST", "/repos/:owner/:name/security/reports", Op::Security(SecurityOp::ReportVulnerability), &[]),
268 route("PATCH", "/repos/:owner/:name/security/reports/:id", Op::Security(SecurityOp::ReviewReport), &[]),
Fast pages, required checks on the branch, self-hosted runners, honest incidents269 route("GET", "/repos", Op::ListRepos, &[("q", "query")]),
270 route(
271 "GET",
272 "/search",
273 Op::Search,
274 &[("q", "query"), ("type", "type"), ("page", "page"), ("per_page", "per_page")],
275 ),
276 route("POST", "/repos", Op::CreateRepo, &[]),
277 route("GET", "/repos/:owner/:name", Op::GetRepo, &[]),
Merge GitHub pull request #8 (claude/cool-newton-7nm8r9): Docker deployments on g1t.page behind a switch that stays off until Cloudflare's container APIs are checked live, workflows a workspace manages in its repositories, tarball and zipball downloads, refs/pull/* kept to g1t alone, framework detection for g1t.page builds, and a workflow's event payload matching GitHub's.278 // A branch, tag or commit as one archive: a redirect to a link good for
279 // ten minutes, as GitHub's go to codeload.
280 route("GET", "/repos/:owner/:name/tarball/:ref", Op::Archives(ArchiveOp::DownloadTarball), &[]),
281 route("GET", "/repos/:owner/:name/zipball/:ref", Op::Archives(ArchiveOp::DownloadZipball), &[]),
Fast pages, required checks on the branch, self-hosted runners, honest incidents282 route("PATCH", "/repos/:owner/:name", Op::UpdateRepo, &[]),
283 route("POST", "/repos/:owner/:name/transfer", Op::TransferRepo, &[]),
284 route("DELETE", "/repos/:owner/:name", Op::DeleteRepo, &[]),
285 route(
286 "GET",
287 "/workspaces/:workspace/repos/deleted",
288 Op::ListDeletedRepos,
289 &[],
290 ),
291 route("POST", "/repos/:owner/:name/restore", Op::RestoreRepo, &[]),
292 route("POST", "/repos/:owner/:name/purge", Op::PurgeRepo, &[]),
293 route("POST", "/repos/:owner/:name/rename", Op::RenameRepo, &[]),
294 route("POST", "/repos/:owner/:name/archive", Op::ArchiveRepo, &[]),
295 route("POST", "/repos/:owner/:name/unarchive", Op::UnarchiveRepo, &[]),
296 route(
297 "POST",
298 "/repos/:owner/:name/visibility",
299 Op::SetRepoVisibility,
300 &[],
301 ),
302 route(
303 "POST",
304 "/repos/:owner/:name/branches/:branch/rename",
305 Op::RenameBranch,
306 &[],
307 ),
The Branches page does things to branches now. It has tabs (Overview, Yours, Active, Stale, All, kept in the address as ?tab=) and search; Overview shows the default branch, then the first five of your branches, the active ones and the stale ones, each linking to the rest. Each branch is a row with its name in a code chip with a copy button, who made its latest commit and when, that commit's checks, behind and ahead counts with bars drawn against the largest shown, and its pull request (the open one, else the newest merged or closed) with its state; on a phone the columns fold under the name. Yours is branches whose latest commit is the viewer's; stale is no commit in 90 days.308 route("POST", "/repos/:owner/:name/branches", Op::CreateBranch, &[]),
309 route("DELETE", "/repos/:owner/:name/branches/:branch", Op::DeleteBranch, &[("head", "head")]),
Fast pages, required checks on the branch, self-hosted runners, honest incidents310 route(
311 "GET",
312 "/repos/:owner/:name/settings",
313 Op::GetRepoSettings,
314 &[],
315 ),
316 route(
317 "PATCH",
318 "/repos/:owner/:name/settings",
319 Op::UpdateRepoSettings,
320 &[],
321 ),
322 route("GET", "/repos/:owner/:name/check-names", Op::ListCheckNames, &[]),
Merge checks: statuses and check runs on every commit323 // Checks: GitHub's addresses for statuses, check runs and check suites.
324 route("POST", "/repos/:owner/:name/statuses/:sha", Op::Checks(ChecksOp::CreateCommitStatus), &[]),
325 route("GET", "/repos/:owner/:name/commits/:ref/statuses", Op::Checks(ChecksOp::ListCommitStatuses), &[]),
326 route("GET", "/repos/:owner/:name/commits/:ref/status", Op::Checks(ChecksOp::GetCombinedStatus), &[]),
327 route(
328 "GET",
329 "/repos/:owner/:name/commits/:ref/check-runs",
330 Op::Checks(ChecksOp::ListCheckRunsForRef),
331 &[("check_name", "check_name"), ("status", "status"), ("app", "app"), ("filter", "filter")],
332 ),
333 route(
334 "GET",
335 "/repos/:owner/:name/commits/:ref/check-suites",
336 Op::Checks(ChecksOp::ListCheckSuitesForRef),
337 &[("app", "app"), ("check_name", "check_name")],
338 ),
339 route("POST", "/repos/:owner/:name/check-runs", Op::Checks(ChecksOp::CreateCheckRun), &[]),
340 route("GET", "/repos/:owner/:name/check-runs/:id", Op::Checks(ChecksOp::GetCheckRun), &[]),
341 route("PATCH", "/repos/:owner/:name/check-runs/:id", Op::Checks(ChecksOp::UpdateCheckRun), &[]),
342 route("GET", "/repos/:owner/:name/check-runs/:id/annotations", Op::Checks(ChecksOp::ListCheckRunAnnotations), &[]),
343 route("POST", "/repos/:owner/:name/check-runs/:id/rerequest", Op::Checks(ChecksOp::RerequestCheckRun), &[]),
344 route("GET", "/repos/:owner/:name/check-suites/:id", Op::Checks(ChecksOp::GetCheckSuite), &[]),
345 route("POST", "/repos/:owner/:name/check-suites/:id/rerequest", Op::Checks(ChecksOp::RerequestCheckSuite), &[]),
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge346 // Rulesets: a repository's, a workspace's, the rules of one branch,
347 // and how they judged pushes and merges.
348 route("GET", "/repos/:owner/:name/rulesets", Op::Rules(RulesOp::ListRepoRulesets), &[("include_parents", "include_parents")]),
349 route("POST", "/repos/:owner/:name/rulesets", Op::Rules(RulesOp::CreateRepoRuleset), &[]),
350 route("GET", "/repos/:owner/:name/rulesets/:id", Op::Rules(RulesOp::GetRepoRuleset), &[]),
351 route("PUT", "/repos/:owner/:name/rulesets/:id", Op::Rules(RulesOp::UpdateRepoRuleset), &[]),
352 route("DELETE", "/repos/:owner/:name/rulesets/:id", Op::Rules(RulesOp::DeleteRepoRuleset), &[]),
353 route("GET", "/repos/:owner/:name/rules/branches/:branch", Op::Rules(RulesOp::GetBranchRules), &[("target", "target")]),
354 route(
355 "GET",
356 "/repos/:owner/:name/rules/evaluations",
357 Op::Rules(RulesOp::ListRuleEvaluations),
358 &[("ruleset_id", "ruleset_id"), ("verdict", "verdict"), ("problems_only", "problems_only"), ("before", "before"), ("limit", "limit")],
359 ),
360 route("GET", "/workspaces/:workspace/rulesets", Op::Rules(RulesOp::ListWorkspaceRulesets), &[]),
361 route("POST", "/workspaces/:workspace/rulesets", Op::Rules(RulesOp::CreateWorkspaceRuleset), &[]),
362 route("GET", "/workspaces/:workspace/rulesets/:id", Op::Rules(RulesOp::GetWorkspaceRuleset), &[]),
363 route("PUT", "/workspaces/:workspace/rulesets/:id", Op::Rules(RulesOp::UpdateWorkspaceRuleset), &[]),
364 route("DELETE", "/workspaces/:workspace/rulesets/:id", Op::Rules(RulesOp::DeleteWorkspaceRuleset), &[]),
365 route(
366 "GET",
367 "/workspaces/:workspace/rules/evaluations",
368 Op::Rules(RulesOp::ListWorkspaceRuleEvaluations),
369 &[("ruleset_id", "ruleset_id"), ("verdict", "verdict"), ("problems_only", "problems_only"), ("before", "before"), ("limit", "limit")],
370 ),
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97371 // Deployments wherever they run, their statuses, and environments.
372 route(
373 "GET",
374 "/repos/:owner/:name/deployments",
375 Op::Deployments(DeploymentsOp::ListDeployments),
376 &[("environment", "environment"), ("ref", "ref"), ("sha", "sha"), ("task", "task"), ("state", "state"), ("source", "source"), ("creator", "creator"), ("page", "page"), ("per_page", "per_page")],
377 ),
378 route("POST", "/repos/:owner/:name/deployments", Op::Deployments(DeploymentsOp::CreateDeployment), &[]),
Merge branch 'worktree-agent-a4d35ee70c50683d0' into claude/cool-newton-7nm8r9379 // A g1t Actions job opening a g1t.page build it runs itself.
380 route("POST", "/repos/:owner/:name/deployments/builds", Op::Deployments(DeploymentsOp::StartDeploymentBuild), &[]),
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97381 route("GET", "/repos/:owner/:name/deployments/:id", Op::Deployments(DeploymentsOp::GetDeployment), &[]),
382 route("GET", "/repos/:owner/:name/deployments/:id/statuses", Op::Deployments(DeploymentsOp::ListDeploymentStatuses), &[]),
383 route("POST", "/repos/:owner/:name/deployments/:id/statuses", Op::Deployments(DeploymentsOp::CreateDeploymentStatus), &[]),
384 route("GET", "/repos/:owner/:name/environments", Op::Deployments(DeploymentsOp::ListEnvironments), &[]),
385 route("GET", "/repos/:owner/:name/environments/:environment", Op::Deployments(DeploymentsOp::GetEnvironment), &[]),
Merge branch 'worktree-agent-a3abfcce648e87dca'386 // Environments' protection rules, and the runs they hold.
387 route("PUT", "/repos/:owner/:name/environments/:environment", Op::Protection(ProtectionOp::UpdateEnvironment), &[]),
388 route("DELETE", "/repos/:owner/:name/environments/:environment", Op::Protection(ProtectionOp::DeleteEnvironment), &[]),
389 route(
390 "GET",
391 "/repos/:owner/:name/actions/runs/:id/pending_deployments",
392 Op::Protection(ProtectionOp::GetPendingDeployments),
393 &[],
394 ),
395 route(
396 "POST",
397 "/repos/:owner/:name/actions/runs/:id/pending_deployments",
398 Op::Protection(ProtectionOp::ReviewPendingDeployments),
399 &[],
400 ),
401 route("POST", "/repos/:owner/:name/actions/runs/:id/approve", Op::Protection(ProtectionOp::ApproveWorkflowRun), &[]),
402 route("GET", "/repos/:owner/:name/actions/permissions/workflow", Op::Protection(ProtectionOp::GetWorkflowPermissions), &[]),
403 route("PUT", "/repos/:owner/:name/actions/permissions/workflow", Op::Protection(ProtectionOp::SetWorkflowPermissions), &[]),
404 route(
405 "GET",
406 "/repos/:owner/:name/actions/permissions/fork-pr-contributor-approval",
407 Op::Protection(ProtectionOp::GetForkPrApproval),
408 &[],
409 ),
410 route(
411 "PUT",
412 "/repos/:owner/:name/actions/permissions/fork-pr-contributor-approval",
413 Op::Protection(ProtectionOp::SetForkPrApproval),
414 &[],
415 ),
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts416 route("GET", "/repos/:owner/:name/actions/permissions/access", Op::Protection(ProtectionOp::GetActionsAccess), &[]),
417 route("PUT", "/repos/:owner/:name/actions/permissions/access", Op::Protection(ProtectionOp::SetActionsAccess), &[]),
Merge branch 'worktree-agent-a3abfcce648e87dca'418 route("POST", "/repos/:owner/:name/dispatches", Op::Protection(ProtectionOp::CreateRepositoryDispatch), &[]),
419 route(
420 "GET",
421 "/workspaces/:workspace/actions/permissions/workflow",
422 Op::Protection(ProtectionOp::GetWorkspaceWorkflowPermissions),
423 &[],
424 ),
425 route(
426 "PUT",
427 "/workspaces/:workspace/actions/permissions/workflow",
428 Op::Protection(ProtectionOp::SetWorkspaceWorkflowPermissions),
429 &[],
430 ),
Merge GitHub pull request #8 (claude/cool-newton-7nm8r9): Docker deployments on g1t.page behind a switch that stays off until Cloudflare's container APIs are checked live, workflows a workspace manages in its repositories, tarball and zipball downloads, refs/pull/* kept to g1t alone, framework detection for g1t.page builds, and a workflow's event payload matching GitHub's.431 route(
432 "GET",
433 "/workspaces/:workspace/actions/managed-workflows",
434 Op::Protection(ProtectionOp::ListWorkspaceManagedWorkflows),
435 &[],
436 ),
437 route(
438 "POST",
439 "/workspaces/:workspace/actions/managed-workflows",
440 Op::Protection(ProtectionOp::CreateWorkspaceManagedWorkflow),
441 &[],
442 ),
443 route(
444 "PATCH",
445 "/workspaces/:workspace/actions/managed-workflows/:workflow",
446 Op::Protection(ProtectionOp::UpdateWorkspaceManagedWorkflow),
447 &[],
448 ),
449 route(
450 "DELETE",
451 "/workspaces/:workspace/actions/managed-workflows/:workflow",
452 Op::Protection(ProtectionOp::DeleteWorkspaceManagedWorkflow),
453 &[],
454 ),
Fast pages, required checks on the branch, self-hosted runners, honest incidents455 route("GET", "/repos/:owner/:name/queue", Op::GetMergeQueue, &[]),
456 route(
457 "POST",
458 "/repos/:owner/:name/pulls/:number/messages",
459 Op::MessageAgent,
460 &[],
461 ),
462 route(
463 "POST",
464 "/repos/:owner/:name/pulls/:number/messages/take",
465 Op::TakeMessages,
466 &[],
467 ),
468 route(
469 "POST",
470 "/repos/:owner/:name/messages/:id/answer",
471 Op::AnswerMessage,
472 &[],
473 ),
474 route("POST", "/repos/:owner/:name/memory", Op::Remember, &[]),
475 route(
476 "GET",
477 "/repos/:owner/:name/memory",
478 Op::Recall,
479 &[("q", "query"), ("limit", "limit")],
480 ),
481 route(
482 "GET",
483 "/repos/:owner/:name/events",
484 Op::ListEvents,
485 &[("before", "before")],
486 ),
487 route("GET", "/repos/:owner/:name/labels", Op::ListLabels, &[]),
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar488 route("POST", "/repos/:owner/:name/labels", Op::CreateLabel, &[]),
489 route("POST", "/repos/:owner/:name/labels/defaults", Op::AddDefaultLabels, &[]),
490 route("PATCH", "/repos/:owner/:name/labels/:label", Op::UpdateLabel, &[]),
491 route("DELETE", "/repos/:owner/:name/labels/:label", Op::DeleteLabel, &[]),
492 route("GET", "/repos/:owner/:name/issues/:number/labels", Op::ListIssueLabels, &[]),
493 route("POST", "/repos/:owner/:name/issues/:number/labels", Op::AddIssueLabels, &[]),
494 route("PUT", "/repos/:owner/:name/issues/:number/labels", Op::SetIssueLabels, &[]),
495 route("DELETE", "/repos/:owner/:name/issues/:number/labels", Op::RemoveIssueLabels, &[]),
496 route("DELETE", "/repos/:owner/:name/issues/:number/labels/:label", Op::RemoveIssueLabels, &[]),
497 route("GET", "/repos/:owner/:name/milestones", Op::ListMilestones, &[("state", "state")]),
498 route("POST", "/repos/:owner/:name/milestones", Op::CreateMilestone, &[]),
499 route("GET", "/repos/:owner/:name/milestones/:milestone", Op::GetMilestone, &[]),
500 route("PATCH", "/repos/:owner/:name/milestones/:milestone", Op::UpdateMilestone, &[]),
501 route("DELETE", "/repos/:owner/:name/milestones/:milestone", Op::DeleteMilestone, &[]),
Fast pages, required checks on the branch, self-hosted runners, honest incidents502 route(
503 "GET",
504 "/repos/:owner/:name/issues",
505 Op::ListIssues,
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar506 &[("state", "state"), ("label", "label"), ("milestone", "milestone")],
Fast pages, required checks on the branch, self-hosted runners, honest incidents507 ),
508 route("POST", "/repos/:owner/:name/issues", Op::CreateIssue, &[]),
509 route(
510 "GET",
511 "/repos/:owner/:name/issues/:number",
512 Op::GetIssue,
513 &[],
514 ),
515 route(
516 "PATCH",
517 "/repos/:owner/:name/issues/:number",
518 Op::UpdateIssue,
519 &[],
520 ),
521 route(
522 "POST",
523 "/repos/:owner/:name/issues/:number/close",
524 Op::CloseIssue,
525 &[],
526 ),
527 route(
528 "POST",
529 "/repos/:owner/:name/issues/:number/reopen",
530 Op::ReopenIssue,
531 &[],
532 ),
533 route(
534 "POST",
535 "/repos/:owner/:name/issues/:number/assign",
536 Op::AssignIssue,
537 &[],
538 ),
539 route(
540 "POST",
541 "/repos/:owner/:name/issues/import",
542 Op::ImportIssue,
543 &[],
544 ),
545 route(
546 "POST",
547 "/repos/:owner/:name/issues/delegate",
548 Op::Delegate,
549 &[],
550 ),
551 route(
552 "GET",
553 "/repos/:owner/:name/context",
554 Op::GetContext,
555 &[("reference", "reference")],
556 ),
557 route(
558 "GET",
559 "/workspaces/:workspace/context/search",
560 Op::SearchContext,
561 &[("q", "query"), ("project", "project"), ("kinds", "kinds"), ("limit", "limit")],
562 ),
563 route(
564 "GET",
565 "/workspaces/:workspace/context/:kind/:id",
566 Op::GetEntity,
567 &[],
568 ),
569 route(
570 "GET",
571 "/workspaces/:workspace/integrations",
572 Op::ListIntegrations,
573 &[],
574 ),
575 route(
576 "POST",
577 "/workspaces/:workspace/integrations",
578 Op::ConnectIntegration,
579 &[],
580 ),
581 route(
582 "GET",
583 "/repos/:owner/:name/hooks",
584 Op::ListWebhooks,
585 &[],
586 ),
587 route(
588 "POST",
589 "/repos/:owner/:name/hooks",
590 Op::CreateWebhook,
591 &[],
592 ),
593 route(
594 "PATCH",
595 "/repos/:owner/:name/hooks/:id",
596 Op::UpdateWebhook,
597 &[],
598 ),
599 route(
600 "DELETE",
601 "/repos/:owner/:name/hooks/:id",
602 Op::DeleteWebhook,
603 &[],
604 ),
605 route(
606 "POST",
607 "/repos/:owner/:name/hooks/:id/pings",
608 Op::PingWebhook,
609 &[],
610 ),
611 route(
612 "GET",
613 "/repos/:owner/:name/hooks/:id/deliveries",
614 Op::ListWebhookDeliveries,
615 &[],
616 ),
617 route(
618 "POST",
619 "/repos/:owner/:name/hooks/:id/deliveries/:delivery/redeliver",
620 Op::RedeliverWebhook,
621 &[],
622 ),
623 route(
624 "GET",
625 "/workspaces/:workspace/hooks",
626 Op::ListWebhooks,
627 &[],
628 ),
629 route(
630 "POST",
631 "/workspaces/:workspace/hooks",
632 Op::CreateWebhook,
633 &[],
634 ),
635 route(
636 "PATCH",
637 "/workspaces/:workspace/hooks/:id",
638 Op::UpdateWebhook,
639 &[],
640 ),
641 route(
642 "DELETE",
643 "/workspaces/:workspace/hooks/:id",
644 Op::DeleteWebhook,
645 &[],
646 ),
647 route(
648 "POST",
649 "/workspaces/:workspace/hooks/:id/pings",
650 Op::PingWebhook,
651 &[],
652 ),
653 route(
654 "GET",
655 "/workspaces/:workspace/hooks/:id/deliveries",
656 Op::ListWebhookDeliveries,
657 &[],
658 ),
659 route(
660 "POST",
661 "/workspaces/:workspace/hooks/:id/deliveries/:delivery/redeliver",
662 Op::RedeliverWebhook,
663 &[],
664 ),
665 route(
666 "GET",
667 "/workspaces/:workspace/model-routes",
668 Op::GetModelRoutes,
669 &[],
670 ),
671 route(
672 "PUT",
673 "/workspaces/:workspace/model-routes",
674 Op::SetModelRoutes,
675 &[],
676 ),
677 route(
AI Gateway: OpenAI's format, open models, and your own providers678 "PATCH",
679 "/workspaces/:workspace/integrations/:id",
680 Op::UpdateIntegration,
681 &[],
682 ),
683 route(
Fast pages, required checks on the branch, self-hosted runners, honest incidents684 "DELETE",
685 "/workspaces/:workspace/integrations/:id",
686 Op::DisconnectIntegration,
687 &[],
688 ),
689 route(
690 "POST",
691 "/workspaces/:workspace/integrations/:id/test",
692 Op::TestIntegration,
693 &[],
694 ),
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9695 // Customize: marketplaces, Discover, plugins and connectors. A plugin's
696 // ref is `name@marketplace`, URL-encoded (`@` as `%40`).
697 route("GET", "/workspaces/:workspace/customize/marketplaces", Op::Customize(CustomizeOp::ListMarketplaces), &[]),
698 route("POST", "/workspaces/:workspace/customize/marketplaces", Op::Customize(CustomizeOp::AddMarketplace), &[]),
699 route("DELETE", "/workspaces/:workspace/customize/marketplaces/:marketplace", Op::Customize(CustomizeOp::RemoveMarketplace), &[]),
700 route("POST", "/workspaces/:workspace/customize/marketplaces/:marketplace/refresh", Op::Customize(CustomizeOp::RefreshMarketplace), &[]),
701 route("GET", "/workspaces/:workspace/customize/discover", Op::Customize(CustomizeOp::Discover), &[]),
702 route("GET", "/workspaces/:workspace/customize/plugins", Op::Customize(CustomizeOp::ListPlugins), &[]),
703 route("POST", "/workspaces/:workspace/customize/plugins", Op::Customize(CustomizeOp::InstallPlugin), &[]),
704 route("GET", "/workspaces/:workspace/customize/plugins/:ref", Op::Customize(CustomizeOp::GetPlugin), &[]),
705 route("PATCH", "/workspaces/:workspace/customize/plugins/:ref", Op::Customize(CustomizeOp::SetPluginEnabled), &[]),
706 route("DELETE", "/workspaces/:workspace/customize/plugins/:ref", Op::Customize(CustomizeOp::UninstallPlugin), &[]),
707 route("POST", "/workspaces/:workspace/customize/plugins/:ref/update", Op::Customize(CustomizeOp::UpdatePlugin), &[]),
708 route("GET", "/workspaces/:workspace/customize/agents", Op::Customize(CustomizeOp::ListPluginAgents), &[]),
709 route("GET", "/workspaces/:workspace/customize/connectors", Op::Customize(CustomizeOp::ListConnectors), &[]),
710 route("POST", "/workspaces/:workspace/customize/connectors", Op::Customize(CustomizeOp::AddConnector), &[]),
711 route("GET", "/workspaces/:workspace/customize/connectors/:id", Op::Customize(CustomizeOp::GetConnector), &[]),
712 route("PATCH", "/workspaces/:workspace/customize/connectors/:id", Op::Customize(CustomizeOp::SetConnectorKey), &[]),
713 route("DELETE", "/workspaces/:workspace/customize/connectors/:id", Op::Customize(CustomizeOp::RemoveConnector), &[]),
714 route("POST", "/workspaces/:workspace/customize/connectors/:id/refresh", Op::Customize(CustomizeOp::RefreshConnector), &[]),
715 route("POST", "/workspaces/:workspace/customize/connectors/:id/grants", Op::Customize(CustomizeOp::GrantConnector), &[]),
716 route("DELETE", "/workspaces/:workspace/customize/connectors/:id/grants/:grant", Op::Customize(CustomizeOp::RevokeConnector), &[]),
717 route("GET", "/workspaces/:workspace/customize/bundle", Op::Customize(CustomizeOp::Bundle), &[]),
Fast pages, required checks on the branch, self-hosted runners, honest incidents718 route(
719 "GET",
720 "/repos/:owner/:name/actions/workflows",
721 Op::ListWorkflows,
722 &[],
723 ),
724 route(
725 "GET",
726 "/repos/:owner/:name/actions/workflows/:workflow/runs",
727 Op::ListWorkflowRuns,
728 &[("branch", "branch"), ("event", "event"), ("per_page", "limit")],
729 ),
730 route(
731 "POST",
732 "/repos/:owner/:name/actions/workflows/:workflow/dispatches",
733 Op::DispatchWorkflow,
734 &[],
735 ),
736 route(
737 "PATCH",
738 "/repos/:owner/:name/actions/workflows/:workflow",
739 Op::UpdateWorkflow,
740 &[],
741 ),
742 route(
743 "PUT",
744 "/repos/:owner/:name/actions/workflows/:workflow/enable",
745 Op::UpdateWorkflow,
746 &[],
747 ),
748 route(
749 "PUT",
750 "/repos/:owner/:name/actions/workflows/:workflow/disable",
751 Op::UpdateWorkflow,
752 &[],
753 ),
754 route(
755 "GET",
756 "/repos/:owner/:name/actions/runs",
757 Op::ListWorkflowRuns,
758 &[("workflow", "workflow"), ("branch", "branch"), ("event", "event"), ("pull", "pull"), ("head_sha", "sha"), ("per_page", "limit")],
759 ),
760 route(
761 "GET",
762 "/repos/:owner/:name/actions/runs/:id",
763 Op::GetWorkflowRun,
764 &[],
765 ),
766 route(
Merge Actions runs: summaries, attempts and re-runs, graceful cancel, log downloads, badges (actions 0009)767 "GET",
768 "/repos/:owner/:name/actions/runs/:id/attempts/:attempt",
769 Op::GetWorkflowRun,
770 &[],
771 ),
772 route(
Fast pages, required checks on the branch, self-hosted runners, honest incidents773 "POST",
774 "/repos/:owner/:name/actions/runs/:id/cancel",
775 Op::CancelWorkflowRun,
776 &[],
777 ),
778 route(
779 "POST",
Merge Actions runs: summaries, attempts and re-runs, graceful cancel, log downloads, badges (actions 0009)780 "/repos/:owner/:name/actions/runs/:id/force-cancel",
781 Op::CancelWorkflowRun,
782 &[],
783 ),
784 route(
785 "POST",
Fast pages, required checks on the branch, self-hosted runners, honest incidents786 "/repos/:owner/:name/actions/runs/:id/rerun",
787 Op::RerunWorkflowRun,
788 &[],
789 ),
790 route(
791 "POST",
792 "/repos/:owner/:name/actions/runs/:id/rerun-failed-jobs",
793 Op::RerunWorkflowRun,
794 &[],
795 ),
796 route(
Merge Actions runs: summaries, attempts and re-runs, graceful cancel, log downloads, badges (actions 0009)797 "POST",
798 "/repos/:owner/:name/actions/jobs/:job/rerun",
799 Op::RerunWorkflowRun,
800 &[],
801 ),
802 route(
Fast pages, required checks on the branch, self-hosted runners, honest incidents803 "GET",
804 "/repos/:owner/:name/actions/jobs/:job/logs",
805 Op::GetJobLogs,
806 &[("after", "after")],
807 ),
Merge packages: roles, Actions access, source label, soft delete, API808 // Packages, at GitHub's addresses with the workspace in place of the
809 // organization. A name with a slash is one URL-encoded segment.
810 route("GET", "/workspaces/:workspace/packages", Op::Packages(PackagesOp::ListPackages), &[("package_type", "package_type"), ("q", "q"), ("state", "state")]),
811 route("GET", "/workspaces/:workspace/packages/:package_type/:package_name", Op::Packages(PackagesOp::GetPackage), &[]),
812 route("PATCH", "/workspaces/:workspace/packages/:package_type/:package_name", Op::Packages(PackagesOp::UpdatePackage), &[]),
813 route("DELETE", "/workspaces/:workspace/packages/:package_type/:package_name", Op::Packages(PackagesOp::DeletePackage), &[]),
814 route("POST", "/workspaces/:workspace/packages/:package_type/:package_name/restore", Op::Packages(PackagesOp::RestorePackage), &[]),
815 route("GET", "/workspaces/:workspace/packages/:package_type/:package_name/versions", Op::Packages(PackagesOp::ListVersions), &[("state", "state")]),
816 route("GET", "/workspaces/:workspace/packages/:package_type/:package_name/versions/:version_id", Op::Packages(PackagesOp::GetVersion), &[]),
817 route("DELETE", "/workspaces/:workspace/packages/:package_type/:package_name/versions/:version_id", Op::Packages(PackagesOp::DeleteVersion), &[]),
818 route("POST", "/workspaces/:workspace/packages/:package_type/:package_name/versions/:version_id/restore", Op::Packages(PackagesOp::RestoreVersion), &[]),
819 route("PUT", "/workspaces/:workspace/packages/:package_type/:package_name/repository", Op::Packages(PackagesOp::LinkPackage), &[]),
820 route("DELETE", "/workspaces/:workspace/packages/:package_type/:package_name/repository", Op::Packages(PackagesOp::UnlinkPackage), &[]),
821 route("GET", "/workspaces/:workspace/packages/:package_type/:package_name/access", Op::Packages(PackagesOp::ListAccess), &[]),
822 route("PUT", "/workspaces/:workspace/packages/:package_type/:package_name/access", Op::Packages(PackagesOp::SetAccess), &[]),
823 route("DELETE", "/workspaces/:workspace/packages/:package_type/:package_name/access", Op::Packages(PackagesOp::RemoveAccess), &[("username", "username"), ("team", "team")]),
824 route("GET", "/workspaces/:workspace/packages/:package_type/:package_name/actions-access", Op::Packages(PackagesOp::ListActionsAccess), &[]),
825 route("PUT", "/workspaces/:workspace/packages/:package_type/:package_name/actions-access", Op::Packages(PackagesOp::SetActionsAccess), &[]),
826 route("DELETE", "/workspaces/:workspace/packages/:package_type/:package_name/actions-access/:repository", Op::Packages(PackagesOp::RemoveActionsAccess), &[]),
Actions: OIDC tokens, the toolkit's cache and artifact services, and artifacts in R2827 // Artifacts, at GitHub's addresses. `…/zip` answers with a redirect to
828 // a signed link (lib.rs).
829 route("GET", "/repos/:owner/:name/actions/artifacts", Op::Artifacts(ArtifactsOp::ListArtifacts), &[("name", "name"), ("page", "page"), ("per_page", "per_page")]),
830 route("GET", "/repos/:owner/:name/actions/runs/:id/artifacts", Op::Artifacts(ArtifactsOp::ListRunArtifacts), &[("name", "name")]),
831 route("GET", "/repos/:owner/:name/actions/artifacts/:id", Op::Artifacts(ArtifactsOp::GetArtifact), &[]),
832 route("GET", "/repos/:owner/:name/actions/artifacts/:id/zip", Op::Artifacts(ArtifactsOp::DownloadArtifact), &[]),
833 route("DELETE", "/repos/:owner/:name/actions/artifacts/:id", Op::Artifacts(ArtifactsOp::DeleteArtifact), &[]),
834 route("GET", "/repos/:owner/:name/actions/permissions/artifact-and-log-retention", Op::Artifacts(ArtifactsOp::GetArtifactRetention), &[]),
835 route("PUT", "/repos/:owner/:name/actions/permissions/artifact-and-log-retention", Op::Artifacts(ArtifactsOp::SetArtifactRetention), &[]),
Fast pages, required checks on the branch, self-hosted runners, honest incidents836 route(
837 "GET",
838 "/repos/:owner/:name/actions/secrets",
839 Op::ListActionsSecrets,
840 &[],
841 ),
842 route(
843 "PUT",
844 "/repos/:owner/:name/actions/secrets/:setting",
845 Op::SetActionsSecret,
846 &[],
847 ),
848 route(
849 "DELETE",
850 "/repos/:owner/:name/actions/secrets/:setting",
851 Op::DeleteActionsSecret,
852 &[],
853 ),
854 route(
855 "GET",
856 "/repos/:owner/:name/actions/variables",
857 Op::ListActionsVariables,
858 &[],
859 ),
860 route(
861 "POST",
862 "/repos/:owner/:name/actions/variables",
863 Op::SetActionsVariable,
864 &[],
865 ),
866 route(
867 "PATCH",
868 "/repos/:owner/:name/actions/variables/:setting",
869 Op::SetActionsVariable,
870 &[],
871 ),
872 route(
873 "DELETE",
874 "/repos/:owner/:name/actions/variables/:setting",
875 Op::DeleteActionsVariable,
876 &[],
877 ),
878 route(
879 "GET",
880 "/workspaces/:workspace/actions/secrets",
881 Op::ListActionsSecrets,
882 &[],
883 ),
884 route(
885 "PUT",
886 "/workspaces/:workspace/actions/secrets/:setting",
887 Op::SetActionsSecret,
888 &[],
889 ),
890 route(
891 "DELETE",
892 "/workspaces/:workspace/actions/secrets/:setting",
893 Op::DeleteActionsSecret,
894 &[],
895 ),
896 route(
897 "GET",
898 "/workspaces/:workspace/actions/variables",
899 Op::ListActionsVariables,
900 &[],
901 ),
902 route(
903 "POST",
904 "/workspaces/:workspace/actions/variables",
905 Op::SetActionsVariable,
906 &[],
907 ),
908 route(
909 "PATCH",
910 "/workspaces/:workspace/actions/variables/:setting",
911 Op::SetActionsVariable,
912 &[],
913 ),
914 route(
915 "DELETE",
916 "/workspaces/:workspace/actions/variables/:setting",
917 Op::DeleteActionsVariable,
918 &[],
919 ),
920 // Self-hosted runners: a repository's own, or a workspace's.
921 route("GET", "/repos/:owner/:name/actions/runners", Op::ListRunners, &[]),
922 route("POST", "/repos/:owner/:name/actions/runners/registration-token", Op::CreateRunnerRegistrationToken, &[]),
923 route("DELETE", "/repos/:owner/:name/actions/runners/:id", Op::RemoveRunner, &[]),
924 route("GET", "/repos/:owner/:name/actions/runner-settings", Op::GetRunnerSettings, &[]),
925 route("PATCH", "/repos/:owner/:name/actions/runner-settings", Op::UpdateRunnerSettings, &[]),
926 route("GET", "/workspaces/:workspace/actions/runners", Op::ListRunners, &[]),
927 route("POST", "/workspaces/:workspace/actions/runners/registration-token", Op::CreateRunnerRegistrationToken, &[]),
928 route("DELETE", "/workspaces/:workspace/actions/runners/:id", Op::RemoveRunner, &[]),
929 route("GET", "/workspaces/:workspace/actions/runner-settings", Op::GetRunnerSettings, &[]),
930 route("PATCH", "/workspaces/:workspace/actions/runner-settings", Op::UpdateRunnerSettings, &[]),
931 route("GET", "/workspaces/:workspace/actions/runner-groups", Op::ListRunnerGroups, &[]),
932 route("POST", "/workspaces/:workspace/actions/runner-groups", Op::CreateRunnerGroup, &[]),
933 route("PATCH", "/workspaces/:workspace/actions/runner-groups/:id", Op::UpdateRunnerGroup, &[]),
934 route("DELETE", "/workspaces/:workspace/actions/runner-groups/:id", Op::DeleteRunnerGroup, &[]),
Every agent can have its own computer. A session that needs one wakes it: a home of its own on g1t cloud, one per agent and never shared, where it runs commands, reads and writes files and keeps what it made, with each session working in its own folder under a shared home; after ten idle minutes it sleeps, its home kept as a snapshot and restored when it wakes, and Reset wipes the home while memory and artifacts stay. Its shell and files are abilities with the usual choices, Alone, Alone when asked, Ask first or Never, offered only inside sessions and never to a chat reply; every command shows on the session with its output, and the agent's new Computer tab shows the state, the disk used of the five gigabytes included, the recent commands, and Wake, Put to sleep and Reset. Machine time counts only while it is awake, on the sandbox lines of the ledger that name the agent and who asked, held to the same spend caps as the session; the disk itself costs nothing in this version. The runner gained a long-lived supervisor that answers the computer's requests inside the container, and the runner service a computer per agent that keeps its snapshot in the agent homes bucket when one is attached, and says so when none is. The REST API and the agent tool can read a computer, wake it, put it to sleep and reset it. The agents, abilities, sessions, runners, billing and deploy guides say how it works and what an operator sets up; pinning a computer to your own runner, its browser and take-over come next.935 // A workspace agent's own computer.
936 route("GET", "/workspaces/:workspace/agents/:agent/computer", Op::GetAgentComputer, &[]),
937 route("POST", "/workspaces/:workspace/agents/:agent/computer/wake", Op::WakeAgentComputer, &[]),
938 route("POST", "/workspaces/:workspace/agents/:agent/computer/sleep", Op::SleepAgentComputer, &[]),
939 route("POST", "/workspaces/:workspace/agents/:agent/computer/reset", Op::ResetAgentComputer, &[]),
940 route("GET", "/workspaces/:workspace/agents/:agent/computer/commands", Op::ListAgentComputerCommands, &[]),
Fast pages, required checks on the branch, self-hosted runners, honest incidents941 route("POST", "/repos/:owner/:name/plans", Op::PlanWork, &[]),
942 route("GET", "/repos/:owner/:name/plans/:plan", Op::GetPlan, &[]),
943 route(
944 "POST",
945 "/repos/:owner/:name/plans/:plan/apply",
946 Op::ApplyPlan,
947 &[],
948 ),
949 route(
950 "POST",
951 "/repos/:owner/:name/issues/:number/comments",
952 Op::AddComment,
953 &[],
954 ),
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts955 route("PATCH", "/repos/:owner/:name/issues/comments/:comment_id", Op::EditComment, &[]),
956 route("DELETE", "/repos/:owner/:name/issues/comments/:comment_id", Op::DeleteComment, &[]),
Fast pages, required checks on the branch, self-hosted runners, honest incidents957 route(
958 "GET",
959 "/repos/:owner/:name/pulls",
960 Op::ListPullRequests,
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar961 &[("state", "state"), ("label", "label"), ("milestone", "milestone"), ("base", "base")],
Fast pages, required checks on the branch, self-hosted runners, honest incidents962 ),
963 route(
964 "POST",
965 "/repos/:owner/:name/pulls",
966 Op::CreatePullRequest,
967 &[],
968 ),
969 route(
970 "GET",
971 "/repos/:owner/:name/pulls/:number",
972 Op::GetPullRequest,
973 &[],
974 ),
975 route(
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar976 "PATCH",
977 "/repos/:owner/:name/pulls/:number",
978 Op::UpdatePullRequest,
979 &[],
980 ),
981 route(
Fast pages, required checks on the branch, self-hosted runners, honest incidents982 "GET",
983 "/repos/:owner/:name/pulls/:number/changes",
984 Op::GetPullRequestChanges,
985 &[],
986 ),
987 route(
988 "POST",
989 "/repos/:owner/:name/pulls/:number/reviews",
990 Op::ReviewPullRequest,
991 &[],
992 ),
993 route(
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar994 "POST",
995 "/repos/:owner/:name/pulls/:number/requested_reviewers",
996 Op::RequestReviewers,
997 &[],
998 ),
999 route(
1000 "DELETE",
1001 "/repos/:owner/:name/pulls/:number/requested_reviewers",
1002 Op::RemoveRequestedReviewers,
1003 &[],
1004 ),
1005 route(
Fast pages, required checks on the branch, self-hosted runners, honest incidents1006 "GET",
1007 "/repos/:owner/:name/pulls/:number/session",
1008 Op::ReadSession,
1009 &[("after", "after")],
1010 ),
1011 route(
1012 "POST",
1013 "/repos/:owner/:name/pulls/:number/session",
1014 Op::RecordSession,
1015 &[],
1016 ),
1017 route(
1018 "POST",
1019 "/repos/:owner/:name/pulls/:number/ready",
1020 Op::MarkPullRequestReady,
1021 &[],
1022 ),
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts1023 route("POST", "/repos/:owner/:name/pulls/:number/draft", Op::ConvertPullRequestToDraft, &[]),
Fast pages, required checks on the branch, self-hosted runners, honest incidents1024 route(
1025 "POST",
1026 "/repos/:owner/:name/pulls/:number/close",
1027 Op::ClosePullRequest,
1028 &[],
1029 ),
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts1030 route("POST", "/repos/:owner/:name/pulls/:number/reopen", Op::ReopenPullRequest, &[]),
Fast pages, required checks on the branch, self-hosted runners, honest incidents1031 route(
1032 "POST",
1033 "/repos/:owner/:name/pulls/:number/merge",
1034 Op::MergePullRequest,
1035 &[],
1036 ),
Merge main into Artifacts Phase 21037 // Artifacts mode's docs, slides, designs and dashboards. Search comes
1038 // before an artifact by id, which it would otherwise match.
1039 route("GET", "/workspaces/:workspace/artifacts", Op::Folios(FoliosOp::List), &[("tab", "tab"), ("kind", "kind"), ("space", "space"), ("project", "project"), ("q", "q"), ("state", "state"), ("cursor", "cursor"), ("limit", "limit")]),
1040 route("POST", "/workspaces/:workspace/artifacts", Op::Folios(FoliosOp::Create), &[]),
1041 route("GET", "/workspaces/:workspace/artifacts/search", Op::Folios(FoliosOp::Search), &[("q", "q"), ("kind", "kind"), ("space", "space"), ("project", "project"), ("limit", "limit")]),
1042 route("GET", "/workspaces/:workspace/artifacts/:artifact_id", Op::Folios(FoliosOp::Get), &[]),
1043 route("PATCH", "/workspaces/:workspace/artifacts/:artifact_id", Op::Folios(FoliosOp::Update), &[]),
1044 route("DELETE", "/workspaces/:workspace/artifacts/:artifact_id", Op::Folios(FoliosOp::Trash), &[]),
1045 route("POST", "/workspaces/:workspace/artifacts/:artifact_id/restore", Op::Folios(FoliosOp::Restore), &[]),
1046 route("POST", "/workspaces/:workspace/artifacts/:artifact_id/purge", Op::Folios(FoliosOp::Purge), &[]),
1047 route("GET", "/workspaces/:workspace/artifacts/:artifact_id/content", Op::Folios(FoliosOp::GetContent), &[]),
1048 route("PUT", "/workspaces/:workspace/artifacts/:artifact_id/content", Op::Folios(FoliosOp::Edit), &[]),
1049 route("GET", "/workspaces/:workspace/artifacts/:artifact_id/access", Op::Folios(FoliosOp::GetAccess), &[]),
1050 route("PUT", "/workspaces/:workspace/artifacts/:artifact_id/access", Op::Folios(FoliosOp::SetAccess), &[]),
1051 route("GET", "/workspaces/:workspace/artifacts/:artifact_id/versions", Op::Folios(FoliosOp::ListVersions), &[]),
1052 route("POST", "/workspaces/:workspace/artifacts/:artifact_id/versions/:version_id/restore", Op::Folios(FoliosOp::RestoreVersion), &[]),
1053 route("GET", "/workspaces/:workspace/artifact-templates", Op::Folios(FoliosOp::ListTemplates), &[("kind", "kind")]),
1054 route("GET", "/workspaces/:workspace/artifact-spaces", Op::Folios(FoliosOp::ListSpaces), &[]),
1055 route("POST", "/workspaces/:workspace/datasets/query", Op::Folios(FoliosOp::QueryDataset), &[]),
Fast pages, required checks on the branch, self-hosted runners, honest incidents1056];
1057
1058impl Route {
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts1059 /// Whether the route answers success with `204` and no body, as
1060 /// GitHub's address for the same does. MCP still answers `true`.
1061 pub fn no_content(&self) -> bool {
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r91062 matches!(
1063 self.op,
1064 Op::DeleteComment
1065 | Op::Customize(CustomizeOp::RemoveMarketplace | CustomizeOp::UninstallPlugin | CustomizeOp::RemoveConnector)
1066 )
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts1067 }
1068
Fast pages, required checks on the branch, self-hosted runners, honest incidents1069 /// The names of the route's path parameters, in order.
1070 pub fn params(&self) -> impl Iterator<Item = &'static str> {
1071 self.path
1072 .split('/')
1073 .filter_map(|segment| segment.strip_prefix(':'))
1074 }
1075
1076 /// The values of the path parameters, if `path` is this route's.
1077 fn matches<'a>(&self, path: &'a str) -> Option<Vec<(&'static str, &'a str)>> {
1078 let mut values = Vec::new();
1079 let mut actual = path.trim_end_matches('/').split('/');
1080 for expected in self.path.split('/') {
1081 let segment = actual.next()?;
1082 match expected.strip_prefix(':') {
1083 Some(name) if !segment.is_empty() => values.push((name, segment)),
1084 Some(_) => return None,
1085 None if expected == segment => {}
1086 None => return None,
1087 }
1088 }
1089 actual.next().is_none().then_some(values)
1090 }
1091}
1092
1093/// A path segment with its `%XX` escapes decoded; as given when that is not
1094/// UTF-8.
1095fn percent_decoded(segment: &str) -> String {
1096 let bytes = segment.as_bytes();
1097 let mut out = Vec::with_capacity(bytes.len());
1098 let mut i = 0;
1099 while i < bytes.len() {
1100 let escaped = (bytes[i] == b'%')
1101 .then(|| segment.get(i + 1..i + 3))
1102 .flatten()
1103 .filter(|hex| hex.bytes().all(|byte| byte.is_ascii_hexdigit()))
1104 .and_then(|hex| u8::from_str_radix(hex, 16).ok());
1105 match escaped {
1106 Some(byte) => {
1107 out.push(byte);
1108 i += 3;
1109 }
1110 None => {
1111 out.push(bytes[i]);
1112 i += 1;
1113 }
1114 }
1115 }
1116 String::from_utf8(out).unwrap_or_else(|_| segment.to_owned())
1117}
1118
1119/// The route for a request, and the operation input it describes.
1120///
1121/// The input is the JSON body, overlaid with the query parameters the route
1122/// reads and then with what the path names: `owner` and `name` become
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1123/// `repo`, as does a team's `repo` with its `workspace`, and `number`
1124/// becomes an integer.
Fast pages, required checks on the branch, self-hosted runners, honest incidents1125pub fn resolve(
1126 method: &str,
1127 path: &str,
1128 query: &[(String, String)],
1129 body: Value,
1130) -> Option<(&'static Route, Value)> {
1131 let (route, params) = ROUTES
1132 .iter()
1133 .filter(|route| route.method == method)
1134 .find_map(|route| Some((route, route.matches(path)?)))?;
1135
1136 let mut input = match body {
1137 Value::Object(fields) => fields,
1138 _ => Map::new(),
1139 };
1140 for (name, key) in route.query {
1141 if let Some((_, value)) = query.iter().find(|(query_name, _)| query_name == name) {
1142 input.insert((*key).to_owned(), Value::String(value.clone()));
1143 }
1144 }
1145 let param = |wanted: &str| {
1146 params
1147 .iter()
1148 .find(|(name, _)| *name == wanted)
1149 .map(|(_, value)| *value)
1150 };
1151 if let (Some(owner), Some(name)) = (param("owner"), param("name")) {
1152 input.insert("repo".to_owned(), Value::String(format!("{owner}/{name}")));
1153 }
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb971154 // Every other name the path gives, under that name; the ones below that
1155 // need more (a repository, a number, an encoded name) are set after.
1156 for (key, value) in &params {
1157 if !matches!(*key, "owner" | "name") {
1158 input.insert((*key).to_owned(), Value::String(percent_decoded(value)));
Fast pages, required checks on the branch, self-hosted runners, honest incidents1159 }
1160 }
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1161 // A repository of a team's workspace, named by itself.
1162 if let (Some(workspace), Some(name)) = (param("workspace"), param("repo")) {
1163 input.insert("repo".to_owned(), Value::String(format!("{workspace}/{name}")));
1164 }
1165 // A branch name may hold slashes, sent URL-encoded as one segment, and
1166 // a label's name spaces.
Fast pages, required checks on the branch, self-hosted runners, honest incidents1167 if let Some(branch) = param("branch") {
1168 input.insert("branch".to_owned(), Value::String(percent_decoded(branch)));
1169 }
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb971170 // An environment's name may hold slashes and spaces, URL-encoded.
1171 if let Some(environment) = param("environment") {
1172 input.insert("environment".to_owned(), Value::String(percent_decoded(environment)));
1173 }
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1174 if let Some(label) = param("label") {
1175 input.insert("label".to_owned(), Value::String(percent_decoded(label)));
1176 }
1177 if let Some(milestone) = param("milestone") {
1178 // Not a number: zero, which no milestone has.
1179 input.insert("milestone".to_owned(), milestone.parse::<u32>().unwrap_or(0).into());
1180 }
Fast pages, required checks on the branch, self-hosted runners, honest incidents1181 // GitHub says some things with the path alone.
1182 if route.path.ends_with("/enable") || route.path.ends_with("/disable") {
1183 input.insert("enabled".to_owned(), Value::Bool(route.path.ends_with("/enable")));
1184 }
1185 if route.path.ends_with("/rerun-failed-jobs") {
1186 input.insert("failed_only".to_owned(), Value::Bool(true));
1187 }
Merge Actions runs: summaries, attempts and re-runs, graceful cancel, log downloads, badges (actions 0009)1188 if route.path.ends_with("/force-cancel") {
1189 input.insert("force".to_owned(), Value::Bool(true));
1190 }
API: notifications over REST and MCP, with notifications scopes1191 // Unsaving and waking a thread are a DELETE of what PUT made.
1192 if route.method == "DELETE" && route.path.ends_with("/saved") {
1193 input.insert("saved".to_owned(), Value::Bool(false));
1194 }
1195 if route.method == "DELETE" && route.path.ends_with("/snooze") {
1196 input.remove("until");
1197 }
Fast pages, required checks on the branch, self-hosted runners, honest incidents1198 if let Some(number) = param("number") {
1199 // Not a number: zero, which no issue or pull request has.
1200 input.insert(
1201 "number".to_owned(),
1202 number.parse::<u32>().unwrap_or(0).into(),
1203 );
1204 }
1205 Some((route, Value::Object(input)))
1206}
1207
1208#[cfg(test)]
1209mod tests {
1210 use serde_json::json;
1211
1212 use super::*;
1213
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb971214 /// Every name a route's path gives reaches the operation: a name left
1215 /// off the lists above is dropped, and the operation answers that it
1216 /// was not given (the project routes were, until this test).
1217 #[test]
1218 fn every_path_parameter_reaches_the_input() {
1219 for route in ROUTES.iter() {
1220 let names: Vec<&str> = route.path.split('/').filter_map(|part| part.strip_prefix(':')).collect();
1221 if names.is_empty() {
1222 continue;
1223 }
1224 let path: String = route
1225 .path
1226 .split('/')
1227 .map(|part| match part.strip_prefix(':') {
1228 Some("number" | "milestone") => "7".to_owned(),
1229 Some(name) => format!("{name}-x"),
1230 None => part.to_owned(),
1231 })
1232 .collect::<Vec<_>>()
1233 .join("/");
1234 let (found, input) = resolve(route.method, &path, &[], json!({})).unwrap();
1235 // A path two routes could take is checked under the first.
1236 if found.path != route.path {
1237 continue;
1238 }
1239 for name in names {
1240 let key = match name {
1241 "owner" | "name" => "repo",
1242 "repo" if names_has_workspace(route.path) => "repo",
1243 other => other,
1244 };
1245 assert!(
1246 input.get(key).is_some_and(|value| !value.is_null()),
1247 "{} {}: :{name} does not reach the input",
1248 route.method,
1249 route.path
1250 );
1251 }
1252 }
1253 }
1254
1255 fn names_has_workspace(path: &str) -> bool {
1256 path.split('/').any(|part| part == ":workspace")
1257 }
1258
Fast pages, required checks on the branch, self-hosted runners, honest incidents1259 #[test]
1260 fn a_path_resolves_to_its_operation_and_input() {
1261 let (route, input) = resolve(
1262 "POST",
1263 "/repos/flagon-io/hello/pulls/14/merge",
1264 &[],
1265 json!({ "keep_issue_open": true, "number": 99, "repo": "someone/else" }),
1266 )
1267 .unwrap();
1268 assert_eq!(route.op, Op::MergePullRequest);
1269 // What the path names wins over the body.
1270 assert_eq!(
1271 input,
1272 json!({ "keep_issue_open": true, "number": 14, "repo": "flagon-io/hello" })
1273 );
1274 }
1275
1276 #[test]
1277 fn a_branch_with_slashes_is_one_encoded_segment() {
1278 let (route, input) = resolve(
1279 "POST",
1280 "/repos/flagon-io/hello/branches/feature%2Flogin/rename",
1281 &[],
1282 json!({ "new_name": "feature/sign-in" }),
1283 )
1284 .unwrap();
1285 assert_eq!(route.op, Op::RenameBranch);
1286 assert_eq!(
1287 input,
1288 json!({ "new_name": "feature/sign-in", "branch": "feature/login", "repo": "flagon-io/hello" })
1289 );
1290 assert_eq!(percent_decoded("100%"), "100%");
1291 assert_eq!(percent_decoded("a%2bb%zz"), "a+b%zz");
1292 }
1293
1294 #[test]
1295 fn a_collaborator_is_named_by_username() {
1296 let (route, input) = resolve(
1297 "PATCH",
1298 "/repos/flagon-io/hello/collaborators/ada",
1299 &[],
1300 json!({ "role": "maintain" }),
1301 )
1302 .unwrap();
1303 assert_eq!(route.op, Op::UpdateCollaborator);
1304 assert_eq!(input, json!({ "role": "maintain", "username": "ada", "repo": "flagon-io/hello" }));
1305 let (route, input) = resolve("DELETE", "/user/repository_invitations/rin_1", &[], Value::Null).unwrap();
1306 assert_eq!(route.op, Op::DeclineRepoInvitation);
1307 assert_eq!(input, json!({ "id": "rin_1" }));
1308 }
1309
1310 #[test]
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1311 fn teams_are_addressed_by_workspace_and_slug() {
1312 let query = [("q".to_owned(), "back".to_owned())];
1313 let (route, input) = resolve("GET", "/workspaces/acme/teams", &query, Value::Null).unwrap();
1314 assert_eq!(route.op, Op::ListTeams);
1315 assert_eq!(input, json!({ "query": "back", "workspace": "acme" }));
1316 let (route, input) = resolve("PATCH", "/workspaces/acme/teams/backend", &[], json!({ "name": "Back end" })).unwrap();
1317 assert_eq!(route.op, Op::UpdateTeam);
1318 assert_eq!(input, json!({ "name": "Back end", "workspace": "acme", "team": "backend" }));
1319 let (route, input) =
1320 resolve("PUT", "/workspaces/acme/teams/backend/members/ana", &[], json!({ "role": "maintainer" })).unwrap();
1321 assert_eq!(route.op, Op::SetTeamMember);
1322 assert_eq!(input, json!({ "role": "maintainer", "workspace": "acme", "team": "backend", "username": "ana" }));
1323 let query = [("include_child_teams".to_owned(), "true".to_owned())];
1324 let (route, input) = resolve("GET", "/workspaces/acme/teams/backend/members", &query, Value::Null).unwrap();
1325 assert_eq!(route.op, Op::ListTeamMembers);
1326 assert_eq!(input, json!({ "include_child_teams": "true", "workspace": "acme", "team": "backend" }));
1327 // A repository is named by itself, in the team's workspace.
1328 let (route, input) =
1329 resolve("PUT", "/workspaces/acme/teams/backend/repos/rocket", &[], json!({ "role": "write" })).unwrap();
1330 assert_eq!(route.op, Op::SetTeamRepo);
1331 assert_eq!(input, json!({ "role": "write", "workspace": "acme", "team": "backend", "repo": "acme/rocket" }));
1332 let op = |method: &str, path: &str| resolve(method, path, &[], Value::Null).unwrap().0.op;
1333 assert_eq!(op("DELETE", "/workspaces/acme/teams/backend/repos/rocket"), Op::RemoveTeamRepo);
1334 assert_eq!(op("GET", "/workspaces/acme/teams/backend/teams"), Op::ListChildTeams);
1335 assert_eq!(op("GET", "/workspaces/acme/teams/backend/repos"), Op::ListTeamRepos);
1336 assert_eq!(op("PUT", "/workspaces/acme/teams/backend/review_assignment"), Op::SetTeamReviewAssignment);
1337 assert_eq!(op("DELETE", "/workspaces/acme/teams/backend"), Op::DeleteTeam);
1338 assert_eq!(op("POST", "/workspaces/acme/teams"), Op::CreateTeam);
1339 assert_eq!(op("GET", "/workspaces/acme/teams/backend"), Op::GetTeam);
1340 assert_eq!(op("DELETE", "/workspaces/acme/teams/backend/members/ana"), Op::RemoveTeamMember);
1341 let (route, input) = resolve("GET", "/workspaces/acme/members/ana/teams", &[], Value::Null).unwrap();
1342 assert_eq!(route.op, Op::ListUserTeams);
1343 assert_eq!(input, json!({ "workspace": "acme", "username": "ana" }));
1344 }
1345
1346 #[test]
1347 fn reviewers_are_requested_and_code_owners_checked_on_a_repository() {
1348 let body = json!({ "reviewers": ["ana"], "team_reviewers": ["backend"] });
1349 let (route, input) = resolve("POST", "/repos/acme/rocket/pulls/7/requested_reviewers", &[], body.clone()).unwrap();
1350 assert_eq!(route.op, Op::RequestReviewers);
1351 assert_eq!(
1352 input,
1353 json!({ "reviewers": ["ana"], "team_reviewers": ["backend"], "repo": "acme/rocket", "number": 7 })
1354 );
1355 let (route, _) = resolve("DELETE", "/repos/acme/rocket/pulls/7/requested_reviewers", &[], body).unwrap();
1356 assert_eq!(route.op, Op::RemoveRequestedReviewers);
1357 let query = [("ref".to_owned(), "main".to_owned())];
1358 let (route, input) = resolve("GET", "/repos/acme/rocket/codeowners/errors", &query, Value::Null).unwrap();
1359 assert_eq!(route.op, Op::GetCodeownersErrors);
1360 assert_eq!(input, json!({ "ref": "main", "repo": "acme/rocket" }));
1361 }
1362
1363 #[test]
API: notifications over REST and MCP, with notifications scopes1364 fn notifications_are_addressed_as_threads_and_by_issue() {
1365 let (route, input) = resolve("DELETE", "/notifications/threads/ntf_1", &[], Value::Null).unwrap();
1366 assert_eq!(route.op, Op::MarkThreadDone);
1367 assert_eq!(input, json!({ "id": "ntf_1" }));
1368 let (route, input) = resolve("DELETE", "/notifications/threads/ntf_1/saved", &[], Value::Null).unwrap();
1369 assert_eq!(route.op, Op::SaveThread);
1370 assert_eq!(input, json!({ "id": "ntf_1", "saved": false }));
1371 let (route, input) = resolve("DELETE", "/notifications/threads/ntf_1/snooze", &[], json!({ "until": "x" })).unwrap();
1372 assert_eq!(route.op, Op::SnoozeThread);
1373 assert_eq!(input, json!({ "id": "ntf_1" }));
1374 let query = [("all".to_owned(), "true".to_owned()), ("per_page".to_owned(), "50".to_owned())];
1375 let (route, input) = resolve("GET", "/repos/acme/rocket/notifications", &query, Value::Null).unwrap();
1376 assert_eq!(route.op, Op::ListNotifications);
1377 assert_eq!(input, json!({ "all": "true", "per_page": "50", "repo": "acme/rocket" }));
1378 let (route, input) = resolve("PUT", "/repos/acme/rocket/issues/7/subscription", &[], json!({ "ignored": true })).unwrap();
1379 assert_eq!(route.op, Op::SetThreadSubscription);
1380 assert_eq!(input, json!({ "ignored": true, "number": 7, "repo": "acme/rocket" }));
1381 assert_eq!(resolve("GET", "/user/subscriptions", &[], Value::Null).unwrap().0.op, Op::ListWatchedRepos);
1382 }
1383
1384 #[test]
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1385 fn labels_and_milestones_are_named_in_the_path() {
1386 let (route, input) = resolve("PATCH", "/repos/acme/web/labels/good%20first%20issue", &[], json!({ "color": "7057ff" })).unwrap();
1387 assert_eq!(route.op, Op::UpdateLabel);
1388 assert_eq!(input, json!({ "color": "7057ff", "label": "good first issue", "repo": "acme/web" }));
1389 let (route, input) = resolve("DELETE", "/repos/acme/web/issues/7/labels/bug", &[], Value::Null).unwrap();
1390 assert_eq!(route.op, Op::RemoveIssueLabels);
1391 assert_eq!(input, json!({ "label": "bug", "number": 7, "repo": "acme/web" }));
1392 let (route, input) = resolve("DELETE", "/repos/acme/web/issues/7/labels", &[], Value::Null).unwrap();
1393 assert_eq!(route.op, Op::RemoveIssueLabels);
1394 assert_eq!(input, json!({ "number": 7, "repo": "acme/web" }));
1395 let (route, _) = resolve("POST", "/repos/acme/web/labels/defaults", &[], Value::Null).unwrap();
1396 assert_eq!(route.op, Op::AddDefaultLabels);
1397 let (route, input) = resolve("PATCH", "/repos/acme/web/milestones/3", &[], json!({ "state": "closed" })).unwrap();
1398 assert_eq!(route.op, Op::UpdateMilestone);
1399 assert_eq!(input, json!({ "state": "closed", "milestone": 3, "repo": "acme/web" }));
1400 let (route, input) = resolve("PATCH", "/repos/acme/web/pulls/9", &[], json!({ "base": "release" })).unwrap();
1401 assert_eq!(route.op, Op::UpdatePullRequest);
1402 assert_eq!(input, json!({ "base": "release", "number": 9, "repo": "acme/web" }));
1403 }
1404
1405 #[test]
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts1406 fn pull_requests_reopen_and_turn_draft_and_comments_are_named_by_id() {
1407 let op = |method: &str, path: &str| resolve(method, path, &[], Value::Null).unwrap().0.op;
1408 assert_eq!(op("POST", "/repos/acme/web/pulls/9/reopen"), Op::ReopenPullRequest);
1409 assert_eq!(op("POST", "/repos/acme/web/pulls/9/draft"), Op::ConvertPullRequestToDraft);
1410 assert_eq!(op("POST", "/repos/acme/web/pulls/9/close"), Op::ClosePullRequest);
1411 // Reopening and closing with a PATCH, as `state`.
1412 let (route, input) = resolve("PATCH", "/repos/acme/web/pulls/9", &[], json!({ "state": "open" })).unwrap();
1413 assert_eq!(route.op, Op::UpdatePullRequest);
1414 assert_eq!(input, json!({ "state": "open", "number": 9, "repo": "acme/web" }));
1415 let (route, input) =
1416 resolve("PATCH", "/repos/acme/web/issues/comments/cmt_1", &[], json!({ "body": "Better" })).unwrap();
1417 assert_eq!(route.op, Op::EditComment);
1418 assert_eq!(input, json!({ "body": "Better", "comment_id": "cmt_1", "repo": "acme/web" }));
1419 let (route, input) = resolve("DELETE", "/repos/acme/web/issues/comments/cmt_1", &[], Value::Null).unwrap();
1420 assert_eq!(route.op, Op::DeleteComment);
1421 assert_eq!(input, json!({ "comment_id": "cmt_1", "repo": "acme/web" }));
1422 // Still an issue's comments, labels and subscription.
1423 assert_eq!(op("POST", "/repos/acme/web/issues/7/comments"), Op::AddComment);
1424 assert_eq!(op("DELETE", "/repos/acme/web/issues/7/labels"), Op::RemoveIssueLabels);
1425 assert_eq!(op("DELETE", "/repos/acme/web/issues/7/subscription"), Op::DeleteThreadSubscription);
1426 }
1427
1428 #[test]
Usage, Billing settings and prepaid AI credit; fixes from the UX audit1429 fn billing_is_addressed_by_workspace() {
1430 let query = [("from".to_owned(), "2026-10-01".to_owned()), ("products".to_owned(), "agent,sandboxes".to_owned())];
1431 let (route, input) = resolve("GET", "/workspaces/acme/usage", &query, Value::Null).unwrap();
1432 assert_eq!(route.op, Op::GetUsage);
1433 assert_eq!(input, json!({ "from": "2026-10-01", "products": "agent,sandboxes", "workspace": "acme" }));
1434 let (route, input) = resolve("PUT", "/workspaces/acme/budget", &[], json!({ "alerts": [50] })).unwrap();
1435 assert_eq!(route.op, Op::SetBudget);
1436 assert_eq!(input, json!({ "alerts": [50], "workspace": "acme" }));
1437 let op = |method: &str, path: &str| resolve(method, path, &[], Value::Null).unwrap().0.op;
1438 assert_eq!(op("GET", "/workspaces/acme/budget"), Op::GetBudget);
1439 assert_eq!(op("GET", "/workspaces/acme/ai_credit"), Op::GetAiCredit);
1440 assert_eq!(op("POST", "/workspaces/acme/ai_credit/checkout"), Op::BuyAiCredit);
1441 assert_eq!(op("GET", "/workspaces/acme/invoices"), Op::ListInvoices);
1442 assert_eq!(op("GET", "/workspaces/acme/billing_details"), Op::GetBillingDetails);
Merge the AI Gateway: Anthropic's Messages API on a workspace's tokens1443 assert_eq!(op("GET", "/workspaces/acme/gateway/requests"), Op::ListGatewayRequests);
Usage, Billing settings and prepaid AI credit; fixes from the UX audit1444 }
1445
1446 #[test]
Merge checks: statuses and check runs on every commit1447 fn checks_are_at_githubs_addresses() {
1448 let sha = "a".repeat(40);
1449 let body = json!({ "state": "success", "context": "ci/build" });
1450 let (route, input) = resolve("POST", &format!("/repos/acme/web/statuses/{sha}"), &[], body).unwrap();
1451 assert_eq!(route.op, Op::Checks(ChecksOp::CreateCommitStatus));
1452 assert_eq!(input, json!({ "state": "success", "context": "ci/build", "sha": sha, "repo": "acme/web" }));
1453 let (route, input) = resolve("GET", "/repos/acme/web/commits/release%2F1.x/status", &[], Value::Null).unwrap();
1454 assert_eq!(route.op, Op::Checks(ChecksOp::GetCombinedStatus));
1455 assert_eq!(input, json!({ "ref": "release/1.x", "repo": "acme/web" }));
1456 let query = [("check_name".to_owned(), "lint".to_owned())];
1457 let (route, input) = resolve("GET", "/repos/acme/web/commits/main/check-runs", &query, Value::Null).unwrap();
1458 assert_eq!(route.op, Op::Checks(ChecksOp::ListCheckRunsForRef));
1459 assert_eq!(input, json!({ "check_name": "lint", "ref": "main", "repo": "acme/web" }));
1460 let (route, input) = resolve("PATCH", "/repos/acme/web/check-runs/cr_1", &[], json!({ "conclusion": "success" })).unwrap();
1461 assert_eq!(route.op, Op::Checks(ChecksOp::UpdateCheckRun));
1462 assert_eq!(input, json!({ "conclusion": "success", "id": "cr_1", "repo": "acme/web" }));
1463 let op = |method: &str, path: &str| resolve(method, path, &[], Value::Null).unwrap().0.op;
1464 assert_eq!(op("POST", "/repos/acme/web/check-runs"), Op::Checks(ChecksOp::CreateCheckRun));
1465 assert_eq!(op("GET", "/repos/acme/web/check-runs/cr_1/annotations"), Op::Checks(ChecksOp::ListCheckRunAnnotations));
1466 assert_eq!(op("POST", "/repos/acme/web/check-suites/cs_1/rerequest"), Op::Checks(ChecksOp::RerequestCheckSuite));
1467 assert_eq!(op("GET", "/repos/acme/web/commits/main/check-suites"), Op::Checks(ChecksOp::ListCheckSuitesForRef));
1468 }
1469
1470 #[test]
Merge GitHub pull request #8 (claude/cool-newton-7nm8r9): Docker deployments on g1t.page behind a switch that stays off until Cloudflare's container APIs are checked live, workflows a workspace manages in its repositories, tarball and zipball downloads, refs/pull/* kept to g1t alone, framework detection for g1t.page builds, and a workflow's event payload matching GitHub's.1471 fn archives_are_at_githubs_addresses_and_redirect() {
1472 let sha = "4807077b296e6edbf410d55e72749d3e1170c291";
1473 let (route, input) = resolve("GET", &format!("/repos/acme/web/tarball/{sha}"), &[], Value::Null).unwrap();
1474 assert_eq!(route.op, Op::Archives(ArchiveOp::DownloadTarball));
1475 assert_eq!(input, json!({ "ref": sha, "repo": "acme/web" }));
1476 // A branch with slashes, URL-encoded as octokit sends it.
1477 let (route, input) = resolve("GET", "/repos/acme/web/zipball/release%2F1.x", &[], Value::Null).unwrap();
1478 assert_eq!(route.op, Op::Archives(ArchiveOp::DownloadZipball));
1479 assert_eq!(input, json!({ "ref": "release/1.x", "repo": "acme/web" }));
1480 // Both answer with a redirect to their link, as the artifact download does.
1481 assert!(route.op.redirects());
1482 assert!(Op::Archives(ArchiveOp::DownloadTarball).redirects());
1483 assert!(Op::Artifacts(ArtifactsOp::DownloadArtifact).redirects());
1484 assert!(!Op::GetRepo.redirects());
1485 // Anyone may ask for a public repository's.
1486 assert!(!route.op.needs_user());
1487 }
1488
1489 #[test]
Fast pages, required checks on the branch, self-hosted runners, honest incidents1490 fn query_parameters_are_renamed() {
1491 let query = [
1492 ("q".to_owned(), "parser".to_owned()),
1493 ("x".to_owned(), "y".to_owned()),
1494 ];
1495 let (route, input) = resolve("GET", "/repos", &query, Value::Null).unwrap();
1496 assert_eq!(route.op, Op::ListRepos);
1497 assert_eq!(input, json!({ "query": "parser" }));
1498 }
1499
1500 #[test]
1501 fn method_and_shape_must_match() {
1502 assert!(resolve("GET", "/repos/a/b/issues/1/close", &[], Value::Null).is_none());
1503 assert!(resolve("GET", "/repos/a", &[], Value::Null).is_none());
1504 assert!(resolve("GET", "/repos/a/b/issues/1/extra", &[], Value::Null).is_none());
1505 assert!(resolve("GET", "/repos/a/b/", &[], Value::Null).is_some());
1506 }
1507
1508 #[test]
1509 fn every_parameter_and_query_name_is_an_input() {
1510 for route in ROUTES {
1511 let properties = route.op.properties();
1512 for (_, key) in route.query {
1513 assert!(properties.contains_key(*key), "{}: {key}", route.path);
1514 }
1515 for name in route.params() {
1516 let covered = matches!(name, "owner" | "name") && properties.contains_key("repo")
1517 || properties.contains_key(name);
1518 assert!(covered, "{}: {name}", route.path);
1519 }
1520 }
1521 }
1522
1523 #[test]
1524 fn every_operation_has_a_route() {
1525 for op in Op::ALL {
1526 assert!(ROUTES.iter().any(|route| route.op == op), "{}", op.name());
1527 }
1528 }
1529}

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