Run CI on a Batch
How the merge queue runs your CI on each batch, on a draft pull request by default or on the pushed queue branch alone, and how to set up your CI for each.
To check a batch, the merge queue builds a branch holding it and waits for your CI to report on that branch. It can bring the branch to your CI in two ways:
-
On a draft pull request, the default. The queue opens a draft pull request for the branch, and any CI that builds pull requests runs on it.
-
On the pushed branch alone, in Preview. The queue opens no pull request, and your CI runs on the push to the branch.
Both check the same code against the same merge_conditions. What differs is
the event that starts your CI and what shows up in your repository.
A queue rule whose checks run in place builds no branch: its CI runs on the original pull request, and nothing on this page applies to it.
How the queue checks a batch
Section titled How the queue checks a batchThe queue builds the batch on top of the base branch: the pull requests ahead of
the batch, then the batch’s own pull requests. It pushes that to a branch under
the queue_branch_prefix of the queue
rule, mergify/merge-queue/ by default. It then waits for the checks reported
on the branch’s head commit and matches them, by name, against
merge_conditions.
A batch gets a fresh CI run each time it is checked again. A retry recreates the draft, or pushes the batch under a new branch name when there is no draft, and each test of a split batch gets a branch of its own. When a batch merges, fails or is reset, the queue deletes its branch.
Where the two ways differ:
| With a draft | Without a draft | |
|---|---|---|
| What starts your CI | The draft’s pull_request event |
The push to the queue branch |
| What lists the batch’s pull requests | The draft’s description | A Mergify Merge Queue check run |
Your pull request list and pull_request webhooks |
Show the draft | Show nothing |
| When the batch ends | Deleting the branch closes the draft | The branch is deleted |
batch_pr_number |
The draft’s number | null |
| Queue rules | All of them | Not all |
| Availability | Default | Preview, turned on in Mergify |
With a draft pull request
Section titled With a draft pull requestBy default, the queue opens a draft pull request for the queue branch, the batch pull request other pages refer to, and your CI runs on it as on any other pull request. A CI that builds pull requests needs no setup for the queue, and the checks on the draft are the ones the queue reads.
The draft is a pull request like the others: it shows up in your pull request
list and fires pull_request webhooks, so anything that reacts to new pull
requests, such as deploy previews, runs for it. Keep Merge Queue Pull Requests
Out of Your Way explains how to recognize the
draft and filter it out of searches, notifications, webhooks and CI.
A job that runs only in the queue tells a queue build from any other by the
draft’s head branch, github.head_ref in GitHub Actions, or by its title, which
starts with merge queue: . See Two-step CI.
Without a draft pull request
Section titled Without a draft pull requestIf your CI can run on a push, you can turn the drafts off. Nothing then shows up
in your pull request list and no pull_request webhook fires for the queue, so
nothing that reacts to new pull requests runs for it.
What changes without a draft
Section titled What changes without a draft-
No pull request. Your CI learns about the batch from the push alone, so it has to run on a push to the queue branch prefix. See Set up your CI for each CI system.
-
A
Mergify Merge Queuecheck run on the head commit of the queue branch takes the place of the draft’s description. It names the pull requests in the batch, links to the queue on the dashboard, and once the batch is done, says how its check ended. It concludesneutral, so it never fails a branch. -
The same conditions.
merge_conditionsare matched against the checks reported on the head commit, by name, as they are on a draft. On most CI systems a check keeps its name whichever event started it; the recipes below name the exceptions. -
The end of a batch deletes its branch, with no draft to close. See Cancel runs when a batch ends.
Queue rules that still need a draft
Section titled Queue rules that still need a draftSome queue rules land a batch through its draft pull request, so they cannot do without one. A queue rule needs a draft when it uses any of:
-
merge_method: fast-forwardorqueue_branch_merge_method: fast-forward; -
merge_method: merge-batchwith amerge_queue.modeother thanserial.
On a base branch where drafts are off, Mergify does not queue a pull request for such a rule, and says which rule and why. A rule whose checks run in place is not affected, since it opens no draft in the first place.
Turn drafts off
Section titled Turn drafts offSet up your CI first, merge that change to the base branch, and only then turn the drafts off. The queue branch is built on the base branch, so it carries the CI configuration the base branch has: a trigger that is still on a pull request of yours does nothing for the queue. A batch whose CI never starts gets no result: it waits until its checks timeout fails it, if one applies.
The setting is stored by Mergify, not in your configuration file. Change it in the dashboard, under Merge Queue → Configuration, on the Draft pull requests for batches card of the Checks section. It can be set at four levels, and a base branch takes it from the narrowest one that sets it:
- the base branch itself;
- a pattern of base branch names, such as
release/*; - the repository;
- the organization.
The Merge Queue API sets the same levels, each with its own
/merge-queue/settings endpoint. To turn drafts off for main:
curl -X PUT \ -H "Authorization: Bearer <my-application-key>" \ -H "Content-Type: application/json" \ -d '{"batch_pull_request": false}' \ https://api.mergify.com/v1/repos/<owner>/<repository>/merge-queue/settings/branches/mainGET /repos/<owner>/<repository>/branches/main/merge-queue/settings returns
the value main ends up with and the level it comes from.
Changing it at the repository or a branch level requires the admin role on the repository; at the organization level, it requires the organization admin role.
The queue applies the change on its next pass. A batch already being checked with a draft finishes with it; turning drafts back on checks the batches in flight again, each with a draft.
Set up your CI
Section titled Set up your CIEach recipe makes your CI build a branch pushed under mergify/merge-queue/.
With a custom queue_branch_prefix, use that prefix instead. Keep running the
same jobs as on a queue pull request: the batch merges only once the checks in
your merge_conditions pass on its head commit.
GitHub Actions
Section titled GitHub ActionsAdd a push trigger on the queue branch prefix next to pull_request:
on: pull_request: push: branches: - "mergify/merge-queue/**"Mergify pushes with its GitHub App token, and a push made with an app token
starts workflows. The job names do not change, so neither do the check names
your merge_conditions list.
While drafts are still on, a queue branch then runs the workflow twice, once
for the push and once for its draft. Add the trigger right before you turn the
drafts off, or skip the push run on queue branches until then.
CircleCI
Section titled CircleCIOAuth app integration. With Only build pull requests on, CircleCI builds a branch only once a pull request is open for it, so it never builds a queue branch. Turn the setting off, or keep it and list the queue branches as an override through CircleCI’s API. The list holds regular expressions and replaces the current one, so include your default branch:
curl -X PUT \ -H "Circle-Token: <your-circleci-token>" \ -H "Content-Type: application/json" \ -d '{"feature_flags": {"pr-only-branch-overrides": "main, mergify/merge-queue/.*"}}' \ https://circleci.com/api/v1.1/project/github/<owner>/<repository>/settingsGitHub App integration. A pipeline whose only trigger is PR opened or
pushed to, default branch and tag pushes never runs for a queue branch, and
neither does Pushes to merge queues, which matches GitHub’s own merge queue
branches. Add an All pushes trigger in the project settings, then keep the
other pushes out with a when condition on the workflow:
workflows: merge-queue: when: matches: pattern: "^mergify/merge-queue/.*" value: << pipeline.git.branch >> jobs: - testBuildkite
Section titled BuildkiteA pipeline with Build branches on in its GitHub settings already builds the queue branch when it is pushed. If you build pull requests only, turn Build branches on, and use the branch filter to limit it to your default branch and the queue branches:
main mergify/merge-queue/*The commit status keeps its name, so merge_conditions do not change.
Jenkins
Section titled JenkinsWith a multibranch pipeline on the GitHub Branch Source plugin, the queue
branch is discovered as a branch once it has no pull request. The default
Discover branches strategy, Exclude branches that are also filed as PRs,
already builds it, and so does All branches. Only branches that are also
filed as PRs never does: change it to one of the other two. A branch name
filter, if you set one, has to include mergify/merge-queue/*.
A branch build reports continuous-integration/jenkins/branch, while a pull
request build reports continuous-integration/jenkins/pr-merge or
continuous-integration/jenkins/pr-head. List the branch one in your
merge_conditions:
queue_rules: - name: default merge_conditions: - check-success = continuous-integration/jenkins/branchA when { branch 'mergify/merge-queue/*' } block matches the queue branch on a
branch build, where BRANCH_NAME is the branch name itself.
GitLab CI
Section titled GitLab CIWith CI/CD for external repositories,
GitLab mirrors the queue branch and runs a branch pipeline for it, where it used
to run an external pull request pipeline as well. If your workflow:rules
admit only external_pull_request_event pipelines, admit the queue branches
too:
workflow: rules: - if: $CI_PIPELINE_SOURCE == "external_pull_request_event" - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH - if: $CI_COMMIT_BRANCH =~ /^mergify\/merge-queue\//Azure Pipelines
Section titled Azure PipelinesA YAML pipeline with no trigger key builds every pushed branch, the queue
branches included. With trigger: none, or a branch list, add the queue
branches to it:
trigger: branches: include: - main - mergify/merge-queue/*TeamCity
Section titled TeamCityThe Pull Requests build feature builds pull requests, not pushed branches. Add the queue branches to the branch specification of the VCS root, so the VCS trigger builds them, and keep the Commit status publisher build feature, which reports the result to GitHub:
+:refs/heads/main+:refs/heads/mergify/merge-queue/*Travis CI
Section titled Travis CITurn on Build pushed branches in the repository settings, and if your
.travis.yml has a branches safelist, add the queue branches to it:
branches: only: - main - /^mergify\/merge-queue\/.*$/Travis CI reports a push build as Travis CI - Branch and a pull request build
as Travis CI - Pull Request. List the branch one in your merge_conditions.
Adapt jobs that read the pull request
Section titled Adapt jobs that read the pull requestA push build has no pull request, so anything a job reads from it is empty. That matters for the conditions that tell a queue build from any other.
In GitHub Actions, github.head_ref and github.event.pull_request are empty
on a push, and github.ref_name holds the branch name. A condition that
covers both events reads whichever is set:
jobs: pre-merge-tests: if: startsWith(github.head_ref || github.ref_name, 'mergify/merge-queue/')The same goes for the Two-step CI jobs
that run only in the queue, and for a step gated on the draft’s title, such as
startsWith(github.event.pull_request.title, 'merge queue:'): test the branch
name instead. On other CI systems, test the variable that holds the pushed
branch:
| CI system | Branch variable |
|---|---|
| CircleCI | pipeline.git.branch, or CIRCLE_BRANCH in a job |
| Buildkite | BUILDKITE_BRANCH |
| Jenkins | BRANCH_NAME |
| GitLab CI | CI_COMMIT_BRANCH |
| Azure Pipelines | Build.SourceBranch, which starts with refs/heads/ |
| TeamCity | teamcity.build.branch |
| Travis CI | TRAVIS_BRANCH |
What the queue wrote in the draft’s description is in a Git note on the
batch’s head commit, which mergify ci queue-info reads on a push build
too. Without a draft, batch_pr_number is null in
previous_failed_batches and previous_check_retries, and batch_pr_head_sha
still names the commit each earlier run checked, which is what the recipes on
that page use. mergify ci scopes and mergify ci git-refs read the note on a
push build too, as long as your Mergify CLI is up to date.
Cancel runs when a batch ends
Section titled Cancel runs when a batch endsWhen a batch merges, fails or is reset, Mergify deletes its queue branch. With a
draft, a workflow on pull_request closed could cancel the runs still going;
without one, the branch deletion is the event to react to. In GitHub Actions,
cancel the runs of a deleted queue branch on delete:
name: Cancel queue branch runs
on: delete:
permissions: actions: write
jobs: cancel: if: github.event.ref_type == 'branch' && startsWith(github.event.ref, 'mergify/merge-queue/') runs-on: ubuntu-latest steps: - name: Cancel the runs of the deleted branch env: GH_TOKEN: ${{ github.token }} BRANCH: ${{ github.event.ref }} run: | for status in pending waiting queued in_progress; do gh run list --repo "$GITHUB_REPOSITORY" --branch "$BRANCH" \ --status "$status" --json databaseId --jq '.[].databaseId' | xargs -r -n1 gh run cancel --repo "$GITHUB_REPOSITORY" doneGitHub runs a delete workflow only from the default branch, so merge it there.
A retry pushes the batch under a new branch name and deletes the old one, so
this also stops the runs of the attempt it replaces.
Was this page helpful?
Thanks for your feedback!