This is an automated email from the ASF dual-hosted git repository.
Yicong-Huang pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/texera.git
The following commit(s) were added to refs/heads/main by this push:
new b492a622bd ci: enable GitHub merge queue (#5036)
b492a622bd is described below
commit b492a622bdb0a4d7b59a8473e68c3f60f93a1585
Author: Yicong Huang <[email protected]>
AuthorDate: Tue May 12 13:59:49 2026 -0700
ci: enable GitHub merge queue (#5036)
### What changes were proposed in this PR?
This PR enables GitHub merge queue for the default branch so ready PRs
no longer need to repeatedly update against `main` after other PRs land.
Configuration changes:
- Add the required `meta.environment: github_rulesets` section for
ASF-managed GitHub rulesets.
- Replace the previous `protected_branches.main` configuration with a
default-branch ruleset.
- Keep the existing review gate: 1 approval, resolved review
conversations, no code-owner requirement, and no last-push approval
requirement.
- Keep the existing required checks: `Required Checks`, `Check License
Headers`, and `Validate PR title`.
- Preserve GitHub auto-merge support.
- Disable the old strict branch-up-to-date policy so merge queue
validates the final merge result instead of requiring every PR branch to
be updated manually.
- Set the merge queue check timeout to 30 minutes because required
checks are expected to finish in under 20 minutes, leaving buffer for
runner startup or occasional slow runs.
Workflow changes:
- Add the `merge_group` event to required workflows so required checks
run on merge queue refs.
- Keep PR title validation on `pull_request_target`; make the
`merge_group` path a no-op because the PR title is validated before a PR
enters the queue.
Expected behavior:
- Merge queue creates a temporary ref for `main + queued PR(s)` and runs
required checks there.
- CI may run once before a PR enters the queue and once after it enters
the queue.
- The second run protects `main` from integration failures in the queued
merge result.
- If a merge-group run fails or times out, GitHub does not merge that
group and the affected PR needs to be fixed/requeued.
Temporary auto-queue removal:
- Remove `.github/workflows/auto-queue.yml` because GitHub native merge
queue replaces that scripted auto-queue behavior.
- Do not add any `emergency` label behavior in this PR. Emergency
priority should be handled manually through GitHub queue controls or
admin bypass if truly necessary.
### Any related issues, documentation, discussions?
Closes #4553
### How was this PR tested?
Ran local configuration checks:
```bash
ruby -e 'require "yaml"; [".asf.yaml",
".github/workflows/required-checks.yml", ".github/workflows/check-header.yml",
".github/workflows/lint-pr.yml"].each { |f| YAML.load_file(f); puts "ok #{f}" }'
ruby -e 'require "yaml"; cfg=YAML.load_file(".asf.yaml"); abort "missing
meta" unless cfg["meta"] && cfg["meta"]["environment"] == "github_rulesets";
mq=cfg["github"]["rulesets"].find { |r| r["name"] == "Merge Queue" }; abort
"missing merge queue" unless mq; abort "main branch protection still present"
if cfg.dig("github", "protected_branches", "main"); checks=mq["rules"].find {
|r| r["type"] == "required_status_checks"
}["parameters"]["required_status_checks"].map { |c| c["context"] [...]
git diff --check
```
No automated runtime tests were added because this PR only changes
repository/GitHub Actions configuration.
### Was this PR authored or co-authored using generative AI tooling?
Generated-by: OpenAI Codex (GPT-5)
---
.asf.yaml | 67 +++++--
.github/workflows/auto-queue.yml | 335 ----------------------------------
.github/workflows/check-header.yml | 1 +
.github/workflows/lint-pr.yml | 5 +
.github/workflows/required-checks.yml | 1 +
5 files changed, 57 insertions(+), 352 deletions(-)
diff --git a/.asf.yaml b/.asf.yaml
index 14e9e9f4c1..d26c2d4dbe 100644
--- a/.asf.yaml
+++ b/.asf.yaml
@@ -17,6 +17,9 @@
# https://cwiki.apache.org/confluence/display/INFRA/Git+-+.asf.yaml+features
+meta:
+ environment: github_rulesets
+
github:
description: "Human-AI Collaborative Data Science Using Visual Workflows"
homepage: https://texera.apache.org/
@@ -61,23 +64,53 @@ github:
squash_commit_message: PR_TITLE_AND_DESC
merge: false
rebase: false
-
- protected_branches:
- main:
- required_status_checks:
- # strict means "Require branches to be up to date before merging".
- strict: true
- # contexts are the names of checks that must pass
- contexts:
- - Required Checks
- - Check License Headers
- - Validate PR title
- required_pull_request_reviews:
- dismiss_stale_reviews: false
- require_code_owner_reviews: false
- required_approving_review_count: 1
- required_linear_history: true
- required_conversation_resolution: true
+
+ rulesets:
+ - name: Merge Queue
+ target: branch
+ source_type: Repository
+ source:
+ enforcement: active
+ conditions:
+ ref_name:
+ exclude: []
+ include:
+ - "~DEFAULT_BRANCH"
+ rules:
+ - type: deletion
+ - type: non_fast_forward
+ - type: merge_queue
+ parameters:
+ merge_method: SQUASH
+ max_entries_to_build: 5
+ min_entries_to_merge: 1
+ max_entries_to_merge: 5
+ min_entries_to_merge_wait_minutes: 5
+ grouping_strategy: ALLGREEN
+ check_response_timeout_minutes: 30
+ - type: pull_request
+ parameters:
+ dismiss_stale_reviews_on_push: false
+ require_code_owner_review: false
+ require_last_push_approval: false
+ required_approving_review_count: 1
+ required_review_thread_resolution: true
+ - type: required_linear_history
+ - type: required_status_checks
+ parameters:
+ strict_required_status_checks_policy: false
+ do_not_enforce_on_create: false
+ required_status_checks:
+ - context: Required Checks
+ integration_id: -1
+ - context: Check License Headers
+ integration_id: -1
+ - context: Validate PR title
+ integration_id: -1
+ bypass_actors:
+ - actor_id: 4
+ actor_type: RepositoryRole
+ bypass_mode: always
notifications:
commits: [email protected]
diff --git a/.github/workflows/auto-queue.yml b/.github/workflows/auto-queue.yml
deleted file mode 100644
index 1953578b1c..0000000000
--- a/.github/workflows/auto-queue.yml
+++ /dev/null
@@ -1,335 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-# Temporary stand-in for GitHub Merge Queue.
-#
-# Triggers:
-# * push to main: advance the queue right after a merge.
-# * pull_request {auto_merge_enabled, ready_for_review}: a PR just
-# became eligible — kick the queue without waiting for cron.
-# * pull_request_review {submitted}: an approval may have just made
-# a PR eligible (script filters non-approval review states).
-# * workflow_run {Required Checks, completed}: the head PR's CI
-# just finished. On success, auto-merge fires and the next push to
-# main triggers us; on failure, the head PR's CI moves from PENDING
-# to FAILURE so the in-flight guard releases — this trigger gives
-# us a same-second kick instead of waiting on cron.
-# * 5-minute cron: bounded safety net for any missed event delivery
-# and for PRs that became BEHIND without producing any of the above.
-# * workflow_dispatch: manual smoke test.
-#
-# Strategy: scan open PRs targeting main and pick the oldest eligible PR with
-# mergeStateStatus=BEHIND, then call updateBranch on it. A PR is eligible only
-# if it would actually merge once CI passes — auto-merge enabled, not a draft,
-# not conflicting, reviewDecision=APPROVED, and zero unresolved review threads.
-# This avoids burning CI on PRs blocked on review.
-#
-# Emergency priority: a PR carrying the `emergency` label is bumped before
-# any non-emergency PR regardless of CREATED_AT ordering, AND its presence
-# in BEHIND bypasses the in-flight guard so a non-emergency PR's running
-# CI does not delay the bump. Non-emergency PRs continue to wait for the
-# queue head as usual.
-#
-# In-flight guard: if any eligible PR is already past the BEHIND state and
-# its required CI is still running (mergeStateStatus != BEHIND and
-# statusCheckRollup state is PENDING/EXPECTED), the run exits without
-# bumping anyone else. That PR is the queue head; bumping a different PR
-# while it is in flight would just preempt CI capacity for a PR that
-# would still need re-bumping after the head merges. PRs that are
-# BEHIND with PENDING checks do NOT count as in-flight — that CI is on
-# pre-update code and would need to re-run after updateBranch anyway.
-#
-# mergeStateStatus is computed asynchronously and is UNKNOWN for a window
-# after a base-branch push. If at least one eligible PR is UNKNOWN, retry
-# with backoff up to ~2min to let it settle. If everything is settled and
-# nothing is BEHIND, exit without retrying — there's no work.
-#
-# Token: needs AUTO_MERGE_TOKEN with contents:write + pull_requests:write so
-# the resulting push retriggers required CI on the PR. Falls back to
-# GITHUB_TOKEN, in which case auto-merge will not actually fire (GITHUB_TOKEN
-# pushes don't trigger downstream workflows).
-name: Auto Queue
-
-on:
- push:
- branches: [main]
- pull_request:
- types: [auto_merge_enabled, ready_for_review]
- pull_request_review:
- types: [submitted]
- workflow_run:
- workflows: [Required Checks]
- types: [completed]
- schedule:
- - cron: '*/5 * * * *'
- workflow_dispatch:
-
-permissions:
- contents: write
- pull-requests: write
-
-concurrency:
- group: autoqueue-${{ github.repository }}
- cancel-in-progress: false
-
-jobs:
- update-next-auto-merge-pr:
- runs-on: ubuntu-latest
- steps:
- - uses: actions/github-script@v7
- with:
- github-token: ${{ secrets.AUTO_MERGE_TOKEN || secrets.GITHUB_TOKEN }}
- script: |
- const { owner, repo } = context.repo;
-
- // pull_request_review fires for any submitted review (Comment /
- // Approve / Request changes). Only Approve can newly satisfy the
- // reviewDecision=APPROVED gate, so other states are pure no-ops
- // worth short-circuiting before the GraphQL call.
- if (
- context.eventName === 'pull_request_review' &&
- context.payload.review?.state !== 'approved'
- ) {
- core.info(
- `Skip: pull_request_review state=` +
- `${context.payload.review?.state} (only "approved" can ` +
- `change queue eligibility).`
- );
- return;
- }
-
- const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
- // 0, 10, 20, 30, 30, 30 = 120s total wall-clock budget across
- // attempts. Short ramp catches the common case where
- // mergeStateStatus settles within ~30s of a base-branch push;
- // the tail keeps trying for the rare slow case.
- const BACKOFFS_MS = [0, 10000, 20000, 30000, 30000, 30000];
-
- const query = `
- query($owner:String!, $name:String!) {
- repository(owner:$owner, name:$name) {
- pullRequests(
- states: OPEN,
- baseRefName: "main",
- first: 100,
- orderBy: {field: CREATED_AT, direction: ASC}
- ) {
- nodes {
- number
- title
- isDraft
- mergeable
- mergeStateStatus
- reviewDecision
- autoMergeRequest { enabledAt }
- labels(first: 20) {
- nodes { name }
- }
- reviewThreads(first: 100) {
- nodes { isResolved }
- }
- commits(last: 1) {
- nodes {
- commit {
- statusCheckRollup { state }
- }
- }
- }
- }
- }
- }
- }`;
-
- // Carrying the `emergency` label lifts a PR above all other
- // eligible PRs: it is bumped first regardless of CREATED_AT, and
- // its presence in BEHIND bypasses the in-flight guard so a
- // non-emergency PR's running CI does not block the bump.
- const EMERGENCY_LABEL = 'emergency';
- function isEmergency(p) {
- return (p.labels?.nodes ?? []).some(
- (l) => l.name === EMERGENCY_LABEL,
- );
- }
-
- function classify(p) {
- if (!p.autoMergeRequest) return 'skip: auto-merge not enabled';
- if (p.isDraft) return 'skip: draft';
- if (p.mergeable === 'CONFLICTING') return 'skip:
mergeable=CONFLICTING';
- if (p.reviewDecision !== 'APPROVED') {
- return `skip: reviewDecision=${p.reviewDecision || 'NONE'}`;
- }
- const threads = p.reviewThreads?.nodes ?? [];
- const unresolved = threads.filter((t) => !t.isResolved).length;
- if (unresolved > 0) {
- return `skip: ${unresolved} unresolved review thread(s)`;
- }
- const tag = isEmergency(p) ? ' [emergency]' : '';
- return `eligible${tag}: mergeable=${p.mergeable}
state=${p.mergeStateStatus}`;
- }
-
- const start = Date.now();
-
- for (let attempt = 0; attempt < BACKOFFS_MS.length; attempt++) {
- if (BACKOFFS_MS[attempt] > 0) {
- const elapsedS = Math.round((Date.now() - start) / 1000);
- core.info(
- `Waiting ${BACKOFFS_MS[attempt] / 1000}s before attempt ` +
- `${attempt + 1}/${BACKOFFS_MS.length} (elapsed
${elapsedS}s).`
- );
- await sleep(BACKOFFS_MS[attempt]);
- }
-
- core.startGroup(`Attempt ${attempt + 1}/${BACKOFFS_MS.length}`);
- let data;
- try {
- data = await github.graphql(query, { owner, name: repo });
- } catch (e) {
- // Transient GitHub API failures (5xx, "terminated", etc.)
- // shouldn't kill the whole run — the backoff loop is exactly
- // the right place to absorb them. Try again next attempt.
- core.warning(
- `GraphQL query failed (status ${e.status ?? '?'}): ` +
- `${e.message}. Retrying after backoff.`
- );
- core.endGroup();
- continue;
- }
- const all = data.repository.pullRequests.nodes;
- core.info(`Scanned ${all.length} open PR(s) targeting main.`);
-
- const behind = [];
- const unknown = [];
- const inFlight = [];
- for (const p of all) {
- const verdict = classify(p);
- core.info(` #${p.number} ${verdict} — ${p.title}`);
- if (!verdict.startsWith('eligible')) continue;
- if (p.mergeStateStatus === 'BEHIND') {
- behind.push(p);
- continue;
- }
- if (p.mergeStateStatus === 'UNKNOWN') {
- unknown.push(p);
- continue;
- }
- // Eligible AND not BEHIND/UNKNOWN: this PR is ahead of any
- // BEHIND PR in the queue. Treat it as in-flight only if its
- // current required CI is actually working toward a merge.
- // PENDING/EXPECTED (CI still running on the with-main code)
- // means "wait for it"; SUCCESS (about to auto-merge) means
- // "wait for it"; FAILURE/ERROR (CI failed) is NOT in-flight
- // — auto-merge will not fire, queue can advance past it.
- const ciState =
- p.commits?.nodes?.[0]?.commit?.statusCheckRollup?.state;
- if (
- ciState === 'PENDING' ||
- ciState === 'EXPECTED' ||
- ciState === 'SUCCESS'
- ) {
- inFlight.push({ pr: p, ciState });
- }
- }
-
- // Stable partition: emergency-labeled PRs go first; within
- // each priority class the GraphQL ASC-by-CREATED_AT order
- // is preserved.
- const emergencyBehind = behind.filter(isEmergency);
- const normalBehind = behind.filter((p) => !isEmergency(p));
- const orderedBehind = [...emergencyBehind, ...normalBehind];
-
- core.info(
- `Eligible: ${behind.length} BEHIND ` +
- `(${emergencyBehind.length} emergency), ` +
- `${unknown.length} UNKNOWN, ` +
- `${inFlight.length} in-flight (queue head still merging), ` +
- `rest blocked on failed CI or non-CI gates.`
- );
-
- // Emergency BEHIND bypasses the in-flight guard: an emergency
- // is by definition something that should preempt CI capacity
- // on a non-emergency PR. Without an emergency, fall back to
- // the normal "wait for the queue head" behavior.
- if (inFlight.length > 0 && emergencyBehind.length === 0) {
- const head = inFlight[0];
- core.info(
- `Skip: PR #${head.pr.number} is in flight ` +
- `(state=${head.pr.mergeStateStatus}, ci=${head.ciState}). ` +
- `Letting it finish to avoid preempting CI on a PR we may ` +
- `need to re-bump.`
- );
- core.endGroup();
- return;
- }
- if (inFlight.length > 0 && emergencyBehind.length > 0) {
- core.info(
- `${emergencyBehind.length} emergency PR(s) BEHIND — ` +
- `bypassing in-flight guard for #${inFlight[0].pr.number}.`
- );
- }
-
- if (orderedBehind.length > 0) {
- let updated = null;
- for (const pr of orderedBehind) {
- const tag = isEmergency(pr) ? ' [emergency]' : '';
- core.info(`→ updateBranch #${pr.number}${tag}`);
- try {
- const res = await github.rest.pulls.updateBranch({
- owner, repo, pull_number: pr.number,
- });
- core.info(
- `✓ #${pr.number} updateBranch dispatched (HTTP
${res.status}).`
- );
- updated = pr.number;
- break;
- } catch (e) {
- core.warning(
- `✗ #${pr.number} updateBranch failed ` +
- `(status ${e.status ?? '?'}): ${e.message}`
- );
- }
- }
- core.endGroup();
- if (updated !== null) {
- core.info(`Done: #${updated} updated on attempt ${attempt +
1}.`);
- return;
- }
- core.info(
- 'All BEHIND PRs failed updateBranch this attempt; retrying
after backoff.'
- );
- continue;
- }
-
- if (unknown.length > 0) {
- core.info(
- `No BEHIND PRs yet; ${unknown.length} eligible PR(s) ` +
- 'still UNKNOWN — retrying after backoff to let GitHub
settle.'
- );
- core.endGroup();
- continue;
- }
-
- core.info(
- 'No BEHIND or UNKNOWN eligible PRs — nothing to do this run.'
- );
- core.endGroup();
- return;
- }
-
- const totalS = Math.round((Date.now() - start) / 1000);
- core.info(
- `Exhausted ${BACKOFFS_MS.length} attempt(s) over ${totalS}s ` +
- `without finding a BEHIND PR to update.`
- );
diff --git a/.github/workflows/check-header.yml
b/.github/workflows/check-header.yml
index 5557bc5c78..6255328413 100644
--- a/.github/workflows/check-header.yml
+++ b/.github/workflows/check-header.yml
@@ -22,6 +22,7 @@ on:
- 'ci-enable/**'
- 'main'
pull_request:
+ merge_group:
workflow_dispatch:
jobs:
diff --git a/.github/workflows/lint-pr.yml b/.github/workflows/lint-pr.yml
index 8d55eecbe6..998d4f960b 100644
--- a/.github/workflows/lint-pr.yml
+++ b/.github/workflows/lint-pr.yml
@@ -23,6 +23,7 @@ on:
- edited
- reopened
- synchronize
+ merge_group:
jobs:
main:
@@ -32,5 +33,9 @@ jobs:
pull-requests: read
steps:
- uses:
amannn/action-semantic-pull-request@48f256284bd46cdaab1048c3721360e808335d50 #
v6.1.1
+ if: github.event_name == 'pull_request_target'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ - name: Skip PR title validation for merge group
+ if: github.event_name == 'merge_group'
+ run: echo "PR title validation is handled before pull requests enter
the merge queue."
diff --git a/.github/workflows/required-checks.yml
b/.github/workflows/required-checks.yml
index 54c8600638..957ec5f4ba 100644
--- a/.github/workflows/required-checks.yml
+++ b/.github/workflows/required-checks.yml
@@ -30,6 +30,7 @@ on:
- synchronize
- labeled
- unlabeled
+ merge_group:
workflow_dispatch:
permissions: