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
382 linesCodeBlameRaw

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

The code page reads like a forge: a branch menu, search and a Code button above the files1//! GitHub: signing in with a GitHub account, and bringing repositories
2//! across through g1t's GitHub App. Mirrors `packages/contracts/src/github.ts`.
3//!
4//! One GitHub App does both. Its user authorization (OAuth web flow, with
5//! PKCE) signs people in and lets g1t list what they installed it on; its
6//! installations give g1t access to the repositories they chose. The app
7//! is optional: with none configured every method here says so, and the
8//! site shows no GitHub buttons.
9//!
10//! The identity service (`POST /rpc/<method>`) keeps linked accounts and
11//! their user tokens:
12//!
13//! - `github_enabled` (no arguments) returns `bool`.
14//! - `github_start` takes `GithubStartArgs`, returns `Outcome<GithubStart>`.
15//! - `github_finish` takes `GithubFinishArgs`, returns `Outcome<GithubFinished>`.
16//! - `github_pending` takes `GithubPendingArgs`, returns `Outcome<GithubPending>`.
17//! - `github_sign_up` takes `GithubSignUpArgs`, returns `Outcome<SignedIn>`.
18//! - `github_claim` takes `GithubClaimArgs`, returns `Outcome<GithubAccount>`.
19//! - `github_account` takes `UserArgs`, returns `GithubAccountView`.
20//! - `github_unlink` takes `UserArgs`, returns `Outcome<bool>`.
21//! - `github_user_token` takes `GithubUserTokenArgs`, returns `Outcome<String>`.
22//! - `github_revoked` takes `GithubRevokedArgs`, returns `u32`.
23//! - `github_usernames` takes `GithubUsernamesArgs`, returns a map.
24//!
Merge branch 'rm-mirroring'25//! The integrations service keeps installations and the repositories
26//! imported through them, and receives the app's webhook at `https://api.g1t.sh/hooks/github`:
The code page reads like a forge: a branch menu, search and a Code button above the files27//!
28//! - `github_status` takes `GithubStatusArgs`, returns `Outcome<GithubAppStatus>`.
29//! - `github_add_installation` takes `GithubInstallationArgs`, returns
30//! `Outcome<GithubInstallation>`.
31//! - `github_remove_installation` takes `GithubInstallationArgs`, returns
32//! `Outcome<bool>`.
A GitHub App installed straight on GitHub can be added to a workspace: Import from GitHub lists the installations your GitHub account can see that this workspace hasn't added, each with Add for owners, and coming back from GitHub without g1t's own start asks which of your workspaces to add it to instead of stopping; the GitHub guide says how.33//! - `github_visible_installations` takes `GithubVisibleArgs`, returns
34//! `Outcome<Vec<GithubVisibleInstallation>>`.
The code page reads like a forge: a branch menu, search and a Code button above the files35//! - `github_repositories` takes `GithubRepositoriesArgs`, returns
36//! `Outcome<GithubRepositories>`.
37//! - `github_import` takes `GithubImportArgs`, returns `Outcome<GithubRepoLink>`.
38//! - `github_link` takes `GithubLinkArgs`, returns `Option<GithubRepoLink>`.
39//! - `github_receive` takes `GithubReceiveArgs`, returns `integrations::Received`.
40
41use std::collections::HashMap;
42
43use serde::{Deserialize, Serialize};
44
45use crate::User;
46use crate::identity::SignedIn;
47
48/// Why someone is sent to GitHub.
49#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
50#[serde(rename_all = "snake_case")]
51pub enum GithubPurpose {
52 /// Sign in, or create an account.
53 SignIn,
54 /// Link GitHub to the signed-in account.
55 Link,
56}
57
58impl GithubPurpose {
59 pub fn as_str(self) -> &'static str {
60 match self {
61 GithubPurpose::SignIn => "sign_in",
62 GithubPurpose::Link => "link",
63 }
64 }
65}
66
67#[derive(Debug, Serialize, Deserialize)]
68#[serde(rename_all = "camelCase")]
69pub struct GithubStartArgs {
70 pub purpose: GithubPurpose,
71 /// The signed-in person, for `link`.
72 #[serde(default)]
73 pub user: Option<User>,
74 /// Exactly the callback registered on the app, such as
75 /// `https://g1t.sh/auth/github/callback`.
76 pub redirect_uri: String,
77 /// A same-site path to return to afterwards.
78 #[serde(default)]
79 pub next: String,
80 /// An invite code, from `/register?invite=…`, carried through GitHub
81 /// for a new account while g1t is invite-only.
82 #[serde(default)]
83 pub invite_code: Option<String>,
84}
85
86/// Where to send the browser, and the state to bind to it in a cookie.
87#[derive(Debug, Serialize, Deserialize)]
88#[serde(rename_all = "camelCase")]
89pub struct GithubStart {
90 pub authorize_url: String,
91 pub state: String,
92}
93
94#[derive(Debug, Serialize, Deserialize)]
95pub struct GithubFinishArgs {
96 pub state: String,
97 pub code: String,
98}
99
100/// How a return from GitHub ended.
101#[derive(Debug, Serialize, Deserialize)]
102#[serde(tag = "kind", rename_all = "snake_case")]
103pub enum GithubFinished {
104 /// Signed in to the account the GitHub account is linked to, or to a
105 /// new one made with it.
106 SignedIn {
107 #[serde(rename = "signedIn")]
108 signed_in: SignedIn,
109 created: bool,
110 next: String,
111 },
112 /// Linked to the account that asked.
113 Linked { login: String, next: String },
114 /// An account with one of its verified emails exists: the person signs
115 /// in to it, and `github_claim` then links it.
116 NeedsLink { pending: String, login: String, next: String },
117 /// A new account, but the GitHub login cannot be its username.
118 /// A new account, but the GitHub login cannot be its username, or g1t
119 /// is invite-only and no invite code came with the sign-in.
120 NeedsUsername {
121 pending: String,
122 login: String,
123 suggestion: String,
124 next: String,
125 #[serde(rename = "inviteRequired")]
126 invite_required: bool,
127 },
128}
129
130#[derive(Debug, Serialize, Deserialize)]
131pub struct GithubPendingArgs {
132 pub pending: String,
133}
134
135/// A GitHub sign-in waiting on a username or on signing in to link.
136#[derive(Debug, Serialize, Deserialize)]
137pub struct GithubPending {
138 pub login: String,
139 /// `link` or `username`.
140 pub kind: String,
141 pub suggestion: Option<String>,
142 pub next: String,
143 /// Whether the person must give an invite code to create the account.
144 #[serde(rename = "inviteRequired", default)]
145 pub invite_required: bool,
146}
147
148#[derive(Debug, Serialize, Deserialize)]
149pub struct GithubSignUpArgs {
150 pub pending: String,
151 pub username: String,
152 /// Needed while g1t is invite-only, unless one came with the sign-in.
153 #[serde(rename = "inviteCode", default)]
154 pub invite_code: Option<String>,
155}
156
157#[derive(Debug, Serialize, Deserialize)]
158pub struct GithubClaimArgs {
159 pub pending: String,
160 pub user: User,
161}
162
163/// A linked GitHub account. The numeric id is what identifies it; the
164/// login is for showing, and is refreshed at each sign-in.
165#[derive(Clone, Debug, Serialize, Deserialize)]
166#[serde(rename_all = "camelCase")]
167pub struct GithubAccount {
168 pub github_id: u64,
169 pub login: String,
170 /// RFC 3339.
171 pub linked_at: String,
172 /// Whether g1t holds a working user token, needed to list installations.
173 pub authorized: bool,
174}
175
176#[derive(Debug, Default, Serialize, Deserialize)]
177#[serde(rename_all = "camelCase")]
178pub struct GithubAccountView {
179 /// Whether this g1t has a GitHub App configured for sign-in.
180 pub enabled: bool,
181 pub account: Option<GithubAccount>,
182 /// Whether the account has a password, so GitHub is not its only way in.
183 pub has_password: bool,
184}
185
186#[derive(Debug, Serialize, Deserialize)]
187#[serde(rename_all = "camelCase")]
188pub struct GithubUserTokenArgs {
189 pub user_id: String,
190}
191
192#[derive(Debug, Serialize, Deserialize)]
193#[serde(rename_all = "camelCase")]
194pub struct GithubRevokedArgs {
195 pub github_id: u64,
196}
197
198/// `github_usernames`: the g1t usernames of linked GitHub accounts. Returns
199/// a map from GitHub id, as a string, to username.
200#[derive(Debug, Serialize, Deserialize)]
201#[serde(rename_all = "camelCase")]
202pub struct GithubUsernamesArgs {
203 pub github_ids: Vec<u64>,
204}
205
206// --- The app's installations and repositories (integrations) -------------
207
208#[derive(Clone, Debug, Serialize, Deserialize)]
209#[serde(rename_all = "camelCase")]
210pub struct GithubInstallation {
211 pub id: u64,
212 pub workspace: String,
213 /// The GitHub user or organization it is installed on.
214 pub account: String,
215 /// `User` or `Organization`.
216 pub account_type: String,
217 /// `all` or `selected` repositories.
218 pub repository_selection: String,
219 pub suspended: bool,
220 /// Where to change which repositories it can see.
221 pub settings_url: String,
222 pub created_at: String,
223}
224
225#[derive(Debug, Serialize, Deserialize)]
226#[serde(rename_all = "camelCase")]
227pub struct GithubStatusArgs {
228 pub viewer: User,
229 pub workspace: String,
230}
231
232#[derive(Debug, Default, Serialize, Deserialize)]
233#[serde(rename_all = "camelCase")]
234pub struct GithubAppStatus {
235 /// Whether this g1t has a GitHub App configured for repositories.
236 pub configured: bool,
237 /// `https://github.com/apps/<slug>/installations/new`.
238 pub install_url: Option<String>,
239 /// Whether the viewer has linked GitHub with a working user token.
240 pub linked: bool,
241 pub installations: Vec<GithubInstallation>,
242}
243
244#[derive(Debug, Serialize, Deserialize)]
245#[serde(rename_all = "camelCase")]
246pub struct GithubInstallationArgs {
247 pub actor: User,
248 pub workspace: String,
249 pub installation_id: u64,
250}
251
A GitHub App installed straight on GitHub can be added to a workspace: Import from GitHub lists the installations your GitHub account can see that this workspace hasn't added, each with Add for owners, and coming back from GitHub without g1t's own start asks which of your workspaces to add it to instead of stopping; the GitHub guide says how.252/// `github_visible_installations`: the app's installations the person's
253/// own GitHub account can see, for claiming one that was installed on
254/// GitHub directly rather than from g1t.
The code page reads like a forge: a branch menu, search and a Code button above the files255#[derive(Debug, Serialize, Deserialize)]
256#[serde(rename_all = "camelCase")]
A GitHub App installed straight on GitHub can be added to a workspace: Import from GitHub lists the installations your GitHub account can see that this workspace hasn't added, each with Add for owners, and coming back from GitHub without g1t's own start asks which of your workspaces to add it to instead of stopping; the GitHub guide says how.257pub struct GithubVisibleArgs {
258 pub actor: User,
259}
260
261/// An installation of the app, as the person's GitHub account sees it.
262#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
263#[serde(rename_all = "camelCase")]
264pub struct GithubVisibleInstallation {
265 pub id: u64,
266 /// The GitHub user or organization it is installed on.
267 pub account: String,
268 /// `User` or `Organization`.
269 pub account_type: String,
270 /// `all` or `selected` repositories.
271 pub repository_selection: String,
272 pub suspended: bool,
273 pub settings_url: String,
274 /// The person's workspaces it is recorded in already.
275 pub recorded_in: Vec<String>,
276}
277
278#[derive(Debug, Serialize, Deserialize)]
279#[serde(rename_all = "camelCase")]
The code page reads like a forge: a branch menu, search and a Code button above the files280pub struct GithubRepositoriesArgs {
281 pub actor: User,
282 pub workspace: String,
283 pub installation_id: u64,
284 #[serde(default)]
285 pub page: Option<u32>,
286}
287
288#[derive(Clone, Debug, Serialize, Deserialize)]
289#[serde(rename_all = "camelCase")]
290pub struct GithubRepository {
291 pub id: u64,
292 /// `owner/name`.
293 pub full_name: String,
294 pub name: String,
295 pub private: bool,
296 pub description: Option<String>,
297 pub default_branch: String,
Merge branch 'rm-mirroring'298 /// The g1t repository already imported from it, as `workspace/name`.
The code page reads like a forge: a branch menu, search and a Code button above the files299 pub linked_to: Option<String>,
300}
301
302#[derive(Debug, Default, Serialize, Deserialize)]
303#[serde(rename_all = "camelCase")]
304pub struct GithubRepositories {
305 pub repositories: Vec<GithubRepository>,
306 pub total: u32,
307 pub page: u32,
308 pub per_page: u32,
309}
310
311#[derive(Debug, Serialize, Deserialize)]
312#[serde(rename_all = "camelCase")]
313pub struct GithubImportArgs {
314 pub actor: User,
315 pub workspace: String,
316 pub installation_id: u64,
317 pub github_repo_id: u64,
318 /// The g1t repository's name; the GitHub one's when left out.
319 #[serde(default)]
320 pub name: Option<String>,
321 /// Private on g1t; as on GitHub when left out.
322 #[serde(default)]
323 pub private: Option<bool>,
324 /// Also copy issues, with their labels, milestone and state.
325 #[serde(default)]
326 pub issues: bool,
327}
328
Merge branch 'rm-mirroring'329/// Where a g1t repository was imported from on GitHub. The import is
330/// copied once: the g1t repository is then its own.
The code page reads like a forge: a branch menu, search and a Code button above the files331#[derive(Clone, Debug, Serialize, Deserialize)]
332#[serde(rename_all = "camelCase")]
333pub struct GithubRepoLink {
334 pub repo_id: String,
335 /// `workspace/name` on g1t.
336 pub repo: String,
337 pub installation_id: u64,
338 pub github_repo_id: u64,
339 /// `owner/name` on GitHub.
340 pub full_name: String,
Merge branch 'rm-mirroring'341 /// RFC 3339; when it was imported.
The code page reads like a forge: a branch menu, search and a Code button above the files342 pub synced_at: Option<String>,
343 pub last_error: Option<String>,
344 /// Issues copied so far, when they were asked for.
345 pub issues_imported: u32,
346}
347
348#[derive(Debug, Serialize, Deserialize)]
349#[serde(rename_all = "camelCase")]
350pub struct GithubLinkArgs {
351 pub repo_id: String,
352}
353
354#[derive(Debug, Serialize, Deserialize)]
355pub struct GithubReceiveArgs {
356 /// Header names in lowercase.
357 pub headers: HashMap<String, String>,
358 pub body: String,
359}
A GitHub App installed straight on GitHub can be added to a workspace: Import from GitHub lists the installations your GitHub account can see that this workspace hasn't added, each with Add for owners, and coming back from GitHub without g1t's own start asks which of your workspaces to add it to instead of stopping; the GitHub guide says how.360
361#[cfg(test)]
362mod tests {
363 use super::*;
364
365 #[test]
366 fn a_visible_installation_reads_as_the_site_expects() {
367 let item = GithubVisibleInstallation {
368 id: 7,
369 account: "flagon-io".into(),
370 account_type: "Organization".into(),
371 repository_selection: "all".into(),
372 suspended: false,
373 settings_url: "https://github.com/organizations/flagon-io/settings/installations/7".into(),
374 recorded_in: vec!["flagon-io".into()],
375 };
376 let json = serde_json::to_value(&item).unwrap();
377 assert_eq!(json["accountType"], "Organization");
378 assert_eq!(json["repositorySelection"], "all");
379 assert_eq!(json["recordedIn"][0], "flagon-io");
380 assert_eq!(serde_json::from_value::<GithubVisibleInstallation>(json).unwrap(), item);
381 }
382}

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