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

Commit

Merge remote-tracking branch 'g1t/main' into queue-methods

syntaqxcommitted Parentsb5c43d329aba25Browse files
29 files+264−3913 files not counted0/29 viewed
+2−2
33983398
33993399 [[package]]
34003400 name = "rustls"
3401−version = "0.23.45"
3401+version = "0.23.46"
34023402 source = "registry+https://github.com/rust-lang/crates.io-index"
3403−checksum = "0d41d731c7d2f962d1ccc364cec258de3c0e93b38c2fb3ba97ac74513048d634"
3403+checksum = "48e13bd8c0e9365c43cfa5c9e8f9ad49d3c8444926c9aac819e0e4dc503c8fdf"
34043404 dependencies = [
34053405 "aws-lc-rs",
34063406 "log",
+1−0
275275 Op::Security(SecurityOp::GetDependencyGraph),
276276 Op::Security(SecurityOp::GetSbom),
277277 Op::Security(SecurityOp::CompareDependencies),
278+ Op::Security(SecurityOp::RerunDependencyReview),
278279 ],
279280 ),
280281 (
+2−1
807807 }
808808
809809 impl Op {
810− pub const ALL: [Op; 402] = [
810+ pub const ALL: [Op; 403] = [
811811 Op::Whoami,
812812 Op::GetWorkspace,
813813 Op::CreateWorkspace,
10401040 Op::Security(SecurityOp::GetDependencyGraph),
10411041 Op::Security(SecurityOp::GetSbom),
10421042 Op::Security(SecurityOp::CompareDependencies),
1043+ Op::Security(SecurityOp::RerunDependencyReview),
10431044 Op::Security(SecurityOp::GetSettings),
10441045 Op::Security(SecurityOp::UpdateSettings),
10451046 Op::Security(SecurityOp::GetWorkspaceSettings),
+46−0
92869286 "deny_licenses": []
92879287 }
92889288 },
9289+ "rerun_dependency_review": {
9290+ "params": {
9291+ "owner": "flagon-io",
9292+ "name": "hello",
9293+ "number": 12
9294+ },
9295+ "response": {
9296+ "base": "4b1d7c0e9a2f6e3d8c5b0a7f4e1d2c3b4a5f6e7d",
9297+ "head": "9f2c4e1a7b3d5f60812a4c6e8b0d2f4a6c8e0b13",
9298+ "changes": [
9299+ {
9300+ "change_type": "added",
9301+ "manifest": "package-lock.json",
9302+ "ecosystem": "npm",
9303+ "name": "lodash",
9304+ "version": "4.17.21",
9305+ "relationship": "direct",
9306+ "development": false,
9307+ "license": "MIT",
9308+ "purl": "pkg:npm/lodash@4.17.21",
9309+ "vulnerabilities": [],
9310+ "denied_license": false,
9311+ "failing": false
9312+ },
9313+ {
9314+ "change_type": "removed",
9315+ "manifest": "package-lock.json",
9316+ "ecosystem": "npm",
9317+ "name": "lodash",
9318+ "version": "4.17.20",
9319+ "relationship": "direct",
9320+ "development": false,
9321+ "license": "MIT",
9322+ "purl": "pkg:npm/lodash@4.17.20",
9323+ "vulnerabilities": [],
9324+ "denied_license": false,
9325+ "failing": false
9326+ }
9327+ ],
9328+ "passed": true,
9329+ "headline": "2 dependency changes, none vulnerable",
9330+ "fail_on": "high",
9331+ "deny_licenses": []
9332+ },
9333+ "notes": "The pull request's one dependency review comment is changed in place; nothing new is posted when the review says the same."
9334+ },
92899335 "get_security_settings": {
92909336 "params": {
92919337 "owner": "flagon-io",
+1−0
332332 route("GET", "/repos/:owner/:name/dependency-graph", Op::Security(SecurityOp::GetDependencyGraph), &[]),
333333 route("GET", "/repos/:owner/:name/dependency-graph/sbom", Op::Security(SecurityOp::GetSbom), &[]),
334334 route("GET", "/repos/:owner/:name/dependency-graph/compare/:basehead", Op::Security(SecurityOp::CompareDependencies), &[]),
335+ route("POST", "/repos/:owner/:name/pulls/:number/dependency-review/rerun", Op::Security(SecurityOp::RerunDependencyReview), &[]),
335336 route("GET", "/repos/:owner/:name/security/settings", Op::Security(SecurityOp::GetSettings), &[]),
336337 route("PATCH", "/repos/:owner/:name/security/settings", Op::Security(SecurityOp::UpdateSettings), &[]),
337338 route("GET", "/workspaces/:workspace/security/settings", Op::Security(SecurityOp::GetWorkspaceSettings), &[]),
+18−1
5454 GetDependencyGraph,
5555 GetSbom,
5656 CompareDependencies,
57+ RerunDependencyReview,
5758 GetSettings,
5859 UpdateSettings,
5960 GetWorkspaceSettings,
7374 /// Every one: `Op::ALL` lists each as `Op::Security(…)`, which a test
7475 /// checks against this.
7576 #[cfg(test)]
76− pub const ALL: [SecurityOp; 38] = [
77+ pub const ALL: [SecurityOp; 39] = [
7778 SecurityOp::ListSecretAlerts,
7879 SecurityOp::GetSecretAlert,
7980 SecurityOp::UpdateSecretAlert,
99100 SecurityOp::GetDependencyGraph,
100101 SecurityOp::GetSbom,
101102 SecurityOp::CompareDependencies,
103+ SecurityOp::RerunDependencyReview,
102104 SecurityOp::GetSettings,
103105 SecurityOp::UpdateSettings,
104106 SecurityOp::GetWorkspaceSettings,
141143 SecurityOp::GetDependencyGraph => "get_dependency_graph",
142144 SecurityOp::GetSbom => "get_sbom",
143145 SecurityOp::CompareDependencies => "compare_dependencies",
146+ SecurityOp::RerunDependencyReview => "rerun_dependency_review",
144147 SecurityOp::GetSettings => "get_security_settings",
145148 SecurityOp::UpdateSettings => "update_security_settings",
146149 SecurityOp::GetWorkspaceSettings => "get_workspace_security_settings",
185188 SecurityOp::GetDependencyGraph => "Get the dependency graph",
186189 SecurityOp::GetSbom => "Export an SBOM",
187190 SecurityOp::CompareDependencies => "Compare dependencies",
191+ SecurityOp::RerunDependencyReview => "Re-run dependency review",
188192 SecurityOp::GetSettings => "Get a repository's security settings",
189193 SecurityOp::UpdateSettings => "Update a repository's security settings",
190194 SecurityOp::GetWorkspaceSettings => "Get a workspace's security settings",
228232 SecurityOp::GetDependencyGraph => "Get the dependency graph: every package the lockfiles on the default branch resolve, per lockfile, with whether it is direct or transitive (where the lockfile says), for development, its license when recorded, its package URL and its open vulnerability alerts.",
229233 SecurityOp::GetSbom => "Export the dependency graph as an SPDX 2.3 JSON document, in sbom. Every package is named by its package URL.",
230234 SecurityOp::CompareDependencies => "Compare the dependencies at two commits, branches or tags (basehead, as base...head): what was added and removed per lockfile, with the known vulnerabilities of what was added and whether it passes the repository's dependency review policy. Needs the g1t plan on a private repository.",
235+ SecurityOp::RerunDependencyReview => "Run the Dependency review check again on an open pull request's head, with the advisories known now: its status is set again and its one comment on the pull request is changed in place (or added, when it has dependency changes to show and none is there yet). Returns the review. g1t reviews a pull request by itself when it opens and when its head moves, carrying the result over when the head's lockfile changes are the ones reviewed before. Takes the Write role. Needs the g1t plan on a private repository.",
231236 SecurityOp::GetSettings => "Get a repository's security settings: when the Code scanning check fails, dependency review and its policy, whether secrets in the history open alerts, whether it takes private vulnerability reports, its workspace's settings, and whether the paid features are on for it.",
232237 SecurityOp::UpdateSettings => "Change a repository's security settings: code_scanning_gate (none, errors, critical, high, medium or any), dependency_review, review_fail_on (critical, high, medium, low or none), review_deny_licenses (SPDX ids), review_comment, history_alerts (whether secrets found in the history open alerts; push protection stays on) and private_vulnerability_reporting. Takes the Admin role, or a security manager of the workspace. Require the Code scanning and Dependency review checks in branch protection to gate merges on them.",
233238 SecurityOp::GetWorkspaceSettings => "Get a workspace's security settings (delegated bypass, validity checks) and whether it has Security and quality, which comes with the g1t plan.",
428433 &["repo", "id", "state"],
429434 ),
430435 SecurityOp::GetDependencyGraph | SecurityOp::GetSbom | SecurityOp::GetSettings => (json!({ "repo": repo() }), &["repo"]),
436+ SecurityOp::RerunDependencyReview => (
437+ json!({ "repo": repo(), "number": { "type": "integer", "description": "The pull request's number." } }),
438+ &["repo", "number"],
439+ ),
431440 SecurityOp::CompareDependencies => (
432441 json!({
433442 "repo": repo(),
10371046 };
10381047 pass(services, "dependency_review", &DependencyReviewArgs { viewer: viewer.clone(), repo, base, head }).await
10391048 }
1049+ SecurityOp::RerunDependencyReview => {
1050+ let Some(repo) = repo else { return failed(FailureCode::Invalid, &need_repo()) };
1051+ let Some(number) = whole(input, "number").filter(|number| *number > 0) else {
1052+ return failed(FailureCode::Invalid, "number is the pull request's number.");
1053+ };
1054+ pass(services, "rerun_dependency_review", &PullScanningArgs { viewer: viewer.clone(), repo, number }).await
1055+ }
10401056 SecurityOp::GetSettings => {
10411057 let Some(repo) = repo else { return failed(FailureCode::Invalid, &need_repo()) };
10421058 pass(services, "security_settings", &SecuritySettingsArgs { viewer: viewer.clone(), repo }).await
12411257 }
12421258 assert_eq!(scope_for("get_sbom"), Some(Scope::SecurityRead));
12431259 assert_eq!(scope_for("upload_sarif"), Some(Scope::SecurityWrite));
1260+ assert_eq!(scope_for("rerun_dependency_review"), Some(Scope::SecurityWrite));
12441261 // No run decides about security.
12451262 for name in ["bypass_push_protection", "review_bypass_request", "update_security_settings"] {
12461263 assert!(g1t_contracts::credentials::NEVER.contains(&name), "{name}");
+1−0
515515 a("dependency_graph", Op::Security(SecurityOp::GetDependencyGraph), "Every package the lockfiles resolve, direct or transitive"),
516516 a("sbom", Op::Security(SecurityOp::GetSbom), "The dependency graph as an SPDX 2.3 document"),
517517 a("compare_dependencies", Op::Security(SecurityOp::CompareDependencies), "What changes in dependencies between base...head"),
518+ a("rerun_dependency_review", Op::Security(SecurityOp::RerunDependencyReview), "Run a pull request's Dependency review check again"),
518519 a("settings", Op::Security(SecurityOp::GetSettings), "A repository's security settings"),
519520 a("update_settings", Op::Security(SecurityOp::UpdateSettings), "Change when checks fail and dependency review's policy"),
520521 a("workspace_settings", Op::Security(SecurityOp::GetWorkspaceSettings), "A workspace's delegated bypass and validity checks"),
+3−3
713713 checksum = "d045de693cb712d0b22c6a64be5b953f67b3ce00ab5ad3dd5d8b441886ab8e1a"
714714 dependencies = [
715715 "quote",
716− "syn 3.0.7",
716+ "syn 2.0.119",
717717 ]
718718
719719 [[package]]
34873487
34883488 [[package]]
34893489 name = "rustls"
3490−version = "0.23.45"
3490+version = "0.23.46"
34913491 source = "registry+https://github.com/rust-lang/crates.io-index"
3492−checksum = "0d41d731c7d2f962d1ccc364cec258de3c0e93b38c2fb3ba97ac74513048d634"
3492+checksum = "48e13bd8c0e9365c43cfa5c9e8f9ad49d3c8444926c9aac819e0e4dc503c8fdf"
34933493 dependencies = [
34943494 "once_cell",
34953495 "ring",
+11−1
527527
528528 A pull request that only has fallen behind its target, without conflicts,
529529 still merges: merging brings it up to date first, unless the repository
530−requires pull requests to be up to date.
530+requires pull requests to be up to date. Bringing it up to date makes a new
531+commit, so when the target branch requires checks, the merge box says
532+**Merging once checks pass** and the pull request lands when the required
533+checks pass on that commit, with nothing more to press. If one of them fails
534+there, it is not merged: the pull request says which check failed, and you
535+push a fix and merge again. With no required checks, it lands as soon as it
536+is up to date.
537+
538+If the repository does not answer in time while merging, the merge box says
539+g1t couldn't reach the repository. Merge again: if the first merge went
540+through meanwhile, merging again records it rather than merging twice.
531541
532542 ## Keeping it up to date
533543
+13−1
8080 `g1t.sh/<owner>/<project>/security/pulls/<number>`. Require **Dependency
8181 review** under **Settings → Branches** to block merges on it.
8282
83+A pull request has one review comment. When a later review says something
84+new, that comment is changed in place; when it says the same, the comment
85+is left alone. A push whose lockfile changes are the ones already reviewed,
86+such as catching the branch up with its base, is not reviewed again: the
87+check's result carries over to the new head.
88+
89+Advisories are published every day, so a review can be made again: **Re-run
90+dependency review** in the pull request's checks, or **Re-run** on its
91+Security checks page, takes the Write role. It updates the check and the
92+same comment.
93+
8394 In the repository's **Security settings**:
8495
8596 | Setting | Default | |
115126 | `GET /repos/{owner}/{name}/dependency-graph` | The graph, per lockfile. | `security:read` |
116127 | `GET /repos/{owner}/{name}/dependency-graph/sbom` | The SPDX document, in `sbom`. | `security:read` |
117128 | `GET /repos/{owner}/{name}/dependency-graph/compare/{basehead}` | What changed between two commits, and whether it passes review. | `security:read` |
129+| `POST /repos/{owner}/{name}/pulls/{number}/dependency-review/rerun` | Runs a pull request's Dependency review check again and returns the review. | `security:write` |
118130 | `GET /repos/{owner}/{name}/vulnerability-alerts` | Lists alerts; filter with `state`, `severity`, `ecosystem`, `package`. | `security:read` |
119131 | `GET /workspaces/{workspace}/vulnerability-alerts` | The same across a workspace. | `security:read` |
120132 | `GET /repos/{owner}/{name}/vulnerability-alerts/{id}` | One alert. | `security:read` |
121133 | `PATCH /repos/{owner}/{name}/vulnerability-alerts/{id}` | `state` `dismissed` with `reason` and `comment`, or `open`. | `security:write` |
122134
123135 Over MCP, the [`security` tool](/reference/mcp/#security) has
124−`dependency_graph`, `sbom`, `compare_dependencies`, `vulnerability_alerts`,
136+`dependency_graph`, `sbom`, `compare_dependencies`, `rerun_dependency_review`, `vulnerability_alerts`,
125137 `vulnerability_alert` and `update_vulnerability_alert`. Webhooks:
126138 `vulnerability_alert.created`, `.fixed`, `.dismissed` and `.reopened`.
+1−0
618618 | [`dependency_graph`](/reference/api/supply-chain/get-dependency-graph/) | Every package the lockfiles resolve, direct or transitive, with licenses. | `repo` | `security:read` |
619619 | [`sbom`](/reference/api/supply-chain/get-sbom/) | The dependency graph as an SPDX 2.3 document, in `sbom`. | `repo` | `security:read` |
620620 | [`compare_dependencies`](/reference/api/supply-chain/compare-dependencies/) | What changes between `basehead` (`base...head`), and whether it passes dependency review. | `repo`, `basehead` | `security:read` |
621+| [`rerun_dependency_review`](/reference/api/supply-chain/rerun-dependency-review/) | Runs a pull request's Dependency review check again; its one comment changes in place. Write role. | `repo`, `number` | `security:write` |
621622 | [`settings`](/reference/api/security-settings/get-security-settings/) | A repository's security settings, and whether the paid features are on. | `repo` | `security:read` |
622623 | [`update_settings`](/reference/api/security-settings/update-security-settings/) | Change `code_scanning_gate`, `dependency_review`, `review_fail_on`, `review_deny_licenses`, `review_comment`. Admin role, or a security manager. | `repo` | `security:write` |
623624 | [`workspace_settings`](/reference/api/security-settings/get-workspace-security-settings/) | A workspace's delegated bypass and validity checks. | `workspace` | `security:read` |
+0−0

Binary or large file; its contents are not shown.

+25−5
219219 );
220220 }
221221
222−/** Where one rule's review stands, in a few words. */
223−function ReviewStatus({ review }: { review: OwnerReview }) {
222+/**
223+ * Where one rule's review stands, in a few words. When the ruleset does not
224+ * require code owners' approval (`required` false), a rule nobody approved
225+ * yet is said plainly, not as something merging waits for.
226+ */
227+function ReviewStatus({ review, required }: { review: OwnerReview; required: boolean }) {
224228 const names = (list: string[]) => list.join(", ");
225229 if (review.changes_requested_by.length > 0) {
226230 return (
246250 </span>
247251 );
248252 }
253+ if (!required) {
254+ return (
255+ <span className="text-xs text-faint">
256+ {review.approved_by.length > 0 ? `Approved by ${names(review.approved_by)}` : "No approval yet"}
257+ </span>
258+ );
259+ }
249260 const more = Math.max(review.required - review.approved_by.length, 1);
250261 return (
251262 <span className="flex items-center gap-1 text-xs text-warn">
257268 }
258269
259270 /** One rule: its section, pattern and line, its owners, the files it covers, and where its review stands. */
260−function OwnerReviewRow({ review, fileHref }: { review: OwnerReview; fileHref: (line: number) => string }) {
271+function OwnerReviewRow({
272+ review,
273+ required,
274+ fileHref,
275+}: {
276+ review: OwnerReview;
277+ /** Whether the ruleset requires code owners' approval to merge. */
278+ required: boolean;
279+ fileHref: (line: number) => string;
280+}) {
261281 const many = review.files.length > 3;
262282 const files = (
263283 <ul className="mt-1 space-y-0.5">
277297 line {review.line}
278298 </Link>
279299 <span className="ml-auto">
280− <ReviewStatus review={review} />
300+ <ReviewStatus review={review} required={required} />
281301 </span>
282302 </div>
283303 <p className="mt-1 flex flex-wrap items-center gap-x-2 gap-y-0.5 text-xs text-muted">
343363 ) : (
344364 <ul className="divide-y divide-line">
345365 {owners.reviews.map((review) => (
346− <OwnerReviewRow key={`${review.section ?? ""}:${review.line}`} review={review} fileHref={fileHref} />
366+ <OwnerReviewRow key={`${review.section ?? ""}:${review.line}`} review={review} required={owners.required} fileHref={fileHref} />
347367 ))}
348368 </ul>
349369 )}
+24−1
2121 import { useMemo, useState } from "react";
2222 import { Form, Link } from "react-router";
2323
24−import type { CheckResult, CheckRun, CommitStatus, Job, Mergeable, Pull, RequiredCheck } from "@g1t/contracts";
24+import {
25+ type CheckResult,
26+ type CheckRun,
27+ type CommitStatus,
28+ DEPENDENCY_REVIEW_CHECK,
29+ type Job,
30+ type Mergeable,
31+ type Pull,
32+ type RequiredCheck,
33+} from "@g1t/contracts";
2534
2635 import { useAddresses } from "../lib/addresses";
2736 import { duration } from "./actions";
406415 pull.fork ? `/${pull.fork.namespace}/${pull.fork.name}/commit/${sha}` : `${base}/commit/${sha}`;
407416 const older = run != null && pull.headCommit != null && run.headCommit !== pull.headCommit;
408417 const failedRuns = statuses.filter((status) => statusStanding(status) === "failed" && runIdOf(status));
418+ // g1t's own Dependency review check, which anyone who may start runs can
419+ // have made again (after an advisory is published, say).
420+ const dependencyReview = statuses.some((status) => status.context === DEPENDENCY_REVIEW_CHECK && status.source === "security");
409421
410422 return (
411423 <div aria-busy={loading || undefined}>
458470 </Hint>
459471 </Form>
460472 ))}
473+ {canRerunWorkflows && dependencyReview && !settled && (
474+ <Form method="post">
475+ <input type="hidden" name="action" value="rerun-dependency-review" />
476+ <Hint label="Review its dependency changes again, with the advisories known now">
477+ <SubmitButton variant="outline" match={{ action: "rerun-dependency-review" }} pending="Re-running…">
478+ <RotateCw size={13} />
479+ Re-run dependency review
480+ </SubmitButton>
481+ </Hint>
482+ </Form>
483+ )}
461484 </div>
462485 </div>
463486 {error && (
+77−17
402402 );
403403 }
404404
405−/** Something that happened, as one line on the conversation's rail. */
406−function TimelineEvent({ comment, base, filesUrl }: { comment: Comment; base?: string; filesUrl?: string }) {
405+/**
406+ * One line on the conversation's rail: a mark on the rail (a dot unless
407+ * `mark` says otherwise), who, what and when.
408+ */
409+function TimelineRow({ comment, mark, children }: { comment: Comment; mark?: ReactNode; children: ReactNode }) {
407410 return (
408411 <div className="flex items-center gap-3 text-sm text-muted">
409412 <span className="hidden w-8 shrink-0 justify-center sm:flex">
410− <span className="size-2 rounded-full border border-line-strong bg-bg" />
413+ {mark ?? <span className="size-2 rounded-full border border-line-strong bg-bg" />}
411414 </span>
412415 <span className="flex min-w-0 flex-wrap items-center gap-x-1.5 gap-y-0.5">
413− <Avatar name={comment.author.username} size={16} />
414− <PersonLink name={comment.author.username} className="font-medium text-fg hover:underline" />
415− <span>
416− <WithReferences text={comment.body} base={base} filesUrl={filesUrl} />
417− </span>
416+ {comment.agent ? (
417+ <>
418+ <Avatar name={comment.agent.displayName} agent size={16} />
419+ <AgentName agent={comment.agent} />
420+ </>
421+ ) : (
422+ <>
423+ <Avatar name={comment.author.username} size={16} />
424+ <PersonLink name={comment.author.username} className="font-medium text-fg hover:underline" />
425+ </>
426+ )}
427+ {children}
428+ {comment.actingFor && (
429+ <span className="text-xs text-faint">
430+ on behalf of{" "}
431+ <PersonLink name={comment.actingFor.username} className="hover:text-fg hover:underline">
432+ @{comment.actingFor.username}
433+ </PersonLink>
434+ </span>
435+ )}
418436 <span className="text-faint">
419437 · <TimeAgo at={comment.createdAt} />
420438 </span>
423441 );
424442 }
425443
444+/** Something that happened, as one line on the conversation's rail. */
445+function TimelineEvent({ comment, base, filesUrl }: { comment: Comment; base?: string; filesUrl?: string }) {
446+ return (
447+ <TimelineRow comment={comment}>
448+ <span>
449+ <WithReferences text={comment.body} base={base} filesUrl={filesUrl} />
450+ </span>
451+ </TimelineRow>
452+ );
453+}
454+
455+/** A review's verdict, approved or changes requested, in its own colour. */
456+function Verdict({ verdict }: { verdict: keyof typeof VERDICTS }) {
457+ const { label, style } = VERDICTS[verdict];
458+ return (
459+ <span className={`flex items-center gap-1 font-medium ${style}`}>
460+ {verdict === "approve" ? <CircleCheck size={14} /> : <CircleSlash size={14} />}
461+ {label}
462+ </span>
463+ );
464+}
465+
466+/**
467+ * A review that gave a verdict and said nothing else: one line on the rail,
468+ * not a comment with an empty body. There is nothing to edit, and a
469+ * verdict is not deleted, so it has no actions.
470+ */
471+function TimelineVerdict({ comment, verdict }: { comment: Comment; verdict: keyof typeof VERDICTS }) {
472+ return (
473+ <TimelineRow
474+ comment={comment}
475+ mark={
476+ verdict === "approve" ? (
477+ <CircleCheck size={16} className="text-success" />
478+ ) : (
479+ <CircleSlash size={16} className="text-danger" />
480+ )
481+ }
482+ >
483+ <span className={VERDICTS[verdict].style}>{VERDICTS[verdict].label}</span>
484+ {comment.advisory && <AdvisoryChip />}
485+ </TimelineRow>
486+ );
487+}
488+
426489 /** Edit and Delete, under a comment the viewer may change. */
427490 function CommentActions({
428491 comment,
521584 if (comment.kind === "event") {
522585 return <TimelineEvent key={comment.id} comment={comment} base={base} filesUrl={review?.changesUrl} />;
523586 }
524− const verdict = comment.verdict && VERDICTS[comment.verdict];
587+ const verdict = comment.verdict;
588+ // A verdict with nothing said and no line it is about is one line, not a card.
589+ if (verdict && !comment.body.trim() && !comment.path) {
590+ return <TimelineVerdict key={comment.id} comment={comment} verdict={verdict} />;
591+ }
525592 const can = mayChangeComment(comment, viewerId, canModerate);
526593 return (
527594 <TimelineItem
537604 action={
538605 <>
539606 {verdict ? (
540− <span className={`flex items-center gap-1 font-medium ${verdict.style}`}>
541− {comment.verdict === "approve" ? (
542− <CircleCheck size={14} />
543− ) : (
544− <CircleSlash size={14} />
545− )}
546− {verdict.label}
547− </span>
607+ <Verdict verdict={verdict} />
548608 ) : (
549609 <span>{comment.advisory ? "reviewed" : "commented"}</span>
550610 )}
+39−7
3838 type RepoSettings,
3939 type SessionEntry,
4040 type Viewer,
41+ fail,
4142 pullComparison,
4243 workOwner,
4344 } from "@g1t/contracts";
8990 import { ViolationList } from "../../components/rules";
9091 import { PullCodeOwnersPanel, TeamReviewer } from "../../components/codeowners";
9192 import { notFound } from "../../lib/not-found.server";
92−import { actions, deployments, identity, inbox, repos, work } from "../../lib/services.server";
93+import { actions, deployments, identity, inbox, repos, securitySuite, work } from "../../lib/services.server";
9394 import { commitChecksFor } from "../../lib/commit-checks.server";
9495 import { CommitChecksBadge } from "../../components/commit-checks";
9596 import { DeployLink } from "../../components/deploy";
394395 const PULL_NEEDS: Record<string, Capability> = {
395396 "rerun-workflow": "run",
396397 "rerun-failed": "run",
398+ "rerun-dependency-review": "run",
397399 merge: "merge",
398400 unqueue: "merge",
399401 milestone: "triage",
487489 const rerun = await actions.rerun(user, path, String(form.get("run") ?? ""), true);
488490 return rerun.ok ? null : { error: rerun.error.message, action };
489491 }
492+ // The Dependency review check, made again on its head: its comment
493+ // changes in place. Who may is the security service's call.
494+ if (action === "rerun-dependency-review") {
495+ const rerun = await securitySuite.rerunDependencyReview(user, path, number);
496+ return rerun.ok ? null : { error: rerun.error.message, action };
497+ }
490498 // Every failed run on its head, run again: Mission control's quick action.
491499 if (action === "rerun-failed") {
492500 const found = await work.getPull(path, number, user);
516524 }
517525 const result =
518526 action === "merge"
519− ? await work.mergePull(user, path, number, mergeOptions(form))
527+ ? // A merge that did not answer in time (lib/deadline.ts) or broke
528+ // off says so in the merge box, not as an error page: trying again
529+ // is safe, as a merge that went through meanwhile is recognised.
530+ await work
531+ .mergePull(user, path, number, mergeOptions(form))
532+ .catch(() => fail("unavailable", "g1t couldn't reach the repository to merge this. Try again in a moment."))
520533 : action === "unqueue"
521534 ? await work.removeFromQueue(user, path, number)
522535 : action === "close"
688701 );
689702 }
690703
704+/** The required checks a caught-up merge still waits for: "CI passes", "CI and Deploy pass". */
705+function awaitedChecks(checks: { name: string; state: string }[]): string {
706+ const names = checks.filter((check) => check.state !== "success").map((check) => check.name);
707+ if (names.length === 0) return "the required checks pass";
708+ if (names.length === 1) return `the required check ${names[0]} passes`;
709+ return `the required checks ${names.slice(0, -1).join(", ")} and ${names[names.length - 1]} pass`;
710+}
711+
691712 export default function PullPage({ loaderData, actionData, params }: Route.ComponentProps) {
692713 const {
693714 pull,
753774 canRerunWorkflows={canMerge}
754775 settingsUrl={canProtect ? `${base}/settings/branches` : null}
755776 conflicting={mergeable === "conflicting" && !noChecks}
756− error={actionData?.action === "rerun-failed" || actionData?.action === "rerun-workflow" ? actionData.error : null}
777+ error={actionData?.action === "rerun-failed" || actionData?.action === "rerun-workflow" || actionData?.action === "rerun-dependency-review"
778+ ? actionData.error
779+ : null}
757780 loading={loading}
758781 />
759782 );
12671290 </StatusRow>
12681291 )}
12691292 {landing ? (
1293+ // Brought up to date first, then landed once the
1294+ // required checks pass on that new commit.
12701295 <StatusRow
12711296 icon={<Loader size={16} className="animate-spin text-accent" />}
1272− title="Merging"
1297+ title={!behind && unchecked ? "Merging once checks pass" : "Merging"}
12731298 >
1274− {defaultBranch} has moved, so it is being brought up to date first. It
1275− lands as soon as that is done.
1299+ {behind
1300+ ? `${defaultBranch} has moved, so it is being brought up to date first. ${
1301+ requiredChecks.length > 0
1302+ ? "It lands once the required checks pass on that commit."
1303+ : "It lands as soon as that is done."
1304+ }`
1305+ : unchecked
1306+ ? `Brought up to date with ${defaultBranch}. It lands once ${awaitedChecks(requiredChecks)} on that commit.`
1307+ : `Brought up to date with ${defaultBranch}. Landing now.`}
12761308 </StatusRow>
12771309 ) : conflicting ? (
12781310 <ConflictsSection
14491481 </Form>
14501482 )}
14511483 {actionData &&
1452− !["merge", "comment", "rerun-failed", "rerun-workflow", "update", "reviewers", "assign", "edit-comment", "delete-comment", "auto-merge-on", "auto-merge-off", "delete-branch", "restore-branch"].includes(
1484+ !["merge", "comment", "rerun-failed", "rerun-workflow", "rerun-dependency-review", "update", "reviewers", "assign", "edit-comment", "delete-comment", "auto-merge-on", "auto-merge-off", "delete-branch", "restore-branch"].includes(
14531485 String(actionData.action),
14541486 ) && <ErrorText>{actionData.error}</ErrorText>}
14551487 </div>
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…

Comments

Sign in to comment.