Merge commit '9232a85b'
45 files+209−3729 files not counted0/45 viewed
| 3398 | 3398 | ||
| 3399 | 3399 | [[package]] | |
| 3400 | 3400 | name = "rustls" | |
| 3401 | − | version = "0.23.45" | |
| 3401 | + | version = "0.23.46" | |
| 3402 | 3402 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 3403 | − | checksum = "0d41d731c7d2f962d1ccc364cec258de3c0e93b38c2fb3ba97ac74513048d634" | |
| 3403 | + | checksum = "48e13bd8c0e9365c43cfa5c9e8f9ad49d3c8444926c9aac819e0e4dc503c8fdf" | |
| 3404 | 3404 | dependencies = [ | |
| 3405 | 3405 | "aws-lc-rs", | |
| 3406 | 3406 | "log", |
| 275 | 275 | Op::Security(SecurityOp::GetDependencyGraph), | |
| 276 | 276 | Op::Security(SecurityOp::GetSbom), | |
| 277 | 277 | Op::Security(SecurityOp::CompareDependencies), | |
| 278 | + | Op::Security(SecurityOp::RerunDependencyReview), | |
| 278 | 279 | ], | |
| 279 | 280 | ), | |
| 280 | 281 | ( |
| 808 | 808 | } | |
| 809 | 809 | ||
| 810 | 810 | impl Op { | |
| 811 | − | pub const ALL: [Op; 402] = [ | |
| 811 | + | pub const ALL: [Op; 403] = [ | |
| 812 | 812 | Op::Whoami, | |
| 813 | 813 | Op::GetWorkspace, | |
| 814 | 814 | Op::CreateWorkspace, | |
| ⋯ | |||
| 1041 | 1041 | Op::Security(SecurityOp::GetDependencyGraph), | |
| 1042 | 1042 | Op::Security(SecurityOp::GetSbom), | |
| 1043 | 1043 | Op::Security(SecurityOp::CompareDependencies), | |
| 1044 | + | Op::Security(SecurityOp::RerunDependencyReview), | |
| 1044 | 1045 | Op::Security(SecurityOp::GetSettings), | |
| 1045 | 1046 | Op::Security(SecurityOp::UpdateSettings), | |
| 1046 | 1047 | Op::Security(SecurityOp::GetWorkspaceSettings), | |
| ⋯ | |||
| 1586 | 1587 | "Search all of g1t: repositories (name, description, topics, README), code on default branches (file names and contents), issues, pull requests, people and workspaces. Covers everything public, and private content in workspaces you belong to; signed out, public only. Write words, \"exact phrases\", -words to leave out, and qualifiers: repo:owner/name, org:workspace, language:rust, path:src/ (a glob with *), is:issue, is:pr, is:open, is:closed, is:merged, author:username, label:bug. type picks the kind of results (repositories, code, issues, pulls or people); without it, the qualifiers decide. Returns one page of results with the matches highlighted, code with line numbers, and how many there are of each kind." | |
| 1587 | 1588 | } | |
| 1588 | 1589 | Op::GetMergeQueue => { | |
| 1589 | − | "A repository's merge queue: the pull requests waiting to land, in order, each with the state it is being tested in (the default branch with the pull requests ahead of it merged in) and how that went; then those that recently landed or left. With the queue on, merging a pull request adds it here." | |
| 1590 | + | "A repository's merge queue: the pull requests waiting to land, in order, each with the state it is being tested in (the default branch with the pull requests ahead of it landed in turn, each by its merge method) and how that went; then those that recently landed or left. With the queue on, merging a pull request adds it here." | |
| 1590 | 1591 | } | |
| 1591 | 1592 | Op::CreateRepo => { | |
| 1592 | 1593 | "Create a repository in one of your workspaces, empty or as a copy of a public git repository elsewhere." | |
| ⋯ | |||
| 1694 | 1695 | "What a pull request changes: the files it touches and their line-by-line diff against the commit it started from. Use it to review a pull request or to compare several made for the same issue. A large one lists every file (up to 300; truncated says when there were more) but has the lines of only the first, about 20,000 lines read: the files after that come with omitted true and no lines or counts. Ask again with paths (up to 50 at a time) to have those files' lines." | |
| 1695 | 1696 | } | |
| 1696 | 1697 | Op::MergePullRequest => { | |
| 1697 | − | "Land a pull request on its base, the branch it merges into (the default branch unless it names another). merge_method says how: merge (a merge commit joins its branch to the base, its commits included), squash (one new commit holds everything it changed), rebase (each of its commits is made again on top of the base, in order) or fast_forward (the base moves to its head, exactly the commit that was checked; only when its branch contains the base's head). Left out, the repository's default_merge_method. A method the repository does not allow (allow_merge_commit, allow_squash_merge, allow_rebase_merge, allow_fast_forward on update_repo_settings), or that a rule for the base does not (allowed_merge_methods, and required linear history, which refuses merge), is refused. commit_title and commit_message are a merge or squash commit's message: its first line and the rest; left out, the repository's merge_commit_message or squash_commit_message decides. With sha, the merge is refused (409) if the pull request's head is no longer that commit. delete_branch deletes its branch once it has merged (left out, the repository's delete_branch_on_merge); restore_pull_request_head_branch brings it back. Merging needs the Write role or higher, and only once it is marked ready and it meets every rule that holds for its base (see rules and required_checks on get_pull_request: approvals, checks, deployments, merge windows and the rest, from the repository's and its workspace's rulesets); the refusal names the first rule not met. With ignore_checks, someone who may merge can bypass required checks where the rule allows it; with bypass_rules, someone a ruleset lists as a bypass actor merges past its rules, and it is recorded. Merging into the default branch closes the issues in its closes: the issue it was made for and those its title and description name after a closing keyword (Fixes #12), where the repository closes issues (auto_close_issues); the other pull requests still in progress for its issue close as superseded. Merging into another branch closes nothing. Where the repository has a merge queue, a pull request into the default branch joins the queue instead of landing at once. If its base has moved since the pull request was opened, it is brought up to date first and lands when that is done; a repository that requires pull requests into its default branch to be up to date refuses instead: update_pull_request_branch, then merge again. Check status in the result to see whether it has landed, and merge_method and merge_commit for how." | |
| 1698 | + | "Land a pull request on its base, the branch it merges into (the default branch unless it names another). merge_method says how: merge (a merge commit joins its branch to the base, its commits included), squash (one new commit holds everything it changed), rebase (each of its commits is made again on top of the base, in order) or fast_forward (the base moves to its head, exactly the commit that was checked; only when its branch contains the base's head). Left out, the repository's default_merge_method. A method the repository does not allow (allow_merge_commit, allow_squash_merge, allow_rebase_merge, allow_fast_forward on update_repo_settings), or that a rule for the base does not (allowed_merge_methods, and required linear history, which refuses merge), is refused. commit_title and commit_message are a merge or squash commit's message: its first line and the rest; left out, the repository's merge_commit_message or squash_commit_message decides. With sha, the merge is refused (409) if the pull request's head is no longer that commit. delete_branch deletes its branch once it has merged (left out, the repository's delete_branch_on_merge); restore_pull_request_head_branch brings it back. Merging needs the Write role or higher, and only once it is marked ready and it meets every rule that holds for its base (see rules and required_checks on get_pull_request: approvals, checks, deployments, merge windows and the rest, from the repository's and its workspace's rulesets); the refusal names the first rule not met. With ignore_checks, someone who may merge can bypass required checks where the rule allows it; with bypass_rules, someone a ruleset lists as a bypass actor merges past its rules, and it is recorded. Merging into the default branch closes the issues in its closes: the issue it was made for and those its title and description name after a closing keyword (Fixes #12), where the repository closes issues (auto_close_issues); the other pull requests still in progress for its issue close as superseded. Merging into another branch closes nothing. Where the repository has a merge queue, a pull request into the default branch joins the queue instead of landing at once, to be tested and landed by merge_method (the merge queue rule's when absent) with the commit message given. If its base has moved since the pull request was opened, it is brought up to date first and lands when that is done; a repository that requires pull requests into its default branch to be up to date refuses instead: update_pull_request_branch, then merge again. Check status in the result to see whether it has landed, and merge_method and merge_commit for how." | |
| 1698 | 1699 | } | |
| 1699 | 1700 | Op::GetPullRequestMergeOptions => { | |
| 1700 | 1701 | "How a pull request may merge now, as the merge box offers it to you: the methods it may merge by (the repository's, as the rules for its base allow them) and the one offered first (default_method); each method's default commit title and message, and why one cannot be used now (unavailable); whether it is behind its base, can fast-forward as it is, should be offered an update (suggest_update) or must be up to date (require_up_to_date); whether it goes through the merge queue; whether you may turn on auto-merge; whether deleting its branch starts ticked and is possible; and whether commits made merging it are signed off." | |
| 9286 | 9286 | "deny_licenses": [] | |
| 9287 | 9287 | } | |
| 9288 | 9288 | }, | |
| 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 | + | }, | |
| 9289 | 9335 | "get_security_settings": { | |
| 9290 | 9336 | "params": { | |
| 9291 | 9337 | "owner": "flagon-io", |
| 332 | 332 | route("GET", "/repos/:owner/:name/dependency-graph", Op::Security(SecurityOp::GetDependencyGraph), &[]), | |
| 333 | 333 | route("GET", "/repos/:owner/:name/dependency-graph/sbom", Op::Security(SecurityOp::GetSbom), &[]), | |
| 334 | 334 | 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), &[]), | |
| 335 | 336 | route("GET", "/repos/:owner/:name/security/settings", Op::Security(SecurityOp::GetSettings), &[]), | |
| 336 | 337 | route("PATCH", "/repos/:owner/:name/security/settings", Op::Security(SecurityOp::UpdateSettings), &[]), | |
| 337 | 338 | route("GET", "/workspaces/:workspace/security/settings", Op::Security(SecurityOp::GetWorkspaceSettings), &[]), |
| 54 | 54 | GetDependencyGraph, | |
| 55 | 55 | GetSbom, | |
| 56 | 56 | CompareDependencies, | |
| 57 | + | RerunDependencyReview, | |
| 57 | 58 | GetSettings, | |
| 58 | 59 | UpdateSettings, | |
| 59 | 60 | GetWorkspaceSettings, | |
| ⋯ | |||
| 73 | 74 | /// Every one: `Op::ALL` lists each as `Op::Security(…)`, which a test | |
| 74 | 75 | /// checks against this. | |
| 75 | 76 | #[cfg(test)] | |
| 76 | − | pub const ALL: [SecurityOp; 38] = [ | |
| 77 | + | pub const ALL: [SecurityOp; 39] = [ | |
| 77 | 78 | SecurityOp::ListSecretAlerts, | |
| 78 | 79 | SecurityOp::GetSecretAlert, | |
| 79 | 80 | SecurityOp::UpdateSecretAlert, | |
| ⋯ | |||
| 99 | 100 | SecurityOp::GetDependencyGraph, | |
| 100 | 101 | SecurityOp::GetSbom, | |
| 101 | 102 | SecurityOp::CompareDependencies, | |
| 103 | + | SecurityOp::RerunDependencyReview, | |
| 102 | 104 | SecurityOp::GetSettings, | |
| 103 | 105 | SecurityOp::UpdateSettings, | |
| 104 | 106 | SecurityOp::GetWorkspaceSettings, | |
| ⋯ | |||
| 141 | 143 | SecurityOp::GetDependencyGraph => "get_dependency_graph", | |
| 142 | 144 | SecurityOp::GetSbom => "get_sbom", | |
| 143 | 145 | SecurityOp::CompareDependencies => "compare_dependencies", | |
| 146 | + | SecurityOp::RerunDependencyReview => "rerun_dependency_review", | |
| 144 | 147 | SecurityOp::GetSettings => "get_security_settings", | |
| 145 | 148 | SecurityOp::UpdateSettings => "update_security_settings", | |
| 146 | 149 | SecurityOp::GetWorkspaceSettings => "get_workspace_security_settings", | |
| ⋯ | |||
| 185 | 188 | SecurityOp::GetDependencyGraph => "Get the dependency graph", | |
| 186 | 189 | SecurityOp::GetSbom => "Export an SBOM", | |
| 187 | 190 | SecurityOp::CompareDependencies => "Compare dependencies", | |
| 191 | + | SecurityOp::RerunDependencyReview => "Re-run dependency review", | |
| 188 | 192 | SecurityOp::GetSettings => "Get a repository's security settings", | |
| 189 | 193 | SecurityOp::UpdateSettings => "Update a repository's security settings", | |
| 190 | 194 | SecurityOp::GetWorkspaceSettings => "Get a workspace's security settings", | |
| ⋯ | |||
| 228 | 232 | 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.", | |
| 229 | 233 | SecurityOp::GetSbom => "Export the dependency graph as an SPDX 2.3 JSON document, in sbom. Every package is named by its package URL.", | |
| 230 | 234 | 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.", | |
| 231 | 236 | 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.", | |
| 232 | 237 | 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.", | |
| 233 | 238 | 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.", | |
| ⋯ | |||
| 428 | 433 | &["repo", "id", "state"], | |
| 429 | 434 | ), | |
| 430 | 435 | 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 | + | ), | |
| 431 | 440 | SecurityOp::CompareDependencies => ( | |
| 432 | 441 | json!({ | |
| 433 | 442 | "repo": repo(), | |
| ⋯ | |||
| 1037 | 1046 | }; | |
| 1038 | 1047 | pass(services, "dependency_review", &DependencyReviewArgs { viewer: viewer.clone(), repo, base, head }).await | |
| 1039 | 1048 | } | |
| 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 | + | } | |
| 1040 | 1056 | SecurityOp::GetSettings => { | |
| 1041 | 1057 | let Some(repo) = repo else { return failed(FailureCode::Invalid, &need_repo()) }; | |
| 1042 | 1058 | pass(services, "security_settings", &SecuritySettingsArgs { viewer: viewer.clone(), repo }).await | |
| ⋯ | |||
| 1241 | 1257 | } | |
| 1242 | 1258 | assert_eq!(scope_for("get_sbom"), Some(Scope::SecurityRead)); | |
| 1243 | 1259 | assert_eq!(scope_for("upload_sarif"), Some(Scope::SecurityWrite)); | |
| 1260 | + | assert_eq!(scope_for("rerun_dependency_review"), Some(Scope::SecurityWrite)); | |
| 1244 | 1261 | // No run decides about security. | |
| 1245 | 1262 | for name in ["bypass_push_protection", "review_bypass_request", "update_security_settings"] { | |
| 1246 | 1263 | assert!(g1t_contracts::credentials::NEVER.contains(&name), "{name}"); | |
| 515 | 515 | a("dependency_graph", Op::Security(SecurityOp::GetDependencyGraph), "Every package the lockfiles resolve, direct or transitive"), | |
| 516 | 516 | a("sbom", Op::Security(SecurityOp::GetSbom), "The dependency graph as an SPDX 2.3 document"), | |
| 517 | 517 | 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"), | |
| 518 | 519 | a("settings", Op::Security(SecurityOp::GetSettings), "A repository's security settings"), | |
| 519 | 520 | a("update_settings", Op::Security(SecurityOp::UpdateSettings), "Change when checks fail and dependency review's policy"), | |
| 520 | 521 | a("workspace_settings", Op::Security(SecurityOp::GetWorkspaceSettings), "A workspace's delegated bypass and validity checks"), |
| 713 | 713 | checksum = "d045de693cb712d0b22c6a64be5b953f67b3ce00ab5ad3dd5d8b441886ab8e1a" | |
| 714 | 714 | dependencies = [ | |
| 715 | 715 | "quote", | |
| 716 | − | "syn 3.0.7", | |
| 716 | + | "syn 2.0.119", | |
| 717 | 717 | ] | |
| 718 | 718 | ||
| 719 | 719 | [[package]] | |
| ⋯ | |||
| 3487 | 3487 | ||
| 3488 | 3488 | [[package]] | |
| 3489 | 3489 | name = "rustls" | |
| 3490 | − | version = "0.23.45" | |
| 3490 | + | version = "0.23.46" | |
| 3491 | 3491 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 3492 | − | checksum = "0d41d731c7d2f962d1ccc364cec258de3c0e93b38c2fb3ba97ac74513048d634" | |
| 3492 | + | checksum = "48e13bd8c0e9365c43cfa5c9e8f9ad49d3c8444926c9aac819e0e4dc503c8fdf" | |
| 3493 | 3493 | dependencies = [ | |
| 3494 | 3494 | "once_cell", | |
| 3495 | 3495 | "ring", | |
| 156 | 156 | [The merge queue](/guides/merge-queue/#who-answers-for-it) has the table of | |
| 157 | 157 | outcomes from a person's side. | |
| 158 | 158 | ||
| 159 | − | <Aside title="What a merge queue needs"> | |
| 160 | − | Building candidates takes sandboxes, so the merge queue needs a paid or | |
| 161 | − | trial workspace, or the open-source pool for a public repository. | |
| 159 | + | <Aside title="How states are built"> | |
| 160 | + | `work` builds each candidate in the repository itself, through repos' | |
| 161 | + | `build_queue_step`: the entry landed by its own merge method on the | |
| 162 | + | state ahead of it, with the commits `land` would make there, pushed to | |
| 163 | + | the attempt's `g1t-queue/` branch. Landing is then a fast-forward to | |
| 164 | + | exactly that commit, and no sandbox is involved. | |
| 162 | 165 | [The merge queue](/guides/merge-queue/) has the settings. | |
| 163 | 166 | </Aside> | |
| 164 | 167 |
| 9 | 9 | on, a pull request is tested together with everything ahead of it before it | |
| 10 | 10 | lands, and `main` only ever moves to a state whose required checks passed. | |
| 11 | 11 | ||
| 12 | − | The merge queue runs in g1t's sandboxes, which need | |
| 13 | − | [the g1t plan](/guides/usage-and-billing/#the-g1t-plan) or the trial after | |
| 14 | − | a card check; a public repository can use the open-source pool instead. | |
| 15 | − | Without one, an entry fails at once with a message saying so; turn the | |
| 16 | − | queue off to merge directly. | |
| 12 | + | g1t builds the queue's states in the repository itself, without a | |
| 13 | + | sandbox. What checks them is the repository's `merge_group` | |
| 14 | + | [workflows](/guides/actions/), which run and are billed like any other | |
| 15 | + | workflow run. | |
| 17 | 16 | ||
| 18 | 17 | ## Turn it on | |
| 19 | 18 | ||
| ⋯ | |||
| 60 | 59 | ||
| 61 | 60 | ## How entries are tested | |
| 62 | 61 | ||
| 63 | − | g1t takes up to four entries (the rule's `max_entries_to_build`) from the front of the queue and tests them all | |
| 64 | − | at once, speculatively, each in its own sandbox. Each sandbox builds `main` | |
| 65 | − | with that entry and every entry ahead of it merged in, in queue order: | |
| 62 | + | g1t takes up to four entries (the rule's `max_entries_to_build`) from the | |
| 63 | + | front of the queue and tests them all at once, speculatively. Each entry's | |
| 64 | + | state is the one before it with that entry landed on top, in queue order: | |
| 66 | 65 | ||
| 67 | 66 | | Entry | Tested as | | |
| 68 | 67 | | --- | --- | | |
| ⋯ | |||
| 71 | 70 | | 3rd | `main` + #41 + #44 + #46 | | |
| 72 | 71 | | 4th | `main` + #41 + #44 + #46 + #47 | | |
| 73 | 72 | ||
| 73 | + | Each entry is landed on the state ahead of it by its own | |
| 74 | + | [merge method](/guides/pull-requests/#merge-methods), exactly as merging it | |
| 75 | + | there would: | |
| 76 | + | ||
| 77 | + | - **Merge commit**: a merge commit, with the message for the repository's | |
| 78 | + | merge commits or the one written when it was queued. | |
| 79 | + | - **Squash**: one commit, by the pull request's author, with the squash | |
| 80 | + | message and its `Co-authored-by:` trailers. | |
| 81 | + | - **Rebase**: each of its commits made again on top, keeping their authors. | |
| 82 | + | - **Fast-forward**: its own head, which must already contain everything | |
| 83 | + | ahead of it; otherwise it fails, saying to bring it up to date or merge | |
| 84 | + | it another way. | |
| 85 | + | ||
| 86 | + | The method is the one asked for when it was merged into the queue (the | |
| 87 | + | merge box's choice, `merge_method` in the API), else the merge queue | |
| 88 | + | rule's `merge_method` (merge commit unless set). Both must be methods the | |
| 89 | + | repository and the branch's [rules](/guides/rules/) allow, or merging is | |
| 90 | + | refused, saying which are; linear history rules out merge commits. | |
| 91 | + | A commit message written then (`commit_title`, `commit_message`) is kept | |
| 92 | + | with the entry; otherwise the repository's default message is used, worked | |
| 93 | + | out when the entry is first built. Sign-off is added where the repository | |
| 94 | + | requires it. The same limits apply as to merging directly: g1t makes at | |
| 95 | + | most 1,000 changed files, 100 files merged line by line and 250 rebased | |
| 96 | + | commits itself. | |
| 97 | + | ||
| 74 | 98 | If every entry passes, the four can land one after another without being | |
| 75 | 99 | tested again. The next batch starts when nothing is being tested. A batch | |
| 76 | 100 | that takes longer than 45 minutes (`check_response_timeout_minutes`) is tested again. | |
| ⋯ | |||
| 110 | 134 | Each build has its own attempt token. A result from an old attempt cannot | |
| 111 | 135 | replace a retry or put a removed entry back in the queue. Workflow results | |
| 112 | 136 | apply only to the combined commit still being checked. | |
| 113 | − | Retries use separate Git branches, so a late sandbox cannot overwrite the | |
| 137 | + | Retries use separate Git branches, so a late build cannot overwrite the | |
| 114 | 138 | new attempt's branch. Workflow results wait until the whole group has been | |
| 115 | 139 | registered; a dispatch error does not count as a pass. | |
| 116 | 140 | ||
| 117 | 141 | ## How entries land | |
| 118 | 142 | ||
| 119 | 143 | Entries land in order. When an entry has passed and everything ahead of it | |
| 120 | − | has landed, `main` moves to exactly the state that was tested. The issue | |
| 121 | − | closes and the other pull requests for it are superseded, as with any | |
| 122 | − | merge. | |
| 144 | + | has landed, `main` moves to exactly the state that was tested: since each | |
| 145 | + | state was built on the one ahead of it, that is a fast-forward to the | |
| 146 | + | commits its method made, which are the commits its checks ran on. The pull | |
| 147 | + | request is recorded as merged by its method, with the commit `main` moved | |
| 148 | + | to as its merge commit. The issue closes and the other pull requests for | |
| 149 | + | it are superseded, as with any merge. | |
| 123 | 150 | ||
| 124 | 151 | Current reviews and branch rules are checked again before publication, | |
| 125 | − | against the combined commit's checks. Once publication starts, removal, | |
| 152 | + | against the combined commit's checks and the entry's merge method. Once publication starts, removal, | |
| 126 | 153 | closing or conversion to draft returns a conflict until it finishes. If the | |
| 127 | 154 | service stops after updating Git, the next queue poll resumes bookkeeping | |
| 128 | 155 | for that same commit instead of publishing a different branch tip. | |
| ⋯ | |||
| 145 | 172 | ## When an entry fails | |
| 146 | 173 | ||
| 147 | 174 | An entry fails when its `merge_group` workflows or required checks fail | |
| 148 | − | on the combined state, when it does not merge cleanly with what is ahead of it, or | |
| 149 | − | when the state cannot be built. | |
| 175 | + | on the combined state, when its change clashes with what is ahead of it, or | |
| 176 | + | when the state cannot be built (a fast-forward that does not contain what | |
| 177 | + | is ahead, or more than g1t makes itself). One that cannot be built leaves | |
| 178 | + | the queue, and the entries behind it are built in the next batch without | |
| 179 | + | it. | |
| 150 | 180 | ||
| 151 | 181 | ### Who answers for it | |
| 152 | 182 | ||
| ⋯ | |||
| 210 | 240 | changes together, or may not be deterministic. Diagnosis stops after three | |
| 211 | 241 | extra builds, retaining the last combination demonstrated to fail. | |
| 212 | 242 | ||
| 213 | − | These combinations are built in sandboxes as the queue's states are, on | |
| 214 | − | `g1t-queue/<id>` branches that are deleted afterwards, and never land. They | |
| 243 | + | These combinations are built as the queue's states are, each entry by | |
| 244 | + | its own method and with the same commit messages, on `g1t-queue/<id>` | |
| 245 | + | branches that are deleted afterwards, and never land. They | |
| 215 | 246 | do not hold up the queue: the entries behind are tested again at once. When | |
| 216 | 247 | a combination cannot be built or checked, or its checks are still running | |
| 217 | 248 | past the rule's timeout when the queue next moves, g1t stops and reports | |
| 357 | 357 | those it refuses and nothing changes: squash or merge instead of rebasing, | |
| 358 | 358 | or merge the base in a clone. | |
| 359 | 359 | ||
| 360 | + | With the [merge queue](/guides/merge-queue/#how-entries-are-tested) on, | |
| 361 | + | the method chosen (or the queue rule's, when none is) decides how the | |
| 362 | + | pull request's state is built in the queue, and what is tested is what | |
| 363 | + | lands. | |
| 364 | + | ||
| 360 | 365 | ### Commit messages | |
| 361 | 366 | ||
| 362 | 367 | A merge commit and a squash commit get a message. You can write it in the | |
| ⋯ | |||
| 522 | 527 | ||
| 523 | 528 | A pull request that only has fallen behind its target, without conflicts, | |
| 524 | 529 | still merges: merging brings it up to date first, unless the repository | |
| 525 | − | 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. | |
| 526 | 541 | ||
| 527 | 542 | ## Keeping it up to date | |
| 528 | 543 | ||
| 80 | 80 | `g1t.sh/<owner>/<project>/security/pulls/<number>`. Require **Dependency | |
| 81 | 81 | review** under **Settings → Branches** to block merges on it. | |
| 82 | 82 | ||
| 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 | + | ||
| 83 | 94 | In the repository's **Security settings**: | |
| 84 | 95 | ||
| 85 | 96 | | Setting | Default | | | |
| ⋯ | |||
| 115 | 126 | | `GET /repos/{owner}/{name}/dependency-graph` | The graph, per lockfile. | `security:read` | | |
| 116 | 127 | | `GET /repos/{owner}/{name}/dependency-graph/sbom` | The SPDX document, in `sbom`. | `security:read` | | |
| 117 | 128 | | `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` | | |
| 118 | 130 | | `GET /repos/{owner}/{name}/vulnerability-alerts` | Lists alerts; filter with `state`, `severity`, `ecosystem`, `package`. | `security:read` | | |
| 119 | 131 | | `GET /workspaces/{workspace}/vulnerability-alerts` | The same across a workspace. | `security:read` | | |
| 120 | 132 | | `GET /repos/{owner}/{name}/vulnerability-alerts/{id}` | One alert. | `security:read` | | |
| 121 | 133 | | `PATCH /repos/{owner}/{name}/vulnerability-alerts/{id}` | `state` `dismissed` with `reason` and `comment`, or `open`. | `security:write` | | |
| 122 | 134 | ||
| 123 | 135 | 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`, | |
| 125 | 137 | `vulnerability_alert` and `update_vulnerability_alert`. Webhooks: | |
| 126 | 138 | `vulnerability_alert.created`, `.fixed`, `.dismissed` and `.reopened`. | |
| 618 | 618 | | [`dependency_graph`](/reference/api/supply-chain/get-dependency-graph/) | Every package the lockfiles resolve, direct or transitive, with licenses. | `repo` | `security:read` | | |
| 619 | 619 | | [`sbom`](/reference/api/supply-chain/get-sbom/) | The dependency graph as an SPDX 2.3 document, in `sbom`. | `repo` | `security:read` | | |
| 620 | 620 | | [`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` | | |
| 621 | 622 | | [`settings`](/reference/api/security-settings/get-security-settings/) | A repository's security settings, and whether the paid features are on. | `repo` | `security:read` | | |
| 622 | 623 | | [`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` | | |
| 623 | 624 | | [`workspace_settings`](/reference/api/security-settings/get-workspace-security-settings/) | A workspace's delegated bypass and validity checks. | `workspace` | `security:read` | |
Binary or large file; its contents are not shown.
| 219 | 219 | ); | |
| 220 | 220 | } | |
| 221 | 221 | ||
| 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 }) { | |
| 224 | 228 | const names = (list: string[]) => list.join(", "); | |
| 225 | 229 | if (review.changes_requested_by.length > 0) { | |
| 226 | 230 | return ( | |
| ⋯ | |||
| 246 | 250 | </span> | |
| 247 | 251 | ); | |
| 248 | 252 | } | |
| 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 | + | } | |
| 249 | 260 | const more = Math.max(review.required - review.approved_by.length, 1); | |
| 250 | 261 | return ( | |
| 251 | 262 | <span className="flex items-center gap-1 text-xs text-warn"> | |
| ⋯ | |||
| 257 | 268 | } | |
| 258 | 269 | ||
| 259 | 270 | /** 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 | + | }) { | |
| 261 | 281 | const many = review.files.length > 3; | |
| 262 | 282 | const files = ( | |
| 263 | 283 | <ul className="mt-1 space-y-0.5"> | |
| ⋯ | |||
| 277 | 297 | line {review.line} | |
| 278 | 298 | </Link> | |
| 279 | 299 | <span className="ml-auto"> | |
| 280 | − | <ReviewStatus review={review} /> | |
| 300 | + | <ReviewStatus review={review} required={required} /> | |
| 281 | 301 | </span> | |
| 282 | 302 | </div> | |
| 283 | 303 | <p className="mt-1 flex flex-wrap items-center gap-x-2 gap-y-0.5 text-xs text-muted"> | |
| ⋯ | |||
| 343 | 363 | ) : ( | |
| 344 | 364 | <ul className="divide-y divide-line"> | |
| 345 | 365 | {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} /> | |
| 347 | 367 | ))} | |
| 348 | 368 | </ul> | |
| 349 | 369 | )} | |
| 21 | 21 | import { useMemo, useState } from "react"; | |
| 22 | 22 | import { Form, Link } from "react-router"; | |
| 23 | 23 | ||
| 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"; | |
| 25 | 34 | ||
| 26 | 35 | import { useAddresses } from "../lib/addresses"; | |
| 27 | 36 | import { duration } from "./actions"; | |
| ⋯ | |||
| 406 | 415 | pull.fork ? `/${pull.fork.namespace}/${pull.fork.name}/commit/${sha}` : `${base}/commit/${sha}`; | |
| 407 | 416 | const older = run != null && pull.headCommit != null && run.headCommit !== pull.headCommit; | |
| 408 | 417 | 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"); | |
| 409 | 421 | ||
| 410 | 422 | return ( | |
| 411 | 423 | <div aria-busy={loading || undefined}> | |
| ⋯ | |||
| 458 | 470 | </Hint> | |
| 459 | 471 | </Form> | |
| 460 | 472 | ))} | |
| 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 | + | )} | |
| 461 | 484 | </div> | |
| 462 | 485 | </div> | |
| 463 | 486 | {error && ( | |
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…
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…
Loading the diff…
Loading the diff…
Loading the diff…
Comments
Sign in to comment.