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.

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