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,850 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 /// The push made the default branch: the repository's first, whose
629 /// commits were pushed nowhere before, so their messages count too.
630 #[serde(default)]
631 pub first: bool,
632}
633
634/// What `pushed` found.
635#[derive(Debug, Default, Serialize, Deserialize)]
636#[serde(rename_all = "camelCase")]
637pub struct Pushed {
638 /// Those of `heads` the push brought in.
639 pub landed: Vec<String>,
640 /// With `messages`: the commits brought in, oldest first, at most
641 /// `MAX_PUSHED`.
642 pub commits: Vec<PushedCommit>,
643}
644
645/// The most commits `pushed` lists.
646pub const MAX_PUSHED: usize = 250;
647
648/// A commit a push brought in.
649#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
650pub struct PushedCommit {
651 pub hash: String,
652 pub message: String,
653}
654
655/// `build_queue_step`: one entry of a merge queue landed, by its method,
656/// on a commit of the target that is not a branch's head: the default
657/// branch, or the state built for the entries ahead. Makes the commits
658/// `land` would make there (`merge`, `squash` and `rebase` new ones; a
659/// `fast_forward` none, its head only if that contains `onto`) and points
660/// `push_branch` of the target at the result, so that landing it later is
661/// a fast-forward to exactly what was tested. The branch it merges into
662/// does not move. Returns `Outcome<QueueStep>`.
663#[derive(Debug, Serialize, Deserialize)]
664#[serde(rename_all = "camelCase")]
665pub struct BuildQueueStepArgs {
666 /// The repository the queue lands on.
667 pub target_id: String,
668 /// The commit to build on.
669 pub onto: String,
670 /// Where the entry's change is: its fork, or the target itself.
671 pub source_id: String,
672 pub branch: String,
673 /// The entry's head as it was queued; refused if the branch moved.
674 pub commit: String,
675 pub method: crate::rules::MergeMethod,
676 /// For `merge` and `squash`: the commit's message. Required for those.
677 #[serde(default, skip_serializing_if = "Option::is_none")]
678 pub message: Option<String>,
679 /// For `squash`: who the commit is by; `actor` when absent.
680 #[serde(default, skip_serializing_if = "Option::is_none")]
681 pub author: Option<User>,
682 #[serde(default)]
683 pub sign_off: bool,
684 /// Whoever queued it: the committer, who must be able to merge.
685 pub actor: User,
686 /// The queue's branch of the target to point at the result.
687 pub push_branch: String,
688}
689
690/// What `build_queue_step` made.
691#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
692#[serde(tag = "kind", rename_all = "snake_case")]
693pub enum QueueStep {
694 /// The state with the entry in it, now at `push_branch`: `onto` itself
695 /// when it held the entry's changes already.
696 Built {
697 commit: String,
698 /// For `rebase`: each commit made, with the one it was made from.
699 #[serde(default)]
700 rebased: Vec<(String, String)>,
701 },
702 /// Its change clashes with `onto` in these files.
703 Conflict { message: String, paths: Vec<String> },
704 /// It cannot be built there, and why: a fast-forward that does not
705 /// contain `onto`, or more than g1t makes itself.
706 Refused { message: String },
707}
708
709/// How a pull request's branch is brought up to date with its base
710/// (`update_pull_branch`).
711#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
712#[serde(rename_all = "snake_case")]
713pub enum UpdateMethod {
714 /// A merge commit on the branch joins the base's new commits.
715 #[default]
716 Merge,
717 /// The branch's commits are made again on the base's head, and the
718 /// branch is moved there (a force push).
719 Rebase,
720}
721
722#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
723#[serde(rename_all = "lowercase")]
724pub enum FileStatus {
725 Added,
726 Modified,
727 Deleted,
728}
729
730#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
731#[serde(rename_all = "lowercase")]
732pub enum LineKind {
733 /// Unchanged, shown for context.
734 Context,
735 Add,
736 Delete,
737}
738
739#[derive(Clone, Debug, Serialize, Deserialize)]
740pub struct DiffLine {
741 pub kind: LineKind,
742 /// Line number in the old file; absent for added lines.
743 pub old: Option<u32>,
744 /// Line number in the new file; absent for deleted lines.
745 pub new: Option<u32>,
746 pub text: String,
747}
748
749/// A run of changed lines with their surrounding context.
750#[derive(Clone, Debug, Serialize, Deserialize)]
751pub struct Hunk {
752 pub lines: Vec<DiffLine>,
753}
754
755#[derive(Clone, Debug, Serialize, Deserialize)]
756pub struct FileDiff {
757 pub path: String,
758 pub status: FileStatus,
759 pub additions: u32,
760 pub deletions: u32,
761 /// True when the file is binary or too large, so no lines are shown.
762 pub binary: bool,
763 pub hunks: Vec<Hunk>,
764 /// True when the comparison reached its limit on lines before this
765 /// file, so it is listed without its lines (and with no counts): ask
766 /// `compare` again with its path in `paths` to have them.
767 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
768 pub omitted: bool,
769}
770
771/// What changed between two commits.
772#[derive(Clone, Debug, Serialize, Deserialize)]
773pub struct Comparison {
774 /// Null when the head has no earlier commit to compare against.
775 pub base: Option<String>,
776 pub head: String,
777 pub files: Vec<FileDiff>,
778 /// True when more files changed than are listed. A listed file whose
779 /// lines were left out says so itself (`FileDiff::omitted`).
780 pub truncated: bool,
781}
782
783/// `compare`: what `head` changes relative to `base`.
784///
785/// `head` is a branch or a commit, and defaults to the default branch.
786/// With no `base`, a fork is compared against the point where it and the
787/// repository it came from last agreed; a branch against the point where it
788/// left the default branch; and the default branch against its head's
789/// parent. Returns `Outcome<Comparison>`.
790#[derive(Debug, Serialize, Deserialize)]
791#[serde(rename_all = "camelCase")]
792pub struct CompareArgs {
793 pub repo_id: String,
794 pub viewer: Viewer,
795 #[serde(default)]
796 pub base: Option<String>,
797 #[serde(default)]
798 pub head: Option<String>,
799 /// With no `base`: the branch whose shared point with the head it is
800 /// compared from, instead of the default branch. A pull request into
801 /// another branch is compared this way.
802 #[serde(default)]
803 pub base_branch: Option<String>,
804 /// Only these files, with their lines: how a file left out of a large
805 /// comparison is read on its own. At most 50.
806 #[serde(default)]
807 pub paths: Option<Vec<String>>,
808 /// With no `base`: a repository `repo_id` is a fork of (directly or
809 /// through other forks) whose branch (`base_branch`, else its default
810 /// branch) the head is compared from, as for a pull request from a
811 /// fork. Both must be readable by the viewer.
812 #[serde(default, skip_serializing_if = "Option::is_none")]
813 pub target_id: Option<String>,
814}
815
816/// Lines `start` to `end` of a file, inclusive and counted from 1, last
817/// changed by `commit`.
818#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
819pub struct BlameRange {
820 pub start: u32,
821 pub end: u32,
822 pub commit: String,
823}
824
825/// Who last changed each line of a file.
826#[derive(Clone, Debug, Serialize, Deserialize)]
827pub struct Blame {
828 /// The commit the file was read at.
829 pub head: String,
830 /// Every line, in order, in runs that share a commit.
831 pub ranges: Vec<BlameRange>,
832 /// The commits the ranges name, each once.
833 pub commits: Vec<Commit>,
834 /// True when the history was too long to read in full (too many
835 /// commits, or too long to read), so the oldest lines are given to the
836 /// oldest commit read that has them.
837 pub partial: bool,
838}
839
840/// `blame`: who last changed each line of `path` as of `ref` (the default
841/// branch if absent). Returns `Outcome<Blame>`; not found when the file is
842/// missing or is not text.
843#[derive(Debug, Serialize, Deserialize)]
844pub struct BlameArgs {
845 pub path: RepoPath,
846 pub viewer: Viewer,
847 #[serde(default, rename = "ref")]
848 pub git_ref: Option<String>,
849 #[serde(rename = "filePath")]
850 pub file_path: String,
851}
852
853/// A branch and the commit it points to.
854#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
855pub struct Branch {
856 pub name: String,
857 pub hash: String,
858}
859
860/// `last_commits`: which commit last changed each entry of a directory at
861/// `ref` (the default branch when absent). Returns `Outcome<LastCommits>`.
862#[derive(Debug, Serialize, Deserialize)]
863#[serde(rename_all = "camelCase")]
864pub struct LastCommitsArgs {
865 pub path: RepoPath,
866 pub viewer: Viewer,
867 #[serde(default, rename = "ref")]
868 pub git_ref: Option<String>,
869 #[serde(default)]
870 pub tree_path: String,
871 /// Answer within this many milliseconds with what was found; absent,
872 /// within 20 seconds. A walk that stops short keeps its progress, and
873 /// the next call goes on from it.
874 #[serde(default)]
875 pub budget_ms: Option<u64>,
876}
877
878/// `path_log`: the commits on the first-parent chain from `ref` (the
879/// default branch when absent) that changed `file_path`, a file or a
880/// directory, newest first, at most `limit`. Returns `Outcome<PathLog>`.
881#[derive(Debug, Serialize, Deserialize)]
882#[serde(rename_all = "camelCase")]
883pub struct PathLogArgs {
884 pub path: RepoPath,
885 pub viewer: Viewer,
886 #[serde(default, rename = "ref")]
887 pub git_ref: Option<String>,
888 pub file_path: String,
889 pub limit: u32,
890 /// Answer within this many milliseconds with what was found.
891 #[serde(default)]
892 pub budget_ms: Option<u64>,
893}
894
895/// A path's history: `complete` is false when the walk ran out of time or
896/// reads before the first commit and before `limit`.
897#[derive(Clone, Debug, Serialize, Deserialize)]
898#[serde(rename_all = "camelCase")]
899pub struct PathLog {
900 pub commits: Vec<Commit>,
901 pub complete: bool,
902}
903
904/// An entry of a directory and the commit that last changed it.
905#[derive(Clone, Debug, Serialize, Deserialize)]
906pub struct LastCommit {
907 pub name: String,
908 pub commit: Commit,
909}
910
911/// The entries' last commits. `complete` is false when the walk stopped
912/// (or the history ran out) before every entry was placed; those entries
913/// are left out, and a later call goes on placing them.
914#[derive(Clone, Debug, Serialize, Deserialize)]
915pub struct LastCommits {
916 pub entries: Vec<LastCommit>,
917 pub complete: bool,
918}
919
920/// `branch_drift`: how far each of `heads` (branch head commits) has moved
921/// from `base` (the default branch's head commit), and each one's head
922/// commit, in one call. Every answer is kept by the pair of hashes: neither
923/// history can change, so neither can it. Returns `Outcome<BranchDrifts>`.
924#[derive(Debug, Serialize, Deserialize)]
925#[serde(rename_all = "camelCase")]
926pub struct BranchDriftArgs {
927 pub path: RepoPath,
928 pub viewer: Viewer,
929 pub base: String,
930 pub heads: Vec<String>,
931}
932
933/// Commits a branch has that the default branch does not (`ahead`), and
934/// the other way round (`behind`), as `git rev-list --left-right --count`.
935#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
936pub struct Drift {
937 pub ahead: u32,
938 pub behind: u32,
939}
940
941/// One branch head's commit and drift. `drift` is absent when the two
942/// histories do not meet within what is read (or could not be read).
943#[derive(Clone, Debug, Serialize, Deserialize)]
944pub struct BranchDrift {
945 pub head: String,
946 pub commit: Option<Commit>,
947 pub drift: Option<Drift>,
948}
949
950/// `base`'s own commit, and each head's answer in the order asked.
951#[derive(Clone, Debug, Serialize, Deserialize)]
952pub struct BranchDrifts {
953 pub base: Option<Commit>,
954 pub branches: Vec<BranchDrift>,
955}
956
957/// `tags`: the repository's tags, newest commit first, each with the
958/// commit it names. Returns `Outcome<Vec<Tag>>`.
959#[derive(Debug, Serialize, Deserialize)]
960pub struct TagsArgs {
961 pub path: RepoPath,
962 pub viewer: Viewer,
963}
964
965/// A tag, and its commit when it could be read.
966#[derive(Clone, Debug, Serialize, Deserialize)]
967pub struct Tag {
968 pub name: String,
969 pub commit: Option<Commit>,
970}
971
972/// `branches`: the repository's branches, default branch first.
973/// Returns `Outcome<Vec<Branch>>`.
974#[derive(Debug, Serialize, Deserialize)]
975pub struct BranchesArgs {
976 pub path: RepoPath,
977 pub viewer: Viewer,
978}
979
980/// `behind`: whether the default branch of the repository a pull request
981/// would merge into has commits its source does not. For services that
982/// have already decided the caller may see the pull request; it reveals
983/// one bit. Returns `bool`.
984#[derive(Debug, Serialize, Deserialize)]
985#[serde(rename_all = "camelCase")]
986pub struct BehindArgs {
987 /// The pull request's fork, or the repository itself for a branch.
988 pub source_id: String,
989 /// The branch of the source. A fork is compared on its default branch.
990 #[serde(default)]
991 pub branch: Option<String>,
992 /// The branch of the target it would merge into; the default branch
993 /// when absent.
994 #[serde(default)]
995 pub target_branch: Option<String>,
996 /// The repository the pull request merges into, when its source is a
997 /// fork of it (`Repo::parent_id`, directly or through other forks)
998 /// rather than a working copy or the repository itself. Refused when
999 /// the source is not in its fork network.
1000 #[serde(default, skip_serializing_if = "Option::is_none")]
1001 pub target_id: Option<String>,
1002}
1003
1004/// `divergence`: how a pull request's source and the default branch it
1005/// would merge into have moved apart since they last agreed: the files each
1006/// side changed. Takes `BehindArgs`. For services that have already decided
1007/// the caller may see the pull request; it reveals paths, not contents.
1008/// Returns `Option<Divergence>`, null when either side has no commits.
1009#[derive(Clone, Debug, Default, Serialize, Deserialize)]
1010#[serde(rename_all = "camelCase")]
1011pub struct Divergence {
1012 /// The source's commit.
1013 pub head: String,
1014 /// The default branch's commit.
1015 pub base: String,
1016 /// Where they last agreed, if that could be found.
1017 pub merge_base: Option<String>,
1018 /// Whether the default branch has commits the source does not.
1019 pub behind: bool,
1020 /// The files the source changed since the merge base.
1021 pub ours: Vec<String>,
1022 /// The files the default branch changed since the merge base. Empty
1023 /// when it is not behind.
1024 pub theirs: Vec<String>,
1025 /// Whether either list was cut short.
1026 pub truncated: bool,
1027}
1028
1029/// `update_pull_branch`: brings a pull request's source up to date with the
1030/// default branch it would merge into, without a sandbox, when that can be
1031/// done safely: merges the default branch's head into the source's head and
1032/// pushes the merge commit to the source's branch, as `actor`, only if the
1033/// branch has not moved meanwhile. It applies only when the two sides
1034/// changed different files since they last agreed; otherwise the answer is
1035/// [`PullBranchUpdate::NeedsAgent`] and nothing is pushed. Refused unless
1036/// `actor` may push to the source. Returns `Outcome<PullBranchUpdate>`.
1037#[derive(Debug, Serialize, Deserialize)]
1038#[serde(rename_all = "camelCase")]
1039pub struct UpdatePullBranchArgs {
1040 /// The pull request's fork, or the repository itself for a branch.
1041 pub source_id: String,
1042 /// The branch of the source. A fork is updated on its default branch.
1043 #[serde(default)]
1044 pub branch: Option<String>,
1045 /// The pull request's number, to name it in the merge commit's message
1046 /// when its branch has the same name as the default branch.
1047 pub number: u32,
1048 /// Who asked: the merge commit's author and committer, and the pusher.
1049 pub actor: User,
1050 /// The branch of the target to merge in; its default branch when absent.
1051 #[serde(default)]
1052 pub target_branch: Option<String>,
1053 /// The repository the pull request merges into, when its source is a
1054 /// fork of it (`Repo::parent_id`, directly or through other forks)
1055 /// rather than a working copy or the repository itself. Refused when
1056 /// the source is not in its fork network.
1057 #[serde(default, skip_serializing_if = "Option::is_none")]
1058 pub target_id: Option<String>,
1059 /// Merge the base in (the default), or rebase the branch onto it.
1060 #[serde(default)]
1061 pub method: UpdateMethod,
1062 /// Refuse if the branch no longer points here.
1063 #[serde(default, skip_serializing_if = "Option::is_none")]
1064 pub expected_head: Option<String>,
1065}
1066
1067/// Why an update has to be left to a sandbox.
1068#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
1069#[serde(rename_all = "snake_case")]
1070pub enum NeedsAgentReason {
1071 /// Both sides changed some of the same files; merging them needs a
1072 /// real merge, which may or may not conflict.
1073 Overlap,
1074 /// Merging is known to conflict.
1075 Conflicting,
1076 /// The update could not be worked out here, such as when the two sides
1077 /// share no history g1t can see, or the change is too large to list.
1078 Unsupported,
1079}
1080
1081/// What came of `update_pull_branch` (or the work service's `catch_up_pull`).
1082#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1083#[serde(tag = "outcome", rename_all = "snake_case")]
1084pub enum PullBranchUpdate {
1085 /// The merge commit was pushed: the branch moved from `previous` to
1086 /// `commit`.
1087 Updated { commit: String, previous: String },
1088 /// The source already holds the default branch's head.
1089 UpToDate { commit: String },
1090 /// Nothing was pushed; a sandbox has to merge it. `paths` are the
1091 /// files both sides changed, or that conflict, when known.
1092 NeedsAgent {
1093 reason: NeedsAgentReason,
1094 detail: String,
1095 paths: Vec<String>,
1096 },
1097}
1098
1099/// `pull_merge_ref`: points `refs/pull/<number>/head` in the repository a
1100/// pull request merges into at its head, fetching the head's objects from
1101/// the fork first when it comes from one, and `refs/pull/<number>/merge`
1102/// at the commit that merges that head into the base branch's tip, made
1103/// without a sandbox: what GitHub's `pull_request` runs build. For the
1104/// actions service, which has already decided the pull request's events
1105/// may start runs. Returns `PullMergeRef`.
1106#[derive(Debug, Serialize, Deserialize)]
1107#[serde(rename_all = "camelCase")]
1108pub struct PullMergeRefArgs {
1109 /// The repository the pull request merges into.
1110 pub repo_id: String,
1111 /// The pull request's fork, or the repository itself for a branch.
1112 pub source_id: String,
1113 /// The pull request's head commit.
1114 pub head: String,
1115 pub number: u32,
1116 /// The branch it merges into; the default branch when absent.
1117 #[serde(default)]
1118 pub target_branch: Option<String>,
1119}
1120
1121/// What came of `pull_merge_ref`.
1122#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1123#[serde(tag = "outcome", rename_all = "snake_case")]
1124pub enum PullMergeRef {
1125 /// `refs/pull/<number>/merge` is `merge`, whose parents are `base`
1126 /// (the base branch's tip) and `head`.
1127 Merged { head: String, base: String, merge: String },
1128 /// The head and the base branch change the same lines, or the same
1129 /// binary file, differently: there is no merge commit, and
1130 /// `refs/pull/<number>/merge` is removed, as on GitHub.
1131 Conflict { head: String, base: String, paths: Vec<String> },
1132 /// g1t could not make the merge commit itself (a file renamed on one
1133 /// side and changed on the other, a very large change, a history it
1134 /// could not follow), or either side has no commits: `reason` says why.
1135 Unavailable { reason: String },
1136}
1137
1138/// The ref a pull request's head is kept at in the repository it merges
1139/// into, as on GitHub.
1140pub fn pull_head_ref(number: u32) -> String {
1141 format!("refs/pull/{number}/head")
1142}
1143
1144/// The ref of a pull request's test merge commit, as on GitHub.
1145pub fn pull_merge_ref(number: u32) -> String {
1146 format!("refs/pull/{number}/merge")
1147}
1148
1149/// `head`: the commit a branch points to, or null. For services reacting
1150/// to a push, which have no viewer; it reveals nothing but a commit hash.
1151/// Returns `Option<String>`.
1152#[derive(Debug, Serialize, Deserialize)]
1153#[serde(rename_all = "camelCase")]
1154pub struct HeadArgs {
1155 pub repo_id: String,
1156 /// Empty for the repository's default branch.
1157 pub branch: String,
1158}
1159
1160/// Where g1t keeps branches of its own in a repository, such as the merge
1161/// queue's tested states. Only these can be removed with `delete_branch`.
1162pub const G1T_BRANCH_PREFIX: &str = "g1t-";
1163
1164/// `delete_branch`: removes a branch g1t made for itself once it is done
1165/// with it, never one of people's: the name must start with
1166/// [`G1T_BRANCH_PREFIX`], or `head` must name the commit it points to (a
1167/// dependency update's branch, which g1t pushed and whose pull request it
1168/// closed). Never the default branch. For services, which have no viewer.
1169/// Returns `Outcome<bool>`: whether there was such a branch.
1170#[derive(Debug, Serialize, Deserialize)]
1171#[serde(rename_all = "camelCase")]
1172pub struct DeleteBranchArgs {
1173 pub repo_id: String,
1174 pub branch: String,
1175 /// The commit the branch must still point to. A branch that moved
1176 /// since (someone pushed to it) is left alone.
1177 #[serde(default, skip_serializing_if = "Option::is_none")]
1178 pub head: Option<String>,
1179}
1180
1181/// `commit_file`: writes one file on a new branch made from the default
1182/// branch's head, as one commit by `actor`, without a sandbox. For a change
1183/// g1t proposes on someone's behalf, such as a starter workflow, which then
1184/// becomes a pull request. Refused unless `actor` may push, when the branch
1185/// already exists, or when the file is already there. Returns
1186/// `Outcome<CommittedFile>`.
1187#[derive(Debug, Serialize, Deserialize)]
1188#[serde(rename_all = "camelCase")]
1189pub struct CommitFileArgs {
1190 pub repo: RepoPath,
1191 pub actor: User,
1192 /// The new branch, which must not exist yet.
1193 pub branch: String,
1194 /// Where the file goes, such as `.g1t/workflows/ci.yml`.
1195 pub path: String,
1196 pub content: String,
1197 pub message: String,
1198 /// Add a `Signed-off-by:` trailer for `actor`: required where the
1199 /// repository asks web commits to be signed off.
1200 #[serde(default)]
1201 pub sign_off: bool,
1202}
1203
1204/// The commit `commit_file` made.
1205#[derive(Clone, Debug, Serialize, Deserialize)]
1206#[serde(rename_all = "camelCase")]
1207pub struct CommittedFile {
1208 pub branch: String,
1209 pub commit: String,
1210}
1211
1212/// `readable`: of these repository ids, the repositories the viewer may
1213/// read, as `get_by_id` decides; forks and unknown ids are left out. For
1214/// services that hold ids and must show only what the viewer could open.
1215/// At most [`MAX_READABLE`] ids are looked at. Returns `Vec<Repo>`.
1216#[derive(Debug, Serialize, Deserialize)]
1217pub struct ReadableArgs {
1218 pub ids: Vec<String>,
1219 pub viewer: Viewer,
1220}
1221
1222/// The most ids one `readable` call looks at.
1223pub const MAX_READABLE: usize = 500;
1224
1225/// `commit_days`: how many commits a person pushed each day (UTC) since
1226/// `since` (`YYYY-MM-DD`), to the default branch or `gh-pages` of
1227/// repositories the viewer may read (as `readable` decides), for the
1228/// contribution calendar. Credited to whoever pushed; at most 50 commits a
1229/// push, counted along the branch's first-parent line. Returns
1230/// `Vec<crate::work::ContributionDay>`, oldest first, days with none left out.
1231#[derive(Debug, Serialize, Deserialize)]
1232pub struct CommitDaysArgs {
1233 pub user_id: String,
1234 pub since: String,
1235 pub viewer: Viewer,
1236}
1237
1238/// `public_namespaces`: the workspaces in which this account made a public
1239/// repository, and so a public project, which anyone can see on its page.
1240/// Returns `Vec<String>` of workspace slugs.
1241#[derive(Debug, Serialize, Deserialize)]
1242#[serde(rename_all = "camelCase")]
1243pub struct PublicNamespacesArgs {
1244 pub owner_id: String,
1245}
1246
1247/// One file on a branch, or one a change touched: its path and blob.
1248#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1249pub struct FileEntry {
1250 pub path: String,
1251 /// The blob it holds now; null when the change deleted it.
1252 pub hash: Option<String>,
1253 /// `blob`, `exec` (executable) or `symlink` (the blob is where it
1254 /// points); absent for a deleted file.
1255 #[serde(default, skip_serializing_if = "Option::is_none")]
1256 pub kind: Option<EntryKind>,
1257}
1258
1259/// Files, and whether there were more than were listed.
1260#[derive(Clone, Debug, Default, Serialize, Deserialize)]
1261#[serde(rename_all = "camelCase")]
1262pub struct FileList {
1263 /// The commit the files were read at; null for an empty repository.
1264 pub commit: Option<String>,
1265 /// When that commit was made (RFC 3339), what an archive dates its
1266 /// files by.
1267 #[serde(default, skip_serializing_if = "Option::is_none")]
1268 pub date: Option<String>,
1269 pub files: Vec<FileEntry>,
1270 pub truncated: bool,
1271}
1272
1273/// `list_files`: every file on a branch (the default branch when absent),
1274/// path order by level, never descending into a directory named in
1275/// `skip_dirs`. For services that index a repository; no viewer, since it
1276/// is only reached by g1t's own services. Returns `FileList`.
1277#[derive(Debug, Default, Serialize, Deserialize)]
1278#[serde(rename_all = "camelCase")]
1279pub struct ListFilesArgs {
1280 pub repo_id: String,
1281 #[serde(default, rename = "ref")]
1282 pub git_ref: Option<String>,
1283 #[serde(default)]
1284 pub skip_dirs: Vec<String>,
1285 /// At most this many files; capped at [`MAX_LISTED_FILES`].
1286 pub limit: u32,
1287 /// Symlinks too, for an archive: by default only files whose text can
1288 /// be read are listed.
1289 #[serde(default)]
1290 pub links: bool,
1291}
1292
1293/// `changed_files`: the files that differ between two commits, as
1294/// `list_files` reads them. With no `base`, every file at `head`. Returns
1295/// `FileList`.
1296#[derive(Debug, Default, Serialize, Deserialize)]
1297#[serde(rename_all = "camelCase")]
1298pub struct ChangedFilesArgs {
1299 pub repo_id: String,
1300 #[serde(default)]
1301 pub base: Option<String>,
1302 pub head: String,
1303 #[serde(default)]
1304 pub skip_dirs: Vec<String>,
1305 pub limit: u32,
1306}
1307
1308/// `archive_link`: a link to download a branch, tag or commit as an
1309/// archive, for someone who can read the repository: the site's
1310/// `/<owner>/<repo>/archive/<commit>.tar.gz` (or `.zip`) with `?token=`,
1311/// good for [`ARCHIVE_LINK_SECONDS`] without a session or an access token.
1312/// The default branch when `ref` is absent. Returns `Outcome<ArchiveLink>`;
1313/// refused as `invalid` where the repos service has no `REPOS_KEY`.
1314#[derive(Debug, Serialize, Deserialize)]
1315#[serde(rename_all = "camelCase")]
1316pub struct ArchiveLinkArgs {
1317 pub path: RepoPath,
1318 pub viewer: Viewer,
1319 #[serde(default, rename = "ref")]
1320 pub git_ref: Option<String>,
1321}
1322
1323/// How long an archive link lasts.
1324pub const ARCHIVE_LINK_SECONDS: u64 = 10 * 60;
1325
1326#[derive(Clone, Debug, Serialize, Deserialize)]
1327#[serde(rename_all = "camelCase")]
1328pub struct ArchiveLink {
1329 pub repo: Repo,
1330 /// The commit the ref named, which the link downloads.
1331 pub commit: String,
1332 /// What the link carries as `?token=`.
1333 pub token: String,
1334 /// RFC 3339.
1335 pub expires_at: String,
1336}
1337
1338/// `open_archive_token`: what an archive link's token lets anyone holding
1339/// it download, when it is for the repository at `path` and has not
1340/// expired. Returns `Option<ArchiveGrant>`. No viewer: the token is the
1341/// permission.
1342#[derive(Debug, Serialize, Deserialize)]
1343#[serde(rename_all = "camelCase")]
1344pub struct OpenArchiveTokenArgs {
1345 pub path: RepoPath,
1346 pub token: String,
1347}
1348
1349#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1350#[serde(rename_all = "camelCase")]
1351pub struct ArchiveGrant {
1352 pub repo_id: String,
1353 /// The repository's workspace and name as they are now.
1354 pub namespace: String,
1355 pub name: String,
1356 pub commit: String,
1357}
1358
1359/// The most files one `list_files` or `changed_files` call lists.
1360pub const MAX_LISTED_FILES: u32 = 10_000;
1361
1362/// `read_blobs`: the text of these blobs of a repository, for services
1363/// that index it. A blob larger than `max_bytes`, or binary, comes back
1364/// with no text. Returns `Vec<BlobText>`, in the order asked.
1365#[derive(Debug, Default, Serialize, Deserialize)]
1366#[serde(rename_all = "camelCase")]
1367pub struct ReadBlobsArgs {
1368 pub repo_id: String,
1369 pub hashes: Vec<String>,
1370 pub max_bytes: u32,
1371}
1372
1373/// The most blobs one `read_blobs` call reads.
1374pub const MAX_READ_BLOBS: usize = 100;
1375
1376/// `refs`: a repository's branches and tags with the commit each points to
1377/// (annotated tags peeled), for services that follow them, such as the
1378/// packages service's Composer registry. No viewer: g1t's own services
1379/// only. Returns `Option<RepoRefs>`, null for a fork or an unknown id.
1380#[derive(Debug, Default, Serialize, Deserialize)]
1381#[serde(rename_all = "camelCase")]
1382pub struct RefsArgs {
1383 pub repo_id: String,
1384}
1385
1386#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1387pub struct GitRefEntry {
1388 /// The full ref: `refs/heads/main`, `refs/tags/v1.0.0`.
1389 pub name: String,
1390 pub commit: String,
1391}
1392
1393#[derive(Clone, Debug, Serialize, Deserialize)]
1394pub struct RepoRefs {
1395 pub repo: Repo,
1396 pub refs: Vec<GitRefEntry>,
1397}
1398
1399/// `raw_file`: one file's bytes at a ref or commit, base64, for g1t's own
1400/// services (no viewer). Returns `Option<RawFile>`: null when the file is
1401/// missing or larger than `max_bytes`.
1402#[derive(Debug, Default, Serialize, Deserialize)]
1403#[serde(rename_all = "camelCase")]
1404pub struct RawFileArgs {
1405 pub repo_id: String,
1406 #[serde(rename = "ref")]
1407 pub git_ref: String,
1408 pub path: String,
1409 pub max_bytes: u32,
1410}
1411
1412#[derive(Clone, Debug, Serialize, Deserialize)]
1413pub struct RawFile {
1414 pub size: u64,
1415 /// Standard base64.
1416 pub data: String,
1417}
1418
1419/// `raw_blobs`: blobs' bytes, base64, in the order asked, at most
1420/// [`MAX_READ_BLOBS`]; `data` is null for one missing or larger than
1421/// `max_bytes`. For g1t's own services. Returns `Vec<RawBlob>`.
1422#[derive(Debug, Default, Serialize, Deserialize)]
1423#[serde(rename_all = "camelCase")]
1424pub struct RawBlobsArgs {
1425 pub repo_id: String,
1426 pub hashes: Vec<String>,
1427 pub max_bytes: u32,
1428}
1429
1430#[derive(Clone, Debug, Serialize, Deserialize)]
1431pub struct RawBlob {
1432 pub hash: String,
1433 pub size: u64,
1434 pub data: Option<String>,
1435}
1436
1437#[derive(Clone, Debug, Serialize, Deserialize)]
1438pub struct BlobText {
1439 pub hash: String,
1440 pub size: u64,
1441 /// Null when the blob is missing, binary or larger than asked.
1442 pub text: Option<String>,
1443}
1444
1445/// `all_ids`: every repository that is not a fork, by id, a page at a
1446/// time, for services that index all of them. Returns `IdPage`.
1447#[derive(Debug, Default, Serialize, Deserialize)]
1448pub struct AllIdsArgs {
1449 /// Ids after this one.
1450 #[serde(default)]
1451 pub after: Option<String>,
1452 pub limit: u32,
1453}
1454
1455#[derive(Clone, Debug, Default, Serialize, Deserialize)]
1456pub struct IdPage {
1457 pub ids: Vec<String>,
1458 /// Where the next page starts; null on the last.
1459 pub next: Option<String>,
1460}
1461
1462/// `repo_creators` takes [`AllIdsArgs`]: every repository that is not a
1463/// fork, with the account that created it, a page at a time, for identity
1464/// giving creators the Admin role. Returns [`CreatorPage`].
1465#[derive(Clone, Debug, Default, Serialize, Deserialize)]
1466pub struct CreatorPage {
1467 pub repos: Vec<RepoCreator>,
1468 /// Where the next page starts; null on the last.
1469 pub next: Option<String>,
1470}
1471
1472#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1473pub struct RepoCreator {
1474 pub id: String,
1475 /// Its workspace's slug.
1476 pub namespace: String,
1477 pub name: String,
1478 /// The account that created it.
1479 pub owner_id: String,
1480}
1481
1482/// `visibility`: which of these repositories (`namespace/name`) are
1483/// private, for billing, which pays for work on public ones from g1t's
1484/// open-source pool. A pull request's working copy answers as the
1485/// repository it is a copy of. Unknown paths are left out. Returns
1486/// `Vec<RepoVisibility>`.
1487#[derive(Debug, Default, Serialize, Deserialize)]
1488pub struct VisibilityArgs {
1489 pub paths: Vec<String>,
1490}
1491
1492#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1493pub struct RepoVisibility {
1494 pub path: String,
1495 pub is_private: bool,
1496}
1497
1498/// `git_operations`: how many git operations (clones, fetches and pushes
1499/// through g1t's git endpoints) each workspace's repositories had in a
1500/// month, for billing's git meter. Cloudflare Artifacts charges per
1501/// operation from 2026-10-14. Pushes from agents' sandboxes go to the
1502/// store directly and are not counted here. Returns
1503/// `Vec<WorkspaceGitOperations>`.
1504#[derive(Debug, Serialize, Deserialize)]
1505pub struct GitOperationsArgs {
1506 /// YYYY-MM.
1507 pub month: String,
1508 /// Count only from this hour on, `YYYY-MM-DDTHH`, such as the day the
1509 /// provider starts charging.
1510 #[serde(default)]
1511 pub since: Option<String>,
1512 /// One workspace only; every workspace with any when absent.
1513 #[serde(default)]
1514 pub namespace: Option<String>,
1515}
1516
1517#[derive(Clone, Debug, Serialize, Deserialize)]
1518pub struct WorkspaceGitOperations {
1519 pub namespace: String,
1520 pub operations: u64,
1521}
1522
1523/// `storage`: what each workspace's private repositories hold, as far as
1524/// g1t can measure it, for billing's daily storage meter. Returns
1525/// `Vec<WorkspaceStorage>`.
1526///
1527/// The git store does not report a repository's size. What is counted is
1528/// the bytes of every pack pushed through g1t's git endpoints to the
1529/// repository or to its pull requests' working copies. Pushes made from
1530/// agents' sandboxes, which go to the store directly, and imports are not
1531/// counted, so it is a lower bound on what is stored.
1532#[derive(Debug, Default, Serialize, Deserialize)]
1533pub struct StorageArgs {}
1534
1535#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1536pub struct WorkspaceStorage {
1537 pub namespace: String,
1538 pub private_bytes: i64,
1539 pub public_bytes: i64,
1540}
1541
1542/// `transfer`: moves a repository to another workspace, keeping its name,
1543/// its id and everything kept under it. The actor must own both
1544/// workspaces. The old path keeps working as a redirect (see
1545/// `resolve_path`) until a repository is made there. Publishes
1546/// `repo.transferred`. Returns `Outcome<Repo>`, the repository at its new
1547/// path.
1548#[derive(Debug, Serialize, Deserialize)]
1549#[serde(rename_all = "camelCase")]
1550pub struct TransferArgs {
1551 pub actor: User,
1552 pub path: RepoPath,
1553 /// The destination workspace's slug.
1554 pub to: String,
1555 /// Where the request came in, for the audit log; g1t.sh when absent.
1556 #[serde(default)]
1557 pub surface: Option<crate::audit::Surface>,
1558}
1559
1560/// `resolve_path`: where a repository that was transferred away from
1561/// `path` is now, while nothing else is there. Returns `Option<RepoPath>`:
1562/// null when `path` is a repository, or never was one that moved. Callers
1563/// check the viewer may see the repository at its new path, as for any
1564/// other.
1565#[derive(Debug, Serialize, Deserialize)]
1566pub struct ResolvePathArgs {
1567 pub path: RepoPath,
1568}
1569
1570/// `namespace_count`: how many repositories (not pull request working
1571/// copies) a workspace holds, private or not, for deciding whether it can
1572/// be deleted. Returns `u32`.
1573#[derive(Debug, Serialize, Deserialize)]
1574pub struct NamespaceCountArgs {
1575 pub namespace: String,
1576}
1577
1578/// `delete`: deletes a repository. Owners of its workspace only, who type
1579/// its full name (`namespace/name`) as `confirm`. It is hidden at once,
1580/// git refuses it, and nothing runs for it; it can be restored for
1581/// [`RESTORE_DAYS`] days, then it is purged, its git data with it. Its
1582/// name stays taken until then, or until it is purged sooner from the
1583/// workspace's Recently deleted list. Publishes `repo.deleted`. Returns
1584/// `Outcome<DeletedRepo>`.
1585#[derive(Debug, Serialize, Deserialize)]
1586#[serde(rename_all = "camelCase")]
1587pub struct DeleteArgs {
1588 pub actor: User,
1589 pub path: RepoPath,
1590 #[serde(default)]
1591 pub confirm: String,
1592 #[serde(default)]
1593 pub surface: Option<crate::audit::Surface>,
1594}
1595
1596/// `deleted`: a workspace's recently deleted repositories, newest first.
1597/// Owners only; empty for anyone else. Returns `Vec<DeletedRepo>`.
1598#[derive(Debug, Serialize, Deserialize)]
1599pub struct DeletedArgs {
1600 pub viewer: Viewer,
1601 pub namespace: String,
1602}
1603
1604/// `restore` and `purge`: a deleted repository, by the path it had.
1605/// `restore` brings it back as it was, at that path (`repo.restored`).
1606/// `purge` removes it for good now, its git data with it, and frees its
1607/// name (`repo.purged`); it takes the full name typed as `confirm`.
1608/// Owners only. Return `Outcome<Repo>` and `Outcome<bool>`.
1609#[derive(Debug, Serialize, Deserialize)]
1610#[serde(rename_all = "camelCase")]
1611pub struct DeletedRepoArgs {
1612 pub actor: User,
1613 pub path: RepoPath,
1614 #[serde(default)]
1615 pub confirm: Option<String>,
1616 #[serde(default)]
1617 pub surface: Option<crate::audit::Surface>,
1618}
1619
1620/// `purge_due`: purges deleted repositories whose time has passed, at
1621/// most `limit` (25 when absent). The service's own schedule runs it.
1622/// Returns `u32`, how many were purged.
1623#[derive(Debug, Default, Serialize, Deserialize)]
1624pub struct PurgeDueArgs {
1625 #[serde(default)]
1626 pub limit: Option<u32>,
1627}
1628
1629/// `rename`: gives a repository a new name in its workspace, keeping its
1630/// id, its git data and everything kept under it. Owners only. The old
1631/// path keeps redirecting, as after a transfer, until a repository is made
1632/// there. Publishes `repo.renamed`. Returns `Outcome<Repo>`.
1633#[derive(Debug, Serialize, Deserialize)]
1634#[serde(rename_all = "camelCase")]
1635pub struct RenameArgs {
1636 pub actor: User,
1637 pub path: RepoPath,
1638 pub name: String,
1639 #[serde(default)]
1640 pub surface: Option<crate::audit::Surface>,
1641}
1642
1643/// `archive`: makes a repository read-only (`archived: true`), or writable
1644/// again. Owners only. While archived, pushes and merges are refused,
1645/// issues and pull requests are locked, and agents and workflows do not
1646/// run; deployments keep serving. Publishes `repo.archived` or
1647/// `repo.unarchived`. Returns `Outcome<Repo>`.
1648#[derive(Debug, Serialize, Deserialize)]
1649#[serde(rename_all = "camelCase")]
1650pub struct ArchiveArgs {
1651 pub actor: User,
1652 pub path: RepoPath,
1653 pub archived: bool,
1654 #[serde(default)]
1655 pub surface: Option<crate::audit::Surface>,
1656}
1657
1658/// `set_visibility`: makes a repository public or private. Owners only,
1659/// who type its full name as `confirm`. A free workspace takes a private
1660/// repository only while its private storage has room. Publishes
1661/// `repo.updated` and `repo.visibility_changed`. Returns `Outcome<Repo>`.
1662#[derive(Debug, Serialize, Deserialize)]
1663#[serde(rename_all = "camelCase")]
1664pub struct SetVisibilityArgs {
1665 pub actor: User,
1666 pub path: RepoPath,
1667 pub is_private: bool,
1668 #[serde(default)]
1669 pub confirm: String,
1670 #[serde(default)]
1671 pub surface: Option<crate::audit::Surface>,
1672}
1673
1674/// `set_default_branch`: makes another existing branch the one everything
1675/// lands on. Members of its workspace. Open pull requests then merge into
1676/// it. Publishes `repo.default_branch_changed`. Returns `Outcome<Repo>`.
1677#[derive(Debug, Serialize, Deserialize)]
1678#[serde(rename_all = "camelCase")]
1679pub struct SetDefaultBranchArgs {
1680 pub actor: User,
1681 pub path: RepoPath,
1682 pub branch: String,
1683 #[serde(default)]
1684 pub surface: Option<crate::audit::Surface>,
1685}
1686
1687/// `rename_branch`: renames a branch. Members of its workspace; only an
1688/// owner renames the default branch, which stays the default. Pull
1689/// requests from it follow, and web addresses naming the old branch
1690/// redirect until a branch of that name is made again. Publishes
1691/// `branch.renamed` (and `repo.default_branch_changed` for the default).
1692/// Returns `Outcome<Repo>`.
1693#[derive(Debug, Serialize, Deserialize)]
1694#[serde(rename_all = "camelCase")]
1695pub struct RenameBranchArgs {
1696 pub actor: User,
1697 pub path: RepoPath,
1698 pub from: String,
1699 pub to: String,
1700 #[serde(default)]
1701 pub surface: Option<crate::audit::Surface>,
1702}
1703
1704/// `create_branch`: makes a branch named `name` at `from`: another
1705/// branch, a tag or a commit (the default branch's head when empty). The
1706/// actor needs to be able to push, and the rules for creating the branch
1707/// hold. Restoring a deleted branch is making it again at the commit
1708/// `remove_branch` answered with. Publishes `git.push`. Returns
1709/// `Outcome<Branch>`.
1710#[derive(Debug, Serialize, Deserialize)]
1711#[serde(rename_all = "camelCase")]
1712pub struct CreateBranchArgs {
1713 pub actor: User,
1714 pub path: RepoPath,
1715 pub name: String,
1716 #[serde(default)]
1717 pub from: String,
1718 #[serde(default)]
1719 pub surface: Option<crate::audit::Surface>,
1720}
1721
1722/// `remove_branch`: deletes a branch for someone who can push, never the
1723/// default branch; the rules for deleting it hold. `head`, when given, is
1724/// the commit it must still point to: a branch that moved since is left
1725/// alone. Publishes `git.deleted`. Returns `Outcome<Branch>`: the branch
1726/// as it was, so it can be made again at the same commit with
1727/// `create_branch`. (`delete_branch` is the services' own, with no actor.)
1728#[derive(Debug, Serialize, Deserialize)]
1729#[serde(rename_all = "camelCase")]
1730pub struct RemoveBranchArgs {
1731 pub actor: User,
1732 pub path: RepoPath,
1733 pub branch: String,
1734 #[serde(default, skip_serializing_if = "Option::is_none")]
1735 pub head: Option<String>,
1736 #[serde(default)]
1737 pub surface: Option<crate::audit::Surface>,
1738}
1739
1740/// `resolve_branch`: what a branch renamed away from `branch` is called
1741/// now, for web addresses that name the old one; null when `branch` was
1742/// never renamed or exists again. Returns `Option<String>`.
1743#[derive(Debug, Serialize, Deserialize)]
1744#[serde(rename_all = "camelCase")]
1745pub struct ResolveBranchArgs {
1746 pub repo_id: String,
1747 pub branch: String,
1748}
1749
1750/// `status_by_id`: whether a repository is archived or deleted, for g1t's
1751/// own services deciding whether to act on it. An unknown id answers as deleted. Returns `RepoStatus`.
1752#[derive(Debug, Serialize, Deserialize)]
1753pub struct StatusByIdArgs {
1754 pub id: String,
1755}
1756
1757#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
1758pub struct RepoStatus {
1759 pub archived: bool,
1760 pub deleted: bool,
1761}
1762
1763impl RepoStatus {
1764 /// Whether work may start on it: neither archived nor deleted.
1765 pub fn active(&self) -> bool {
1766 !self.archived && !self.deleted
1767 }
1768}
1769
1770/// What a person is told when something would change an archived
1771/// repository.
1772pub fn archived_message(namespace: &str, name: &str) -> String {
1773 format!(
1774 "{namespace}/{name} is archived, so it is read-only. An owner can unarchive it in its settings."
1775 )
1776}
1777
1778/// The path a repository was transferred from, and when, as `transfer`
1779/// keeps it so old addresses redirect.
1780#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1781#[serde(rename_all = "camelCase")]
1782pub struct RepoRedirect {
1783 pub from: RepoPath,
1784 pub repo_id: String,
1785 /// RFC 3339.
1786 pub created_at: String,
1787}
1788
1789#[cfg(test)]
1790mod topic_tests {
1791 use super::*;
1792
1793 fn topics(list: &[&str]) -> Result<Vec<String>, String> {
1794 clean_topics(&list.iter().map(|t| t.to_string()).collect::<Vec<_>>())
1795 }
1796
1797 #[test]
1798 fn topics_are_tidied() {
1799 assert_eq!(topics(&["Rust", " web_server ", "rust", ""]).unwrap(), vec!["rust", "web-server"]);
1800 }
1801
1802 #[test]
1803 fn websites_are_tidied() {
1804 assert_eq!(clean_website(" example.com ").unwrap().as_deref(), Some("https://example.com"));
1805 assert_eq!(clean_website("http://a.io/x").unwrap().as_deref(), Some("http://a.io/x"));
1806 assert_eq!(clean_website("").unwrap(), None);
1807 assert!(clean_website("ftp://a.io").is_err());
1808 assert!(clean_website("localhost").is_err());
1809 assert!(clean_website("https://a b.io").is_err());
1810 }
1811
1812 #[test]
1813 fn branch_names_follow_git() {
1814 for good in ["main", "trunk", "release/1.2", "feat-x_y"] {
1815 assert!(is_valid_branch_name(good), "{good}");
1816 }
1817 for bad in ["", "-x", "a..b", "a b", "x.lock", "a/", ".hidden", "a/.b", "g1t-queue", "a~1", "a:b", "@"] {
1818 assert!(!is_valid_branch_name(bad), "{bad}");
1819 }
1820 }
1821
1822 #[test]
1823 fn odd_topics_are_refused() {
1824 assert!(topics(&["c++"]).is_err());
1825 assert!(topics(&["-lead"]).is_err());
1826 assert!(topics(&[&"a".repeat(36)]).is_err());
1827 let many: Vec<String> = (0..21).map(|i| format!("t{i}")).collect();
1828 assert!(clean_topics(&many).is_err());
1829 }
1830}
1831
1832#[cfg(test)]
1833mod tests {
1834 use super::*;
1835
1836 #[test]
1837 fn a_pull_branch_update_reads_as_the_web_expects() {
1838 let update = PullBranchUpdate::NeedsAgent {
1839 reason: NeedsAgentReason::Overlap,
1840 detail: "both".into(),
1841 paths: vec!["a.rs".into()],
1842 };
1843 assert_eq!(
1844 serde_json::to_value(&update).unwrap(),
1845 serde_json::json!({ "outcome": "needs_agent", "reason": "overlap", "detail": "both", "paths": ["a.rs"] })
1846 );
1847 let done = PullBranchUpdate::UpToDate { commit: "c".into() };
1848 assert_eq!(serde_json::to_value(&done).unwrap(), serde_json::json!({ "outcome": "up_to_date", "commit": "c" }));
1849 }
1850}