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.

g1t.sh
250 linesCodeBlameRaw
1import type { RepoPath } from "./repos";
2import type { Result } from "./result";
3import type { User, Viewer } from "./identity";
4
5/**
6 * Rulesets: what may happen to a repository's branches and tags, and what a
7 * pull request needs before it merges. A ruleset belongs to a repository or
8 * to a workspace (and through it to the repositories it selects), targets
9 * branches or tags by name, is `active`, `evaluate` (a dry run that records
10 * what it would have refused) or `disabled`, lists who may bypass it, and
11 * holds rules. Rulesets stack: every rule of each holds.
12 *
13 * Mirrors `crates/contracts/src/rules.rs`. Rulesets travel in the shape the
14 * API shows them, `snake_case`, so an export imports anywhere unchanged.
15 */
16
17export const DEFAULT_BRANCH = "~DEFAULT_BRANCH";
18export const ALL = "~ALL";
19export const MAX_RULESETS = 75;
20
21export type Enforcement = "active" | "evaluate" | "disabled";
22export type Target = "branch" | "tag";
23export type Level = "repository" | "workspace";
24export type AppliesTo = "everyone" | "agents" | "people";
25export type BypassActorKind = "role" | "team" | "user" | "token" | "g1t";
26export type BypassMode = "always" | "pull_requests";
27/**
28 * How a pull request merges: a merge commit, one squashed commit, its
29 * commits made again on the base, or the base moved to its head exactly
30 * (no new commit; only when it contains the base's head).
31 */
32export type MergeMethod = "merge" | "squash" | "rebase" | "fast_forward";
33export type Integration = "actions" | "deployments" | "security" | "g1t" | "api";
34export type PatternOperator = "starts_with" | "ends_with" | "contains" | "regex";
35
36export type Weekday = "mon" | "tue" | "wed" | "thu" | "fri" | "sat" | "sun";
37
38export type RefCondition = { include: string[]; exclude: string[] };
39export type RepositoryCondition = {
40 include: string[];
41 exclude: string[];
42 visibility: "any" | "public" | "private";
43 topics: string[];
44};
45export type Conditions = { ref_name: RefCondition; repository?: RepositoryCondition };
46export type BypassActor = { kind: BypassActorKind; value: string; mode: BypassMode };
47
48export type PullRequestParameters = {
49 required_approvals: number;
50 count_agent_approvals: boolean;
51 dismiss_stale_reviews_on_push: boolean;
52 require_code_owner_review: boolean;
53 require_last_push_approval: boolean;
54 allowed_merge_methods: MergeMethod[];
55 /** Only the ruleset made from branch protection that did not refuse pushes. */
56 allow_direct_pushes?: boolean;
57};
58export type RulesetCheck = { context: string; integration?: Integration };
59export type StatusChecksParameters = {
60 checks: RulesetCheck[];
61 strict: boolean;
62 paths: string[];
63 allow_bypass_on_merge: boolean;
64};
65export type MergeQueueParameters = {
66 merge_method: MergeMethod;
67 max_entries_to_build: number;
68 min_entries_to_merge: number;
69 min_entries_wait_minutes: number;
70 check_response_timeout_minutes: number;
71};
72export type PatternParameters = { name: string; operator: PatternOperator; pattern: string; negate: boolean };
73export type WeeklyWindow = { days: Weekday[]; start: string; end: string };
74export type Period = { start: string; end?: string | null; reason: string };
75export type MergeWindowParameters = {
76 time_zone: string;
77 windows: WeeklyWindow[];
78 freezes: Period[];
79 exceptions: Period[];
80};
81
82/** Every rule type, with its parameters. */
83export type Rule =
84 | { type: "creation"; parameters: Record<string, never> }
85 | { type: "update"; parameters: Record<string, never> }
86 | { type: "deletion"; parameters: Record<string, never> }
87 | { type: "non_fast_forward"; parameters: Record<string, never> }
88 | { type: "required_linear_history"; parameters: Record<string, never> }
89 | { type: "required_signatures"; parameters: Record<string, never> }
90 | { type: "pull_request"; parameters: PullRequestParameters }
91 | { type: "required_status_checks"; parameters: StatusChecksParameters }
92 | { type: "merge_queue"; parameters: MergeQueueParameters }
93 | { type: "required_deployments"; parameters: { environments: string[] } }
94 | { type: "commit_message_pattern"; parameters: PatternParameters }
95 | { type: "commit_author_email_pattern"; parameters: PatternParameters }
96 | { type: "committer_email_pattern"; parameters: PatternParameters }
97 | { type: "branch_name_pattern"; parameters: PatternParameters }
98 | { type: "tag_name_pattern"; parameters: PatternParameters }
99 | { type: "file_path_restriction"; parameters: { restricted_file_paths: string[] } }
100 | { type: "file_extension_restriction"; parameters: { restricted_file_extensions: string[] } }
101 | { type: "max_file_size"; parameters: { max_file_size_mb: number } }
102 | { type: "max_file_path_length"; parameters: { max_file_path_length: number } }
103 | { type: "max_files_changed"; parameters: { max_files: number } }
104 | { type: "secret_scanning"; parameters: Record<string, never> }
105 | { type: "path_review"; parameters: { paths: string[]; required_approvals: number; team?: string | null } }
106 | { type: "merge_window"; parameters: MergeWindowParameters }
107 | { type: "agent_auto_merge"; parameters: { allowed: boolean } };
108
109/**
110 * Rule types no longer offered, which were about g1t's own agents. A stored
111 * ruleset that has one still reads: the rule is left out. Mirrors
112 * `RETIRED_RULES` in `crates/contracts/src/rules.rs`.
113 */
114export const RETIRED_RULES: readonly string[] = ["confidence_threshold", "cost_cap"];
115
116export type RuleType = Rule["type"];
117export type RuleEntry = Rule & { applies_to: AppliesTo };
118
119/** What a ruleset says: what is created, changed, exported and imported. */
120export type RulesetSpec = {
121 name: string;
122 enforcement: Enforcement;
123 target: Target;
124 conditions: Conditions;
125 bypass_actors: BypassActor[];
126 rules: RuleEntry[];
127};
128
129export type Ruleset = RulesetSpec & {
130 id: string;
131 level: Level;
132 workspace: string;
133 repo_id?: string;
134 repository?: string;
135 /** `branch_protection` for the one made from branch protection settings. */
136 source?: string;
137 created_by: string;
138 created_at: string;
139 updated_by: string;
140 updated_at: string;
141};
142
143export type RulesetSummary = {
144 id: string;
145 name: string;
146 level: Level;
147 enforcement: Enforcement;
148 bypass_actors: BypassActor[];
149};
150
151export type EffectiveRule = RuleEntry & {
152 ruleset_id: string;
153 ruleset_name: string;
154 level: Level;
155 enforcement: Enforcement;
156};
157
158export type EffectiveRules = {
159 name: string;
160 target: Target;
161 default_branch: boolean;
162 rules: EffectiveRule[];
163 rulesets: RulesetSummary[];
164};
165
166export type RuleVerdict = "pass" | "fail" | "bypass";
167export type Action = "push" | "merge" | "create_ref" | "delete_ref" | "rename_ref" | "commit";
168
169export type Violation = {
170 rule: string;
171 ruleset_id: string;
172 ruleset_name: string;
173 enforcement: Enforcement;
174 message: string;
175 remedy: string;
176};
177
178export type Evaluation = {
179 id: string;
180 repo_id: string;
181 workspace: string;
182 ruleset_id: string;
183 ruleset_name: string;
184 enforcement: Enforcement;
185 action: Action;
186 git_ref: string;
187 actor: string;
188 actor_kind: string;
189 verdict: RuleVerdict;
190 violations: Violation[];
191 number: number | null;
192 sha: string | null;
193 repository: string;
194 created_at: string;
195};
196
197export type Insights = {
198 days: number;
199 total: number;
200 passed: number;
201 blocked: number;
202 would_block: number;
203 bypassed: number;
204 by_ruleset: {
205 ruleset_id: string;
206 ruleset_name: string;
207 enforcement: Enforcement;
208 total: number;
209 blocked: number;
210 would_block: number;
211 bypassed: number;
212 }[];
213 by_rule: { rule: string; count: number }[];
214};
215
216export type EvaluationPage = { evaluations: Evaluation[]; next: string | null; insights: Insights };
217
218/** What a pull request's merge box shows of the rules of its base. */
219export type MergeRules = {
220 unmet: Violation[];
221 bypassable: Violation[];
222 evaluate: Violation[];
223 rulesets: RulesetSummary[];
224 merge_queue: boolean;
225 /** What the active rules ask, as they stack. */
226 required_approvals: number;
227 strict: boolean;
228 allow_bypass_on_merge: boolean;
229};
230
231/** Whose rulesets: a repository's or a workspace's. */
232export type RulesetOwner = { repo: RepoPath } | { workspace: string };
233
234export type EvaluationFilter = {
235 ruleset_id?: string;
236 verdict?: RuleVerdict;
237 problems_only?: boolean;
238 before?: string;
239 limit?: number;
240};
241
242export interface RulesApi {
243 listRulesets(owner: RulesetOwner, viewer: Viewer, includeParents?: boolean): Promise<Result<Ruleset[]>>;
244 getRuleset(owner: RulesetOwner, id: string, viewer: Viewer): Promise<Result<Ruleset>>;
245 /** Creates one (no `id`) or replaces one. */
246 saveRuleset(actor: User, owner: RulesetOwner, ruleset: RulesetSpec, id?: string): Promise<Result<Ruleset>>;
247 deleteRuleset(actor: User, owner: RulesetOwner, id: string): Promise<Result<boolean>>;
248 effectiveRules(repo: RepoPath, name: string, viewer: Viewer, target?: Target): Promise<Result<EffectiveRules>>;
249 ruleEvaluations(owner: RulesetOwner, viewer: Viewer, filter?: EvaluationFilter): Promise<Result<EvaluationPage>>;
250}