flagon-io/g1t

public

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

g1t/crates/contracts/src/repos.rs

714 lines23,427 bytesCodeBlame

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

Rust repos service with shipping; pull requests kept in the model1//! 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,
Workspaces own repositories14 /// The slug of the workspace that owns it: the first URL segment.
Rust repos service with shipping; pull requests kept in the model15 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,
Issues and pull requests replace intents and attempts21 /// Set when this repo is a pull request's working copy of another repo.
Rust repos service with shipping; pull requests kept in the model22 pub fork_of: Option<String>,
Agents as a team: lifecycle, merge queue, billing and a new shell23 /// Whether the default branch is protected: it changes only by merging
24 /// a pull request, and pushes to it are refused.
25 #[serde(default)]
26 pub protected: bool,
RFC 3339 timestamps in identity and repos27 /// RFC 3339.
28 pub created_at: String,
Search across all of g1t, Explore, and a command palette29 /// Words that say what it is about, for search and Explore: lowercase
30 /// letters, digits and hyphens. See [`clean_topics`].
31 #[serde(default)]
32 pub topics: Vec<String>,
33}
34
35/// The most topics a repository has.
36pub const MAX_TOPICS: usize = 20;
37/// The longest topic.
38pub const MAX_TOPIC_CHARS: usize = 35;
39
40/// Topics as they are kept: lowercase, spaces and underscores made
41/// hyphens, each of letters, digits and hyphens, starting with a letter or
42/// digit, without repeats, at most [`MAX_TOPICS`]. Anything else is the
43/// first topic that could not be read.
44pub fn clean_topics(topics: &[String]) -> Result<Vec<String>, String> {
45 let mut kept: Vec<String> = Vec::new();
46 for topic in topics {
47 let topic: String = topic
48 .trim()
49 .to_lowercase()
50 .chars()
51 .map(|c| if c == ' ' || c == '_' { '-' } else { c })
52 .collect();
53 if topic.is_empty() {
54 continue;
55 }
56 let valid = topic.chars().count() <= MAX_TOPIC_CHARS
57 && topic.chars().all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '-')
58 && topic.chars().next().is_some_and(|c| c.is_ascii_alphanumeric());
59 if !valid {
60 return Err(format!(
61 "\"{topic}\" is not a topic: use letters, digits and hyphens, at most {MAX_TOPIC_CHARS} characters."
62 ));
63 }
64 if !kept.contains(&topic) {
65 kept.push(topic);
66 }
67 }
68 if kept.len() > MAX_TOPICS {
69 return Err(format!("A repository has at most {MAX_TOPICS} topics."));
70 }
71 Ok(kept)
Rust repos service with shipping; pull requests kept in the model72}
73
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API74#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
Rust repos service with shipping; pull requests kept in the model75pub struct RepoPath {
76 pub namespace: String,
77 pub name: String,
78}
79
80#[derive(Clone, Debug, Serialize, Deserialize)]
81pub struct Signature {
82 pub name: String,
83 pub email: String,
84}
85
86#[derive(Clone, Debug, Serialize, Deserialize)]
87#[serde(rename_all = "camelCase")]
88pub struct Commit {
89 pub hash: String,
90 pub tree_hash: String,
91 pub message: String,
92 pub author: Signature,
93 pub parents: Vec<String>,
RFC 3339 timestamps in identity and repos94 /// RFC 3339.
95 pub authored_at: String,
Rust repos service with shipping; pull requests kept in the model96}
97
98#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
99#[serde(rename_all = "lowercase")]
100pub enum EntryKind {
101 Tree,
102 Blob,
103 Symlink,
104 Gitlink,
105 Exec,
106}
107
108#[derive(Clone, Debug, Serialize, Deserialize)]
109pub struct TreeEntry {
110 pub name: String,
111 pub hash: String,
112 pub kind: EntryKind,
113}
114
115#[derive(Clone, Debug, Serialize, Deserialize)]
116pub struct Readme {
117 pub name: String,
118 /// Null when the file is binary or too large to show.
119 pub text: Option<String>,
120}
121
122#[derive(Clone, Debug, Serialize, Deserialize)]
123pub struct TreeView {
124 pub repo: Repo,
125 #[serde(rename = "ref")]
126 pub git_ref: String,
127 pub path: String,
128 /// Null when the repo has no commits yet.
129 pub head: Option<Commit>,
130 pub entries: Vec<TreeEntry>,
131 pub readme: Option<Readme>,
132}
133
134#[derive(Clone, Debug, Serialize, Deserialize)]
135pub struct BlobView {
136 pub repo: Repo,
137 #[serde(rename = "ref")]
138 pub git_ref: String,
139 pub path: String,
140 pub size: u64,
141 /// Null when the file is binary or too large to show.
142 pub text: Option<String>,
143}
144
145/// A git remote and a short-lived credential for it.
146#[derive(Clone, Debug, Serialize, Deserialize)]
147pub struct GitAccess {
148 pub remote: String,
149 pub token: String,
150}
151
152#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
153pub enum GitService {
154 #[serde(rename = "git-upload-pack")]
155 UploadPack,
156 #[serde(rename = "git-receive-pack")]
157 ReceivePack,
158}
159
Issues and pull requests replace intents and attempts160/// The result of landing a pull request.
Rust repos service with shipping; pull requests kept in the model161#[derive(Clone, Debug, Serialize, Deserialize)]
162pub struct Landed {
Diffs on attempts; hosted agent presented as the g1t agent163 /// The commit the branch points to now.
Rust repos service with shipping; pull requests kept in the model164 pub commit: String,
Diffs on attempts; hosted agent presented as the g1t agent165 /// The commit it pointed to before, if it had one. Comparing against
Issues and pull requests replace intents and attempts166 /// this shows what the pull request changed.
Diffs on attempts; hosted agent presented as the g1t agent167 pub previous: Option<String>,
Rust repos service with shipping; pull requests kept in the model168}
169
170/// `get`. Returns `Outcome<Repo>`.
171#[derive(Debug, Serialize, Deserialize)]
172pub struct GetArgs {
173 pub path: RepoPath,
174 pub viewer: Viewer,
175}
176
Automations: rules in .g1t/automations that act when something happens177/// `path_by_id`: where a repository is, whoever may see it. For g1t's own
178/// services, which hold a repository's id from an event and act for its
179/// workspace; nothing outside reaches it. Returns `Option<RepoPath>`, null
180/// for a fork or an unknown id.
181#[derive(Debug, Serialize, Deserialize)]
182pub struct PathByIdArgs {
183 pub id: String,
184}
185
Rust repos service with shipping; pull requests kept in the model186/// `get_by_id`. Returns `Outcome<Repo>`.
187#[derive(Debug, Serialize, Deserialize)]
188pub struct GetByIdArgs {
189 pub id: String,
190 pub viewer: Viewer,
191}
192
193/// `list`: repos the viewer may see, newest first. Returns `Vec<Repo>`.
194#[derive(Debug, Default, Serialize, Deserialize)]
Workspaces own repositories195#[serde(rename_all = "camelCase")]
Rust repos service with shipping; pull requests kept in the model196pub struct ListArgs {
197 pub viewer: Viewer,
198 #[serde(default)]
199 pub query: Option<String>,
Workspaces own repositories200 /// Only repos in this workspace.
Rust repos service with shipping; pull requests kept in the model201 #[serde(default)]
202 pub namespace: Option<String>,
Workspaces own repositories203 /// Only repos in workspaces the viewer belongs to.
204 #[serde(default)]
205 pub member_only: bool,
Rust repos service with shipping; pull requests kept in the model206}
207
208/// `create`. Returns `Outcome<Repo>`.
209#[derive(Debug, Serialize, Deserialize)]
210#[serde(rename_all = "camelCase")]
211pub struct CreateArgs {
Workspaces own repositories212 /// Who is creating it; they must belong to the workspace.
Rust repos service with shipping; pull requests kept in the model213 pub owner: User,
Workspaces own repositories214 /// The workspace it is created in.
215 pub namespace: String,
Rust repos service with shipping; pull requests kept in the model216 pub name: String,
217 #[serde(default)]
218 pub description: Option<String>,
219 #[serde(default)]
220 pub is_private: bool,
Agents as a team: lifecycle, merge queue, billing and a new shell221 /// The https address of a public git repository to copy the default
222 /// branch of, such as `https://github.com/owner/repo`.
223 #[serde(default)]
224 pub import_url: Option<String>,
Rust repos service with shipping; pull requests kept in the model225}
226
Agents as a team: lifecycle, merge queue, billing and a new shell227/// `update`: changes whichever of a repository's details are given.
228/// Members of its workspace only. Returns `Outcome<Repo>`.
229#[derive(Debug, Serialize, Deserialize)]
230#[serde(rename_all = "camelCase")]
231pub struct UpdateArgs {
232 pub actor: User,
233 pub path: RepoPath,
234 /// An empty description clears it.
235 #[serde(default)]
236 pub description: Option<String>,
237 #[serde(default)]
238 pub is_private: Option<bool>,
239 #[serde(default)]
240 pub protected: Option<bool>,
Search across all of g1t, Explore, and a command palette241 /// Replaces its topics; an empty list clears them.
242 #[serde(default)]
243 pub topics: Option<Vec<String>>,
Agents as a team: lifecycle, merge queue, billing and a new shell244}
245
Rust repos service with shipping; pull requests kept in the model246/// `tree`. Returns `Outcome<TreeView>`.
247#[derive(Debug, Serialize, Deserialize)]
248#[serde(rename_all = "camelCase")]
249pub struct TreeArgs {
250 pub path: RepoPath,
251 pub viewer: Viewer,
252 /// The default branch when absent.
253 #[serde(default, rename = "ref")]
254 pub git_ref: Option<String>,
255 #[serde(default)]
256 pub tree_path: String,
257}
258
259/// `blob`. Returns `Outcome<BlobView>`.
260#[derive(Debug, Serialize, Deserialize)]
261#[serde(rename_all = "camelCase")]
262pub struct BlobArgs {
263 pub path: RepoPath,
264 pub viewer: Viewer,
265 #[serde(rename = "ref")]
266 pub git_ref: String,
267 pub file_path: String,
268}
269
270/// `log`. Returns `Outcome<Vec<Commit>>`.
271#[derive(Debug, Serialize, Deserialize)]
272pub struct LogArgs {
273 pub path: RepoPath,
274 pub viewer: Viewer,
275 #[serde(default, rename = "ref")]
276 pub git_ref: Option<String>,
277 pub limit: u32,
278}
279
Issues and pull requests replace intents and attempts280/// `fork_for_pull`: a copy-on-write copy of the source repo, hidden from
281/// listings, for one pull request to be made in. Returns `Outcome<Repo>`.
Rust repos service with shipping; pull requests kept in the model282#[derive(Debug, Serialize, Deserialize)]
283#[serde(rename_all = "camelCase")]
284pub struct ForkArgs {
285 pub source_id: String,
Issues and pull requests replace intents and attempts286 pub pull_id: String,
Rust repos service with shipping; pull requests kept in the model287 pub actor: User,
288}
289
290/// `git_access`: authorizes a git operation and says where to send it.
291/// Pushing to a repo that does not exist creates it in the pusher's own
292/// namespace. Returns `Outcome<GitAccess>`.
293#[derive(Debug, Serialize, Deserialize)]
294pub struct GitAccessArgs {
295 pub path: RepoPath,
296 pub viewer: Viewer,
297 pub service: GitService,
298}
299
Pull requests from branches300/// `land`: moves a repository's default branch to the head of a pull
301/// request's source. Refused with `conflict` when the source is behind,
302/// since that would discard commits. Returns `Outcome<Landed>`.
Rust repos service with shipping; pull requests kept in the model303#[derive(Debug, Serialize, Deserialize)]
304#[serde(rename_all = "camelCase")]
305pub struct LandArgs {
Pull requests from branches306 /// The repository holding the commits: a pull request's fork, or the
307 /// target itself when landing one of its own branches.
308 pub source_id: String,
309 /// The branch of the source to land. Required when the source is the
310 /// target; a fork lands its default branch.
311 #[serde(default)]
312 pub branch: Option<String>,
Rust repos service with shipping; pull requests kept in the model313 pub actor: User,
314}
Diffs on attempts; hosted agent presented as the g1t agent315
316#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
317#[serde(rename_all = "lowercase")]
318pub enum FileStatus {
319 Added,
320 Modified,
321 Deleted,
322}
323
324#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
325#[serde(rename_all = "lowercase")]
326pub enum LineKind {
327 /// Unchanged, shown for context.
328 Context,
329 Add,
330 Delete,
331}
332
333#[derive(Clone, Debug, Serialize, Deserialize)]
334pub struct DiffLine {
335 pub kind: LineKind,
336 /// Line number in the old file; absent for added lines.
337 pub old: Option<u32>,
338 /// Line number in the new file; absent for deleted lines.
339 pub new: Option<u32>,
340 pub text: String,
341}
342
343/// A run of changed lines with their surrounding context.
344#[derive(Clone, Debug, Serialize, Deserialize)]
345pub struct Hunk {
346 pub lines: Vec<DiffLine>,
347}
348
349#[derive(Clone, Debug, Serialize, Deserialize)]
350pub struct FileDiff {
351 pub path: String,
352 pub status: FileStatus,
353 pub additions: u32,
354 pub deletions: u32,
355 /// True when the file is binary or too large, so no lines are shown.
356 pub binary: bool,
357 pub hunks: Vec<Hunk>,
358}
359
360/// What changed between two commits.
361#[derive(Clone, Debug, Serialize, Deserialize)]
362pub struct Comparison {
363 /// Null when the head has no earlier commit to compare against.
364 pub base: Option<String>,
365 pub head: String,
366 pub files: Vec<FileDiff>,
367 /// True when the change was too large to return in full.
368 pub truncated: bool,
369}
370
Pull requests from branches371/// `compare`: what `head` changes relative to `base`.
Diffs on attempts; hosted agent presented as the g1t agent372///
Pull requests from branches373/// `head` is a branch or a commit, and defaults to the default branch.
374/// With no `base`, a fork is compared against the point where it and the
375/// repository it came from last agreed; a branch against the point where it
376/// left the default branch; and the default branch against its head's
377/// parent. Returns `Outcome<Comparison>`.
Diffs on attempts; hosted agent presented as the g1t agent378#[derive(Debug, Serialize, Deserialize)]
379#[serde(rename_all = "camelCase")]
380pub struct CompareArgs {
381 pub repo_id: String,
382 pub viewer: Viewer,
383 #[serde(default)]
384 pub base: Option<String>,
Pull requests from branches385 #[serde(default)]
386 pub head: Option<String>,
387}
388
Agents as a team: lifecycle, merge queue, billing and a new shell389/// Lines `start` to `end` of a file, inclusive and counted from 1, last
390/// changed by `commit`.
391#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
392pub struct BlameRange {
393 pub start: u32,
394 pub end: u32,
395 pub commit: String,
396}
397
398/// Who last changed each line of a file.
399#[derive(Clone, Debug, Serialize, Deserialize)]
400pub struct Blame {
401 /// The commit the file was read at.
402 pub head: String,
403 /// Every line, in order, in runs that share a commit.
404 pub ranges: Vec<BlameRange>,
405 /// The commits the ranges name, each once.
406 pub commits: Vec<Commit>,
407 /// True when the history was too long to read in full, so the oldest
408 /// lines are given to the oldest commit read.
409 pub partial: bool,
410}
411
412/// `blame`: who last changed each line of `path` as of `ref` (the default
413/// branch if absent). Returns `Outcome<Blame>`; not found when the file is
414/// missing or is not text.
415#[derive(Debug, Serialize, Deserialize)]
416pub struct BlameArgs {
417 pub path: RepoPath,
418 pub viewer: Viewer,
419 #[serde(default, rename = "ref")]
420 pub git_ref: Option<String>,
421 #[serde(rename = "filePath")]
422 pub file_path: String,
423}
424
Pull requests from branches425/// A branch and the commit it points to.
426#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
427pub struct Branch {
428 pub name: String,
429 pub hash: String,
430}
431
432/// `branches`: the repository's branches, default branch first.
433/// Returns `Outcome<Vec<Branch>>`.
434#[derive(Debug, Serialize, Deserialize)]
435pub struct BranchesArgs {
436 pub path: RepoPath,
437 pub viewer: Viewer,
438}
439
Agents as a team: lifecycle, merge queue, billing and a new shell440/// `behind`: whether the default branch of the repository a pull request
441/// would merge into has commits its source does not. For services that
442/// have already decided the caller may see the pull request; it reveals
443/// one bit. Returns `bool`.
444#[derive(Debug, Serialize, Deserialize)]
445#[serde(rename_all = "camelCase")]
446pub struct BehindArgs {
447 /// The pull request's fork, or the repository itself for a branch.
448 pub source_id: String,
449 /// The branch of the source. A fork is compared on its default branch.
450 #[serde(default)]
451 pub branch: Option<String>,
452}
453
Agents and memory, checks and conflicts, profiles, slug renames, custom domains454/// `divergence`: how a pull request's source and the default branch it
455/// would merge into have moved apart since they last agreed: the files each
456/// side changed. Takes `BehindArgs`. For services that have already decided
457/// the caller may see the pull request; it reveals paths, not contents.
458/// Returns `Option<Divergence>`, null when either side has no commits.
459#[derive(Clone, Debug, Default, Serialize, Deserialize)]
460#[serde(rename_all = "camelCase")]
461pub struct Divergence {
462 /// The source's commit.
463 pub head: String,
464 /// The default branch's commit.
465 pub base: String,
466 /// Where they last agreed, if that could be found.
467 pub merge_base: Option<String>,
468 /// Whether the default branch has commits the source does not.
469 pub behind: bool,
470 /// The files the source changed since the merge base.
471 pub ours: Vec<String>,
472 /// The files the default branch changed since the merge base. Empty
473 /// when it is not behind.
474 pub theirs: Vec<String>,
475 /// Whether either list was cut short.
476 pub truncated: bool,
477}
478
Catching up with main takes seconds when the two sides touched different files479/// `update_pull_branch`: brings a pull request's source up to date with the
480/// default branch it would merge into, without a sandbox, when that can be
481/// done safely: merges the default branch's head into the source's head and
482/// pushes the merge commit to the source's branch, as `actor`, only if the
483/// branch has not moved meanwhile. It applies only when the two sides
484/// changed different files since they last agreed; otherwise the answer is
485/// [`PullBranchUpdate::NeedsAgent`] and nothing is pushed. Refused unless
486/// `actor` may push to the source. Returns `Outcome<PullBranchUpdate>`.
487#[derive(Debug, Serialize, Deserialize)]
488#[serde(rename_all = "camelCase")]
489pub struct UpdatePullBranchArgs {
490 /// The pull request's fork, or the repository itself for a branch.
491 pub source_id: String,
492 /// The branch of the source. A fork is updated on its default branch.
493 #[serde(default)]
494 pub branch: Option<String>,
495 /// The pull request's number, to name it in the merge commit's message
496 /// when its branch has the same name as the default branch.
497 pub number: u32,
498 /// Who asked: the merge commit's author and committer, and the pusher.
499 pub actor: User,
500}
501
502/// Why an update has to be left to a sandbox.
503#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
504#[serde(rename_all = "snake_case")]
505pub enum NeedsAgentReason {
506 /// Both sides changed some of the same files; merging them needs a
507 /// real merge, which may or may not conflict.
508 Overlap,
509 /// Merging is known to conflict.
510 Conflicting,
511 /// The update could not be worked out here, such as when the two sides
512 /// share no history g1t can see, or the change is too large to list.
513 Unsupported,
514}
515
516/// What came of `update_pull_branch` (or the work service's `catch_up_pull`).
517#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
518#[serde(tag = "outcome", rename_all = "snake_case")]
519pub enum PullBranchUpdate {
520 /// The merge commit was pushed: the branch moved from `previous` to
521 /// `commit`.
522 Updated { commit: String, previous: String },
523 /// The source already holds the default branch's head.
524 UpToDate { commit: String },
525 /// Nothing was pushed; a sandbox has to merge it. `paths` are the
526 /// files both sides changed, or that conflict, when known.
527 NeedsAgent {
528 reason: NeedsAgentReason,
529 detail: String,
530 paths: Vec<String>,
531 },
532}
533
Pull requests from branches534/// `head`: the commit a branch points to, or null. For services reacting
535/// to a push, which have no viewer; it reveals nothing but a commit hash.
536/// Returns `Option<String>`.
537#[derive(Debug, Serialize, Deserialize)]
538#[serde(rename_all = "camelCase")]
539pub struct HeadArgs {
540 pub repo_id: String,
Workflows run when an agent's pull request is marked ready541 /// Empty for the repository's default branch.
Pull requests from branches542 pub branch: String,
Diffs on attempts; hosted agent presented as the g1t agent543}
Merge queue: tested states are deleted once their entry leaves544
545/// Where g1t keeps branches of its own in a repository, such as the merge
546/// queue's tested states. Only these can be removed with `delete_branch`.
547pub const G1T_BRANCH_PREFIX: &str = "g1t-";
548
549/// `delete_branch`: removes a branch g1t made for itself once it is done
550/// with it, never one of people's: the name must start with
551/// [`G1T_BRANCH_PREFIX`]. For services, which have no viewer. Returns
552/// `Outcome<bool>`: whether there was such a branch.
553#[derive(Debug, Serialize, Deserialize)]
554#[serde(rename_all = "camelCase")]
555pub struct DeleteBranchArgs {
556 pub repo_id: String,
557 pub branch: String,
558}
Agents and memory, checks and conflicts, profiles, slug renames, custom domains559
560/// `readable`: of these repository ids, the repositories the viewer may
561/// read, as `get_by_id` decides; forks and unknown ids are left out. For
562/// services that hold ids and must show only what the viewer could open.
563/// At most [`MAX_READABLE`] ids are looked at. Returns `Vec<Repo>`.
564#[derive(Debug, Serialize, Deserialize)]
565pub struct ReadableArgs {
566 pub ids: Vec<String>,
567 pub viewer: Viewer,
568}
569
570/// The most ids one `readable` call looks at.
571pub const MAX_READABLE: usize = 500;
572
573/// `public_namespaces`: the workspaces in which this account made a public
574/// repository, and so a public project, which anyone can see on its page.
575/// Returns `Vec<String>` of workspace slugs.
576#[derive(Debug, Serialize, Deserialize)]
577#[serde(rename_all = "camelCase")]
578pub struct PublicNamespacesArgs {
579 pub owner_id: String,
580}
Search across all of g1t, Explore, and a command palette581
582/// One file on a branch, or one a change touched: its path and blob.
583#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
584pub struct FileEntry {
585 pub path: String,
586 /// The blob it holds now; null when the change deleted it.
587 pub hash: Option<String>,
588}
589
590/// Files, and whether there were more than were listed.
591#[derive(Clone, Debug, Default, Serialize, Deserialize)]
592#[serde(rename_all = "camelCase")]
593pub struct FileList {
594 /// The commit the files were read at; null for an empty repository.
595 pub commit: Option<String>,
596 pub files: Vec<FileEntry>,
597 pub truncated: bool,
598}
599
600/// `list_files`: every file on a branch (the default branch when absent),
601/// path order by level, never descending into a directory named in
602/// `skip_dirs`. For services that index a repository; no viewer, since it
603/// is only reached by g1t's own services. Returns `FileList`.
604#[derive(Debug, Default, Serialize, Deserialize)]
605#[serde(rename_all = "camelCase")]
606pub struct ListFilesArgs {
607 pub repo_id: String,
608 #[serde(default, rename = "ref")]
609 pub git_ref: Option<String>,
610 #[serde(default)]
611 pub skip_dirs: Vec<String>,
612 /// At most this many files; capped at [`MAX_LISTED_FILES`].
613 pub limit: u32,
614}
615
616/// `changed_files`: the files that differ between two commits, as
617/// `list_files` reads them. With no `base`, every file at `head`. Returns
618/// `FileList`.
619#[derive(Debug, Default, Serialize, Deserialize)]
620#[serde(rename_all = "camelCase")]
621pub struct ChangedFilesArgs {
622 pub repo_id: String,
623 #[serde(default)]
624 pub base: Option<String>,
625 pub head: String,
626 #[serde(default)]
627 pub skip_dirs: Vec<String>,
628 pub limit: u32,
629}
630
631/// The most files one `list_files` or `changed_files` call lists.
632pub const MAX_LISTED_FILES: u32 = 10_000;
633
634/// `read_blobs`: the text of these blobs of a repository, for services
635/// that index it. A blob larger than `max_bytes`, or binary, comes back
636/// with no text. Returns `Vec<BlobText>`, in the order asked.
637#[derive(Debug, Default, Serialize, Deserialize)]
638#[serde(rename_all = "camelCase")]
639pub struct ReadBlobsArgs {
640 pub repo_id: String,
641 pub hashes: Vec<String>,
642 pub max_bytes: u32,
643}
644
645/// The most blobs one `read_blobs` call reads.
646pub const MAX_READ_BLOBS: usize = 100;
647
648#[derive(Clone, Debug, Serialize, Deserialize)]
649pub struct BlobText {
650 pub hash: String,
651 pub size: u64,
652 /// Null when the blob is missing, binary or larger than asked.
653 pub text: Option<String>,
654}
655
656/// `all_ids`: every repository that is not a fork, by id, a page at a
657/// time, for services that index all of them. Returns `IdPage`.
658#[derive(Debug, Default, Serialize, Deserialize)]
659pub struct AllIdsArgs {
660 /// Ids after this one.
661 #[serde(default)]
662 pub after: Option<String>,
663 pub limit: u32,
664}
665
666#[derive(Clone, Debug, Default, Serialize, Deserialize)]
667pub struct IdPage {
668 pub ids: Vec<String>,
669 /// Where the next page starts; null on the last.
670 pub next: Option<String>,
671}
672
673#[cfg(test)]
674mod topic_tests {
675 use super::*;
676
677 fn topics(list: &[&str]) -> Result<Vec<String>, String> {
678 clean_topics(&list.iter().map(|t| t.to_string()).collect::<Vec<_>>())
679 }
680
681 #[test]
682 fn topics_are_tidied() {
683 assert_eq!(topics(&["Rust", " web_server ", "rust", ""]).unwrap(), vec!["rust", "web-server"]);
684 }
685
686 #[test]
687 fn odd_topics_are_refused() {
688 assert!(topics(&["c++"]).is_err());
689 assert!(topics(&["-lead"]).is_err());
690 assert!(topics(&[&"a".repeat(36)]).is_err());
691 let many: Vec<String> = (0..21).map(|i| format!("t{i}")).collect();
692 assert!(clean_topics(&many).is_err());
693 }
694}
Catching up with main takes seconds when the two sides touched different files695
696#[cfg(test)]
697mod tests {
698 use super::*;
699
700 #[test]
701 fn a_pull_branch_update_reads_as_the_web_expects() {
702 let update = PullBranchUpdate::NeedsAgent {
703 reason: NeedsAgentReason::Overlap,
704 detail: "both".into(),
705 paths: vec!["a.rs".into()],
706 };
707 assert_eq!(
708 serde_json::to_value(&update).unwrap(),
709 serde_json::json!({ "outcome": "needs_agent", "reason": "overlap", "detail": "both", "paths": ["a.rs"] })
710 );
711 let done = PullBranchUpdate::UpToDate { commit: "c".into() };
712 assert_eq!(serde_json::to_value(&done).unwrap(), serde_json::json!({ "outcome": "up_to_date", "commit": "c" }));
713 }
714}