diff --git a/CI.md b/CI.md index 490f63c..ccdb06f 100644 --- a/CI.md +++ b/CI.md @@ -68,7 +68,7 @@ required `name (value)` check from it never arrives. ## The merge queue -The `merge-queue` repository ruleset configures it: merge commits, every +The `merge-queue` repository ruleset configures it: squash merges, every entry must pass (`ALLGREEN`), and at most 4 builds at once. That build limit is the CI quota: a queued pull request waits for a free build slot. @@ -103,16 +103,12 @@ requests go to the queue on their own, which tests them together anyway. ## Merge commits -The queue lands every pull request as a merge commit, and a repository with a -queue allows no other merge method. A pull request keeps its own history, so -an upstream merge keeps its upstream parent and goes through the queue like -any other change. No merge method can squash it, and nothing bypasses the -queue for it. - -The default branch's first-parent history has one commit per pull request, -titled with the pull request's title and number. Read it with -`git log --first-parent`, bisect with `git bisect start --first-parent`, and -revert a pull request with `git revert -m 1 `. +The queue squashes. A pull request that must land as a merge commit, such as +an upstream merge, bypasses the queue: repository admins may merge pull +requests directly. Run the full tier on its branch first with +`gh workflow run`, wait for it to pass, then merge with +`gh pr merge --merge --admin`. Apart from an incident (below), nothing else +bypasses the queue. ## Urgent changes @@ -127,7 +123,7 @@ gh api graphql -F id="$(gh pr view --json id --jq .id)" \ -f query='mutation($id:ID!){enqueuePullRequest(input:{pullRequestId:$id, jump:true}){mergeQueueEntry{position}}}' ``` -Bypassing the queue (`gh pr merge --merge --admin`) lands a change without +Bypassing the queue (`gh pr merge --squash --admin`) lands a change without its full CI run. It is only for an incident in which CI itself is broken. The Change Management Policy requires green CI even for a hotfix, so such a bypass is a deviation and goes in the incident log. @@ -151,12 +147,12 @@ A repository opts in by setting its `ci-tier` custom property to don't accept a merge-queue rule. The script also sets the repository's merge settings from -`rulesets/repo-settings.json`: merge commits only, titled with the pull -request's title. Other required checks stay in the repository's own -rulesets. Both rulesets let repository admins merge pull requests directly, -for incidents. To leave the queue, unset the property, delete the -repository's `merge-queue` ruleset, and set its merge settings as it wants -them. +`rulesets/repo-settings.json`: squash merges for the queue, merge commits for +the admin merge above, and no rebase merges. Other required checks stay in +the repository's own rulesets. Both rulesets let repository admins merge +pull requests directly, for merge commits and incidents. To leave the +queue, unset the property, delete the repository's `merge-queue` ruleset, +and set its merge settings as it wants them. - Apply or check the property, rulesets and merge settings: `scripts/apply_rulesets.py [--check]`. It needs an organization owner with @@ -199,19 +195,8 @@ Checked on 2026-10-22 in a temporary private repository: - GitHub's organization rulesets reject a `merge_queue` rule; repository rulesets accept it. -Checked on 2026-10-24 in cocoindex-plus, after its queue moved to merge -commits: - -- An upstream merge (cocoindex-plus#565) went through the queue and landed - as the merge commit its merge group tested. Its second parent is the pull - request's head, the upstream merge itself, so the upstream commit stayed - an ancestor of `main`. -- The queue's merge commit takes the repository's merge settings: the pull - request's title and number as its subject, the pull request's body as its - message. -- With squash and rebase merges disallowed, `gh pr merge --squash --admin` - and `--rebase --admin` fail: "Squash merges are not allowed on this - repository." +Checked on 2026-10-24 in cocoindex-plus: + - The queue marks a failed entry unmergeable at once, but removes it only when it reaches the front. Until then, the entries behind it are built on top of it. diff --git a/rulesets/repo-settings.json b/rulesets/repo-settings.json index 8c79d2b..a36de70 100644 --- a/rulesets/repo-settings.json +++ b/rulesets/repo-settings.json @@ -1,7 +1,7 @@ { "allow_merge_commit": true, - "allow_squash_merge": false, + "allow_squash_merge": true, "allow_rebase_merge": false, - "merge_commit_title": "PR_TITLE", - "merge_commit_message": "PR_BODY" + "merge_commit_title": "MERGE_MESSAGE", + "merge_commit_message": "PR_TITLE" } diff --git a/rulesets/repo/merge-queue.json b/rulesets/repo/merge-queue.json index c28c953..6a259d7 100644 --- a/rulesets/repo/merge-queue.json +++ b/rulesets/repo/merge-queue.json @@ -21,7 +21,7 @@ { "type": "merge_queue", "parameters": { - "merge_method": "MERGE", + "merge_method": "SQUASH", "grouping_strategy": "ALLGREEN", "max_entries_to_build": 4, "max_entries_to_merge": 4, diff --git a/scripts/apply_rulesets.py b/scripts/apply_rulesets.py index 7ddd0f4..7d08876 100644 --- a/scripts/apply_rulesets.py +++ b/scripts/apply_rulesets.py @@ -9,8 +9,9 @@ - rulesets/repo/*.json: repository rulesets, put on every repository whose `ci-tier` is `merge-queue`. GitHub doesn't accept a merge-queue rule in an organization ruleset, so the queue lives here. -- rulesets/repo-settings.json: the merge settings of those repositories. They - allow only the merge method the queue uses. +- rulesets/repo-settings.json: the merge settings of those repositories: + squash for the queue, merge commits for a pull request that must keep its + merge commit, no rebase. Needs the `gh` CLI signed in as an organization owner with the admin:org scope (`gh auth refresh -h github.com -s admin:org`). Org-setting changes follow the diff --git a/scripts/test_apply_rulesets.py b/scripts/test_apply_rulesets.py index 50bfbe4..52a9961 100644 --- a/scripts/test_apply_rulesets.py +++ b/scripts/test_apply_rulesets.py @@ -4,8 +4,8 @@ def test_subset_compare_ignores_extra_keys() -> None: - want = {"name": "x", "rules": [{"type": "merge_queue", "parameters": {"merge_method": "MERGE"}}]} - have = {"name": "x", "id": 1, "rules": [{"type": "merge_queue", "parameters": {"merge_method": "MERGE", "extra": 1}}]} + want = {"name": "x", "rules": [{"type": "merge_queue", "parameters": {"merge_method": "SQUASH"}}]} + have = {"name": "x", "id": 1, "rules": [{"type": "merge_queue", "parameters": {"merge_method": "SQUASH", "extra": 1}}]} assert differences(want, have) == []