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
1,537 linesCodeBlameRaw
1//! 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,
14 /// The slug of the workspace that owns it: the first URL segment.
15 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,
21 /// Set when this repo is a pull request's working copy of another repo.
22 pub fork_of: Option<String>,
23 /// 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,
27 /// RFC 3339.
28 pub created_at: String,
29 /// 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>,
33 /// 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 /// Why it takes no pushes, merges, issues or agents now: it is
48 /// archived. `None` when it takes them.
49 pub fn read_only_reason(&self) -> Option<String> {
50 self.archived().then(|| archived_message(&self.namespace, &self.name))
51 }
52}
53
54/// `storage_options` (no arguments, `{}`): what a workspace may choose
55/// about where its repositories are kept. `eu_available`: an EU namespace
56/// is configured and takes new repositories, so a workspace may keep its
57/// data in the EU (`set_workspace_residency` on identity). Returns
58/// `StorageOptions`.
59#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
60#[serde(rename_all = "camelCase")]
61pub struct StorageOptions {
62 pub eu_available: bool,
63}
64
65/// How long a deleted repository can be restored before it is purged.
66pub const RESTORE_DAYS: u64 = 30;
67
68/// A deleted repository, as its workspace's Recently deleted list shows
69/// it: restorable until `purge_after`.
70#[derive(Clone, Debug, Serialize, Deserialize)]
71#[serde(rename_all = "camelCase")]
72pub struct DeletedRepo {
73 pub id: String,
74 pub namespace: String,
75 pub name: String,
76 pub description: Option<String>,
77 pub is_private: bool,
78 /// RFC 3339.
79 pub deleted_at: String,
80 /// The username of who deleted it.
81 pub deleted_by: String,
82 /// RFC 3339: when it is purged, unless restored first.
83 pub purge_after: String,
84}
85
86/// The longest website address a repository keeps.
87pub const MAX_WEBSITE_CHARS: usize = 255;
88
89/// A website as it is kept: an http(s) address, `https://` added when no
90/// scheme is given; empty clears it. Anything else is refused.
91pub fn clean_website(text: &str) -> Result<Option<String>, String> {
92 let text = text.trim();
93 if text.is_empty() {
94 return Ok(None);
95 }
96 let url = if text.starts_with("https://") || text.starts_with("http://") {
97 text.to_owned()
98 } else if text.contains("://") {
99 return Err("A website is an http or https address.".into());
100 } else {
101 format!("https://{text}")
102 };
103 let host = url
104 .split("://")
105 .nth(1)
106 .unwrap_or("")
107 .split(['/', '?', '#'])
108 .next()
109 .unwrap_or("");
110 if url.chars().count() > MAX_WEBSITE_CHARS
111 || host.is_empty()
112 || !host.contains('.')
113 || url.chars().any(char::is_whitespace)
114 {
115 return Err("That is not a website address, such as https://example.com.".into());
116 }
117 Ok(Some(url))
118}
119
120/// Whether `name` can be a branch people name: what `git check-ref-format
121/// --branch` accepts, less the names g1t keeps for itself
122/// ([`G1T_BRANCH_PREFIX`]).
123pub fn is_valid_branch_name(name: &str) -> bool {
124 !name.is_empty()
125 && name.len() <= 200
126 && !name.starts_with('-')
127 && !name.starts_with('/')
128 && !name.ends_with('/')
129 && !name.ends_with('.')
130 && !name.ends_with(".lock")
131 && !name.contains("..")
132 && !name.contains("//")
133 && !name.contains("@{")
134 && name != "@"
135 && !name.starts_with(G1T_BRANCH_PREFIX)
136 && !name.split('/').any(|part| part.starts_with('.'))
137 && name
138 .chars()
139 .all(|c| !c.is_control() && !matches!(c, ' ' | '~' | '^' | ':' | '?' | '*' | '[' | '\\'))
140}
141
142/// The most topics a repository has.
143pub const MAX_TOPICS: usize = 20;
144/// The longest topic.
145pub const MAX_TOPIC_CHARS: usize = 35;
146
147/// Topics as they are kept: lowercase, spaces and underscores made
148/// hyphens, each of letters, digits and hyphens, starting with a letter or
149/// digit, without repeats, at most [`MAX_TOPICS`]. Anything else is the
150/// first topic that could not be read.
151pub fn clean_topics(topics: &[String]) -> Result<Vec<String>, String> {
152 let mut kept: Vec<String> = Vec::new();
153 for topic in topics {
154 let topic: String = topic
155 .trim()
156 .to_lowercase()
157 .chars()
158 .map(|c| if c == ' ' || c == '_' { '-' } else { c })
159 .collect();
160 if topic.is_empty() {
161 continue;
162 }
163 let valid = topic.chars().count() <= MAX_TOPIC_CHARS
164 && topic.chars().all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '-')
165 && topic.chars().next().is_some_and(|c| c.is_ascii_alphanumeric());
166 if !valid {
167 return Err(format!(
168 "\"{topic}\" is not a topic: use letters, digits and hyphens, at most {MAX_TOPIC_CHARS} characters."
169 ));
170 }
171 if !kept.contains(&topic) {
172 kept.push(topic);
173 }
174 }
175 if kept.len() > MAX_TOPICS {
176 return Err(format!("A repository has at most {MAX_TOPICS} topics."));
177 }
178 Ok(kept)
179}
180
181#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
182pub struct RepoPath {
183 pub namespace: String,
184 pub name: String,
185}
186
187#[derive(Clone, Debug, Serialize, Deserialize)]
188pub struct Signature {
189 pub name: String,
190 pub email: String,
191}
192
193#[derive(Clone, Debug, Serialize, Deserialize)]
194#[serde(rename_all = "camelCase")]
195pub struct Commit {
196 pub hash: String,
197 pub tree_hash: String,
198 pub message: String,
199 pub author: Signature,
200 pub parents: Vec<String>,
201 /// RFC 3339.
202 pub authored_at: String,
203}
204
205#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
206#[serde(rename_all = "lowercase")]
207pub enum EntryKind {
208 Tree,
209 Blob,
210 Symlink,
211 Gitlink,
212 Exec,
213}
214
215#[derive(Clone, Debug, Serialize, Deserialize)]
216pub struct TreeEntry {
217 pub name: String,
218 pub hash: String,
219 pub kind: EntryKind,
220}
221
222#[derive(Clone, Debug, Serialize, Deserialize)]
223pub struct Readme {
224 pub name: String,
225 /// Null when the file is binary or too large to show.
226 pub text: Option<String>,
227}
228
229#[derive(Clone, Debug, Serialize, Deserialize)]
230pub struct TreeView {
231 pub repo: Repo,
232 #[serde(rename = "ref")]
233 pub git_ref: String,
234 pub path: String,
235 /// Null when the repo has no commits yet.
236 pub head: Option<Commit>,
237 pub entries: Vec<TreeEntry>,
238 pub readme: Option<Readme>,
239}
240
241#[derive(Clone, Debug, Serialize, Deserialize)]
242pub struct BlobView {
243 pub repo: Repo,
244 #[serde(rename = "ref")]
245 pub git_ref: String,
246 pub path: String,
247 pub size: u64,
248 /// Null when the file is binary or too large to show.
249 pub text: Option<String>,
250}
251
252/// A git remote and a short-lived credential for it.
253#[derive(Clone, Debug, Serialize, Deserialize)]
254pub struct GitAccess {
255 pub remote: String,
256 pub token: String,
257}
258
259#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
260pub enum GitService {
261 #[serde(rename = "git-upload-pack")]
262 UploadPack,
263 #[serde(rename = "git-receive-pack")]
264 ReceivePack,
265}
266
267/// The result of landing a pull request.
268#[derive(Clone, Debug, Serialize, Deserialize)]
269pub struct Landed {
270 /// The commit the branch points to now.
271 pub commit: String,
272 /// The commit it pointed to before, if it had one. Comparing against
273 /// this shows what the pull request changed.
274 pub previous: Option<String>,
275}
276
277/// `get`. Returns `Outcome<Repo>`.
278#[derive(Debug, Serialize, Deserialize)]
279pub struct GetArgs {
280 pub path: RepoPath,
281 pub viewer: Viewer,
282}
283
284/// `path_by_id`: where a repository is, whoever may see it. For g1t's own
285/// services, which hold a repository's id from an event and act for its
286/// workspace; nothing outside reaches it. Returns `Option<RepoPath>`, null
287/// for a fork or an unknown id.
288#[derive(Debug, Serialize, Deserialize)]
289pub struct PathByIdArgs {
290 pub id: String,
291}
292
293/// `get_by_id`. Returns `Outcome<Repo>`.
294#[derive(Debug, Serialize, Deserialize)]
295pub struct GetByIdArgs {
296 pub id: String,
297 pub viewer: Viewer,
298}
299
300/// `list`: repos the viewer may see, newest first. Returns `Vec<Repo>`.
301#[derive(Debug, Default, Serialize, Deserialize)]
302#[serde(rename_all = "camelCase")]
303pub struct ListArgs {
304 pub viewer: Viewer,
305 #[serde(default)]
306 pub query: Option<String>,
307 /// Only repos in this workspace.
308 #[serde(default)]
309 pub namespace: Option<String>,
310 /// Only repos in workspaces the viewer belongs to.
311 #[serde(default)]
312 pub member_only: bool,
313}
314
315/// `create`. Returns `Outcome<Repo>`.
316#[derive(Debug, Serialize, Deserialize)]
317#[serde(rename_all = "camelCase")]
318pub struct CreateArgs {
319 /// Who is creating it; they must belong to the workspace.
320 pub owner: User,
321 /// The workspace it is created in.
322 pub namespace: String,
323 pub name: String,
324 #[serde(default)]
325 pub description: Option<String>,
326 #[serde(default)]
327 pub is_private: bool,
328 /// The https address of a public git repository to copy the default
329 /// branch of, such as `https://github.com/owner/repo`.
330 #[serde(default)]
331 pub import_url: Option<String>,
332 /// With `import_url`: a GitHub installation access token that opens it,
333 /// for a private repository. Every branch and tag is then copied, not
334 /// only the default branch. Set only by the integrations service.
335 #[serde(default, skip_serializing_if = "Option::is_none")]
336 pub import_token: Option<String>,
337}
338
339/// `update`: changes whichever of a repository's details are given.
340/// Members of its workspace only. Returns `Outcome<Repo>`.
341#[derive(Debug, Serialize, Deserialize)]
342#[serde(rename_all = "camelCase")]
343pub struct UpdateArgs {
344 pub actor: User,
345 pub path: RepoPath,
346 /// An empty description clears it.
347 #[serde(default)]
348 pub description: Option<String>,
349 #[serde(default)]
350 pub is_private: Option<bool>,
351 #[serde(default)]
352 pub protected: Option<bool>,
353 /// Replaces its topics; an empty list clears them.
354 #[serde(default)]
355 pub topics: Option<Vec<String>>,
356 /// Its home page; an empty string clears it.
357 #[serde(default)]
358 pub website: Option<String>,
359 /// Where the request came in, for the audit log; g1t.sh when absent.
360 #[serde(default)]
361 pub surface: Option<crate::audit::Surface>,
362}
363
364/// `tree`. Returns `Outcome<TreeView>`.
365#[derive(Debug, Serialize, Deserialize)]
366#[serde(rename_all = "camelCase")]
367pub struct TreeArgs {
368 pub path: RepoPath,
369 pub viewer: Viewer,
370 /// The default branch when absent.
371 #[serde(default, rename = "ref")]
372 pub git_ref: Option<String>,
373 #[serde(default)]
374 pub tree_path: String,
375}
376
377/// `blob`. Returns `Outcome<BlobView>`.
378#[derive(Debug, Serialize, Deserialize)]
379#[serde(rename_all = "camelCase")]
380pub struct BlobArgs {
381 pub path: RepoPath,
382 pub viewer: Viewer,
383 #[serde(rename = "ref")]
384 pub git_ref: String,
385 pub file_path: String,
386}
387
388/// `log`. Returns `Outcome<Vec<Commit>>`.
389#[derive(Debug, Serialize, Deserialize)]
390pub struct LogArgs {
391 pub path: RepoPath,
392 pub viewer: Viewer,
393 #[serde(default, rename = "ref")]
394 pub git_ref: Option<String>,
395 pub limit: u32,
396}
397
398/// `fork_for_pull`: a copy-on-write copy of the source repo, hidden from
399/// listings, for one pull request to be made in. Returns `Outcome<Repo>`.
400#[derive(Debug, Serialize, Deserialize)]
401#[serde(rename_all = "camelCase")]
402pub struct ForkArgs {
403 pub source_id: String,
404 pub pull_id: String,
405 pub actor: User,
406}
407
408/// `git_access`: authorizes a git operation and says where to send it.
409/// Pushing to a repo that does not exist creates it in the pusher's own
410/// namespace. Returns `Outcome<GitAccess>`.
411#[derive(Debug, Serialize, Deserialize)]
412pub struct GitAccessArgs {
413 pub path: RepoPath,
414 pub viewer: Viewer,
415 pub service: GitService,
416}
417
418/// `land`: moves the branch a pull request merges into (the repository's
419/// default branch unless `target_branch` names another) to the head of
420/// its source. Refused with `conflict` when the source is behind, since
421/// that would discard commits. Returns `Outcome<Landed>`.
422#[derive(Debug, Serialize, Deserialize)]
423#[serde(rename_all = "camelCase")]
424pub struct LandArgs {
425 /// The repository holding the commits: a pull request's fork, or the
426 /// target itself when landing one of its own branches.
427 pub source_id: String,
428 /// The branch of the source to land. Required when the source is the
429 /// target; a fork lands its default branch.
430 #[serde(default)]
431 pub branch: Option<String>,
432 pub actor: User,
433 /// The branch of the target to land on; its default branch when absent.
434 #[serde(default)]
435 pub target_branch: Option<String>,
436}
437
438#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
439#[serde(rename_all = "lowercase")]
440pub enum FileStatus {
441 Added,
442 Modified,
443 Deleted,
444}
445
446#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
447#[serde(rename_all = "lowercase")]
448pub enum LineKind {
449 /// Unchanged, shown for context.
450 Context,
451 Add,
452 Delete,
453}
454
455#[derive(Clone, Debug, Serialize, Deserialize)]
456pub struct DiffLine {
457 pub kind: LineKind,
458 /// Line number in the old file; absent for added lines.
459 pub old: Option<u32>,
460 /// Line number in the new file; absent for deleted lines.
461 pub new: Option<u32>,
462 pub text: String,
463}
464
465/// A run of changed lines with their surrounding context.
466#[derive(Clone, Debug, Serialize, Deserialize)]
467pub struct Hunk {
468 pub lines: Vec<DiffLine>,
469}
470
471#[derive(Clone, Debug, Serialize, Deserialize)]
472pub struct FileDiff {
473 pub path: String,
474 pub status: FileStatus,
475 pub additions: u32,
476 pub deletions: u32,
477 /// True when the file is binary or too large, so no lines are shown.
478 pub binary: bool,
479 pub hunks: Vec<Hunk>,
480 /// True when the comparison reached its limit on lines before this
481 /// file, so it is listed without its lines (and with no counts): ask
482 /// `compare` again with its path in `paths` to have them.
483 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
484 pub omitted: bool,
485}
486
487/// What changed between two commits.
488#[derive(Clone, Debug, Serialize, Deserialize)]
489pub struct Comparison {
490 /// Null when the head has no earlier commit to compare against.
491 pub base: Option<String>,
492 pub head: String,
493 pub files: Vec<FileDiff>,
494 /// True when more files changed than are listed. A listed file whose
495 /// lines were left out says so itself (`FileDiff::omitted`).
496 pub truncated: bool,
497}
498
499/// `compare`: what `head` changes relative to `base`.
500///
501/// `head` is a branch or a commit, and defaults to the default branch.
502/// With no `base`, a fork is compared against the point where it and the
503/// repository it came from last agreed; a branch against the point where it
504/// left the default branch; and the default branch against its head's
505/// parent. Returns `Outcome<Comparison>`.
506#[derive(Debug, Serialize, Deserialize)]
507#[serde(rename_all = "camelCase")]
508pub struct CompareArgs {
509 pub repo_id: String,
510 pub viewer: Viewer,
511 #[serde(default)]
512 pub base: Option<String>,
513 #[serde(default)]
514 pub head: Option<String>,
515 /// With no `base`: the branch whose shared point with the head it is
516 /// compared from, instead of the default branch. A pull request into
517 /// another branch is compared this way.
518 #[serde(default)]
519 pub base_branch: Option<String>,
520 /// Only these files, with their lines: how a file left out of a large
521 /// comparison is read on its own. At most 50.
522 #[serde(default)]
523 pub paths: Option<Vec<String>>,
524}
525
526/// Lines `start` to `end` of a file, inclusive and counted from 1, last
527/// changed by `commit`.
528#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
529pub struct BlameRange {
530 pub start: u32,
531 pub end: u32,
532 pub commit: String,
533}
534
535/// Who last changed each line of a file.
536#[derive(Clone, Debug, Serialize, Deserialize)]
537pub struct Blame {
538 /// The commit the file was read at.
539 pub head: String,
540 /// Every line, in order, in runs that share a commit.
541 pub ranges: Vec<BlameRange>,
542 /// The commits the ranges name, each once.
543 pub commits: Vec<Commit>,
544 /// True when the history was too long to read in full, so the oldest
545 /// lines are given to the oldest commit read.
546 pub partial: bool,
547}
548
549/// `blame`: who last changed each line of `path` as of `ref` (the default
550/// branch if absent). Returns `Outcome<Blame>`; not found when the file is
551/// missing or is not text.
552#[derive(Debug, Serialize, Deserialize)]
553pub struct BlameArgs {
554 pub path: RepoPath,
555 pub viewer: Viewer,
556 #[serde(default, rename = "ref")]
557 pub git_ref: Option<String>,
558 #[serde(rename = "filePath")]
559 pub file_path: String,
560}
561
562/// A branch and the commit it points to.
563#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
564pub struct Branch {
565 pub name: String,
566 pub hash: String,
567}
568
569/// `last_commits`: which commit last changed each entry of a directory at
570/// `ref` (the default branch when absent). Returns `Outcome<LastCommits>`.
571#[derive(Debug, Serialize, Deserialize)]
572#[serde(rename_all = "camelCase")]
573pub struct LastCommitsArgs {
574 pub path: RepoPath,
575 pub viewer: Viewer,
576 #[serde(default, rename = "ref")]
577 pub git_ref: Option<String>,
578 #[serde(default)]
579 pub tree_path: String,
580 /// Answer within this many milliseconds with what was found; absent,
581 /// within 20 seconds. A walk that stops short keeps its progress, and
582 /// the next call goes on from it.
583 #[serde(default)]
584 pub budget_ms: Option<u64>,
585}
586
587/// `path_log`: the commits on the first-parent chain from `ref` (the
588/// default branch when absent) that changed `file_path`, a file or a
589/// directory, newest first, at most `limit`. Returns `Outcome<PathLog>`.
590#[derive(Debug, Serialize, Deserialize)]
591#[serde(rename_all = "camelCase")]
592pub struct PathLogArgs {
593 pub path: RepoPath,
594 pub viewer: Viewer,
595 #[serde(default, rename = "ref")]
596 pub git_ref: Option<String>,
597 pub file_path: String,
598 pub limit: u32,
599 /// Answer within this many milliseconds with what was found.
600 #[serde(default)]
601 pub budget_ms: Option<u64>,
602}
603
604/// A path's history: `complete` is false when the walk ran out of time or
605/// reads before the first commit and before `limit`.
606#[derive(Clone, Debug, Serialize, Deserialize)]
607#[serde(rename_all = "camelCase")]
608pub struct PathLog {
609 pub commits: Vec<Commit>,
610 pub complete: bool,
611}
612
613/// An entry of a directory and the commit that last changed it.
614#[derive(Clone, Debug, Serialize, Deserialize)]
615pub struct LastCommit {
616 pub name: String,
617 pub commit: Commit,
618}
619
620/// The entries' last commits. `complete` is false when the walk stopped
621/// (or the history ran out) before every entry was placed; those entries
622/// are left out, and a later call goes on placing them.
623#[derive(Clone, Debug, Serialize, Deserialize)]
624pub struct LastCommits {
625 pub entries: Vec<LastCommit>,
626 pub complete: bool,
627}
628
629/// `branch_drift`: how far each of `heads` (branch head commits) has moved
630/// from `base` (the default branch's head commit), and each one's head
631/// commit, in one call. Every answer is kept by the pair of hashes: neither
632/// history can change, so neither can it. Returns `Outcome<BranchDrifts>`.
633#[derive(Debug, Serialize, Deserialize)]
634#[serde(rename_all = "camelCase")]
635pub struct BranchDriftArgs {
636 pub path: RepoPath,
637 pub viewer: Viewer,
638 pub base: String,
639 pub heads: Vec<String>,
640}
641
642/// Commits a branch has that the default branch does not (`ahead`), and
643/// the other way round (`behind`), as `git rev-list --left-right --count`.
644#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
645pub struct Drift {
646 pub ahead: u32,
647 pub behind: u32,
648}
649
650/// One branch head's commit and drift. `drift` is absent when the two
651/// histories do not meet within what is read (or could not be read).
652#[derive(Clone, Debug, Serialize, Deserialize)]
653pub struct BranchDrift {
654 pub head: String,
655 pub commit: Option<Commit>,
656 pub drift: Option<Drift>,
657}
658
659/// `base`'s own commit, and each head's answer in the order asked.
660#[derive(Clone, Debug, Serialize, Deserialize)]
661pub struct BranchDrifts {
662 pub base: Option<Commit>,
663 pub branches: Vec<BranchDrift>,
664}
665
666/// `tags`: the repository's tags, newest commit first, each with the
667/// commit it names. Returns `Outcome<Vec<Tag>>`.
668#[derive(Debug, Serialize, Deserialize)]
669pub struct TagsArgs {
670 pub path: RepoPath,
671 pub viewer: Viewer,
672}
673
674/// A tag, and its commit when it could be read.
675#[derive(Clone, Debug, Serialize, Deserialize)]
676pub struct Tag {
677 pub name: String,
678 pub commit: Option<Commit>,
679}
680
681/// `branches`: the repository's branches, default branch first.
682/// Returns `Outcome<Vec<Branch>>`.
683#[derive(Debug, Serialize, Deserialize)]
684pub struct BranchesArgs {
685 pub path: RepoPath,
686 pub viewer: Viewer,
687}
688
689/// `behind`: whether the default branch of the repository a pull request
690/// would merge into has commits its source does not. For services that
691/// have already decided the caller may see the pull request; it reveals
692/// one bit. Returns `bool`.
693#[derive(Debug, Serialize, Deserialize)]
694#[serde(rename_all = "camelCase")]
695pub struct BehindArgs {
696 /// The pull request's fork, or the repository itself for a branch.
697 pub source_id: String,
698 /// The branch of the source. A fork is compared on its default branch.
699 #[serde(default)]
700 pub branch: Option<String>,
701 /// The branch of the target it would merge into; the default branch
702 /// when absent.
703 #[serde(default)]
704 pub target_branch: Option<String>,
705}
706
707/// `divergence`: how a pull request's source and the default branch it
708/// would merge into have moved apart since they last agreed: the files each
709/// side changed. Takes `BehindArgs`. For services that have already decided
710/// the caller may see the pull request; it reveals paths, not contents.
711/// Returns `Option<Divergence>`, null when either side has no commits.
712#[derive(Clone, Debug, Default, Serialize, Deserialize)]
713#[serde(rename_all = "camelCase")]
714pub struct Divergence {
715 /// The source's commit.
716 pub head: String,
717 /// The default branch's commit.
718 pub base: String,
719 /// Where they last agreed, if that could be found.
720 pub merge_base: Option<String>,
721 /// Whether the default branch has commits the source does not.
722 pub behind: bool,
723 /// The files the source changed since the merge base.
724 pub ours: Vec<String>,
725 /// The files the default branch changed since the merge base. Empty
726 /// when it is not behind.
727 pub theirs: Vec<String>,
728 /// Whether either list was cut short.
729 pub truncated: bool,
730}
731
732/// `update_pull_branch`: brings a pull request's source up to date with the
733/// default branch it would merge into, without a sandbox, when that can be
734/// done safely: merges the default branch's head into the source's head and
735/// pushes the merge commit to the source's branch, as `actor`, only if the
736/// branch has not moved meanwhile. It applies only when the two sides
737/// changed different files since they last agreed; otherwise the answer is
738/// [`PullBranchUpdate::NeedsAgent`] and nothing is pushed. Refused unless
739/// `actor` may push to the source. Returns `Outcome<PullBranchUpdate>`.
740#[derive(Debug, Serialize, Deserialize)]
741#[serde(rename_all = "camelCase")]
742pub struct UpdatePullBranchArgs {
743 /// The pull request's fork, or the repository itself for a branch.
744 pub source_id: String,
745 /// The branch of the source. A fork is updated on its default branch.
746 #[serde(default)]
747 pub branch: Option<String>,
748 /// The pull request's number, to name it in the merge commit's message
749 /// when its branch has the same name as the default branch.
750 pub number: u32,
751 /// Who asked: the merge commit's author and committer, and the pusher.
752 pub actor: User,
753 /// The branch of the target to merge in; its default branch when absent.
754 #[serde(default)]
755 pub target_branch: Option<String>,
756}
757
758/// Why an update has to be left to a sandbox.
759#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
760#[serde(rename_all = "snake_case")]
761pub enum NeedsAgentReason {
762 /// Both sides changed some of the same files; merging them needs a
763 /// real merge, which may or may not conflict.
764 Overlap,
765 /// Merging is known to conflict.
766 Conflicting,
767 /// The update could not be worked out here, such as when the two sides
768 /// share no history g1t can see, or the change is too large to list.
769 Unsupported,
770}
771
772/// What came of `update_pull_branch` (or the work service's `catch_up_pull`).
773#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
774#[serde(tag = "outcome", rename_all = "snake_case")]
775pub enum PullBranchUpdate {
776 /// The merge commit was pushed: the branch moved from `previous` to
777 /// `commit`.
778 Updated { commit: String, previous: String },
779 /// The source already holds the default branch's head.
780 UpToDate { commit: String },
781 /// Nothing was pushed; a sandbox has to merge it. `paths` are the
782 /// files both sides changed, or that conflict, when known.
783 NeedsAgent {
784 reason: NeedsAgentReason,
785 detail: String,
786 paths: Vec<String>,
787 },
788}
789
790/// `pull_merge_ref`: points `refs/pull/<number>/head` in the repository a
791/// pull request merges into at its head, fetching the head's objects from
792/// the fork first when it comes from one, and `refs/pull/<number>/merge`
793/// at the commit that merges that head into the base branch's tip, made
794/// without a sandbox: what GitHub's `pull_request` runs build. For the
795/// actions service, which has already decided the pull request's events
796/// may start runs. Returns `PullMergeRef`.
797#[derive(Debug, Serialize, Deserialize)]
798#[serde(rename_all = "camelCase")]
799pub struct PullMergeRefArgs {
800 /// The repository the pull request merges into.
801 pub repo_id: String,
802 /// The pull request's fork, or the repository itself for a branch.
803 pub source_id: String,
804 /// The pull request's head commit.
805 pub head: String,
806 pub number: u32,
807 /// The branch it merges into; the default branch when absent.
808 #[serde(default)]
809 pub target_branch: Option<String>,
810}
811
812/// What came of `pull_merge_ref`.
813#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
814#[serde(tag = "outcome", rename_all = "snake_case")]
815pub enum PullMergeRef {
816 /// `refs/pull/<number>/merge` is `merge`, whose parents are `base`
817 /// (the base branch's tip) and `head`.
818 Merged { head: String, base: String, merge: String },
819 /// The head and the base branch change the same lines, or the same
820 /// binary file, differently: there is no merge commit, and
821 /// `refs/pull/<number>/merge` is removed, as on GitHub.
822 Conflict { head: String, base: String, paths: Vec<String> },
823 /// g1t could not make the merge commit itself (a file renamed on one
824 /// side and changed on the other, a very large change, a history it
825 /// could not follow), or either side has no commits: `reason` says why.
826 Unavailable { reason: String },
827}
828
829/// The ref a pull request's head is kept at in the repository it merges
830/// into, as on GitHub.
831pub fn pull_head_ref(number: u32) -> String {
832 format!("refs/pull/{number}/head")
833}
834
835/// The ref of a pull request's test merge commit, as on GitHub.
836pub fn pull_merge_ref(number: u32) -> String {
837 format!("refs/pull/{number}/merge")
838}
839
840/// `head`: the commit a branch points to, or null. For services reacting
841/// to a push, which have no viewer; it reveals nothing but a commit hash.
842/// Returns `Option<String>`.
843#[derive(Debug, Serialize, Deserialize)]
844#[serde(rename_all = "camelCase")]
845pub struct HeadArgs {
846 pub repo_id: String,
847 /// Empty for the repository's default branch.
848 pub branch: String,
849}
850
851/// Where g1t keeps branches of its own in a repository, such as the merge
852/// queue's tested states. Only these can be removed with `delete_branch`.
853pub const G1T_BRANCH_PREFIX: &str = "g1t-";
854
855/// `delete_branch`: removes a branch g1t made for itself once it is done
856/// with it, never one of people's: the name must start with
857/// [`G1T_BRANCH_PREFIX`], or `head` must name the commit it points to (a
858/// dependency update's branch, which g1t pushed and whose pull request it
859/// closed). Never the default branch. For services, which have no viewer.
860/// Returns `Outcome<bool>`: whether there was such a branch.
861#[derive(Debug, Serialize, Deserialize)]
862#[serde(rename_all = "camelCase")]
863pub struct DeleteBranchArgs {
864 pub repo_id: String,
865 pub branch: String,
866 /// The commit the branch must still point to. A branch that moved
867 /// since (someone pushed to it) is left alone.
868 #[serde(default, skip_serializing_if = "Option::is_none")]
869 pub head: Option<String>,
870}
871
872/// `commit_file`: writes one file on a new branch made from the default
873/// branch's head, as one commit by `actor`, without a sandbox. For a change
874/// g1t proposes on someone's behalf, such as a starter workflow, which then
875/// becomes a pull request. Refused unless `actor` may push, when the branch
876/// already exists, or when the file is already there. Returns
877/// `Outcome<CommittedFile>`.
878#[derive(Debug, Serialize, Deserialize)]
879#[serde(rename_all = "camelCase")]
880pub struct CommitFileArgs {
881 pub repo: RepoPath,
882 pub actor: User,
883 /// The new branch, which must not exist yet.
884 pub branch: String,
885 /// Where the file goes, such as `.g1t/workflows/ci.yml`.
886 pub path: String,
887 pub content: String,
888 pub message: String,
889}
890
891/// The commit `commit_file` made.
892#[derive(Clone, Debug, Serialize, Deserialize)]
893#[serde(rename_all = "camelCase")]
894pub struct CommittedFile {
895 pub branch: String,
896 pub commit: String,
897}
898
899/// `readable`: of these repository ids, the repositories the viewer may
900/// read, as `get_by_id` decides; forks and unknown ids are left out. For
901/// services that hold ids and must show only what the viewer could open.
902/// At most [`MAX_READABLE`] ids are looked at. Returns `Vec<Repo>`.
903#[derive(Debug, Serialize, Deserialize)]
904pub struct ReadableArgs {
905 pub ids: Vec<String>,
906 pub viewer: Viewer,
907}
908
909/// The most ids one `readable` call looks at.
910pub const MAX_READABLE: usize = 500;
911
912/// `commit_days`: how many commits a person pushed each day (UTC) since
913/// `since` (`YYYY-MM-DD`), to the default branch or `gh-pages` of
914/// repositories the viewer may read (as `readable` decides), for the
915/// contribution calendar. Credited to whoever pushed; at most 50 commits a
916/// push, counted along the branch's first-parent line. Returns
917/// `Vec<crate::work::ContributionDay>`, oldest first, days with none left out.
918#[derive(Debug, Serialize, Deserialize)]
919pub struct CommitDaysArgs {
920 pub user_id: String,
921 pub since: String,
922 pub viewer: Viewer,
923}
924
925/// `public_namespaces`: the workspaces in which this account made a public
926/// repository, and so a public project, which anyone can see on its page.
927/// Returns `Vec<String>` of workspace slugs.
928#[derive(Debug, Serialize, Deserialize)]
929#[serde(rename_all = "camelCase")]
930pub struct PublicNamespacesArgs {
931 pub owner_id: String,
932}
933
934/// One file on a branch, or one a change touched: its path and blob.
935#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
936pub struct FileEntry {
937 pub path: String,
938 /// The blob it holds now; null when the change deleted it.
939 pub hash: Option<String>,
940 /// `blob`, `exec` (executable) or `symlink` (the blob is where it
941 /// points); absent for a deleted file.
942 #[serde(default, skip_serializing_if = "Option::is_none")]
943 pub kind: Option<EntryKind>,
944}
945
946/// Files, and whether there were more than were listed.
947#[derive(Clone, Debug, Default, Serialize, Deserialize)]
948#[serde(rename_all = "camelCase")]
949pub struct FileList {
950 /// The commit the files were read at; null for an empty repository.
951 pub commit: Option<String>,
952 /// When that commit was made (RFC 3339), what an archive dates its
953 /// files by.
954 #[serde(default, skip_serializing_if = "Option::is_none")]
955 pub date: Option<String>,
956 pub files: Vec<FileEntry>,
957 pub truncated: bool,
958}
959
960/// `list_files`: every file on a branch (the default branch when absent),
961/// path order by level, never descending into a directory named in
962/// `skip_dirs`. For services that index a repository; no viewer, since it
963/// is only reached by g1t's own services. Returns `FileList`.
964#[derive(Debug, Default, Serialize, Deserialize)]
965#[serde(rename_all = "camelCase")]
966pub struct ListFilesArgs {
967 pub repo_id: String,
968 #[serde(default, rename = "ref")]
969 pub git_ref: Option<String>,
970 #[serde(default)]
971 pub skip_dirs: Vec<String>,
972 /// At most this many files; capped at [`MAX_LISTED_FILES`].
973 pub limit: u32,
974 /// Symlinks too, for an archive: by default only files whose text can
975 /// be read are listed.
976 #[serde(default)]
977 pub links: bool,
978}
979
980/// `changed_files`: the files that differ between two commits, as
981/// `list_files` reads them. With no `base`, every file at `head`. Returns
982/// `FileList`.
983#[derive(Debug, Default, Serialize, Deserialize)]
984#[serde(rename_all = "camelCase")]
985pub struct ChangedFilesArgs {
986 pub repo_id: String,
987 #[serde(default)]
988 pub base: Option<String>,
989 pub head: String,
990 #[serde(default)]
991 pub skip_dirs: Vec<String>,
992 pub limit: u32,
993}
994
995/// `archive_link`: a link to download a branch, tag or commit as an
996/// archive, for someone who can read the repository: the site's
997/// `/<owner>/<repo>/archive/<commit>.tar.gz` (or `.zip`) with `?token=`,
998/// good for [`ARCHIVE_LINK_SECONDS`] without a session or an access token.
999/// The default branch when `ref` is absent. Returns `Outcome<ArchiveLink>`;
1000/// refused as `invalid` where the repos service has no `REPOS_KEY`.
1001#[derive(Debug, Serialize, Deserialize)]
1002#[serde(rename_all = "camelCase")]
1003pub struct ArchiveLinkArgs {
1004 pub path: RepoPath,
1005 pub viewer: Viewer,
1006 #[serde(default, rename = "ref")]
1007 pub git_ref: Option<String>,
1008}
1009
1010/// How long an archive link lasts.
1011pub const ARCHIVE_LINK_SECONDS: u64 = 10 * 60;
1012
1013#[derive(Clone, Debug, Serialize, Deserialize)]
1014#[serde(rename_all = "camelCase")]
1015pub struct ArchiveLink {
1016 pub repo: Repo,
1017 /// The commit the ref named, which the link downloads.
1018 pub commit: String,
1019 /// What the link carries as `?token=`.
1020 pub token: String,
1021 /// RFC 3339.
1022 pub expires_at: String,
1023}
1024
1025/// `open_archive_token`: what an archive link's token lets anyone holding
1026/// it download, when it is for the repository at `path` and has not
1027/// expired. Returns `Option<ArchiveGrant>`. No viewer: the token is the
1028/// permission.
1029#[derive(Debug, Serialize, Deserialize)]
1030#[serde(rename_all = "camelCase")]
1031pub struct OpenArchiveTokenArgs {
1032 pub path: RepoPath,
1033 pub token: String,
1034}
1035
1036#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1037#[serde(rename_all = "camelCase")]
1038pub struct ArchiveGrant {
1039 pub repo_id: String,
1040 /// The repository's workspace and name as they are now.
1041 pub namespace: String,
1042 pub name: String,
1043 pub commit: String,
1044}
1045
1046/// The most files one `list_files` or `changed_files` call lists.
1047pub const MAX_LISTED_FILES: u32 = 10_000;
1048
1049/// `read_blobs`: the text of these blobs of a repository, for services
1050/// that index it. A blob larger than `max_bytes`, or binary, comes back
1051/// with no text. Returns `Vec<BlobText>`, in the order asked.
1052#[derive(Debug, Default, Serialize, Deserialize)]
1053#[serde(rename_all = "camelCase")]
1054pub struct ReadBlobsArgs {
1055 pub repo_id: String,
1056 pub hashes: Vec<String>,
1057 pub max_bytes: u32,
1058}
1059
1060/// The most blobs one `read_blobs` call reads.
1061pub const MAX_READ_BLOBS: usize = 100;
1062
1063/// `refs`: a repository's branches and tags with the commit each points to
1064/// (annotated tags peeled), for services that follow them, such as the
1065/// packages service's Composer registry. No viewer: g1t's own services
1066/// only. Returns `Option<RepoRefs>`, null for a fork or an unknown id.
1067#[derive(Debug, Default, Serialize, Deserialize)]
1068#[serde(rename_all = "camelCase")]
1069pub struct RefsArgs {
1070 pub repo_id: String,
1071}
1072
1073#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1074pub struct GitRefEntry {
1075 /// The full ref: `refs/heads/main`, `refs/tags/v1.0.0`.
1076 pub name: String,
1077 pub commit: String,
1078}
1079
1080#[derive(Clone, Debug, Serialize, Deserialize)]
1081pub struct RepoRefs {
1082 pub repo: Repo,
1083 pub refs: Vec<GitRefEntry>,
1084}
1085
1086/// `raw_file`: one file's bytes at a ref or commit, base64, for g1t's own
1087/// services (no viewer). Returns `Option<RawFile>`: null when the file is
1088/// missing or larger than `max_bytes`.
1089#[derive(Debug, Default, Serialize, Deserialize)]
1090#[serde(rename_all = "camelCase")]
1091pub struct RawFileArgs {
1092 pub repo_id: String,
1093 #[serde(rename = "ref")]
1094 pub git_ref: String,
1095 pub path: String,
1096 pub max_bytes: u32,
1097}
1098
1099#[derive(Clone, Debug, Serialize, Deserialize)]
1100pub struct RawFile {
1101 pub size: u64,
1102 /// Standard base64.
1103 pub data: String,
1104}
1105
1106/// `raw_blobs`: blobs' bytes, base64, in the order asked, at most
1107/// [`MAX_READ_BLOBS`]; `data` is null for one missing or larger than
1108/// `max_bytes`. For g1t's own services. Returns `Vec<RawBlob>`.
1109#[derive(Debug, Default, Serialize, Deserialize)]
1110#[serde(rename_all = "camelCase")]
1111pub struct RawBlobsArgs {
1112 pub repo_id: String,
1113 pub hashes: Vec<String>,
1114 pub max_bytes: u32,
1115}
1116
1117#[derive(Clone, Debug, Serialize, Deserialize)]
1118pub struct RawBlob {
1119 pub hash: String,
1120 pub size: u64,
1121 pub data: Option<String>,
1122}
1123
1124#[derive(Clone, Debug, Serialize, Deserialize)]
1125pub struct BlobText {
1126 pub hash: String,
1127 pub size: u64,
1128 /// Null when the blob is missing, binary or larger than asked.
1129 pub text: Option<String>,
1130}
1131
1132/// `all_ids`: every repository that is not a fork, by id, a page at a
1133/// time, for services that index all of them. Returns `IdPage`.
1134#[derive(Debug, Default, Serialize, Deserialize)]
1135pub struct AllIdsArgs {
1136 /// Ids after this one.
1137 #[serde(default)]
1138 pub after: Option<String>,
1139 pub limit: u32,
1140}
1141
1142#[derive(Clone, Debug, Default, Serialize, Deserialize)]
1143pub struct IdPage {
1144 pub ids: Vec<String>,
1145 /// Where the next page starts; null on the last.
1146 pub next: Option<String>,
1147}
1148
1149/// `repo_creators` takes [`AllIdsArgs`]: every repository that is not a
1150/// fork, with the account that created it, a page at a time, for identity
1151/// giving creators the Admin role. Returns [`CreatorPage`].
1152#[derive(Clone, Debug, Default, Serialize, Deserialize)]
1153pub struct CreatorPage {
1154 pub repos: Vec<RepoCreator>,
1155 /// Where the next page starts; null on the last.
1156 pub next: Option<String>,
1157}
1158
1159#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1160pub struct RepoCreator {
1161 pub id: String,
1162 /// Its workspace's slug.
1163 pub namespace: String,
1164 pub name: String,
1165 /// The account that created it.
1166 pub owner_id: String,
1167}
1168
1169/// `visibility`: which of these repositories (`namespace/name`) are
1170/// private, for billing, which pays for work on public ones from g1t's
1171/// open-source pool. A pull request's working copy answers as the
1172/// repository it is a copy of. Unknown paths are left out. Returns
1173/// `Vec<RepoVisibility>`.
1174#[derive(Debug, Default, Serialize, Deserialize)]
1175pub struct VisibilityArgs {
1176 pub paths: Vec<String>,
1177}
1178
1179#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1180pub struct RepoVisibility {
1181 pub path: String,
1182 pub is_private: bool,
1183}
1184
1185/// `git_operations`: how many git operations (clones, fetches and pushes
1186/// through g1t's git endpoints) each workspace's repositories had in a
1187/// month, for billing's git meter. Cloudflare Artifacts charges per
1188/// operation from 2026-10-14. Pushes from agents' sandboxes go to the
1189/// store directly and are not counted here. Returns
1190/// `Vec<WorkspaceGitOperations>`.
1191#[derive(Debug, Serialize, Deserialize)]
1192pub struct GitOperationsArgs {
1193 /// YYYY-MM.
1194 pub month: String,
1195 /// Count only from this hour on, `YYYY-MM-DDTHH`, such as the day the
1196 /// provider starts charging.
1197 #[serde(default)]
1198 pub since: Option<String>,
1199 /// One workspace only; every workspace with any when absent.
1200 #[serde(default)]
1201 pub namespace: Option<String>,
1202}
1203
1204#[derive(Clone, Debug, Serialize, Deserialize)]
1205pub struct WorkspaceGitOperations {
1206 pub namespace: String,
1207 pub operations: u64,
1208}
1209
1210/// `storage`: what each workspace's private repositories hold, as far as
1211/// g1t can measure it, for billing's daily storage meter. Returns
1212/// `Vec<WorkspaceStorage>`.
1213///
1214/// The git store does not report a repository's size. What is counted is
1215/// the bytes of every pack pushed through g1t's git endpoints to the
1216/// repository or to its pull requests' working copies. Pushes made from
1217/// agents' sandboxes, which go to the store directly, and imports are not
1218/// counted, so it is a lower bound on what is stored.
1219#[derive(Debug, Default, Serialize, Deserialize)]
1220pub struct StorageArgs {}
1221
1222#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1223pub struct WorkspaceStorage {
1224 pub namespace: String,
1225 pub private_bytes: i64,
1226 pub public_bytes: i64,
1227}
1228
1229/// `transfer`: moves a repository to another workspace, keeping its name,
1230/// its id and everything kept under it. The actor must own both
1231/// workspaces. The old path keeps working as a redirect (see
1232/// `resolve_path`) until a repository is made there. Publishes
1233/// `repo.transferred`. Returns `Outcome<Repo>`, the repository at its new
1234/// path.
1235#[derive(Debug, Serialize, Deserialize)]
1236#[serde(rename_all = "camelCase")]
1237pub struct TransferArgs {
1238 pub actor: User,
1239 pub path: RepoPath,
1240 /// The destination workspace's slug.
1241 pub to: String,
1242 /// Where the request came in, for the audit log; g1t.sh when absent.
1243 #[serde(default)]
1244 pub surface: Option<crate::audit::Surface>,
1245}
1246
1247/// `resolve_path`: where a repository that was transferred away from
1248/// `path` is now, while nothing else is there. Returns `Option<RepoPath>`:
1249/// null when `path` is a repository, or never was one that moved. Callers
1250/// check the viewer may see the repository at its new path, as for any
1251/// other.
1252#[derive(Debug, Serialize, Deserialize)]
1253pub struct ResolvePathArgs {
1254 pub path: RepoPath,
1255}
1256
1257/// `namespace_count`: how many repositories (not pull request working
1258/// copies) a workspace holds, private or not, for deciding whether it can
1259/// be deleted. Returns `u32`.
1260#[derive(Debug, Serialize, Deserialize)]
1261pub struct NamespaceCountArgs {
1262 pub namespace: String,
1263}
1264
1265/// `delete`: deletes a repository. Owners of its workspace only, who type
1266/// its full name (`namespace/name`) as `confirm`. It is hidden at once,
1267/// git refuses it, and nothing runs for it; it can be restored for
1268/// [`RESTORE_DAYS`] days, then it is purged, its git data with it. Its
1269/// name stays taken until then, or until it is purged sooner from the
1270/// workspace's Recently deleted list. Publishes `repo.deleted`. Returns
1271/// `Outcome<DeletedRepo>`.
1272#[derive(Debug, Serialize, Deserialize)]
1273#[serde(rename_all = "camelCase")]
1274pub struct DeleteArgs {
1275 pub actor: User,
1276 pub path: RepoPath,
1277 #[serde(default)]
1278 pub confirm: String,
1279 #[serde(default)]
1280 pub surface: Option<crate::audit::Surface>,
1281}
1282
1283/// `deleted`: a workspace's recently deleted repositories, newest first.
1284/// Owners only; empty for anyone else. Returns `Vec<DeletedRepo>`.
1285#[derive(Debug, Serialize, Deserialize)]
1286pub struct DeletedArgs {
1287 pub viewer: Viewer,
1288 pub namespace: String,
1289}
1290
1291/// `restore` and `purge`: a deleted repository, by the path it had.
1292/// `restore` brings it back as it was, at that path (`repo.restored`).
1293/// `purge` removes it for good now, its git data with it, and frees its
1294/// name (`repo.purged`); it takes the full name typed as `confirm`.
1295/// Owners only. Return `Outcome<Repo>` and `Outcome<bool>`.
1296#[derive(Debug, Serialize, Deserialize)]
1297#[serde(rename_all = "camelCase")]
1298pub struct DeletedRepoArgs {
1299 pub actor: User,
1300 pub path: RepoPath,
1301 #[serde(default)]
1302 pub confirm: Option<String>,
1303 #[serde(default)]
1304 pub surface: Option<crate::audit::Surface>,
1305}
1306
1307/// `purge_due`: purges deleted repositories whose time has passed, at
1308/// most `limit` (25 when absent). The service's own schedule runs it.
1309/// Returns `u32`, how many were purged.
1310#[derive(Debug, Default, Serialize, Deserialize)]
1311pub struct PurgeDueArgs {
1312 #[serde(default)]
1313 pub limit: Option<u32>,
1314}
1315
1316/// `rename`: gives a repository a new name in its workspace, keeping its
1317/// id, its git data and everything kept under it. Owners only. The old
1318/// path keeps redirecting, as after a transfer, until a repository is made
1319/// there. Publishes `repo.renamed`. Returns `Outcome<Repo>`.
1320#[derive(Debug, Serialize, Deserialize)]
1321#[serde(rename_all = "camelCase")]
1322pub struct RenameArgs {
1323 pub actor: User,
1324 pub path: RepoPath,
1325 pub name: String,
1326 #[serde(default)]
1327 pub surface: Option<crate::audit::Surface>,
1328}
1329
1330/// `archive`: makes a repository read-only (`archived: true`), or writable
1331/// again. Owners only. While archived, pushes and merges are refused,
1332/// issues and pull requests are locked, and agents and workflows do not
1333/// run; deployments keep serving. Publishes `repo.archived` or
1334/// `repo.unarchived`. Returns `Outcome<Repo>`.
1335#[derive(Debug, Serialize, Deserialize)]
1336#[serde(rename_all = "camelCase")]
1337pub struct ArchiveArgs {
1338 pub actor: User,
1339 pub path: RepoPath,
1340 pub archived: bool,
1341 #[serde(default)]
1342 pub surface: Option<crate::audit::Surface>,
1343}
1344
1345/// `set_visibility`: makes a repository public or private. Owners only,
1346/// who type its full name as `confirm`. A free workspace takes a private
1347/// repository only while its private storage has room. Publishes
1348/// `repo.updated` and `repo.visibility_changed`. Returns `Outcome<Repo>`.
1349#[derive(Debug, Serialize, Deserialize)]
1350#[serde(rename_all = "camelCase")]
1351pub struct SetVisibilityArgs {
1352 pub actor: User,
1353 pub path: RepoPath,
1354 pub is_private: bool,
1355 #[serde(default)]
1356 pub confirm: String,
1357 #[serde(default)]
1358 pub surface: Option<crate::audit::Surface>,
1359}
1360
1361/// `set_default_branch`: makes another existing branch the one everything
1362/// lands on. Members of its workspace. Open pull requests then merge into
1363/// it. Publishes `repo.default_branch_changed`. Returns `Outcome<Repo>`.
1364#[derive(Debug, Serialize, Deserialize)]
1365#[serde(rename_all = "camelCase")]
1366pub struct SetDefaultBranchArgs {
1367 pub actor: User,
1368 pub path: RepoPath,
1369 pub branch: String,
1370 #[serde(default)]
1371 pub surface: Option<crate::audit::Surface>,
1372}
1373
1374/// `rename_branch`: renames a branch. Members of its workspace; only an
1375/// owner renames the default branch, which stays the default. Pull
1376/// requests from it follow, and web addresses naming the old branch
1377/// redirect until a branch of that name is made again. Publishes
1378/// `branch.renamed` (and `repo.default_branch_changed` for the default).
1379/// Returns `Outcome<Repo>`.
1380#[derive(Debug, Serialize, Deserialize)]
1381#[serde(rename_all = "camelCase")]
1382pub struct RenameBranchArgs {
1383 pub actor: User,
1384 pub path: RepoPath,
1385 pub from: String,
1386 pub to: String,
1387 #[serde(default)]
1388 pub surface: Option<crate::audit::Surface>,
1389}
1390
1391/// `create_branch`: makes a branch named `name` at `from`: another
1392/// branch, a tag or a commit (the default branch's head when empty). The
1393/// actor needs to be able to push, and the rules for creating the branch
1394/// hold. Restoring a deleted branch is making it again at the commit
1395/// `remove_branch` answered with. Publishes `git.push`. Returns
1396/// `Outcome<Branch>`.
1397#[derive(Debug, Serialize, Deserialize)]
1398#[serde(rename_all = "camelCase")]
1399pub struct CreateBranchArgs {
1400 pub actor: User,
1401 pub path: RepoPath,
1402 pub name: String,
1403 #[serde(default)]
1404 pub from: String,
1405 #[serde(default)]
1406 pub surface: Option<crate::audit::Surface>,
1407}
1408
1409/// `remove_branch`: deletes a branch for someone who can push, never the
1410/// default branch; the rules for deleting it hold. `head`, when given, is
1411/// the commit it must still point to: a branch that moved since is left
1412/// alone. Publishes `git.deleted`. Returns `Outcome<Branch>`: the branch
1413/// as it was, so it can be made again at the same commit with
1414/// `create_branch`. (`delete_branch` is the services' own, with no actor.)
1415#[derive(Debug, Serialize, Deserialize)]
1416#[serde(rename_all = "camelCase")]
1417pub struct RemoveBranchArgs {
1418 pub actor: User,
1419 pub path: RepoPath,
1420 pub branch: String,
1421 #[serde(default, skip_serializing_if = "Option::is_none")]
1422 pub head: Option<String>,
1423 #[serde(default)]
1424 pub surface: Option<crate::audit::Surface>,
1425}
1426
1427/// `resolve_branch`: what a branch renamed away from `branch` is called
1428/// now, for web addresses that name the old one; null when `branch` was
1429/// never renamed or exists again. Returns `Option<String>`.
1430#[derive(Debug, Serialize, Deserialize)]
1431#[serde(rename_all = "camelCase")]
1432pub struct ResolveBranchArgs {
1433 pub repo_id: String,
1434 pub branch: String,
1435}
1436
1437/// `status_by_id`: whether a repository is archived or deleted, for g1t's
1438/// own services deciding whether to act on it. An unknown id answers as deleted. Returns `RepoStatus`.
1439#[derive(Debug, Serialize, Deserialize)]
1440pub struct StatusByIdArgs {
1441 pub id: String,
1442}
1443
1444#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
1445pub struct RepoStatus {
1446 pub archived: bool,
1447 pub deleted: bool,
1448}
1449
1450impl RepoStatus {
1451 /// Whether work may start on it: neither archived nor deleted.
1452 pub fn active(&self) -> bool {
1453 !self.archived && !self.deleted
1454 }
1455}
1456
1457/// What a person is told when something would change an archived
1458/// repository.
1459pub fn archived_message(namespace: &str, name: &str) -> String {
1460 format!(
1461 "{namespace}/{name} is archived, so it is read-only. An owner can unarchive it in its settings."
1462 )
1463}
1464
1465/// The path a repository was transferred from, and when, as `transfer`
1466/// keeps it so old addresses redirect.
1467#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1468#[serde(rename_all = "camelCase")]
1469pub struct RepoRedirect {
1470 pub from: RepoPath,
1471 pub repo_id: String,
1472 /// RFC 3339.
1473 pub created_at: String,
1474}
1475
1476#[cfg(test)]
1477mod topic_tests {
1478 use super::*;
1479
1480 fn topics(list: &[&str]) -> Result<Vec<String>, String> {
1481 clean_topics(&list.iter().map(|t| t.to_string()).collect::<Vec<_>>())
1482 }
1483
1484 #[test]
1485 fn topics_are_tidied() {
1486 assert_eq!(topics(&["Rust", " web_server ", "rust", ""]).unwrap(), vec!["rust", "web-server"]);
1487 }
1488
1489 #[test]
1490 fn websites_are_tidied() {
1491 assert_eq!(clean_website(" example.com ").unwrap().as_deref(), Some("https://example.com"));
1492 assert_eq!(clean_website("http://a.io/x").unwrap().as_deref(), Some("http://a.io/x"));
1493 assert_eq!(clean_website("").unwrap(), None);
1494 assert!(clean_website("ftp://a.io").is_err());
1495 assert!(clean_website("localhost").is_err());
1496 assert!(clean_website("https://a b.io").is_err());
1497 }
1498
1499 #[test]
1500 fn branch_names_follow_git() {
1501 for good in ["main", "trunk", "release/1.2", "feat-x_y"] {
1502 assert!(is_valid_branch_name(good), "{good}");
1503 }
1504 for bad in ["", "-x", "a..b", "a b", "x.lock", "a/", ".hidden", "a/.b", "g1t-queue", "a~1", "a:b", "@"] {
1505 assert!(!is_valid_branch_name(bad), "{bad}");
1506 }
1507 }
1508
1509 #[test]
1510 fn odd_topics_are_refused() {
1511 assert!(topics(&["c++"]).is_err());
1512 assert!(topics(&["-lead"]).is_err());
1513 assert!(topics(&[&"a".repeat(36)]).is_err());
1514 let many: Vec<String> = (0..21).map(|i| format!("t{i}")).collect();
1515 assert!(clean_topics(&many).is_err());
1516 }
1517}
1518
1519#[cfg(test)]
1520mod tests {
1521 use super::*;
1522
1523 #[test]
1524 fn a_pull_branch_update_reads_as_the_web_expects() {
1525 let update = PullBranchUpdate::NeedsAgent {
1526 reason: NeedsAgentReason::Overlap,
1527 detail: "both".into(),
1528 paths: vec!["a.rs".into()],
1529 };
1530 assert_eq!(
1531 serde_json::to_value(&update).unwrap(),
1532 serde_json::json!({ "outcome": "needs_agent", "reason": "overlap", "detail": "both", "paths": ["a.rs"] })
1533 );
1534 let done = PullBranchUpdate::UpToDate { commit: "c".into() };
1535 assert_eq!(serde_json::to_value(&done).unwrap(), serde_json::json!({ "outcome": "up_to_date", "commit": "c" }));
1536 }
1537}