Skip to content
503 linesCodeBlameRaw
1import type { Contributors, Languages, License, NewRelease, Release, ReleaseChange, RepoAbout, Stargazer, StarredRepo, Stars } from "./about";
2import type { User, Viewer } from "./identity";
3import type { RepoMirror } from "./mirrors";
4import type { Result } from "./result";
5
6export type Repo = {
7 id: string;
8 /** The slug of the workspace that owns it: the first URL segment. */
9 namespace: string;
10 name: string;
11 description: string | null;
12 isPrivate: boolean;
13 ownerId: string;
14 defaultBranch: string;
15 /** Set when this repo is a pull request's working copy of another repo. */
16 forkOf: string | null;
17 /**
18 * Whether the default branch is protected: it changes only by merging a
19 * pull request, and pushes to it are refused.
20 */
21 protected: boolean;
22 /** RFC 3339. */
23 createdAt: string;
24 /**
25 * Words that say what it is about, for search and Explore: lowercase
26 * letters, digits and hyphens, at most 20.
27 */
28 topics: string[];
29 /** Its home page, an http(s) address. */
30 website?: string | null;
31 /**
32 * RFC 3339: when it was archived. While archived it is read-only:
33 * pushes, merges, agents and workflows are refused, and issues and pull
34 * requests are locked. Null when it is not archived.
35 */
36 archivedAt?: string | null;
37 /**
38 * Set when it mirrors a remote that leads. Unless g1t has taken over,
39 * it is read-only: pushes, merges, issues, pull requests and agents are
40 * refused, and nothing runs. See `./mirrors`.
41 */
42 mirror?: RepoMirror | null;
43};
44
45/** How long a deleted repository can be restored before it is purged. */
46export const RESTORE_DAYS = 30;
47
48/** A deleted repository, as its workspace's Recently deleted list shows it. */
49export type DeletedRepo = {
50 id: string;
51 namespace: string;
52 name: string;
53 description: string | null;
54 isPrivate: boolean;
55 /** RFC 3339. */
56 deletedAt: string;
57 /** The username of who deleted it. */
58 deletedBy: string;
59 /** RFC 3339: when it is purged, unless restored first. */
60 purgeAfter: string;
61};
62
63/**
64 * Whether a repository is archived or deleted, and the remote it mirrors,
65 * for services deciding whether to act on it (`mirrorOffers` in
66 * `./mirrors` says what a mirror takes).
67 */
68export type RepoStatus = { archived: boolean; deleted: boolean; mirror?: RepoMirror | null };
69
70/** The most topics a repository has, and the longest topic. */
71export const MAX_TOPICS = 20;
72export const MAX_TOPIC_CHARS = 35;
73
74export type RepoPath = { namespace: string; name: string };
75
76export type Commit = {
77 hash: string;
78 treeHash: string;
79 message: string;
80 author: { name: string; email: string };
81 parents: string[];
82 /** RFC 3339. */
83 authoredAt: string;
84};
85
86export type TreeEntry = {
87 name: string;
88 hash: string;
89 kind: "tree" | "blob" | "symlink" | "gitlink" | "exec";
90};
91
92export type TreeView = {
93 repo: Repo;
94 ref: string;
95 path: string;
96 /** Null when the repo has no commits yet. */
97 head: Commit | null;
98 entries: TreeEntry[];
99 readme: { name: string; text: string | null } | null;
100};
101
102export type BlobView = {
103 repo: Repo;
104 ref: string;
105 path: string;
106 size: number;
107 /** Null when the file is binary or too large to show. */
108 text: string | null;
109};
110
111/** A git remote and a short-lived credential for it. */
112export type GitAccess = { remote: string; token: string };
113
114export type GitService = "git-upload-pack" | "git-receive-pack";
115
116export type CreateRepoInput = {
117 /** The workspace to create it in; the creator must be a member. */
118 namespace: string;
119 name: string;
120 description?: string | null;
121 isPrivate?: boolean;
122 /**
123 * The https address of a public git repository to copy the default branch
124 * of, such as `https://github.com/owner/repo`.
125 */
126 importUrl?: string;
127};
128
129/**
130 * Where repositories may be kept. `euAvailable`: an EU namespace takes new
131 * repositories, so a workspace may keep its data in the EU.
132 */
133export type StorageOptions = { euAvailable: boolean };
134
135/** Repositories: metadata, contents and git access. */
136export interface ReposApi {
137 get(path: RepoPath, viewer: Viewer): Promise<Result<Repo>>;
138 getById(id: string, viewer: Viewer): Promise<Result<Repo>>;
139 /**
140 * Of these ids (at most 500), the repositories the viewer can read, in
141 * one call. Forks and deleted repositories are left out.
142 */
143 readable(ids: string[], viewer: Viewer): Promise<Repo[]>;
144 /**
145 * The workspaces in which this account made a public repository, and so
146 * a public project anyone can see. By account id.
147 */
148 publicNamespaces(ownerId: string): Promise<string[]>;
149 /** Repos the viewer may see, newest first, optionally matching `query`. */
150 list(
151 viewer: Viewer,
152 options?: {
153 query?: string;
154 /** Only repos in this workspace. */
155 namespace?: string;
156 /** Only repos in workspaces the viewer belongs to. */
157 memberOnly?: boolean;
158 },
159 ): Promise<Repo[]>;
160 create(owner: User, input: CreateRepoInput): Promise<Result<Repo>>;
161 /**
162 * Changes whichever details are given. Members of the repository's
163 * workspace only. An empty description clears it.
164 */
165 update(
166 actor: User,
167 path: RepoPath,
168 changes: {
169 description?: string;
170 isPrivate?: boolean;
171 protected?: boolean;
172 topics?: string[];
173 /** An empty string clears it. */
174 website?: string;
175 },
176 ): Promise<Result<Repo>>;
177 /**
178 * Deletes the repository. Owners only, who type its full name as
179 * `confirm`. It is hidden at once and can be restored for
180 * `RESTORE_DAYS` days, then purged. Publishes `repo.deleted`.
181 */
182 delete(actor: User, path: RepoPath, confirm: string): Promise<Result<DeletedRepo>>;
183 /** A workspace's recently deleted repositories, newest first. Owners only; empty otherwise. */
184 deleted(viewer: Viewer, namespace: string): Promise<DeletedRepo[]>;
185 /** Brings a deleted repository back as it was. Owners only. Publishes `repo.restored`. */
186 restore(actor: User, path: RepoPath): Promise<Result<Repo>>;
187 /**
188 * Removes a deleted repository for good now, its git data with it, and
189 * frees its name. Owners only, who type its full name. Publishes `repo.purged`.
190 */
191 purge(actor: User, path: RepoPath, confirm: string): Promise<Result<boolean>>;
192 /**
193 * Renames the repository in its workspace. Owners only. The old path
194 * redirects until a repository is made there. Publishes `repo.renamed`.
195 */
196 rename(actor: User, path: RepoPath, name: string): Promise<Result<Repo>>;
197 /**
198 * Archives the repository (read-only) or unarchives it. Owners only.
199 * Publishes `repo.archived` or `repo.unarchived`.
200 */
201 archive(actor: User, path: RepoPath, archived: boolean): Promise<Result<Repo>>;
202 /**
203 * Makes the repository public or private. Owners only, who type its full
204 * name as `confirm`. Publishes `repo.updated` and `repo.visibility_changed`.
205 */
206 setVisibility(actor: User, path: RepoPath, isPrivate: boolean, confirm: string): Promise<Result<Repo>>;
207 /** Makes another existing branch the default. Members. */
208 setDefaultBranch(actor: User, path: RepoPath, branch: string): Promise<Result<Repo>>;
209 /**
210 * Renames a branch; pull requests from it follow, and addresses naming the
211 * old one redirect. Members; only owners rename the default branch.
212 */
213 renameBranch(actor: User, path: RepoPath, from: string, to: string): Promise<Result<Repo>>;
214 /**
215 * Makes a branch named `name` at `from`: a branch, a tag or a commit (the
216 * default branch when empty). Needs push; the rules for creating it hold.
217 * Restoring a deleted branch is making it again at the commit
218 * `removeBranch` answered with.
219 */
220 createBranch(actor: User, path: RepoPath, name: string, from?: string): Promise<Result<Branch>>;
221 /**
222 * Deletes a branch, never the default. Needs push; the rules for deleting
223 * it hold. Given `head`, a branch that has moved from it is left alone.
224 * Answers with the branch as it was, to restore it with `createBranch`.
225 */
226 removeBranch(actor: User, path: RepoPath, branch: string, head?: string): Promise<Result<Branch>>;
227 /** What a branch renamed away from `branch` is called now, or null. */
228 resolveBranch(repoId: string, branch: string): Promise<string | null>;
229 /** Whether a repository is archived or deleted; an unknown id answers as deleted. */
230 statusById(id: string): Promise<RepoStatus>;
231 /** What a workspace may choose about where its repositories are kept. */
232 storageOptions(): Promise<StorageOptions>;
233 /**
234 * Moves the repository to the workspace `to`, keeping its name, id and
235 * everything under it. The actor must own both workspaces. The old path
236 * redirects until a repository is made there. Publishes `repo.transferred`.
237 */
238 transfer(actor: User, path: RepoPath, to: string): Promise<Result<Repo>>;
239 /**
240 * Where a repository transferred away from `path` is now; null when
241 * `path` is a repository or never was one that moved. Callers check the
242 * viewer may see it there.
243 */
244 resolvePath(path: RepoPath): Promise<RepoPath | null>;
245
246 tree(path: RepoPath, viewer: Viewer, ref: string | null, treePath: string): Promise<Result<TreeView>>;
247 blob(path: RepoPath, viewer: Viewer, ref: string, filePath: string): Promise<Result<BlobView>>;
248 log(path: RepoPath, viewer: Viewer, ref: string | null, limit: number): Promise<Result<Commit[]>>;
249 /**
250 * Who last changed each line of a file as of `ref` (the default branch if
251 * null). Not found when the file is missing or is not text.
252 */
253 blame(path: RepoPath, viewer: Viewer, ref: string | null, filePath: string): Promise<Result<Blame>>;
254
255 /**
256 * A copy-on-write copy of `source`, hidden from listings, for one pull request
257 * to work in.
258 */
259 forkForPull(sourceId: string, pullId: string, actor: User): Promise<Result<Repo>>;
260
261 /**
262 * Authorizes a git operation and returns where to send it. Pushing to a
263 * repo that does not exist creates it in the pusher's own namespace.
264 */
265 gitAccess(path: RepoPath, viewer: Viewer, service: GitService): Promise<Result<GitAccess>>;
266
267 /** The repository's branches, default branch first. */
268 branches(path: RepoPath, viewer: Viewer): Promise<Result<Branch[]>>;
269
270 /** Which commit last changed each entry of a directory at `ref` (the default branch when null). */
271 lastCommits(path: RepoPath, viewer: Viewer, ref: string | null, treePath: string): Promise<Result<LastCommits>>;
272
273 /**
274 * How far each branch head has moved from `base` (the default branch's
275 * head commit), with each head's commit and `base`'s own, in one call.
276 * Kept by the pair of hashes in the repos service.
277 */
278 branchDrift(path: RepoPath, viewer: Viewer, base: string, heads: string[]): Promise<Result<BranchDrifts>>;
279
280 /** The repository's tags, newest commit first, at most 100. */
281 tags(path: RepoPath, viewer: Viewer): Promise<Result<Tag[]>>;
282
283 /**
284 * The Files page's About in one answer: license, security policy,
285 * languages, contributors, stars and releases. What comes from files and
286 * history is kept by commit and worked out in the background when it is
287 * behind the head (see `about.ts`).
288 */
289 about(path: RepoPath, viewer: Viewer): Promise<Result<RepoAbout>>;
290 languages(path: RepoPath, viewer: Viewer): Promise<Result<Languages>>;
291 /** Everyone whose commits are on the default branch, with their weeks. */
292 contributors(path: RepoPath, viewer: Viewer): Promise<Result<Contributors>>;
293 license(path: RepoPath, viewer: Viewer): Promise<Result<License | null>>;
294 /** How many starred it, and whether the viewer did. */
295 stars(path: RepoPath, viewer: Viewer): Promise<Result<Stars>>;
296 /** Stars it for the actor, or takes the star back. */
297 star(actor: User, path: RepoPath, starred: boolean): Promise<Result<Stars>>;
298 /** Who starred it, newest first, 100 a page from 1. */
299 stargazers(path: RepoPath, viewer: Viewer, page?: number): Promise<Result<Stargazer[]>>;
300 /** What a person starred that the viewer can see, newest first, at most 100. */
301 starred(username: string, viewer: Viewer): Promise<StarredRepo[]>;
302 /** Releases, newest first, at most 100: drafts only for those who can push. */
303 releases(path: RepoPath, viewer: Viewer): Promise<Result<Release[]>>;
304 /** One release by id or tag, or the latest. */
305 release(path: RepoPath, viewer: Viewer, which: { id?: string; tag?: string; latest?: boolean }): Promise<Result<Release>>;
306 /** Needs Write. A tag that does not exist yet is made at `target`. */
307 createRelease(actor: User, path: RepoPath, release: NewRelease): Promise<Result<Release>>;
308 updateRelease(actor: User, path: RepoPath, id: string, change: ReleaseChange): Promise<Result<Release>>;
309 /** The tag stays. */
310 deleteRelease(actor: User, path: RepoPath, id: string): Promise<Result<boolean>>;
311
312 /**
313 * Every file at `ref` (the default branch when null), at most `limit`
314 * (10,000 at most); symlinks too with `links`, for an archive. No viewer:
315 * check access first.
316 */
317 listFiles(repoId: string, ref: string | null, limit: number, options?: { links?: boolean }): Promise<FileList>;
318
319 /**
320 * What an archive link's `token` (made by the API's tarball and zipball
321 * routes) lets anyone holding it download from the repository at
322 * `path`: one commit, for ten minutes. Null when it is not for this
323 * repository, has ended or does not open. No viewer: the token is the
324 * permission.
325 */
326 openArchiveToken(path: RepoPath, token: string): Promise<ArchiveGrant | null>;
327
328 /** Blobs' bytes as standard base64, at most 100; `data` is null for one missing or over `maxBytes`. No viewer. */
329 rawBlobs(repoId: string, hashes: string[], maxBytes: number): Promise<RawBlob[]>;
330
331 /** One file's bytes at a branch, tag or commit; null when missing or over `maxBytes`. No viewer: check access first. */
332 rawFile(repoId: string, ref: string, path: string, maxBytes: number): Promise<RawFile | null>;
333
334 /**
335 * Writes one file on a new branch made from the default branch's head, as
336 * one commit by `actor`, for a change g1t proposes on their behalf (a
337 * starter workflow). Refused unless they may push, when the branch exists,
338 * or when the file is already there.
339 */
340 commitFile(
341 repo: RepoPath,
342 actor: User,
343 file: { branch: string; path: string; content: string; message: string },
344 ): Promise<Result<{ branch: string; commit: string }>>;
345
346 /**
347 * Moves a repository's default branch to the head of a pull request's
348 * source: a fork (`sourceId` is the fork) or one of the repository's own
349 * branches (`sourceId` is the repository, and `branch` is required).
350 * Refused with "conflict" when the source is behind, since that would
351 * discard commits.
352 */
353 land(sourceId: string, actor: User, branch?: string | null): Promise<Result<{ commit: string; previous: string | null }>>;
354
355 /**
356 * What `head` changes relative to `base`. `head` is a branch or a commit
357 * and defaults to the default branch. With no `base`, a fork is compared
358 * with the last commit it shares with the repository it came from, and a
359 * branch with the point where it left the default branch.
360 */
361 /**
362 * With no `base`, a branch is compared from where it left `baseBranch`
363 * (the default branch when absent).
364 */
365 compare(
366 repoId: string,
367 viewer: Viewer,
368 base?: string | null,
369 head?: string | null,
370 baseBranch?: string | null,
371 ): Promise<Result<Comparison>>;
372
373 /**
374 * Services only, for the runner's sweep: up to `limit` queued nightly
375 * backups, each now running with a token of its own, so long as no more
376 * than `maxRunning` are then running. Empty when backups are off.
377 */
378 claimBackups(limit: number, maxRunning: number): Promise<BackupClaim[]>;
379
380 /**
381 * Services only: a backup's sandbox stopped before it reported, so the
382 * job is tried again later. Refused harmlessly once it has reported.
383 */
384 failBackup(jobId: string, token: string, error: string): Promise<Result<boolean>>;
385}
386
387/**
388 * A nightly backup to start (`g1t_contracts::backups`): the sandbox is
389 * given the job's id and token, and nothing else.
390 */
391export type BackupClaim = {
392 jobId: string;
393 token: string;
394 repoId: string;
395 path: RepoPath;
396};
397
398/** Lines `start` to `end` (inclusive, from 1) last changed by `commit`. */
399export type BlameRange = { start: number; end: number; commit: string };
400
401/** Who last changed each line of a file. */
402export type Blame = {
403 /** The commit the file was read at. */
404 head: string;
405 /** Every line, in order, in runs that share a commit. */
406 ranges: BlameRange[];
407 /** The commits the ranges name, each once. */
408 commits: Commit[];
409 /** True when the history was too long to read in full. */
410 partial: boolean;
411};
412
413/** A branch and the commit it points to. */
414export type Branch = { name: string; hash: string };
415
416/** Each entry's last commit; `complete` is false when some were not reached. */
417export type LastCommits = { entries: { name: string; commit: Commit }[]; complete: boolean };
418
419/** Commits a branch has that the default branch does not, and the other way round. */
420export type BranchDriftCount = { ahead: number; behind: number };
421
422/**
423 * `base`'s commit, and each head's commit and drift in the order asked;
424 * `drift` is null when the two histories do not meet within what is read.
425 */
426export type BranchDrifts = {
427 base: Commit | null;
428 branches: { head: string; commit: Commit | null; drift: BranchDriftCount | null }[];
429};
430
431/** A tag, and its commit when it could be read. */
432export type Tag = { name: string; commit: Commit | null };
433
434/** Files at a commit, and whether there were more than were listed. */
435export type FileList = {
436 commit: string | null;
437 /** When the commit was made (RFC 3339). */
438 date?: string;
439 /** `kind`: `exec` for an executable file, `symlink` for a link (its blob is where it points). */
440 files: { path: string; hash: string | null; kind?: "blob" | "exec" | "symlink" }[];
441 truncated: boolean;
442};
443
444/** One commit of a repository an archive link lets its holder download. */
445export type ArchiveGrant = { repoId: string; namespace: string; name: string; commit: string };
446
447/** One blob's bytes, standard base64; null when missing or too large. */
448export type RawBlob = { hash: string; size: number; data: string | null };
449
450/** One file's bytes, standard base64. */
451export type RawFile = { size: number; data: string };
452
453/**
454 * What came of bringing a pull request up to date with the default branch
455 * without a sandbox. `needs_agent` pushed nothing: the runner's `update`
456 * merges it in a sandbox, with an agent if it conflicts.
457 */
458export type PullBranchUpdate =
459 | { outcome: "updated"; commit: string; previous: string }
460 | { outcome: "up_to_date"; commit: string }
461 | {
462 outcome: "needs_agent";
463 /**
464 * `overlap`: both sides changed some of the same files. `conflicting`:
465 * merging is known to conflict. `unsupported`: it could not be worked
466 * out without git, such as for a very large change.
467 */
468 reason: "overlap" | "conflicting" | "unsupported";
469 detail: string;
470 /** The files both changed, or that conflict, when known. */
471 paths: string[];
472 };
473
474export type DiffLine = {
475 kind: "context" | "add" | "delete";
476 /** Line number in the old file; null for added lines. */
477 old: number | null;
478 /** Line number in the new file; null for deleted lines. */
479 new: number | null;
480 text: string;
481};
482
483/** A run of changed lines with their surrounding context. */
484export type Hunk = { lines: DiffLine[] };
485
486export type FileDiff = {
487 path: string;
488 status: "added" | "modified" | "deleted";
489 additions: number;
490 deletions: number;
491 /** True when the file is binary or too large, so no lines are shown. */
492 binary: boolean;
493 hunks: Hunk[];
494};
495
496/** What changed between two commits. */
497export type Comparison = {
498 base: string | null;
499 head: string;
500 files: FileDiff[];
501 /** True when the change was too large to return in full. */
502 truncated: boolean;
503};