g1t/crates/contracts/src/repos.rs

1,277 lines43,141 bytesCodeBlame

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

Rust repos service with shipping; pull requests kept in the model1//! The repos service: repository metadata, contents, forks and git access.
2//!
3//! Each `*Args` struct is the argument of the method of the same name,
4//! served at `POST /rpc/<method>`.
5
6use serde::{Deserialize, Serialize};
7
8use crate::{User, Viewer};
9
10#[derive(Clone, Debug, Serialize, Deserialize)]
11#[serde(rename_all = "camelCase")]
12pub struct Repo {
13 pub id: String,
Workspaces own repositories14 /// The slug of the workspace that owns it: the first URL segment.
Rust repos service with shipping; pull requests kept in the model15 pub namespace: String,
16 pub name: String,
17 pub description: Option<String>,
18 pub is_private: bool,
19 pub owner_id: String,
20 pub default_branch: String,
Issues and pull requests replace intents and attempts21 /// Set when this repo is a pull request's working copy of another repo.
Rust repos service with shipping; pull requests kept in the model22 pub fork_of: Option<String>,
Agents as a team: lifecycle, merge queue, billing and a new shell23 /// Whether the default branch is protected: it changes only by merging
24 /// a pull request, and pushes to it are refused.
25 #[serde(default)]
26 pub protected: bool,
RFC 3339 timestamps in identity and repos27 /// RFC 3339.
28 pub created_at: String,
Search across all of g1t, Explore, and a command palette29 /// Words that say what it is about, for search and Explore: lowercase
30 /// letters, digits and hyphens. See [`clean_topics`].
31 #[serde(default)]
32 pub topics: Vec<String>,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look33 /// Its home page, an http(s) address, shown beside its description.
34 /// See [`clean_website`].
35 #[serde(default)]
36 pub website: Option<String>,
37 /// RFC 3339: when it was archived, made read-only. Null when it is not.
38 #[serde(default)]
39 pub archived_at: Option<String>,
40}
41
42impl Repo {
43 pub fn archived(&self) -> bool {
44 self.archived_at.is_some()
45 }
46}
47
48/// How long a deleted repository can be restored before it is purged.
49pub const RESTORE_DAYS: u64 = 30;
50
51/// A deleted repository, as its workspace's Recently deleted list shows
52/// it: restorable until `purge_after`.
53#[derive(Clone, Debug, Serialize, Deserialize)]
54#[serde(rename_all = "camelCase")]
55pub struct DeletedRepo {
56 pub id: String,
57 pub namespace: String,
58 pub name: String,
59 pub description: Option<String>,
60 pub is_private: bool,
61 /// RFC 3339.
62 pub deleted_at: String,
63 /// The username of who deleted it.
64 pub deleted_by: String,
65 /// RFC 3339: when it is purged, unless restored first.
66 pub purge_after: String,
Search across all of g1t, Explore, and a command palette67}
68
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look69/// The longest website address a repository keeps.
70pub const MAX_WEBSITE_CHARS: usize = 255;
71
72/// A website as it is kept: an http(s) address, `https://` added when no
73/// scheme is given; empty clears it. Anything else is refused.
74pub fn clean_website(text: &str) -> Result<Option<String>, String> {
75 let text = text.trim();
76 if text.is_empty() {
77 return Ok(None);
78 }
79 let url = if text.starts_with("https://") || text.starts_with("http://") {
80 text.to_owned()
81 } else if text.contains("://") {
82 return Err("A website is an http or https address.".into());
83 } else {
84 format!("https://{text}")
85 };
86 let host = url
87 .split("://")
88 .nth(1)
89 .unwrap_or("")
90 .split(['/', '?', '#'])
91 .next()
92 .unwrap_or("");
93 if url.chars().count() > MAX_WEBSITE_CHARS
94 || host.is_empty()
95 || !host.contains('.')
96 || url.chars().any(char::is_whitespace)
97 {
98 return Err("That is not a website address, such as https://example.com.".into());
99 }
100 Ok(Some(url))
101}
102
103/// Whether `name` can be a branch people name: what `git check-ref-format
104/// --branch` accepts, less the names g1t keeps for itself
105/// ([`G1T_BRANCH_PREFIX`]).
106pub fn is_valid_branch_name(name: &str) -> bool {
107 !name.is_empty()
108 && name.len() <= 200
109 && !name.starts_with('-')
110 && !name.starts_with('/')
111 && !name.ends_with('/')
112 && !name.ends_with('.')
113 && !name.ends_with(".lock")
114 && !name.contains("..")
115 && !name.contains("//")
116 && !name.contains("@{")
117 && name != "@"
118 && !name.starts_with(G1T_BRANCH_PREFIX)
119 && !name.split('/').any(|part| part.starts_with('.'))
120 && name
121 .chars()
122 .all(|c| !c.is_control() && !matches!(c, ' ' | '~' | '^' | ':' | '?' | '*' | '[' | '\\'))
123}
124
Search across all of g1t, Explore, and a command palette125/// The most topics a repository has.
126pub const MAX_TOPICS: usize = 20;
127/// The longest topic.
128pub const MAX_TOPIC_CHARS: usize = 35;
129
130/// Topics as they are kept: lowercase, spaces and underscores made
131/// hyphens, each of letters, digits and hyphens, starting with a letter or
132/// digit, without repeats, at most [`MAX_TOPICS`]. Anything else is the
133/// first topic that could not be read.
134pub fn clean_topics(topics: &[String]) -> Result<Vec<String>, String> {
135 let mut kept: Vec<String> = Vec::new();
136 for topic in topics {
137 let topic: String = topic
138 .trim()
139 .to_lowercase()
140 .chars()
141 .map(|c| if c == ' ' || c == '_' { '-' } else { c })
142 .collect();
143 if topic.is_empty() {
144 continue;
145 }
146 let valid = topic.chars().count() <= MAX_TOPIC_CHARS
147 && topic.chars().all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '-')
148 && topic.chars().next().is_some_and(|c| c.is_ascii_alphanumeric());
149 if !valid {
150 return Err(format!(
151 "\"{topic}\" is not a topic: use letters, digits and hyphens, at most {MAX_TOPIC_CHARS} characters."
152 ));
153 }
154 if !kept.contains(&topic) {
155 kept.push(topic);
156 }
157 }
158 if kept.len() > MAX_TOPICS {
159 return Err(format!("A repository has at most {MAX_TOPICS} topics."));
160 }
161 Ok(kept)
Rust repos service with shipping; pull requests kept in the model162}
163
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API164#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
Rust repos service with shipping; pull requests kept in the model165pub struct RepoPath {
166 pub namespace: String,
167 pub name: String,
168}
169
170#[derive(Clone, Debug, Serialize, Deserialize)]
171pub struct Signature {
172 pub name: String,
173 pub email: String,
174}
175
176#[derive(Clone, Debug, Serialize, Deserialize)]
177#[serde(rename_all = "camelCase")]
178pub struct Commit {
179 pub hash: String,
180 pub tree_hash: String,
181 pub message: String,
182 pub author: Signature,
183 pub parents: Vec<String>,
RFC 3339 timestamps in identity and repos184 /// RFC 3339.
185 pub authored_at: String,
Rust repos service with shipping; pull requests kept in the model186}
187
188#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
189#[serde(rename_all = "lowercase")]
190pub enum EntryKind {
191 Tree,
192 Blob,
193 Symlink,
194 Gitlink,
195 Exec,
196}
197
198#[derive(Clone, Debug, Serialize, Deserialize)]
199pub struct TreeEntry {
200 pub name: String,
201 pub hash: String,
202 pub kind: EntryKind,
203}
204
205#[derive(Clone, Debug, Serialize, Deserialize)]
206pub struct Readme {
207 pub name: String,
208 /// Null when the file is binary or too large to show.
209 pub text: Option<String>,
210}
211
212#[derive(Clone, Debug, Serialize, Deserialize)]
213pub struct TreeView {
214 pub repo: Repo,
215 #[serde(rename = "ref")]
216 pub git_ref: String,
217 pub path: String,
218 /// Null when the repo has no commits yet.
219 pub head: Option<Commit>,
220 pub entries: Vec<TreeEntry>,
221 pub readme: Option<Readme>,
222}
223
224#[derive(Clone, Debug, Serialize, Deserialize)]
225pub struct BlobView {
226 pub repo: Repo,
227 #[serde(rename = "ref")]
228 pub git_ref: String,
229 pub path: String,
230 pub size: u64,
231 /// Null when the file is binary or too large to show.
232 pub text: Option<String>,
233}
234
235/// A git remote and a short-lived credential for it.
236#[derive(Clone, Debug, Serialize, Deserialize)]
237pub struct GitAccess {
238 pub remote: String,
239 pub token: String,
240}
241
242#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
243pub enum GitService {
244 #[serde(rename = "git-upload-pack")]
245 UploadPack,
246 #[serde(rename = "git-receive-pack")]
247 ReceivePack,
248}
249
Issues and pull requests replace intents and attempts250/// The result of landing a pull request.
Rust repos service with shipping; pull requests kept in the model251#[derive(Clone, Debug, Serialize, Deserialize)]
252pub struct Landed {
Diffs on attempts; hosted agent presented as the g1t agent253 /// The commit the branch points to now.
Rust repos service with shipping; pull requests kept in the model254 pub commit: String,
Diffs on attempts; hosted agent presented as the g1t agent255 /// The commit it pointed to before, if it had one. Comparing against
Issues and pull requests replace intents and attempts256 /// this shows what the pull request changed.
Diffs on attempts; hosted agent presented as the g1t agent257 pub previous: Option<String>,
Rust repos service with shipping; pull requests kept in the model258}
259
260/// `get`. Returns `Outcome<Repo>`.
261#[derive(Debug, Serialize, Deserialize)]
262pub struct GetArgs {
263 pub path: RepoPath,
264 pub viewer: Viewer,
265}
266
Automations: rules in .g1t/automations that act when something happens267/// `path_by_id`: where a repository is, whoever may see it. For g1t's own
268/// services, which hold a repository's id from an event and act for its
269/// workspace; nothing outside reaches it. Returns `Option<RepoPath>`, null
270/// for a fork or an unknown id.
271#[derive(Debug, Serialize, Deserialize)]
272pub struct PathByIdArgs {
273 pub id: String,
274}
275
Rust repos service with shipping; pull requests kept in the model276/// `get_by_id`. Returns `Outcome<Repo>`.
277#[derive(Debug, Serialize, Deserialize)]
278pub struct GetByIdArgs {
279 pub id: String,
280 pub viewer: Viewer,
281}
282
283/// `list`: repos the viewer may see, newest first. Returns `Vec<Repo>`.
284#[derive(Debug, Default, Serialize, Deserialize)]
Workspaces own repositories285#[serde(rename_all = "camelCase")]
Rust repos service with shipping; pull requests kept in the model286pub struct ListArgs {
287 pub viewer: Viewer,
288 #[serde(default)]
289 pub query: Option<String>,
Workspaces own repositories290 /// Only repos in this workspace.
Rust repos service with shipping; pull requests kept in the model291 #[serde(default)]
292 pub namespace: Option<String>,
Workspaces own repositories293 /// Only repos in workspaces the viewer belongs to.
294 #[serde(default)]
295 pub member_only: bool,
Rust repos service with shipping; pull requests kept in the model296}
297
298/// `create`. Returns `Outcome<Repo>`.
299#[derive(Debug, Serialize, Deserialize)]
300#[serde(rename_all = "camelCase")]
301pub struct CreateArgs {
Workspaces own repositories302 /// Who is creating it; they must belong to the workspace.
Rust repos service with shipping; pull requests kept in the model303 pub owner: User,
Workspaces own repositories304 /// The workspace it is created in.
305 pub namespace: String,
Rust repos service with shipping; pull requests kept in the model306 pub name: String,
307 #[serde(default)]
308 pub description: Option<String>,
309 #[serde(default)]
310 pub is_private: bool,
Agents as a team: lifecycle, merge queue, billing and a new shell311 /// The https address of a public git repository to copy the default
312 /// branch of, such as `https://github.com/owner/repo`.
313 #[serde(default)]
314 pub import_url: Option<String>,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look315 /// With `import_url`: a GitHub installation access token that opens it,
316 /// for a private repository. Every branch and tag is then copied, not
317 /// only the default branch. Set only by the integrations service.
318 #[serde(default, skip_serializing_if = "Option::is_none")]
319 pub import_token: Option<String>,
Rust repos service with shipping; pull requests kept in the model320}
321
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look322/// `mirror`: makes a repository's branches and tags match another git
323/// host's, or pushes its own out to one. Services only. Returns
324/// `Outcome<Mirrored>`.
325#[derive(Debug, Serialize, Deserialize)]
326#[serde(rename_all = "camelCase")]
327pub struct MirrorArgs {
328 pub repo_id: String,
329 /// The other host's https address, such as
330 /// `https://github.com/owner/repo.git`.
331 pub url: String,
332 /// A GitHub installation access token for it. Opaque: any length.
333 pub token: String,
334 pub direction: MirrorDirection,
335}
336
337#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
338#[serde(rename_all = "snake_case")]
339pub enum MirrorDirection {
340 /// The repository on g1t follows the other host: its refs are moved,
341 /// and removed, to match.
342 Pull,
343 /// The other host follows g1t: refs g1t has are pushed there; refs only
344 /// the other host has are left alone.
345 Push,
346}
347
348/// What a `mirror` changed.
349#[derive(Clone, Debug, Default, Serialize, Deserialize)]
350#[serde(rename_all = "camelCase")]
351pub struct Mirrored {
352 /// Full ref names created or moved.
353 pub updated: Vec<String>,
354 pub deleted: Vec<String>,
355}
356
Agents as a team: lifecycle, merge queue, billing and a new shell357/// `update`: changes whichever of a repository's details are given.
358/// Members of its workspace only. Returns `Outcome<Repo>`.
359#[derive(Debug, Serialize, Deserialize)]
360#[serde(rename_all = "camelCase")]
361pub struct UpdateArgs {
362 pub actor: User,
363 pub path: RepoPath,
364 /// An empty description clears it.
365 #[serde(default)]
366 pub description: Option<String>,
367 #[serde(default)]
368 pub is_private: Option<bool>,
369 #[serde(default)]
370 pub protected: Option<bool>,
Search across all of g1t, Explore, and a command palette371 /// Replaces its topics; an empty list clears them.
372 #[serde(default)]
373 pub topics: Option<Vec<String>>,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look374 /// Its home page; an empty string clears it.
375 #[serde(default)]
376 pub website: Option<String>,
377 /// Where the request came in, for the audit log; g1t.sh when absent.
378 #[serde(default)]
379 pub surface: Option<crate::audit::Surface>,
Agents as a team: lifecycle, merge queue, billing and a new shell380}
381
Rust repos service with shipping; pull requests kept in the model382/// `tree`. Returns `Outcome<TreeView>`.
383#[derive(Debug, Serialize, Deserialize)]
384#[serde(rename_all = "camelCase")]
385pub struct TreeArgs {
386 pub path: RepoPath,
387 pub viewer: Viewer,
388 /// The default branch when absent.
389 #[serde(default, rename = "ref")]
390 pub git_ref: Option<String>,
391 #[serde(default)]
392 pub tree_path: String,
393}
394
395/// `blob`. Returns `Outcome<BlobView>`.
396#[derive(Debug, Serialize, Deserialize)]
397#[serde(rename_all = "camelCase")]
398pub struct BlobArgs {
399 pub path: RepoPath,
400 pub viewer: Viewer,
401 #[serde(rename = "ref")]
402 pub git_ref: String,
403 pub file_path: String,
404}
405
406/// `log`. Returns `Outcome<Vec<Commit>>`.
407#[derive(Debug, Serialize, Deserialize)]
408pub struct LogArgs {
409 pub path: RepoPath,
410 pub viewer: Viewer,
411 #[serde(default, rename = "ref")]
412 pub git_ref: Option<String>,
413 pub limit: u32,
414}
415
Issues and pull requests replace intents and attempts416/// `fork_for_pull`: a copy-on-write copy of the source repo, hidden from
417/// listings, for one pull request to be made in. Returns `Outcome<Repo>`.
Rust repos service with shipping; pull requests kept in the model418#[derive(Debug, Serialize, Deserialize)]
419#[serde(rename_all = "camelCase")]
420pub struct ForkArgs {
421 pub source_id: String,
Issues and pull requests replace intents and attempts422 pub pull_id: String,
Rust repos service with shipping; pull requests kept in the model423 pub actor: User,
424}
425
426/// `git_access`: authorizes a git operation and says where to send it.
427/// Pushing to a repo that does not exist creates it in the pusher's own
428/// namespace. Returns `Outcome<GitAccess>`.
429#[derive(Debug, Serialize, Deserialize)]
430pub struct GitAccessArgs {
431 pub path: RepoPath,
432 pub viewer: Viewer,
433 pub service: GitService,
434}
435
Pull requests from branches436/// `land`: moves a repository's default branch to the head of a pull
437/// request's source. Refused with `conflict` when the source is behind,
438/// since that would discard commits. Returns `Outcome<Landed>`.
Rust repos service with shipping; pull requests kept in the model439#[derive(Debug, Serialize, Deserialize)]
440#[serde(rename_all = "camelCase")]
441pub struct LandArgs {
Pull requests from branches442 /// The repository holding the commits: a pull request's fork, or the
443 /// target itself when landing one of its own branches.
444 pub source_id: String,
445 /// The branch of the source to land. Required when the source is the
446 /// target; a fork lands its default branch.
447 #[serde(default)]
448 pub branch: Option<String>,
Rust repos service with shipping; pull requests kept in the model449 pub actor: User,
450}
Diffs on attempts; hosted agent presented as the g1t agent451
452#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
453#[serde(rename_all = "lowercase")]
454pub enum FileStatus {
455 Added,
456 Modified,
457 Deleted,
458}
459
460#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
461#[serde(rename_all = "lowercase")]
462pub enum LineKind {
463 /// Unchanged, shown for context.
464 Context,
465 Add,
466 Delete,
467}
468
469#[derive(Clone, Debug, Serialize, Deserialize)]
470pub struct DiffLine {
471 pub kind: LineKind,
472 /// Line number in the old file; absent for added lines.
473 pub old: Option<u32>,
474 /// Line number in the new file; absent for deleted lines.
475 pub new: Option<u32>,
476 pub text: String,
477}
478
479/// A run of changed lines with their surrounding context.
480#[derive(Clone, Debug, Serialize, Deserialize)]
481pub struct Hunk {
482 pub lines: Vec<DiffLine>,
483}
484
485#[derive(Clone, Debug, Serialize, Deserialize)]
486pub struct FileDiff {
487 pub path: String,
488 pub status: FileStatus,
489 pub additions: u32,
490 pub deletions: u32,
491 /// True when the file is binary or too large, so no lines are shown.
492 pub binary: bool,
493 pub hunks: Vec<Hunk>,
494}
495
496/// What changed between two commits.
497#[derive(Clone, Debug, Serialize, Deserialize)]
498pub struct Comparison {
499 /// Null when the head has no earlier commit to compare against.
500 pub base: Option<String>,
501 pub head: String,
502 pub files: Vec<FileDiff>,
503 /// True when the change was too large to return in full.
504 pub truncated: bool,
505}
506
Pull requests from branches507/// `compare`: what `head` changes relative to `base`.
Diffs on attempts; hosted agent presented as the g1t agent508///
Pull requests from branches509/// `head` is a branch or a commit, and defaults to the default branch.
510/// With no `base`, a fork is compared against the point where it and the
511/// repository it came from last agreed; a branch against the point where it
512/// left the default branch; and the default branch against its head's
513/// parent. Returns `Outcome<Comparison>`.
Diffs on attempts; hosted agent presented as the g1t agent514#[derive(Debug, Serialize, Deserialize)]
515#[serde(rename_all = "camelCase")]
516pub struct CompareArgs {
517 pub repo_id: String,
518 pub viewer: Viewer,
519 #[serde(default)]
520 pub base: Option<String>,
Pull requests from branches521 #[serde(default)]
522 pub head: Option<String>,
523}
524
Agents as a team: lifecycle, merge queue, billing and a new shell525/// Lines `start` to `end` of a file, inclusive and counted from 1, last
526/// changed by `commit`.
527#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
528pub struct BlameRange {
529 pub start: u32,
530 pub end: u32,
531 pub commit: String,
532}
533
534/// Who last changed each line of a file.
535#[derive(Clone, Debug, Serialize, Deserialize)]
536pub struct Blame {
537 /// The commit the file was read at.
538 pub head: String,
539 /// Every line, in order, in runs that share a commit.
540 pub ranges: Vec<BlameRange>,
541 /// The commits the ranges name, each once.
542 pub commits: Vec<Commit>,
543 /// True when the history was too long to read in full, so the oldest
544 /// lines are given to the oldest commit read.
545 pub partial: bool,
546}
547
548/// `blame`: who last changed each line of `path` as of `ref` (the default
549/// branch if absent). Returns `Outcome<Blame>`; not found when the file is
550/// missing or is not text.
551#[derive(Debug, Serialize, Deserialize)]
552pub struct BlameArgs {
553 pub path: RepoPath,
554 pub viewer: Viewer,
555 #[serde(default, rename = "ref")]
556 pub git_ref: Option<String>,
557 #[serde(rename = "filePath")]
558 pub file_path: String,
559}
560
Pull requests from branches561/// A branch and the commit it points to.
562#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
563pub struct Branch {
564 pub name: String,
565 pub hash: String,
566}
567
Branches and Tags pages, each file's last commit, and the branch menu on files568/// `last_commits`: which commit last changed each entry of a directory at
569/// `ref` (the default branch when absent). Returns `Outcome<LastCommits>`.
570#[derive(Debug, Serialize, Deserialize)]
571#[serde(rename_all = "camelCase")]
572pub struct LastCommitsArgs {
573 pub path: RepoPath,
574 pub viewer: Viewer,
575 #[serde(default, rename = "ref")]
576 pub git_ref: Option<String>,
577 #[serde(default)]
578 pub tree_path: String,
A last-commits walk runs to the end unless asked for a budget; the page bounds its own wait579 /// Answer within this many milliseconds with what was found, not kept;
580 /// absent, the walk runs to the end and is kept.
581 #[serde(default)]
582 pub budget_ms: Option<u64>,
Branches and Tags pages, each file's last commit, and the branch menu on files583}
584
585/// An entry of a directory and the commit that last changed it.
586#[derive(Clone, Debug, Serialize, Deserialize)]
587pub struct LastCommit {
588 pub name: String,
589 pub commit: Commit,
590}
591
592/// The entries' last commits. `complete` is false when the history walked
593/// ran out before every entry was placed; those entries are left out.
594#[derive(Clone, Debug, Serialize, Deserialize)]
595pub struct LastCommits {
596 pub entries: Vec<LastCommit>,
597 pub complete: bool,
598}
599
600/// `tags`: the repository's tags, newest commit first, each with the
601/// commit it names. Returns `Outcome<Vec<Tag>>`.
602#[derive(Debug, Serialize, Deserialize)]
603pub struct TagsArgs {
604 pub path: RepoPath,
605 pub viewer: Viewer,
606}
607
608/// A tag, and its commit when it could be read.
609#[derive(Clone, Debug, Serialize, Deserialize)]
610pub struct Tag {
611 pub name: String,
612 pub commit: Option<Commit>,
613}
614
Pull requests from branches615/// `branches`: the repository's branches, default branch first.
616/// Returns `Outcome<Vec<Branch>>`.
617#[derive(Debug, Serialize, Deserialize)]
618pub struct BranchesArgs {
619 pub path: RepoPath,
620 pub viewer: Viewer,
621}
622
Agents as a team: lifecycle, merge queue, billing and a new shell623/// `behind`: whether the default branch of the repository a pull request
624/// would merge into has commits its source does not. For services that
625/// have already decided the caller may see the pull request; it reveals
626/// one bit. Returns `bool`.
627#[derive(Debug, Serialize, Deserialize)]
628#[serde(rename_all = "camelCase")]
629pub struct BehindArgs {
630 /// The pull request's fork, or the repository itself for a branch.
631 pub source_id: String,
632 /// The branch of the source. A fork is compared on its default branch.
633 #[serde(default)]
634 pub branch: Option<String>,
635}
636
Agents and memory, checks and conflicts, profiles, slug renames, custom domains637/// `divergence`: how a pull request's source and the default branch it
638/// would merge into have moved apart since they last agreed: the files each
639/// side changed. Takes `BehindArgs`. For services that have already decided
640/// the caller may see the pull request; it reveals paths, not contents.
641/// Returns `Option<Divergence>`, null when either side has no commits.
642#[derive(Clone, Debug, Default, Serialize, Deserialize)]
643#[serde(rename_all = "camelCase")]
644pub struct Divergence {
645 /// The source's commit.
646 pub head: String,
647 /// The default branch's commit.
648 pub base: String,
649 /// Where they last agreed, if that could be found.
650 pub merge_base: Option<String>,
651 /// Whether the default branch has commits the source does not.
652 pub behind: bool,
653 /// The files the source changed since the merge base.
654 pub ours: Vec<String>,
655 /// The files the default branch changed since the merge base. Empty
656 /// when it is not behind.
657 pub theirs: Vec<String>,
658 /// Whether either list was cut short.
659 pub truncated: bool,
660}
661
Catching up with main takes seconds when the two sides touched different files662/// `update_pull_branch`: brings a pull request's source up to date with the
663/// default branch it would merge into, without a sandbox, when that can be
664/// done safely: merges the default branch's head into the source's head and
665/// pushes the merge commit to the source's branch, as `actor`, only if the
666/// branch has not moved meanwhile. It applies only when the two sides
667/// changed different files since they last agreed; otherwise the answer is
668/// [`PullBranchUpdate::NeedsAgent`] and nothing is pushed. Refused unless
669/// `actor` may push to the source. Returns `Outcome<PullBranchUpdate>`.
670#[derive(Debug, Serialize, Deserialize)]
671#[serde(rename_all = "camelCase")]
672pub struct UpdatePullBranchArgs {
673 /// The pull request's fork, or the repository itself for a branch.
674 pub source_id: String,
675 /// The branch of the source. A fork is updated on its default branch.
676 #[serde(default)]
677 pub branch: Option<String>,
678 /// The pull request's number, to name it in the merge commit's message
679 /// when its branch has the same name as the default branch.
680 pub number: u32,
681 /// Who asked: the merge commit's author and committer, and the pusher.
682 pub actor: User,
683}
684
685/// Why an update has to be left to a sandbox.
686#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
687#[serde(rename_all = "snake_case")]
688pub enum NeedsAgentReason {
689 /// Both sides changed some of the same files; merging them needs a
690 /// real merge, which may or may not conflict.
691 Overlap,
692 /// Merging is known to conflict.
693 Conflicting,
694 /// The update could not be worked out here, such as when the two sides
695 /// share no history g1t can see, or the change is too large to list.
696 Unsupported,
697}
698
699/// What came of `update_pull_branch` (or the work service's `catch_up_pull`).
700#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
701#[serde(tag = "outcome", rename_all = "snake_case")]
702pub enum PullBranchUpdate {
703 /// The merge commit was pushed: the branch moved from `previous` to
704 /// `commit`.
705 Updated { commit: String, previous: String },
706 /// The source already holds the default branch's head.
707 UpToDate { commit: String },
708 /// Nothing was pushed; a sandbox has to merge it. `paths` are the
709 /// files both sides changed, or that conflict, when known.
710 NeedsAgent {
711 reason: NeedsAgentReason,
712 detail: String,
713 paths: Vec<String>,
714 },
715}
716
Pull requests from branches717/// `head`: the commit a branch points to, or null. For services reacting
718/// to a push, which have no viewer; it reveals nothing but a commit hash.
719/// Returns `Option<String>`.
720#[derive(Debug, Serialize, Deserialize)]
721#[serde(rename_all = "camelCase")]
722pub struct HeadArgs {
723 pub repo_id: String,
Workflows run when an agent's pull request is marked ready724 /// Empty for the repository's default branch.
Pull requests from branches725 pub branch: String,
Diffs on attempts; hosted agent presented as the g1t agent726}
Merge queue: tested states are deleted once their entry leaves727
728/// Where g1t keeps branches of its own in a repository, such as the merge
729/// queue's tested states. Only these can be removed with `delete_branch`.
730pub const G1T_BRANCH_PREFIX: &str = "g1t-";
731
732/// `delete_branch`: removes a branch g1t made for itself once it is done
733/// with it, never one of people's: the name must start with
734/// [`G1T_BRANCH_PREFIX`]. For services, which have no viewer. Returns
735/// `Outcome<bool>`: whether there was such a branch.
736#[derive(Debug, Serialize, Deserialize)]
737#[serde(rename_all = "camelCase")]
738pub struct DeleteBranchArgs {
739 pub repo_id: String,
740 pub branch: String,
741}
Agents and memory, checks and conflicts, profiles, slug renames, custom domains742
Fast pages, required checks on the branch, self-hosted runners, honest incidents743/// `commit_file`: writes one file on a new branch made from the default
744/// branch's head, as one commit by `actor`, without a sandbox. For a change
745/// g1t proposes on someone's behalf, such as a starter workflow, which then
746/// becomes a pull request. Refused unless `actor` may push, when the branch
747/// already exists, or when the file is already there. Returns
748/// `Outcome<CommittedFile>`.
749#[derive(Debug, Serialize, Deserialize)]
750#[serde(rename_all = "camelCase")]
751pub struct CommitFileArgs {
752 pub repo: RepoPath,
753 pub actor: User,
754 /// The new branch, which must not exist yet.
755 pub branch: String,
756 /// Where the file goes, such as `.g1t/workflows/ci.yml`.
757 pub path: String,
758 pub content: String,
759 pub message: String,
760}
761
762/// The commit `commit_file` made.
763#[derive(Clone, Debug, Serialize, Deserialize)]
764#[serde(rename_all = "camelCase")]
765pub struct CommittedFile {
766 pub branch: String,
767 pub commit: String,
768}
769
Agents and memory, checks and conflicts, profiles, slug renames, custom domains770/// `readable`: of these repository ids, the repositories the viewer may
771/// read, as `get_by_id` decides; forks and unknown ids are left out. For
772/// services that hold ids and must show only what the viewer could open.
773/// At most [`MAX_READABLE`] ids are looked at. Returns `Vec<Repo>`.
774#[derive(Debug, Serialize, Deserialize)]
775pub struct ReadableArgs {
776 pub ids: Vec<String>,
777 pub viewer: Viewer,
778}
779
780/// The most ids one `readable` call looks at.
781pub const MAX_READABLE: usize = 500;
782
783/// `public_namespaces`: the workspaces in which this account made a public
784/// repository, and so a public project, which anyone can see on its page.
785/// Returns `Vec<String>` of workspace slugs.
786#[derive(Debug, Serialize, Deserialize)]
787#[serde(rename_all = "camelCase")]
788pub struct PublicNamespacesArgs {
789 pub owner_id: String,
790}
Search across all of g1t, Explore, and a command palette791
792/// One file on a branch, or one a change touched: its path and blob.
793#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
794pub struct FileEntry {
795 pub path: String,
796 /// The blob it holds now; null when the change deleted it.
797 pub hash: Option<String>,
798}
799
800/// Files, and whether there were more than were listed.
801#[derive(Clone, Debug, Default, Serialize, Deserialize)]
802#[serde(rename_all = "camelCase")]
803pub struct FileList {
804 /// The commit the files were read at; null for an empty repository.
805 pub commit: Option<String>,
806 pub files: Vec<FileEntry>,
807 pub truncated: bool,
808}
809
810/// `list_files`: every file on a branch (the default branch when absent),
811/// path order by level, never descending into a directory named in
812/// `skip_dirs`. For services that index a repository; no viewer, since it
813/// is only reached by g1t's own services. Returns `FileList`.
814#[derive(Debug, Default, Serialize, Deserialize)]
815#[serde(rename_all = "camelCase")]
816pub struct ListFilesArgs {
817 pub repo_id: String,
818 #[serde(default, rename = "ref")]
819 pub git_ref: Option<String>,
820 #[serde(default)]
821 pub skip_dirs: Vec<String>,
822 /// At most this many files; capped at [`MAX_LISTED_FILES`].
823 pub limit: u32,
824}
825
826/// `changed_files`: the files that differ between two commits, as
827/// `list_files` reads them. With no `base`, every file at `head`. Returns
828/// `FileList`.
829#[derive(Debug, Default, Serialize, Deserialize)]
830#[serde(rename_all = "camelCase")]
831pub struct ChangedFilesArgs {
832 pub repo_id: String,
833 #[serde(default)]
834 pub base: Option<String>,
835 pub head: String,
836 #[serde(default)]
837 pub skip_dirs: Vec<String>,
838 pub limit: u32,
839}
840
841/// The most files one `list_files` or `changed_files` call lists.
842pub const MAX_LISTED_FILES: u32 = 10_000;
843
844/// `read_blobs`: the text of these blobs of a repository, for services
845/// that index it. A blob larger than `max_bytes`, or binary, comes back
846/// with no text. Returns `Vec<BlobText>`, in the order asked.
847#[derive(Debug, Default, Serialize, Deserialize)]
848#[serde(rename_all = "camelCase")]
849pub struct ReadBlobsArgs {
850 pub repo_id: String,
851 pub hashes: Vec<String>,
852 pub max_bytes: u32,
853}
854
855/// The most blobs one `read_blobs` call reads.
856pub const MAX_READ_BLOBS: usize = 100;
857
Composer from the workspace's own repositories, and go get from g1t.sh858/// `refs`: a repository's branches and tags with the commit each points to
859/// (annotated tags peeled), for services that follow them, such as the
860/// packages service's Composer registry. No viewer: g1t's own services
861/// only. Returns `Option<RepoRefs>`, null for a fork or an unknown id.
862#[derive(Debug, Default, Serialize, Deserialize)]
863#[serde(rename_all = "camelCase")]
864pub struct RefsArgs {
865 pub repo_id: String,
866}
867
868#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
869pub struct GitRefEntry {
870 /// The full ref: `refs/heads/main`, `refs/tags/v1.0.0`.
871 pub name: String,
872 pub commit: String,
873}
874
875#[derive(Clone, Debug, Serialize, Deserialize)]
876pub struct RepoRefs {
877 pub repo: Repo,
878 pub refs: Vec<GitRefEntry>,
879}
880
881/// `raw_file`: one file's bytes at a ref or commit, base64, for g1t's own
882/// services (no viewer). Returns `Option<RawFile>`: null when the file is
883/// missing or larger than `max_bytes`.
884#[derive(Debug, Default, Serialize, Deserialize)]
885#[serde(rename_all = "camelCase")]
886pub struct RawFileArgs {
887 pub repo_id: String,
888 #[serde(rename = "ref")]
889 pub git_ref: String,
890 pub path: String,
891 pub max_bytes: u32,
892}
893
894#[derive(Clone, Debug, Serialize, Deserialize)]
895pub struct RawFile {
896 pub size: u64,
897 /// Standard base64.
898 pub data: String,
899}
900
901/// `raw_blobs`: blobs' bytes, base64, in the order asked, at most
902/// [`MAX_READ_BLOBS`]; `data` is null for one missing or larger than
903/// `max_bytes`. For g1t's own services. Returns `Vec<RawBlob>`.
904#[derive(Debug, Default, Serialize, Deserialize)]
905#[serde(rename_all = "camelCase")]
906pub struct RawBlobsArgs {
907 pub repo_id: String,
908 pub hashes: Vec<String>,
909 pub max_bytes: u32,
910}
911
912#[derive(Clone, Debug, Serialize, Deserialize)]
913pub struct RawBlob {
914 pub hash: String,
915 pub size: u64,
916 pub data: Option<String>,
917}
918
Search across all of g1t, Explore, and a command palette919#[derive(Clone, Debug, Serialize, Deserialize)]
920pub struct BlobText {
921 pub hash: String,
922 pub size: u64,
923 /// Null when the blob is missing, binary or larger than asked.
924 pub text: Option<String>,
925}
926
927/// `all_ids`: every repository that is not a fork, by id, a page at a
928/// time, for services that index all of them. Returns `IdPage`.
929#[derive(Debug, Default, Serialize, Deserialize)]
930pub struct AllIdsArgs {
931 /// Ids after this one.
932 #[serde(default)]
933 pub after: Option<String>,
934 pub limit: u32,
935}
936
937#[derive(Clone, Debug, Default, Serialize, Deserialize)]
938pub struct IdPage {
939 pub ids: Vec<String>,
940 /// Where the next page starts; null on the last.
941 pub next: Option<String>,
942}
943
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put944/// `visibility`: which of these repositories (`namespace/name`) are
945/// private, for billing, which pays for work on public ones from g1t's
946/// open-source pool. A pull request's working copy answers as the
947/// repository it is a copy of. Unknown paths are left out. Returns
948/// `Vec<RepoVisibility>`.
949#[derive(Debug, Default, Serialize, Deserialize)]
950pub struct VisibilityArgs {
951 pub paths: Vec<String>,
952}
953
954#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
955pub struct RepoVisibility {
956 pub path: String,
957 pub is_private: bool,
958}
959
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look960/// `git_operations`: how many git operations (clones, fetches and pushes
961/// through g1t's git endpoints) each workspace's repositories had in a
962/// month, for billing's git meter. Cloudflare Artifacts charges per
963/// operation from 2026-10-14. Pushes from agents' sandboxes go to the
964/// store directly and are not counted here. Returns
965/// `Vec<WorkspaceGitOperations>`.
966#[derive(Debug, Serialize, Deserialize)]
967pub struct GitOperationsArgs {
968 /// YYYY-MM.
969 pub month: String,
970 /// Count only from this hour on, `YYYY-MM-DDTHH`, such as the day the
971 /// provider starts charging.
972 #[serde(default)]
973 pub since: Option<String>,
974 /// One workspace only; every workspace with any when absent.
975 #[serde(default)]
976 pub namespace: Option<String>,
977}
978
979#[derive(Clone, Debug, Serialize, Deserialize)]
980pub struct WorkspaceGitOperations {
981 pub namespace: String,
982 pub operations: u64,
983}
984
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put985/// `storage`: what each workspace's private repositories hold, as far as
986/// g1t can measure it, for billing's daily storage meter. Returns
987/// `Vec<WorkspaceStorage>`.
988///
989/// The git store does not report a repository's size. What is counted is
990/// the bytes of every pack pushed through g1t's git endpoints to the
991/// repository or to its pull requests' working copies. Pushes made from
992/// agents' sandboxes, which go to the store directly, and imports are not
993/// counted, so it is a lower bound on what is stored.
994#[derive(Debug, Default, Serialize, Deserialize)]
995pub struct StorageArgs {}
996
997#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
998pub struct WorkspaceStorage {
999 pub namespace: String,
1000 pub private_bytes: i64,
1001 pub public_bytes: i64,
1002}
1003
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1004/// `transfer`: moves a repository to another workspace, keeping its name,
1005/// its id and everything kept under it. The actor must own both
1006/// workspaces. The old path keeps working as a redirect (see
1007/// `resolve_path`) until a repository is made there. Publishes
1008/// `repo.transferred`. Returns `Outcome<Repo>`, the repository at its new
1009/// path.
1010#[derive(Debug, Serialize, Deserialize)]
1011#[serde(rename_all = "camelCase")]
1012pub struct TransferArgs {
1013 pub actor: User,
1014 pub path: RepoPath,
1015 /// The destination workspace's slug.
1016 pub to: String,
1017 /// Where the request came in, for the audit log; g1t.sh when absent.
1018 #[serde(default)]
1019 pub surface: Option<crate::audit::Surface>,
1020}
1021
1022/// `resolve_path`: where a repository that was transferred away from
1023/// `path` is now, while nothing else is there. Returns `Option<RepoPath>`:
1024/// null when `path` is a repository, or never was one that moved. Callers
1025/// check the viewer may see the repository at its new path, as for any
1026/// other.
1027#[derive(Debug, Serialize, Deserialize)]
1028pub struct ResolvePathArgs {
1029 pub path: RepoPath,
1030}
1031
1032/// `namespace_count`: how many repositories (not pull request working
1033/// copies) a workspace holds, private or not, for deciding whether it can
1034/// be deleted. Returns `u32`.
1035#[derive(Debug, Serialize, Deserialize)]
1036pub struct NamespaceCountArgs {
1037 pub namespace: String,
1038}
1039
1040/// `delete`: deletes a repository. Owners of its workspace only, who type
1041/// its full name (`namespace/name`) as `confirm`. It is hidden at once,
1042/// git refuses it, and nothing runs for it; it can be restored for
1043/// [`RESTORE_DAYS`] days, then it is purged, its git data with it. Its
1044/// name stays taken until then, or until it is purged sooner from the
1045/// workspace's Recently deleted list. Publishes `repo.deleted`. Returns
1046/// `Outcome<DeletedRepo>`.
1047#[derive(Debug, Serialize, Deserialize)]
1048#[serde(rename_all = "camelCase")]
1049pub struct DeleteArgs {
1050 pub actor: User,
1051 pub path: RepoPath,
1052 #[serde(default)]
1053 pub confirm: String,
1054 #[serde(default)]
1055 pub surface: Option<crate::audit::Surface>,
1056}
1057
1058/// `deleted`: a workspace's recently deleted repositories, newest first.
1059/// Owners only; empty for anyone else. Returns `Vec<DeletedRepo>`.
1060#[derive(Debug, Serialize, Deserialize)]
1061pub struct DeletedArgs {
1062 pub viewer: Viewer,
1063 pub namespace: String,
1064}
1065
1066/// `restore` and `purge`: a deleted repository, by the path it had.
1067/// `restore` brings it back as it was, at that path (`repo.restored`).
1068/// `purge` removes it for good now, its git data with it, and frees its
1069/// name (`repo.purged`); it takes the full name typed as `confirm`.
1070/// Owners only. Return `Outcome<Repo>` and `Outcome<bool>`.
1071#[derive(Debug, Serialize, Deserialize)]
1072#[serde(rename_all = "camelCase")]
1073pub struct DeletedRepoArgs {
1074 pub actor: User,
1075 pub path: RepoPath,
1076 #[serde(default)]
1077 pub confirm: Option<String>,
1078 #[serde(default)]
1079 pub surface: Option<crate::audit::Surface>,
1080}
1081
1082/// `purge_due`: purges deleted repositories whose time has passed, at
1083/// most `limit` (25 when absent). The service's own schedule runs it.
1084/// Returns `u32`, how many were purged.
1085#[derive(Debug, Default, Serialize, Deserialize)]
1086pub struct PurgeDueArgs {
1087 #[serde(default)]
1088 pub limit: Option<u32>,
1089}
1090
1091/// `rename`: gives a repository a new name in its workspace, keeping its
1092/// id, its git data and everything kept under it. Owners only. The old
1093/// path keeps redirecting, as after a transfer, until a repository is made
1094/// there. Publishes `repo.renamed`. Returns `Outcome<Repo>`.
1095#[derive(Debug, Serialize, Deserialize)]
1096#[serde(rename_all = "camelCase")]
1097pub struct RenameArgs {
1098 pub actor: User,
1099 pub path: RepoPath,
1100 pub name: String,
1101 #[serde(default)]
1102 pub surface: Option<crate::audit::Surface>,
1103}
1104
1105/// `archive`: makes a repository read-only (`archived: true`), or writable
1106/// again. Owners only. While archived, pushes and merges are refused,
1107/// issues and pull requests are locked, and agents and workflows do not
1108/// run; deployments keep serving. Publishes `repo.archived` or
1109/// `repo.unarchived`. Returns `Outcome<Repo>`.
1110#[derive(Debug, Serialize, Deserialize)]
1111#[serde(rename_all = "camelCase")]
1112pub struct ArchiveArgs {
1113 pub actor: User,
1114 pub path: RepoPath,
1115 pub archived: bool,
1116 #[serde(default)]
1117 pub surface: Option<crate::audit::Surface>,
1118}
1119
1120/// `set_visibility`: makes a repository public or private. Owners only,
1121/// who type its full name as `confirm`. A free workspace takes a private
1122/// repository only while its private storage has room. Publishes
1123/// `repo.updated` and `repo.visibility_changed`. Returns `Outcome<Repo>`.
1124#[derive(Debug, Serialize, Deserialize)]
1125#[serde(rename_all = "camelCase")]
1126pub struct SetVisibilityArgs {
1127 pub actor: User,
1128 pub path: RepoPath,
1129 pub is_private: bool,
1130 #[serde(default)]
1131 pub confirm: String,
1132 #[serde(default)]
1133 pub surface: Option<crate::audit::Surface>,
1134}
1135
1136/// `set_default_branch`: makes another existing branch the one everything
1137/// lands on. Members of its workspace. Open pull requests then merge into
1138/// it. Publishes `repo.default_branch_changed`. Returns `Outcome<Repo>`.
1139#[derive(Debug, Serialize, Deserialize)]
1140#[serde(rename_all = "camelCase")]
1141pub struct SetDefaultBranchArgs {
1142 pub actor: User,
1143 pub path: RepoPath,
1144 pub branch: String,
1145 #[serde(default)]
1146 pub surface: Option<crate::audit::Surface>,
1147}
1148
1149/// `rename_branch`: renames a branch. Members of its workspace; only an
1150/// owner renames the default branch, which stays the default. Pull
1151/// requests from it follow, and web addresses naming the old branch
1152/// redirect until a branch of that name is made again. Publishes
1153/// `branch.renamed` (and `repo.default_branch_changed` for the default).
1154/// Returns `Outcome<Repo>`.
1155#[derive(Debug, Serialize, Deserialize)]
1156#[serde(rename_all = "camelCase")]
1157pub struct RenameBranchArgs {
1158 pub actor: User,
1159 pub path: RepoPath,
1160 pub from: String,
1161 pub to: String,
1162 #[serde(default)]
1163 pub surface: Option<crate::audit::Surface>,
1164}
1165
1166/// `resolve_branch`: what a branch renamed away from `branch` is called
1167/// now, for web addresses that name the old one; null when `branch` was
1168/// never renamed or exists again. Returns `Option<String>`.
1169#[derive(Debug, Serialize, Deserialize)]
1170#[serde(rename_all = "camelCase")]
1171pub struct ResolveBranchArgs {
1172 pub repo_id: String,
1173 pub branch: String,
1174}
1175
1176/// `status_by_id`: whether a repository is archived or deleted, for g1t's
1177/// own services deciding whether to act on it. An unknown id answers as
1178/// deleted. Returns `RepoStatus`.
1179#[derive(Debug, Serialize, Deserialize)]
1180pub struct StatusByIdArgs {
1181 pub id: String,
1182}
1183
1184#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
1185pub struct RepoStatus {
1186 pub archived: bool,
1187 pub deleted: bool,
1188}
1189
1190impl RepoStatus {
1191 /// Whether work may start on it: neither archived nor deleted.
1192 pub fn active(&self) -> bool {
1193 !self.archived && !self.deleted
1194 }
1195}
1196
1197/// What a person is told when something would change an archived
1198/// repository.
1199pub fn archived_message(namespace: &str, name: &str) -> String {
1200 format!(
1201 "{namespace}/{name} is archived, so it is read-only. An owner can unarchive it in its settings."
1202 )
1203}
1204
1205/// The path a repository was transferred from, and when, as `transfer`
1206/// keeps it so old addresses redirect.
1207#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1208#[serde(rename_all = "camelCase")]
1209pub struct RepoRedirect {
1210 pub from: RepoPath,
1211 pub repo_id: String,
1212 /// RFC 3339.
1213 pub created_at: String,
1214}
1215
Search across all of g1t, Explore, and a command palette1216#[cfg(test)]
1217mod topic_tests {
1218 use super::*;
1219
1220 fn topics(list: &[&str]) -> Result<Vec<String>, String> {
1221 clean_topics(&list.iter().map(|t| t.to_string()).collect::<Vec<_>>())
1222 }
1223
1224 #[test]
1225 fn topics_are_tidied() {
1226 assert_eq!(topics(&["Rust", " web_server ", "rust", ""]).unwrap(), vec!["rust", "web-server"]);
1227 }
1228
1229 #[test]
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1230 fn websites_are_tidied() {
1231 assert_eq!(clean_website(" example.com ").unwrap().as_deref(), Some("https://example.com"));
1232 assert_eq!(clean_website("http://a.io/x").unwrap().as_deref(), Some("http://a.io/x"));
1233 assert_eq!(clean_website("").unwrap(), None);
1234 assert!(clean_website("ftp://a.io").is_err());
1235 assert!(clean_website("localhost").is_err());
1236 assert!(clean_website("https://a b.io").is_err());
1237 }
1238
1239 #[test]
1240 fn branch_names_follow_git() {
1241 for good in ["main", "trunk", "release/1.2", "feat-x_y"] {
1242 assert!(is_valid_branch_name(good), "{good}");
1243 }
1244 for bad in ["", "-x", "a..b", "a b", "x.lock", "a/", ".hidden", "a/.b", "g1t-queue", "a~1", "a:b", "@"] {
1245 assert!(!is_valid_branch_name(bad), "{bad}");
1246 }
1247 }
1248
1249 #[test]
Search across all of g1t, Explore, and a command palette1250 fn odd_topics_are_refused() {
1251 assert!(topics(&["c++"]).is_err());
1252 assert!(topics(&["-lead"]).is_err());
1253 assert!(topics(&[&"a".repeat(36)]).is_err());
1254 let many: Vec<String> = (0..21).map(|i| format!("t{i}")).collect();
1255 assert!(clean_topics(&many).is_err());
1256 }
1257}
Catching up with main takes seconds when the two sides touched different files1258
1259#[cfg(test)]
1260mod tests {
1261 use super::*;
1262
1263 #[test]
1264 fn a_pull_branch_update_reads_as_the_web_expects() {
1265 let update = PullBranchUpdate::NeedsAgent {
1266 reason: NeedsAgentReason::Overlap,
1267 detail: "both".into(),
1268 paths: vec!["a.rs".into()],
1269 };
1270 assert_eq!(
1271 serde_json::to_value(&update).unwrap(),
1272 serde_json::json!({ "outcome": "needs_agent", "reason": "overlap", "detail": "both", "paths": ["a.rs"] })
1273 );
1274 let done = PullBranchUpdate::UpToDate { commit: "c".into() };
1275 assert_eq!(serde_json::to_value(&done).unwrap(), serde_json::json!({ "outcome": "up_to_date", "commit": "c" }));
1276 }
1277}