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
210 linesCodeBlameRaw

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.

Merge rulesets: branch and tag rules, agent-first, enforced on push and merge1//! A repository's settings for how g1t's agents handle its pull requests,
2//! and the branch protection settings that are now its "Default branch
3//! protection" ruleset (rulesets.rs).
4//!
5//! What a merge needs is decided by the rules of the branch it merges into
6//! (`Work::merge_gate`); `approvals_gap` asks them for what people must
7//! still do, as g1t sees it when it merges by itself.
Files pages say Files in the trail and keep the description in About only8
9use g1t_contracts::time::rfc3339;
10use g1t_contracts::work::*;
11use g1t_contracts::{FailureCode, Outcome};
12use g1t_kit::now_ms;
13use serde::Deserialize;
14use worker::Result;
15
16use crate::Work;
17
18const MAX_REQUIRED_APPROVALS: u32 = 6;
19const MAX_REVISIONS: u32 = 5;
20
21#[derive(Deserialize)]
22struct SettingsRow {
23 auto_merge: u8,
24 require_up_to_date: u8,
25 required_approvals: u32,
26 count_agent_approvals: u8,
27 allow_ignoring_checks: u8,
28 agent_review: u8,
29 max_revisions: u32,
30 #[serde(default)]
31 merge_queue: u8,
32 /// JSON array of names.
33 #[serde(default)]
34 required_checks: Option<String>,
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar35 #[serde(default)]
36 require_code_owner_review: u8,
Files pages say Files in the trail and keep the description in About only37 updated_by: String,
38 updated_at: String,
39}
40
41impl From<SettingsRow> for RepoSettings {
42 fn from(row: SettingsRow) -> Self {
43 RepoSettings {
44 auto_merge: row.auto_merge != 0,
45 required_checks: row
46 .required_checks
47 .as_deref()
48 .and_then(|names| serde_json::from_str(names).ok())
49 .unwrap_or_default(),
50 require_up_to_date: row.require_up_to_date != 0,
51 required_approvals: row.required_approvals,
52 count_agent_approvals: row.count_agent_approvals != 0,
53 allow_ignoring_checks: row.allow_ignoring_checks != 0,
54 agent_review: row.agent_review != 0,
55 max_revisions: row.max_revisions,
56 merge_queue: row.merge_queue != 0,
The work, events, webhooks, actions, repos and packages services no longer run g1t's own agents. Work drops the 35 calls that queued issues for g1t, delegated them, messaged and reviewed through the agent, planned, captured memories automatically, rated confidence and capped agent spend, and its pull request lifecycle no longer revises or reviews: a pull request only advances to a merge by the repository's auto-merge setting. Merging a pull request that is behind now catches it up itself, through the repository's branch update, and lands it when the push arrives; when it can't be caught up cleanly the merge says to merge the base in and try again. Recording a session on a pull request, memory recall and remember, overlaps, the merge queue and required checks stay as they were.57 // Nothing is held for low confidence any more.
58 hold_low_confidence: false,
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar59 require_code_owner_review: row.require_code_owner_review != 0,
Files pages say Files in the trail and keep the description in About only60 updated_by: Some(row.updated_by),
61 updated_at: Some(row.updated_at),
62 }
63 }
64}
65
66impl Work {
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge67 /// A repository's stored settings, by its id: how g1t's agents handle
68 /// its pull requests. Defaults if none were set. Branch protection is
69 /// its rules': see `settings_on`.
Files pages say Files in the trail and keep the description in About only70 pub(crate) async fn settings(&self, repo_id: &str) -> Result<RepoSettings> {
71 if let Some(found) = self.prefetched_repo(repo_id) {
72 let row = found.first::<SettingsRow>(crate::prefetch::Slot::Settings)?;
The work, events, webhooks, actions, repos and packages services no longer run g1t's own agents. Work drops the 35 calls that queued issues for g1t, delegated them, messaged and reviewed through the agent, planned, captured memories automatically, rated confidence and capped agent spend, and its pull request lifecycle no longer revises or reviews: a pull request only advances to a merge by the repository's auto-merge setting. Merging a pull request that is behind now catches it up itself, through the repository's branch update, and lands it when the push arrives; when it can't be caught up cleanly the merge says to merge the base in and try again. Recording a session on a pull request, memory recall and remember, overlaps, the merge queue and required checks stay as they were.73 return Ok(row.map_or_else(RepoSettings::default, RepoSettings::from));
Files pages say Files in the trail and keep the description in About only74 }
The work, events, webhooks, actions, repos and packages services no longer run g1t's own agents. Work drops the 35 calls that queued issues for g1t, delegated them, messaged and reviewed through the agent, planned, captured memories automatically, rated confidence and capped agent spend, and its pull request lifecycle no longer revises or reviews: a pull request only advances to a merge by the repository's auto-merge setting. Merging a pull request that is behind now catches it up itself, through the repository's branch update, and lands it when the push arrives; when it can't be caught up cleanly the merge says to merge the base in and try again. Recording a session on a pull request, memory recall and remember, overlaps, the merge queue and required checks stay as they were.75 let row = self
76 .db
77 .prepare("SELECT * FROM repo_settings WHERE repo_id = ?")
78 .bind(&[repo_id.into()])?
79 .first::<SettingsRow>(None)
80 .await?;
81 Ok(row.map_or_else(RepoSettings::default, RepoSettings::from))
Files pages say Files in the trail and keep the description in About only82 }
83
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge84 /// The default branch's settings: the stored ones, with branch
85 /// protection as its rules stack.
Files pages say Files in the trail and keep the description in About only86 pub(crate) async fn get_settings(&self, a: ViewArgs) -> Result<Outcome<RepoSettings>> {
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge87 let repo = match self.repo(&a.repo, &a.viewer).await? {
88 Outcome::Ok(repo) => repo,
89 Outcome::Fail(failure) => return Ok(Outcome::Fail(failure)),
90 };
91 Ok(Outcome::Ok(self.timing.db(3, self.default_branch_settings(&repo)).await?))
Files pages say Files in the trail and keep the description in About only92 }
93
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge94 /// Replaces a repository's settings: how g1t's agents work, kept here,
95 /// and the branch protection ones, written to its "Default branch
96 /// protection" ruleset (made when it has none and they protect
97 /// anything). Rules that only rulesets have stay as they are.
Files pages say Files in the trail and keep the description in About only98 pub(crate) async fn update_settings(
99 &self,
100 a: UpdateSettingsArgs,
101 ) -> Result<Outcome<RepoSettings>> {
102 let repo = match self.repo(&a.repo, &Some(a.actor.clone())).await? {
103 Outcome::Ok(repo) => repo,
104 Outcome::Fail(failure) => return Ok(Outcome::Fail(failure)),
105 };
Merge branch 'mirroring' into artifacts-mode106 if let Outcome::Fail(failure) = crate::retired::not_archived(&repo) {
Files pages say Files in the trail and keep the description in About only107 return Ok(Outcome::Fail(failure));
108 }
109 if !a.actor.verified {
110 return Ok(Outcome::fail(FailureCode::Forbidden, crate::UNVERIFIED));
111 }
112 if let Outcome::Fail(failure) =
113 crate::allowed(Some(&a.actor), &repo, g1t_contracts::access::Capability::ManageSettings)
114 {
115 return Ok(Outcome::Fail(failure));
116 }
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge117 let before = self.default_branch_settings(&repo).await?;
Files pages say Files in the trail and keep the description in About only118 let settings = RepoSettings {
119 required_approvals: a.settings.required_approvals.min(MAX_REQUIRED_APPROVALS),
120 max_revisions: a.settings.max_revisions.min(MAX_REVISIONS),
121 required_checks: tidy_required(&a.settings.required_checks),
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge122 updated_by: Some(a.actor.username.clone()),
Files pages say Files in the trail and keep the description in About only123 updated_at: Some(rfc3339(now_ms())),
124 ..a.settings
125 };
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge126 // Branch protection changes need the role that changes it.
127 if protection_changed(&before, &settings)
128 && let Outcome::Fail(failure) =
129 crate::allowed(Some(&a.actor), &repo, g1t_contracts::access::Capability::ManageProtection)
130 {
131 return Ok(Outcome::Fail(failure));
132 }
Files pages say Files in the trail and keep the description in About only133 self.db
134 .prepare(
135 "INSERT INTO repo_settings
136 (repo_id, auto_merge, require_up_to_date, required_approvals,
137 count_agent_approvals, allow_ignoring_checks, agent_review, max_revisions,
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar138 merge_queue, required_checks, require_code_owner_review, updated_by, updated_at)
139 VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
Files pages say Files in the trail and keep the description in About only140 ON CONFLICT (repo_id) DO UPDATE SET
141 auto_merge = excluded.auto_merge,
142 require_up_to_date = excluded.require_up_to_date,
143 required_approvals = excluded.required_approvals,
144 count_agent_approvals = excluded.count_agent_approvals,
145 allow_ignoring_checks = excluded.allow_ignoring_checks,
146 agent_review = excluded.agent_review,
147 max_revisions = excluded.max_revisions,
148 merge_queue = excluded.merge_queue,
149 required_checks = excluded.required_checks,
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar150 require_code_owner_review = excluded.require_code_owner_review,
Files pages say Files in the trail and keep the description in About only151 updated_by = excluded.updated_by,
152 updated_at = excluded.updated_at",
153 )
154 .bind(&[
155 repo.id.as_str().into(),
156 u32::from(settings.auto_merge).into(),
157 u32::from(settings.require_up_to_date).into(),
158 settings.required_approvals.into(),
159 u32::from(settings.count_agent_approvals).into(),
160 u32::from(settings.allow_ignoring_checks).into(),
161 u32::from(settings.agent_review).into(),
162 settings.max_revisions.into(),
163 u32::from(settings.merge_queue).into(),
164 serde_json::to_string(&settings.required_checks)?.into(),
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar165 u32::from(settings.require_code_owner_review).into(),
Files pages say Files in the trail and keep the description in About only166 settings.updated_by.as_deref().unwrap_or_default().into(),
167 settings.updated_at.as_deref().unwrap_or_default().into(),
168 ])?
169 .run()
170 .await?;
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge171 if protection_changed(&before, &settings) {
172 self.write_branch_protection(&repo, &settings, &a.actor).await?;
173 }
174 Ok(Outcome::Ok(self.default_branch_settings(&repo).await?))
Files pages say Files in the trail and keep the description in About only175 }
176}
177
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge178/// Whether the branch protection part of the settings differs.
179fn protection_changed(before: &RepoSettings, after: &RepoSettings) -> bool {
180 let names = |settings: &RepoSettings| -> Vec<String> {
181 let mut names: Vec<String> = settings.required_checks.iter().map(|name| name.to_lowercase()).collect();
182 names.sort();
183 names
184 };
185 names(before) != names(after)
186 || before.require_up_to_date != after.require_up_to_date
187 || before.required_approvals != after.required_approvals
188 || before.count_agent_approvals != after.count_agent_approvals
189 || before.allow_ignoring_checks != after.allow_ignoring_checks
190 || before.merge_queue != after.merge_queue
191 || before.require_code_owner_review != after.require_code_owner_review
192}
193
Files pages say Files in the trail and keep the description in About only194#[cfg(test)]
195mod tests {
196 use super::*;
197
198 #[test]
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge199 fn only_protection_changes_count_as_protection_changes() {
200 let before = RepoSettings::default();
201 let agents = RepoSettings { auto_merge: true, agent_review: false, max_revisions: 4, ..RepoSettings::default() };
202 assert!(!protection_changed(&before, &agents));
203 let checks = RepoSettings { required_checks: vec!["CI".into()], ..RepoSettings::default() };
204 assert!(protection_changed(&before, &checks));
205 let same = RepoSettings { required_checks: vec!["ci".into()], ..RepoSettings::default() };
206 assert!(!protection_changed(&checks, &same), "names compare without case");
207 let approvals = RepoSettings { required_approvals: 1, ..RepoSettings::default() };
208 assert!(protection_changed(&before, &approvals));
Files pages say Files in the trail and keep the description in About only209 }
210}

This file's history is long; its oldest lines are credited to the oldest commit read.