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