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