diff --git a/CI.md b/CI.md
index cc90a6a..1dce3f9 100644
--- a/CI.md
+++ b/CI.md
@@ -48,7 +48,8 @@ workflow has all of them.
3. **Drafts skip.** Every job has `github.event.pull_request.draft != true`
in its `if`, or needs a job that has it.
4. **A `changes` job** runs `cocoindex-io/.github/actions/ci-mode@v1` (it
- needs `checks: read`) and the path filter. Expensive jobs run when
+ needs `checks: read`, and `pull-requests: read` in a repository with a
+ merge queue) and the path filter. Expensive jobs run when
`full == 'true'` and the filter output is not `'false'`. The filter is
skipped for a stack layer, and so is any other diff against the pull
request's base branch: a stack layer's base is the layer below it, but
@@ -110,6 +111,13 @@ requests directly. Run the full tier on its branch first with
`gh pr merge --merge --admin`. Apart from an incident (below), nothing else
bypasses the queue.
+Label such a pull request `merge-commit` when you open it. `ci-mode` fails
+the merge group of a pull request with that label before any test runs, so
+the queue removes it instead of squashing it. When `ci-mode` can't read the
+labels, the merge group fails too: queueing a pull request again costs one
+build, and a squashed merge commit costs a repair merge. The label has to be
+on the pull request before it enters the queue.
+
## Urgent changes
An urgent fix jumps the queue: it goes to the front, is built next on top of
diff --git a/actions/ci-mode/action.yml b/actions/ci-mode/action.yml
index 2a4692d..6c1299e 100644
--- a/actions/ci-mode/action.yml
+++ b/actions/ci-mode/action.yml
@@ -1,8 +1,10 @@
name: CI mode
description: >-
- Decides whether this run executes the full CI tier or only the fast one.
+ Decides whether this run executes the full CI tier or only the fast one,
+ and fails a merge group whose pull request must land as a merge commit.
See CI.md in cocoindex-io/.github for the tiers. The calling job needs
- `checks: read` and `contents: read`.
+ `checks: read` and `contents: read`, and in a repository with a merge
+ queue also `pull-requests: read`.
outputs:
full:
description: "'true' when this run executes the full tier, 'false' for the fast tier."
@@ -22,6 +24,7 @@ runs:
BASE: ${{ github.event.pull_request.stack.base.ref || github.event.pull_request.base.ref }}
STACK_POSITION: ${{ github.event.pull_request.stack.position }}
STACK_SIZE: ${{ github.event.pull_request.stack.size }}
+ HEAD_REF: ${{ github.event.merge_group.head_ref }}
run: |
full=true
reason="a $EVENT run executes the full tier"
@@ -50,6 +53,27 @@ runs:
else
echo "::warning::Could not read the checks of $SHA, so this run executes the full tier."
fi
+ elif [ "$EVENT" = merge_group ]; then
+ # The queue squashes. A pull request labelled merge-commit, such as
+ # an upstream merge, must keep its merge commit, so its merge group
+ # fails here, before any expensive job starts, and the queue removes
+ # it. A failed lookup fails too: queueing a pull request again costs
+ # one build, and a squashed merge commit costs a repair. The queue's
+ # branch names the pull request: gh-readonly-queue//pr--.
+ pr=${HEAD_REF##*/pr-}
+ pr=${pr%%-*}
+ if ! [[ $pr =~ ^[0-9]+$ ]]; then
+ echo "::error::Could not find the pull request number in the merge group ref $HEAD_REF."
+ exit 1
+ fi
+ if ! labelled=$(gh api "repos/$REPO/pulls/$pr" --jq 'any(.labels[]; .name == "merge-commit")'); then
+ echo "::error::Could not read the labels of #$pr, so its merge group fails. Queue it again; if this repeats, check that the job that runs ci-mode has \`pull-requests: read\`."
+ exit 1
+ fi
+ if [ "$labelled" = true ]; then
+ echo "::error::#$pr is labelled merge-commit, so it must land as a merge commit, and the queue squashes. Merge it around the queue after a full run on its branch passes: gh pr merge $pr --merge --admin (CI.md, Merge commits)."
+ exit 1
+ fi
fi
echo "full=$full" >> "$GITHUB_OUTPUT"
tier=fast