Skip to content

Commit

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.

Claude-Session:
https://claude.ai/code/session_01PnwTRSNaC7vEHTAuBzjhrn
Claudeagent and Claudeagentcommitted Parentc794c3cBrowse files
7 files+108−90/7 viewed
+10−1
106106 must report it, such as workflows or deployments.
107107
108108 A required check is met by a status of that name on the pull request's head,
109−whatever event reported it:
109+whatever event reported it, or by a [g1t Actions](/guides/actions/) job
110+of that name, as on GitHub. A job's check is named as GitHub names it:
111+the job's `name:` (or its id), with a matrix combination's values after it
112+in brackets, such as `test` or `build (ubuntu-latest, 22)`, and
113+`<calling job> / <job>` for a job of a called workflow. A branch rule
114+copied from GitHub that requires `test` is met by the `test` job, and one
115+that requires the workflow's name, such as `CI`, is met by the whole run.
116+Type a job's name to require it: the list offers the names of statuses.
117+Each job's latest attempt counts. One that was skipped passes the check,
118+and one that was cancelled fails it.
110119
111120 | What reported it | What the merge does |
112121 | --- | --- |
+52−1
483483 out
484484 }
485485
486+/// The latest run of each workflow on `sha` (`runs` newest first, as the
487+/// actions service lists them), each of its jobs as the status its check
488+/// run stands as: named as the check run is (the job's name, such as
489+/// `test` or `build (ubuntu-latest)`), so a required check named for a job
490+/// is met by it, as on GitHub. A job still waiting or going is pending; one
491+/// that passed, was skipped or was neutral passed; any other ending failed.
492+pub fn job_statuses(runs: &[(WorkflowRun, Vec<Job>)], sha: &str) -> Vec<CommitStatus> {
493+ latest_runs(runs)
494+ .into_iter()
495+ .filter(|(run, _)| run.sha == sha)
496+ .flat_map(|(run, jobs)| {
497+ jobs.iter().map(move |job| {
498+ let state = match (job.status.as_str(), job.conclusion.as_deref()) {
499+ ("completed", Some("success" | "skipped" | "neutral")) => "success",
500+ ("completed", _) => "failure",
501+ _ => "pending",
502+ };
503+ CommitStatus {
504+ context: job.name.clone(),
505+ state: state.to_owned(),
506+ description: job.reason.clone(),
507+ target_url: None,
508+ updated_at: job.finished_at.clone().or_else(|| job.started_at.clone()).unwrap_or_else(|| run.created_at.clone()),
509+ source: Some("actions".to_owned()),
510+ check_run_id: Some(job.id.clone()),
511+ }
512+ })
513+ })
514+ .collect()
515+}
516+
486517 // --- A commit's checks, added up -----------------------------------------
487518
488519 /// Every check on one commit, as its list shows them: each check run, and
10251056 assert_eq!(check.output.annotations_count, 1);
10261057 assert_eq!(job_annotations(&job)[0].annotation_level, "failure");
10271058 let waiting = Job { status: "waiting".into(), conclusion: None, ..job.clone() };
1028− let suite = run_check_suite(&workflow_run, &[job, waiting]);
1059+ let suite = run_check_suite(&workflow_run, &[job.clone(), waiting.clone()]);
10291060 assert_eq!((suite.status.as_str(), suite.head_branch.as_deref()), ("in_progress", Some("main")));
10301061 assert_eq!(suite.name.as_deref(), Some("CI"));
1062+
1063+ // Each job stands as a status named as GitHub names its check run,
1064+ // from the latest attempt of each workflow only.
1065+ let sha = "b".repeat(40);
1066+ let skipped = Job { id: "job_3".into(), name: "Lint".into(), conclusion: Some("skipped".into()), ..job.clone() };
1067+ let cancelled = Job { id: "job_4".into(), name: "Build (ubuntu-latest)".into(), conclusion: Some("cancelled".into()), ..job.clone() };
1068+ let older = WorkflowRun { id: "run_0".into(), attempt: 0, ..workflow_run.clone() };
1069+ let other = WorkflowRun { sha: "c".repeat(40), ..workflow_run.clone() };
1070+ let statuses = job_statuses(
1071+ &[
1072+ (workflow_run.clone(), vec![job.clone(), waiting, skipped, cancelled]),
1073+ (older, vec![Job { conclusion: Some("failure".into()), ..job.clone() }]),
1074+ (other, vec![job.clone()]),
1075+ ],
1076+ &sha,
1077+ );
1078+ let states: Vec<(&str, &str)> = statuses.iter().map(|s| (s.context.as_str(), s.state.as_str())).collect();
1079+ assert_eq!(states, [("Test", "success"), ("Test", "pending"), ("Lint", "success"), ("Build (ubuntu-latest)", "failure")]);
1080+ assert_eq!(statuses[0].check_run_id.as_deref(), Some("job_1"));
1081+ assert_eq!(statuses[0].source.as_deref(), Some("actions"));
10311082 }
10321083
10331084 #[test]
+1−1
274274 }
275275
276276 /// g1t Actions' runs and jobs, by commits, by a run or by a job.
277− async fn actions_runs(&self, args: ActionsChecksArgs) -> Result<Vec<RunDetail>> {
277+ pub(crate) async fn actions_runs(&self, args: ActionsChecksArgs) -> Result<Vec<RunDetail>> {
278278 // Without the actions service a commit's other checks still show.
279279 Ok(g1t_kit::call(&self.actions, "check_runs", &args).await.unwrap_or_default())
280280 }
+7−1
16181618 )
16191619 .await?;
16201620 let code_owners = self.pull_code_owners(&pull, &comments, &settings).await?;
1621+ let required = if settings.required_checks.is_empty() {
1622+ Vec::new()
1623+ } else {
1624+ let checked = self.with_job_checks(&repo.id, pull.head_commit.as_deref(), statuses.clone()).await?;
1625+ required_checks(&settings.required_checks, &checked)
1626+ };
16211627 Ok(Outcome::Ok(PullDetail {
1622− required_checks: required_checks(&settings.required_checks, &statuses),
1628+ required_checks: required,
16231629 rules: gate.map(|gate| rulesets::merge_rules(&gate.judged, &gate.requirements, pull.base_branch(&repo.default_branch) == repo.default_branch)),
16241630 code_owners,
16251631 comments,
+2−4
568568 .is_none_or(|revised| review.finished_at.as_str() >= revised)
569569 });
570570 let settings = self.settings_for(pull).await?;
571− let workflows = WorkflowFacts::of(
572− &self.statuses(&pull.repo_id, pull.head_commit.as_deref()).await?,
573− &settings.required_checks,
574− );
571+ let statuses = self.statuses(&pull.repo_id, pull.head_commit.as_deref()).await?;
572+ let workflows = self.workflow_facts(&pull.repo_id, pull.head_commit.as_deref(), &statuses, &settings.required_checks).await?;
575573 // Once the agent has finished the change: how sure g1t is of it, and
576574 // whether that holds it for a person.
577575 let (confidence, low_confidence) = if pull.status == PullStatus::Draft {
+4−0
829829 },
830830 )?;
831831 let reviews = latest_reviews(verdicts);
832+ // A required check named for a g1t Actions job is met by that job.
833+ let requires_checks =
834+ applicable.iter().any(|ruleset| ruleset.rules.iter().any(|entry| matches!(&entry.rule, Rule::RequiredStatusChecks(_))));
835+ let statuses = if requires_checks { self.with_job_checks(&repo.id, pull.head_commit.as_deref(), statuses).await? } else { statuses };
832836 let facts = MergeFacts {
833837 git_ref: git_ref.clone(),
834838 agent_change: agent,
+32−1
139139 pub(crate) async fn facts(&self, repo_id: &str, sha: Option<&str>) -> Result<WorkflowFacts> {
140140 let (statuses, settings) =
141141 futures_util::future::try_join(self.statuses(repo_id, sha), self.settings_by_id(repo_id)).await?;
142− Ok(WorkflowFacts::of(&statuses, &settings.required_checks))
142+ self.workflow_facts(repo_id, sha, &statuses, &settings.required_checks).await
143+ }
144+
145+ /// What `statuses`, a commit's, say, with each required check also met
146+ /// by the g1t Actions jobs of its name ([`Work::with_job_checks`]). Jobs
147+ /// count only toward required checks: whether workflows are running or
148+ /// failed is their runs' statuses' to say.
149+ pub(crate) async fn workflow_facts(
150+ &self,
151+ repo_id: &str,
152+ sha: Option<&str>,
153+ statuses: &[CommitStatus],
154+ required: &[String],
155+ ) -> Result<WorkflowFacts> {
156+ let mut facts = WorkflowFacts::of(statuses, required);
157+ if !required.is_empty() {
158+ facts.required = required_checks(required, &self.with_job_checks(repo_id, sha, statuses.to_vec()).await?);
159+ }
160+ Ok(facts)
161+ }
162+
163+ /// `statuses` and, for each g1t Actions job on `sha`, the status its
164+ /// check run stands as (`g1t_contracts::checks::job_statuses`), so a
165+ /// required check named for a job, as GitHub's are, is met by it.
166+ pub(crate) async fn with_job_checks(&self, repo_id: &str, sha: Option<&str>, mut statuses: Vec<CommitStatus>) -> Result<Vec<CommitStatus>> {
167+ let Some(sha) = sha else { return Ok(statuses) };
168+ let details = self
169+ .actions_runs(g1t_contracts::checks::ActionsChecksArgs { repo_id: repo_id.to_owned(), shas: vec![sha.to_owned()], ..Default::default() })
170+ .await?;
171+ let runs: Vec<_> = details.into_iter().map(|detail| (detail.run, detail.jobs)).collect();
172+ statuses.extend(g1t_contracts::checks::job_statuses(&runs, sha));
173+ Ok(statuses)
143174 }
144175
145176 /// The check names reported on a repository's commits lately, for