Skip to content
855 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.

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.1//! Where a pull request stands, and merging it on the repository's say.
2//!
3//! A pull request g1t's agent once made (`made_by_g1t`, `managed = 1`)
4//! still shows where it stands: its checks, the approvals the branch
5//! wants, the merge queue, and whether a person has to step in. Where the
6//! repository's settings say ready pull requests land by themselves
7//! (`auto_merge`), such a pull request is merged here once everything its
8//! branch's rules ask for is met, attributed to `g1t` (`POLICY_ACTOR_ID`).
Project settings say who created the project, by name9//!
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.10//! A merge asked for while a pull request is behind is recorded here
11//! (`request_landing`) and lands once the push that brings it up to date
12//! arrives (`land_if_requested`).
Project settings say who created the project, by name13
14use g1t_contracts::repos::{GetByIdArgs, Repo, RepoPath};
15use g1t_contracts::time::rfc3339;
16use g1t_contracts::work::*;
17use g1t_contracts::{FailureCode, Membership, Outcome, User, Viewer};
18use g1t_kit::now_ms;
19use std::collections::HashMap;
20
21use serde::Deserialize;
22use worker::Result;
23use worker::wasm_bindgen::JsValue;
24
25use crate::Work;
26use crate::prefetch::Slot;
27use crate::reviews::{AGENT_ID, AGENT_NAME};
28use crate::statuses::{self, WorkflowFacts};
29use crate::rows::ValueRow;
30
31/// How long a claimed step is waited for before it may be taken again.
32const CATCH_UP_MINUTES: u64 = 30;
33const MERGE_MINUTES: u64 = 2;
34/// Who a merge made by a repository's settings is attributed to. Not an
35/// account: `g1t` cannot be registered.
36pub(crate) const POLICY_ACTOR_ID: &str = "g1t_policy";
37pub(crate) const POLICY_ACTOR_NAME: &str = "g1t";
38const MANAGED_PAGE: u32 = 200;
39
40/// The part of a pull request's row that tracks its lifecycle.
41#[derive(Deserialize)]
42struct Progress {
43 managed: u8,
44 revisions: u32,
45 revised_at: Option<String>,
46 working_on: Option<String>,
47 working_until: Option<String>,
48 stalled: Option<String>,
49}
50
51/// A merge asked for while the pull request was behind, as stored.
52#[derive(serde::Serialize, Deserialize)]
53#[serde(rename_all = "camelCase")]
54struct LandRequest {
55 actor: User,
56 keep_issue_open: bool,
57}
58
59#[derive(Deserialize)]
60struct LandRow {
61 land_requested: Option<String>,
62 land_requested_at: Option<String>,
63 stalled: Option<String>,
64}
65
66/// What should happen next, if it is g1t's turn.
67pub(crate) enum Next {
68 /// A step is under way, or it is a person's turn.
69 Wait,
70 /// Land it, because the repository says ready pull requests land.
71 Merge,
72}
73
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.74/// Whether g1t's agent made this pull request, and so g1t shows where it
75/// stands. Only pull requests from before g1t stopped hosting agents.
Project settings say who created the project, by name76pub(crate) fn made_by_g1t(pull: &Pull) -> bool {
77 pull.runtime == Runtime::Hosted && pull.agent == AGENT_NAME && pull.fork.is_some()
78}
79
80/// Whether a verdict by `reviewer` is a person's other than the pull
81/// request's owner (whoever asked g1t for it, or its author): neither
82/// theirs nor g1t's agent's.
83pub(crate) fn from_someone_else(pull: &Pull, reviewer: &str) -> bool {
84 !pull.is_owned_by(reviewer) && reviewer != AGENT_ID
85}
86
87fn at(stage: Stage, detail: impl Into<String>, revisions: u32) -> Lifecycle {
88 Lifecycle {
89 stage,
90 detail: detail.into(),
91 revisions,
92 }
93}
94
95/// Everything the next step depends on.
96struct Facts {
97 /// Still being made: not yet marked ready for review.
98 draft: bool,
99 /// Why g1t stopped, if it has.
100 stalled: Option<String>,
101 /// The step under way, if one was claimed and is still being waited for.
102 working_on: Option<String>,
103 /// `failed` when the merge queue took it out.
104 check_status: Option<CheckStatus>,
105 revisions: u32,
106 /// Whether the branch it would land on has moved without it.
107 behind: bool,
108 /// Whether it is known to conflict with the branch it would land on.
109 conflicting: bool,
110 /// Whether the repository lands a ready pull request by itself.
111 auto_merge: bool,
112 /// Whether the repository refuses to merge one that is behind.
113 require_up_to_date: bool,
114 /// What the repository's approval rule still wants, if anything.
115 approvals_missing: Option<String>,
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.116 /// The person who asked for changes since the last revision, if one did.
117 person_request: Option<String>,
Project settings say who created the project, by name118 /// Its place in the merge queue, and what is ahead of it there.
119 queued: Option<(QueueState, Vec<u32>)>,
120 /// What the checks on its head say, against the required ones.
121 workflows: WorkflowFacts,
122}
123
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.124/// Where a pull request stands, and whether to merge it.
Project settings say who created the project, by name125///
126/// The order is: nothing while a step is under way; a person asking for
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.127/// changes holds it; its checks must finish, and the required ones pass;
128/// it must not conflict, and where the repository insists, it must be up
129/// to date; then the approvals the branch wants. Once everything is met,
130/// it merges where the repository says so.
Project settings say who created the project, by name131fn decide(facts: Facts) -> (Lifecycle, Next) {
132 let revisions = facts.revisions;
133 let wait = |stage, detail: &str| (at(stage, detail, revisions), Next::Wait);
134
135 if facts.draft {
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.136 return wait(Stage::Working, "It is still a draft.");
Project settings say who created the project, by name137 }
138 if let Some(reason) = &facts.stalled {
139 return wait(Stage::NeedsYou, reason);
140 }
141 if let Some((state, ahead)) = &facts.queued {
142 let named = ahead.iter().map(|n| format!("#{n}")).collect::<Vec<_>>().join(", ");
143 let detail = match (state, ahead.is_empty()) {
144 (QueueState::Testing, true) => "In the merge queue: being tested on the default branch as it is.".to_owned(),
145 (QueueState::Testing, false) => {
146 format!("In the merge queue: being tested together with {named}, ahead of it.")
147 }
148 (QueueState::Passed, true) => "Passed in the merge queue. Landing.".to_owned(),
149 (QueueState::Passed, false) => {
150 format!("Passed in the merge queue together with {named}. It lands once they have.")
151 }
152 _ => "In the merge queue, waiting for its turn to be tested.".to_owned(),
153 };
154 return wait(Stage::Queued, &detail);
155 }
156 match facts.working_on.as_deref() {
157 Some("catch_up") => {
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.158 return wait(Stage::CatchingUp, "Bringing it up to date with the branch it will land on.");
Project settings say who created the project, by name159 }
160 Some("merge") => return wait(Stage::Ready, "Merging."),
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.161 _ => {}
Project settings say who created the project, by name162 }
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.163 if let Some(name) = &facts.person_request {
164 return wait(Stage::NeedsYou, &format!("{name} asked for changes."));
Project settings say who created the project, by name165 }
166 match facts.check_status {
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.167 Some(CheckStatus::Failed) => return wait(Stage::NeedsYou, "It failed in the merge queue."),
168 Some(CheckStatus::Errored) => return wait(Stage::NeedsYou, "Its checks could not be run."),
Project settings say who created the project, by name169 _ => {}
170 }
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.171 // Only the checks the branch requires hold it.
172 let required = facts.workflows.required_failed();
173 if !required.is_empty() {
174 return wait(
175 Stage::NeedsYou,
176 &format!(
177 "The required {} {} {}.",
178 if required.len() == 1 { "check" } else { "checks" },
179 statuses::list(&required),
180 if required.len() == 1 { "fails" } else { "fail" },
181 ),
182 );
Project settings say who created the project, by name183 }
184 if !facts.workflows.pending.is_empty() {
185 return wait(
186 Stage::Checking,
187 &format!("Waiting for {} to finish.", statuses::list(&facts.workflows.pending)),
188 );
189 }
190 let expected = facts.workflows.expected();
191 if !expected.is_empty() {
192 return wait(
193 Stage::Checking,
194 &format!(
195 "Waiting for the required {} {} to report on its latest commit.",
196 if expected.len() == 1 { "check" } else { "checks" },
197 statuses::list(&expected)
198 ),
199 );
200 }
201 if facts.conflicting {
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.202 return wait(
203 Stage::NeedsYou,
204 "It conflicts with the branch it will land on. Merge that branch in, resolve the conflicts and push.",
Project settings say who created the project, by name205 );
206 }
207 if facts.behind && facts.require_up_to_date {
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.208 return wait(
209 Stage::NeedsYou,
210 "The branch it will land on has moved, and this repository requires pull requests to be up to date. Catch up with it first.",
Project settings say who created the project, by name211 );
212 }
213 if let Some(missing) = &facts.approvals_missing {
214 return wait(Stage::NeedsYou, missing);
215 }
216 if facts.auto_merge {
217 return (
218 at(
219 Stage::Ready,
220 "Everything this repository asks for is met. Merging, as its settings say.",
221 revisions,
222 ),
223 Next::Merge,
224 );
225 }
226 wait(
227 Stage::Ready,
228 if facts.behind {
229 "Ready to merge. Merging brings it up to date with the default branch first."
230 } else {
231 "Everything this repository asks for is met. Ready to merge."
232 },
233 )
234}
235
236impl Work {
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.237 /// Where a pull request stands and whether g1t merges it, remembered
238 /// so lists can show it without working it out again. `None` for one
239 /// g1t is not seeing through.
Project settings say who created the project, by name240 pub(crate) async fn assess(
241 &self,
242 pull: &Pull,
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.243 _issue: &Option<Issue>,
Project settings say who created the project, by name244 behind: bool,
245 ) -> Result<Option<(Lifecycle, Next)>> {
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.246 let assessed = self.assess_now(pull, behind).await?;
247 if let Some((lifecycle, _)) = &assessed
Project settings say who created the project, by name248 && !self.remembered_as(&pull.id, lifecycle)?
249 {
250 self.remember(&pull.id, lifecycle).await?;
251 }
252 Ok(assessed)
253 }
254
255 /// Whether the row read for this request already says `lifecycle`, so
256 /// that showing a pull request does not write it again unchanged.
257 fn remembered_as(&self, pull_id: &str, lifecycle: &Lifecycle) -> Result<bool> {
258 let Some(found) = self.prefetched_pull(pull_id) else {
259 return Ok(false);
260 };
261 Ok(found
262 .first::<crate::rows::Snapshot>(Slot::Pull)?
263 .is_some_and(|stored| {
264 stored.stage == Some(lifecycle.stage) && stored.stage_detail.as_deref() == Some(lifecycle.detail.as_str())
265 }))
266 }
267
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.268 /// Saves where a pull request stands.
Project settings say who created the project, by name269 pub(crate) async fn remember(&self, pull_id: &str, lifecycle: &Lifecycle) -> Result<()> {
270 let stage = serde_json::to_value(lifecycle.stage)?;
271 self.db
272 .prepare("UPDATE pulls SET stage = ?, stage_detail = ? WHERE id = ?")
273 .bind(&[
274 stage.as_str().unwrap_or_default().into(),
275 lifecycle.detail.as_str().into(),
276 pull_id.into(),
277 ])?
278 .run()
279 .await?;
280 Ok(())
281 }
282
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.283 async fn assess_now(&self, pull: &Pull, behind: bool) -> Result<Option<(Lifecycle, Next)>> {
Project settings say who created the project, by name284 if !pull.status.is_active() {
285 return Ok(None);
286 }
287 // As read for this request (prefetch.rs), or now.
288 let prefetched = self.prefetched_pull(&pull.id);
289 let progress = match &prefetched {
290 Some(found) => found.first::<Progress>(Slot::Pull)?,
291 None => {
292 self.db
293 .prepare(
294 "SELECT managed, revisions, revised_at, working_on, working_until, stalled
295 FROM pulls WHERE id = ?",
296 )
297 .bind(&[pull.id.as_str().into()])?
298 .first::<Progress>(None)
299 .await?
300 }
301 };
302 let Some(progress) = progress.filter(|progress| progress.managed != 0) else {
303 return Ok(None);
304 };
305 let now = rfc3339(now_ms());
306 let working_on = progress
307 .working_until
308 .as_deref()
309 .is_some_and(|until| until > now.as_str())
310 .then(|| progress.working_on.clone().unwrap_or_default());
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar311 let settings = self.settings_for(pull).await?;
A required check named for a workflow job, as GitHub's branch rules name them, is met by that job. Each g1t Actions job on a pull request's head counts as a check of its name (`test`, `build (ubuntu-latest, 22)`, or `deploy / build` for a called workflow's job), from the latest attempt of its workflow: skipped passes, cancelled fails, and a job still going holds the merge. So a ruleset or required check copied from GitHub needs no renaming, and one that names the workflow, such as `CI`, is met by the whole run as before. This holds in the merge box, for the merge queue and for agents' merges; jobs count only toward required checks, so a `continue-on-error` job that fails doesn't send an agent back. The pull requests guide says so.312 let statuses = self.statuses(&pull.repo_id, pull.head_commit.as_deref()).await?;
313 let workflows = self.workflow_facts(&pull.repo_id, pull.head_commit.as_deref(), &statuses, &settings.required_checks).await?;
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge314 // The rules of the branch it merges into, as g1t sees them when it
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.315 // merges by itself: what people must still do, and whether an
316 // agent's change may land here unattended.
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge317 let gate = match self.repo_for(pull).await? {
318 Some(repo) => Some(self.merge_gate(&repo, pull, Some(&User::system(&repo.namespace)), false, true).await?),
319 None => None,
320 };
321 let rules_allow_auto_merge = gate
322 .as_ref()
The contracts no longer describe g1t's own agents, and workspaces can keep their AI providers. Every consumer had already stopped using them, so the TypeScript and Rust contracts drop the workspace-agent, ability, look, skill-library, context, runner-agent and mention modules, the chat and docs RPCs agents posted and read through, the folio agent methods, the agent_waiting notification, agent.asked and memory.changed events, AI credit, token and gateway billing types, model routes and sessions, team agents, agent tokens, plan, review, message and confidence types, command rules, cost caps and confidence rules. The agents:run and models:* scopes are no longer offered by any preset, OAuth default or permission form, but a stored token or grant that names them still reads; and the removed operations are gone from the scope table, so the API's check that every operation has exactly one scope is strict again. What stored data needs in order to parse (old run and credential kinds, an agent team lead, retired rules and webhook events, old guardrail and integration settings) still parses, and a run's token is still PrincipalKind::Agent, which now means a run's token and the @g1t rows written before. The customize, marketplace, skills and claude-plugin contracts stay only until the mobile app drops its Customize screens.323 .is_none_or(|gate| g1t_rules::merge::auto_merge_refusal(&gate.requirements).is_none());
Project settings say who created the project, by name324 let (lifecycle, next) = decide(Facts {
325 draft: pull.status == PullStatus::Draft,
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.326 stalled: progress.stalled,
Project settings say who created the project, by name327 working_on,
328 check_status: pull.check_status,
329 revisions: progress.revisions,
330 behind,
331 conflicting: self.conflicting_files(pull).await?.is_some(),
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge332 auto_merge: settings.auto_merge && rules_allow_auto_merge,
Project settings say who created the project, by name333 require_up_to_date: settings.require_up_to_date,
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge334 approvals_missing: gate.as_ref().and_then(|gate| gate.people_gap()),
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.335 person_request: self.person_request(pull, progress.revised_at.as_deref()).await?,
Project settings say who created the project, by name336 queued: self.queued_entry(&pull.id).await?,
337 workflows,
338 });
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.339 Ok(Some((lifecycle, next)))
Project settings say who created the project, by name340 }
341
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.342 /// Who last asked for changes, if it was a person other than its owner
343 /// (whoever asked g1t for it, or its author), that person's latest
344 /// verdict, and after the last revision.
345 async fn person_request(&self, pull: &Pull, revised_at: Option<&str>) -> Result<Option<String>> {
Project settings say who created the project, by name346 #[derive(Deserialize)]
347 struct Verdicts {
348 author_id: String,
349 author_name: String,
350 verdict: Verdict,
351 created_at: String,
352 }
353 let rows = match self.prefetched_pull(&pull.id) {
354 Some(found) => found
355 .rows::<Verdicts>(Slot::Verdicts)?
356 .into_iter()
357 .filter(|row| from_someone_else(pull, &row.author_id))
358 .collect(),
359 None => self
360 .db
361 .prepare(
362 "SELECT author_id, author_name, verdict, created_at FROM comments
363 WHERE repo_id = ? AND number = ? AND verdict IS NOT NULL
364 AND author_id != ? AND author_id != ?
365 ORDER BY id",
366 )
367 .bind(&[
368 pull.repo_id.as_str().into(),
369 pull.number.into(),
370 pull.owner().id.as_str().into(),
371 AGENT_ID.into(),
372 ])?
373 .all()
374 .await?
375 .results::<Verdicts>()?,
376 };
377 // Each person's latest verdict is the one that stands.
378 let mut latest: HashMap<String, Verdicts> = HashMap::new();
379 for row in rows {
380 latest.insert(row.author_id.clone(), row);
381 }
382 Ok(latest
383 .into_values()
384 .filter(|row| row.verdict == Verdict::RequestChanges)
385 .filter(|row| revised_at.is_none_or(|revised| row.created_at.as_str() > revised))
386 .max_by(|a, b| a.created_at.cmp(&b.created_at))
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.387 .map(|row| row.author_name))
Project settings say who created the project, by name388 }
389
390 /// Takes a step for a pull request, if nobody else has. One statement,
391 /// so that two callers cannot both take it.
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.392 pub(crate) async fn claim(&self, pull_id: &str, step: &str, minutes: u64) -> Result<bool> {
Project settings say who created the project, by name393 let now = now_ms();
394 Ok(self
395 .db
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.396 .prepare(
397 "UPDATE pulls SET working_on = ?2, working_until = ?3
Project settings say who created the project, by name398 WHERE id = ?4 AND status = 'open' AND stalled IS NULL
399 AND (working_until IS NULL OR working_until < ?1)
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.400 RETURNING id AS value",
401 )
Project settings say who created the project, by name402 .bind(&[
403 rfc3339(now).into(),
404 step.into(),
405 rfc3339(now + minutes * 60 * 1000).into(),
406 pull_id.into(),
407 ])?
408 .first::<ValueRow>(None)
409 .await?
410 .is_some())
411 }
412
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.413 /// Merges a pull request g1t is seeing through once it is ready, where
414 /// the repository's settings say ready pull requests land by
415 /// themselves. Returns what the runner is to do, which is now always
416 /// nothing: no step is carried out in a sandbox any more.
Project settings say who created the project, by name417 pub(crate) async fn advance(&self, a: AdvanceArgs) -> Result<Advance> {
418 let Some(pull) = self.pull_by_id(&a.pull_id).await? else {
419 return Ok(Advance::None);
420 };
421 if pull.status != PullStatus::Open {
422 return Ok(Advance::None);
423 }
424 // As a member: a private repository would look missing otherwise,
425 // and the pull request would never move.
426 let viewer: Viewer = self.owner_viewer(&pull).await?;
427 let repo: Outcome<Repo> = g1t_kit::call(
428 &self.repos,
429 "get_by_id",
430 &GetByIdArgs {
431 id: pull.repo_id.clone(),
432 viewer,
433 },
434 )
435 .await?;
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.436 let Outcome::Ok(repo) = crate::retired::unless_archived(repo) else {
Project settings say who created the project, by name437 return Ok(Advance::None);
438 };
439 let behind = self.is_behind(&repo.id, &pull).await?;
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.440 if let Some((_, Next::Merge)) = self.assess(&pull, &None, behind).await? {
Project settings say who created the project, by name441 self.merge_by_policy(&repo, &pull).await?;
442 }
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.443 Ok(Advance::None)
Project settings say who created the project, by name444 }
445
446 /// Lands a pull request that is ready, on the authority of the
447 /// repository's settings instead of a person's click.
448 async fn merge_by_policy(&self, repo: &Repo, pull: &Pull) -> Result<()> {
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.449 if !self.claim(&pull.id, "merge", MERGE_MINUTES).await? {
Project settings say who created the project, by name450 return Ok(());
451 }
452 // g1t acts for the workspace whose members turned this on, with the
453 // default base permission (Write), which merging needs.
454 let actor = User {
455 id: POLICY_ACTOR_ID.to_owned(),
456 username: POLICY_ACTOR_NAME.to_owned(),
457 verified: true,
458 workspaces: vec![Membership::member(repo.namespace.to_lowercase())],
459 ..User::default()
460 };
461 let merged = self
462 .merge_pull(PullActionArgs {
463 actor,
464 repo: RepoPath {
465 namespace: repo.namespace.clone(),
466 name: repo.name.clone(),
467 },
468 number: pull.number,
469 summary: String::new(),
470 keep_issue_open: false,
471 ignore_checks: false,
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge472 bypass_rules: false,
Project settings say who created the project, by name473 })
474 .await?;
475 match merged {
476 Outcome::Ok(_) => Ok(()),
477 // Most likely the branch moved in the moment between: let go, and
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.478 // the next look at it tries again.
Project settings say who created the project, by name479 Outcome::Fail(failure) if failure.code == FailureCode::Conflict => {
480 self.db
481 .prepare(
482 "UPDATE pulls SET working_on = NULL, working_until = NULL
483 WHERE id = ? AND working_on = 'merge'",
484 )
485 .bind(&[pull.id.as_str().into()])?
486 .run()
487 .await?;
488 Ok(())
489 }
490 Outcome::Fail(failure) => {
491 self.stall(StallArgs {
492 pull_id: pull.id.clone(),
493 reason: format!("g1t could not merge this: {}", failure.message),
Events: review requests, assignments, stops and deployments are published494 by: None,
Project settings say who created the project, by name495 })
496 .await?;
497 Ok(())
498 }
499 }
500 }
501
502 /// Records that a merge was asked for while the pull request was
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.503 /// behind, so that it lands once the push that brings it up to date
504 /// arrives, and announces it.
Project settings say who created the project, by name505 pub(crate) async fn request_landing(
506 &self,
507 pull: &Pull,
508 actor: &User,
509 keep_issue_open: bool,
510 ) -> Result<()> {
511 let now = now_ms();
512 let request = serde_json::to_string(&LandRequest {
513 actor: actor.clone(),
514 keep_issue_open,
515 })?;
516 self.db
517 .prepare(
518 "UPDATE pulls
519 SET land_requested = ?, land_requested_at = ?, stalled = NULL,
520 working_on = 'catch_up', working_until = ?
521 WHERE id = ?",
522 )
523 .bind(&[
524 request.into(),
525 rfc3339(now).into(),
526 rfc3339(now + CATCH_UP_MINUTES * 60 * 1000).into(),
527 pull.id.as_str().into(),
528 ])?
529 .run()
530 .await?;
531 self.publish(
532 "pull.merge_requested",
533 &pull.repo_id,
534 actor,
535 Self::pull_event(pull),
536 )
537 .await
538 }
539
540 /// The merge waiting on a pull request, if one was asked for recently
541 /// enough to still stand.
542 async fn land_request(&self, pull_id: &str) -> Result<Option<LandRequest>> {
543 let row = self.land_row(pull_id).await?;
544 let oldest = rfc3339(now_ms().saturating_sub(CATCH_UP_MINUTES * 60 * 1000));
545 Ok(row
546 .filter(|row| {
547 row.land_requested_at
548 .as_deref()
549 .is_some_and(|at| at >= oldest.as_str())
550 })
551 .and_then(|row| row.land_requested)
552 .and_then(|request| serde_json::from_str(&request).ok()))
553 }
554
555 /// Whether a merge is waiting on a pull request, and why g1t stopped
556 /// working on it if it did.
557 pub(crate) async fn landing_state(&self, pull_id: &str) -> Result<(bool, Option<String>)> {
558 let stalled = self.land_row(pull_id).await?.and_then(|row| row.stalled);
559 Ok((self.land_request(pull_id).await?.is_some(), stalled))
560 }
561
562 /// The merge waiting on a pull request and why g1t stopped, as read
563 /// for this request (prefetch.rs) or now.
564 async fn land_row(&self, pull_id: &str) -> Result<Option<LandRow>> {
565 if let Some(found) = self.prefetched_pull(pull_id) {
566 return found.first::<LandRow>(Slot::Pull);
567 }
568 self.db
569 .prepare("SELECT land_requested, land_requested_at, stalled FROM pulls WHERE id = ?")
570 .bind(&[pull_id.into()])?
571 .first::<LandRow>(None)
572 .await
573 }
574
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.575 /// Forgets a merge that was waiting on a pull request, and the step
576 /// that was bringing it up to date.
577 pub(crate) async fn forget_landing(&self, pull_id: &str) -> Result<()> {
Project settings say who created the project, by name578 self.db
579 .prepare(
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.580 "UPDATE pulls SET land_requested = NULL, land_requested_at = NULL,
581 working_on = CASE WHEN working_on = 'catch_up' THEN NULL ELSE working_on END,
582 working_until = CASE WHEN working_on = 'catch_up' THEN NULL ELSE working_until END
583 WHERE id = ?",
Project settings say who created the project, by name584 )
585 .bind(&[pull_id.into()])?
586 .run()
587 .await?;
588 Ok(())
589 }
590
591 /// Lands a pull request whose head has just moved, if a merge of it was
592 /// waiting for exactly that. What it was caught up to was already
593 /// checked and reviewed apart from the merge, so the checks are not
594 /// waited for again; a repository that wants them rerun turns on
595 /// "require up to date", and then nothing is landed this way.
596 pub(crate) async fn land_if_requested(&self, pull_id: &str) -> Result<()> {
597 let Some(request) = self.land_request(pull_id).await? else {
598 return Ok(());
599 };
600 self.forget_landing(pull_id).await?;
601 let Some(pull) = self.pull_by_id(pull_id).await? else {
602 return Ok(());
603 };
604 let repo: Outcome<Repo> = g1t_kit::call(
605 &self.repos,
606 "get_by_id",
607 &GetByIdArgs {
608 id: pull.repo_id.clone(),
609 viewer: Some(request.actor.clone()),
610 },
611 )
612 .await?;
613 let Outcome::Ok(repo) = crate::retired::unless_archived(repo) else {
614 return Ok(());
615 };
616 let merged = self
617 .merge_pull(PullActionArgs {
618 actor: request.actor,
619 repo: RepoPath {
620 namespace: repo.namespace,
621 name: repo.name,
622 },
623 number: pull.number,
624 summary: String::new(),
625 keep_issue_open: request.keep_issue_open,
626 ignore_checks: true,
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge627 bypass_rules: false,
Project settings say who created the project, by name628 })
629 .await?;
630 if let Outcome::Fail(failure) = merged {
631 self.stall(StallArgs {
632 pull_id: pull.id,
633 reason: format!(
634 "It was brought up to date but could not be merged: {}",
635 failure.message
636 ),
Events: review requests, assignments, stops and deployments are published637 by: None,
Project settings say who created the project, by name638 })
639 .await?;
640 }
641 Ok(())
642 }
643
Events: review requests, assignments, stops and deployments are published644 /// Stops seeing a pull request through until a person steps in. The
645 /// first stop is published (`pull.stalled`), which tells its people
646 /// that it needs them; a stop on one already stopped only says why.
Project settings say who created the project, by name647 pub(crate) async fn stall(&self, a: StallArgs) -> Result<bool> {
Events: review requests, assignments, stops and deployments are published648 let before = self.pull_by_id(&a.pull_id).await?;
649 let was_stalled = self.is_stalled(&a.pull_id).await?;
Project settings say who created the project, by name650 self.db
651 .prepare(
652 "UPDATE pulls
653 SET stalled = ?, working_on = NULL, working_until = NULL,
654 land_requested = NULL, land_requested_at = NULL
655 WHERE id = ? AND status = 'open'",
656 )
657 .bind(&[a.reason.trim().into(), a.pull_id.as_str().into()])?
658 .run()
659 .await?;
660 self.db
661 .prepare("UPDATE pulls SET stage = 'needs_you', stage_detail = ? WHERE id = ?")
Events: review requests, assignments, stops and deployments are published662 .bind(&[a.reason.trim().into(), a.pull_id.as_str().into()])?
Project settings say who created the project, by name663 .run()
664 .await?;
Events: review requests, assignments, stops and deployments are published665 if let Some(pull) = before.filter(|pull| !was_stalled && pull.status == PullStatus::Open) {
666 self.publish_as(
667 "pull.stalled",
668 &pull.repo_id,
669 a.by.clone(),
670 g1t_contracts::events::PullEvent {
671 detail: Some(a.reason.trim().to_owned()),
672 ..Self::pull_event(&pull)
673 },
674 )
675 .await?;
676 }
Project settings say who created the project, by name677 Ok(true)
678 }
679
Events: review requests, assignments, stops and deployments are published680 /// Whether g1t has stopped seeing the pull request through.
681 pub(crate) async fn is_stalled(&self, pull_id: &str) -> Result<bool> {
682 Ok(self
683 .db
684 .prepare("SELECT 1 AS value FROM pulls WHERE id = ? AND stalled IS NOT NULL")
685 .bind(&[pull_id.into()])?
686 .first::<u32>(Some("value"))
687 .await?
688 .is_some())
689 }
690
691 /// Says that a pull request g1t had stopped on is going again, which
692 /// closes what it was waiting on a person for.
693 pub(crate) async fn announce_resumed(&self, pull_id: &str, actor: Option<String>) -> Result<()> {
694 if let Some(pull) = self.pull_by_id(pull_id).await? {
695 self.publish_as("pull.resumed", &pull.repo_id, actor, Self::pull_event(&pull)).await?;
696 }
697 Ok(())
698 }
699
Project settings say who created the project, by name700 pub(crate) async fn managed_pulls(&self, a: ManagedPullsArgs) -> Result<Vec<String>> {
701 let rows = self
702 .db
703 .prepare(
704 "SELECT id AS value FROM pulls
705 WHERE status = 'open' AND managed = 1 AND stalled IS NULL
706 AND (?1 IS NULL OR repo_id = ?1)
707 ORDER BY updated_at DESC LIMIT ?2",
708 )
709 .bind(&[
710 a.repo_id.map_or(JsValue::NULL, JsValue::from),
711 MANAGED_PAGE.into(),
712 ])?
713 .all()
714 .await?
715 .results::<ValueRow>()?;
716 Ok(rows.into_iter().map(|row| row.value).collect())
717 }
718}
719
720#[cfg(test)]
721mod tests {
722 use super::*;
723
724 #[test]
725 fn a_verdict_from_whoever_asked_for_g1t_s_change_is_not_someone_else_s() {
726 use crate::rows::stored::{ASKER, G1T, pull};
727 let made = pull(G1T, Some(ASKER));
728 assert!(made_by_g1t(&made));
729 assert!(!from_someone_else(&made, ASKER.0));
730 assert!(!from_someone_else(&made, AGENT_ID));
731 assert!(from_someone_else(&made, "usr_reviewer"));
732 let own = pull(ASKER, None);
733 assert!(!from_someone_else(&own, ASKER.0));
734 assert!(from_someone_else(&own, "usr_reviewer"));
735 }
736
737 /// Checks on a head commit: `(context, state)` statuses, against the
738 /// required check names.
739 fn checks(statuses: &[(&str, &str)], required: &[&str]) -> WorkflowFacts {
740 let statuses: Vec<CommitStatus> = statuses
741 .iter()
742 .map(|(context, state)| CommitStatus {
743 context: (*context).to_owned(),
744 state: (*state).to_owned(),
745 description: None,
746 target_url: None,
747 updated_at: String::new(),
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge748 source: None,
Merge checks: statuses and check runs on every commit749 check_run_id: None,
Project settings say who created the project, by name750 })
751 .collect();
752 let required: Vec<String> = required.iter().map(|name| (*name).to_owned()).collect();
753 WorkflowFacts::of(&statuses, &required)
754 }
755
756 /// A pull request that is ready for review, whose required check
757 /// passed, and nothing else yet.
758 fn facts() -> Facts {
759 Facts {
760 draft: false,
761 stalled: None,
762 working_on: None,
763 check_status: None,
764 revisions: 0,
765 behind: false,
766 conflicting: false,
767 auto_merge: false,
768 require_up_to_date: false,
769 approvals_missing: None,
770 person_request: None,
771 queued: None,
772 workflows: checks(&[("CI / pull_request", "success")], &["CI"]),
773 }
774 }
775
776 /// The stage, and a word for the step to take.
777 fn outcome(facts: Facts) -> (Stage, &'static str) {
778 let (lifecycle, next) = decide(facts);
779 let step = match next {
780 Next::Wait => "wait",
781 Next::Merge => "merge",
782 };
783 (lifecycle.stage, step)
784 }
785
786 #[test]
787 fn a_ready_change_lands_by_itself_only_where_the_repository_says_so() {
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.788 assert_eq!(outcome(facts()), (Stage::Ready, "wait"));
789 assert_eq!(outcome(Facts { auto_merge: true, ..facts() }), (Stage::Ready, "merge"));
Project settings say who created the project, by name790 // One that is behind is merged too: merging brings it up to date.
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.791 assert_eq!(outcome(Facts { auto_merge: true, behind: true, ..facts() }), (Stage::Ready, "merge"));
792 // Unless the repository wants it caught up first.
793 let strict = Facts { auto_merge: true, behind: true, require_up_to_date: true, ..facts() };
794 assert_eq!(outcome(strict), (Stage::NeedsYou, "wait"));
Project settings say who created the project, by name795 }
796
797 #[test]
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.798 fn approvals_the_repository_wants_are_waited_for() {
799 let short = Facts {
800 approvals_missing: Some("This repository requires 1 approving review.".to_owned()),
Project settings say who created the project, by name801 auto_merge: true,
802 ..facts()
803 };
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.804 let (lifecycle, next) = decide(short);
805 assert_eq!(lifecycle.stage, Stage::NeedsYou);
806 assert_eq!(lifecycle.detail, "This repository requires 1 approving review.");
807 assert!(matches!(next, Next::Wait));
Project settings say who created the project, by name808 }
809
810 #[test]
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.811 fn checks_are_waited_for_and_a_failing_required_one_needs_a_person() {
812 let running = Facts { workflows: checks(&[("CI / pull_request", "pending")], &["CI"]), auto_merge: true, ..facts() };
813 let (lifecycle, next) = decide(running);
814 assert_eq!(lifecycle.stage, Stage::Checking);
815 assert!(lifecycle.detail.contains("Waiting for CI / pull_request"));
816 assert!(matches!(next, Next::Wait));
817 let failed = Facts { workflows: checks(&[("CI / pull_request", "failure")], &["CI"]), ..facts() };
818 let (lifecycle, _) = decide(failed);
819 assert_eq!(lifecycle.stage, Stage::NeedsYou);
820 assert_eq!(lifecycle.detail, "The required check CI fails.");
821 // A check the branch does not require does not hold it.
822 let lint = checks(&[("CI / pull_request", "success"), ("Lint / pull_request", "failure")], &["CI"]);
823 assert_eq!(outcome(Facts { workflows: lint, ..facts() }), (Stage::Ready, "wait"));
824 let missing = Facts { workflows: checks(&[("CI / pull_request", "success")], &["CI", "Deploy"]), auto_merge: true, ..facts() };
825 let (lifecycle, next) = decide(missing);
826 assert_eq!(lifecycle.detail, "Waiting for the required check Deploy to report on its latest commit.");
827 assert!(matches!(next, Next::Wait), "auto-merge must not land it");
Project settings say who created the project, by name828 }
829
830 #[test]
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.831 fn the_merge_queue_conflicts_and_people_hold_it() {
832 let queued = Facts { queued: Some((QueueState::Testing, vec![12, 14])), auto_merge: true, ..facts() };
833 let (lifecycle, _) = decide(queued);
834 assert_eq!(lifecycle.stage, Stage::Queued);
835 assert!(lifecycle.detail.contains("#12, #14"));
836 assert_eq!(outcome(Facts { check_status: Some(CheckStatus::Failed), auto_merge: true, ..facts() }), (Stage::NeedsYou, "wait"));
837 assert_eq!(outcome(Facts { check_status: Some(CheckStatus::Errored), ..facts() }), (Stage::NeedsYou, "wait"));
838 assert_eq!(outcome(Facts { conflicting: true, auto_merge: true, ..facts() }), (Stage::NeedsYou, "wait"));
839 let asked = Facts { person_request: Some("ana".to_owned()), auto_merge: true, ..facts() };
840 let (lifecycle, next) = decide(asked);
841 assert_eq!(lifecycle.detail, "ana asked for changes.");
842 assert!(matches!(next, Next::Wait));
Project settings say who created the project, by name843 }
844
845 #[test]
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.846 fn drafts_steps_under_way_and_stops_wait() {
847 assert_eq!(outcome(Facts { draft: true, ..facts() }), (Stage::Working, "wait"));
848 assert_eq!(outcome(Facts { working_on: Some("catch_up".into()), auto_merge: true, ..facts() }), (Stage::CatchingUp, "wait"));
849 assert_eq!(outcome(Facts { working_on: Some("merge".into()), auto_merge: true, ..facts() }), (Stage::Ready, "wait"));
850 let (lifecycle, next) = decide(Facts { stalled: Some("It could not be merged.".to_owned()), auto_merge: true, ..facts() });
Project settings say who created the project, by name851 assert_eq!(lifecycle.stage, Stage::NeedsYou);
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.852 assert_eq!(lifecycle.detail, "It could not be merged.");
Project settings say who created the project, by name853 assert!(matches!(next, Next::Wait));
854 }
855}

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