GitLab merge trains Jump to heading
GitLab’s answer to the merge queue is the merge train. When a merge request is added to the train, GitLab creates a pipeline for the result of merging it on top of the target branch plus every merge request ahead of it in the train. If that pipeline passes, the merge request lands; if it fails, it is removed and the merge requests behind it are re-tested without it. The effect is the same guarantee a merge queue gives: the commit that lands on the target branch is exactly the commit that was tested. The mechanics differ enough from GitHub’s merge queue that teams moving between them get surprised. This page covers enabling trains, the pipeline configuration they need, what happens on failure, and tuning, within merge queues and required checks.
When to use this approach Jump to heading
- Your repositories are on GitLab and the default branch breaks after merges that each passed CI.
- Several merge requests land per hour, so “rebase and re-run before merge” is too slow.
- You want the tested commit and the landed commit to be the same.
- On GitHub, the equivalent is covered in setting up a GitHub merge queue.
Step 1 — Enable merged results pipelines, then merge trains Jump to heading
Merge trains build on merged results pipelines — pipelines that run on the merge of the merge request into its target rather than on the source branch alone. Enable both in the project’s merge request settings, and require pipelines to succeed.
glab api -X PUT "projects/$PROJECT_ID" \
-f merge_pipelines_enabled=true \
-f merge_trains_enabled=true \
-f only_allow_merge_if_pipeline_succeeds=true
glab api "projects/$PROJECT_ID" | jq '{merge_pipelines_enabled, merge_trains_enabled}' Step 2 — Make the pipeline run for train events Jump to heading
Pipelines for merge trains have CI_MERGE_REQUEST_EVENT_TYPE set to merge_train. Rules that only allow merge_request_event pipelines usually include them, but rules that filter on branch names or exclude merged results can skip jobs in the train. Check that required jobs run.
workflow:
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
test:
script: make test
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event" # includes merge_train events
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
expensive-e2e:
script: make e2e
rules:
- if: $CI_MERGE_REQUEST_EVENT_TYPE == "merge_train" # only in the train, not on every push Running the slowest tests only in the train is a common optimisation: merge requests get fast feedback during development, and the expensive suite runs once on the exact combination that will land. The broader rule syntax is in mapping GitLab CI rules to branch and path changes.
Step 3 — Add merge requests to the train Jump to heading
Merge requests join the train with “Merge” (or “Set to auto-merge”) once their own pipeline passes. From the CLI or API:
glab mr merge 812 --auto-merge
glab api "projects/$PROJECT_ID/merge_trains/merge_requests/812" | jq '{status, pipeline: .pipeline.status}' # What is in the train right now?
glab api "projects/$PROJECT_ID/merge_trains?scope=active" | jq -r '.[] | "\(.merge_request.iid)\t\(.status)\t\(.pipeline.status)"' Step 4 — Understand what happens on failure Jump to heading
When a car’s pipeline fails, GitLab drops that merge request from the train and restarts the pipelines of every car behind it, now without the failed one. Cars ahead are unaffected.
That restart is why flaky tests hurt trains badly: one flaky failure ejects a good merge request and restarts every pipeline behind it. Fix flakiness before relying on a busy train, as described in handling flaky tests in a merge queue.
Step 5 — Tune for throughput Jump to heading
Trains run pipelines in parallel for several cars at once. Throughput depends on pipeline duration and failure rate. Two settings matter: how many cars run concurrently (a GitLab instance setting on self-managed, fixed on GitLab.com), and whether fast-forward merges are used, which avoids creating merge commits.
# Pipeline duration for train pipelines, last 50
glab api "projects/$PROJECT_ID/pipelines?source=merge_request_event&per_page=50" |
jq -r '.[] | .id' | while read -r id; do
glab api "projects/$PROJECT_ID/pipelines/$id" | jq -r 'select(.duration) | .duration'
done | awk '{s+=$1; n++} END {printf "avg train pipeline: %.1f min\n", s/n/60}' Validation checklist Jump to heading
Frequently Asked Questions Jump to heading
Can urgent merge requests skip the train? Jump to heading
GitLab allows a maintainer to “merge immediately”, which skips the train and restarts every running car afterwards. Reserve it for incidents; the trade-offs are in prioritising urgent changes in a merge queue.
Do merge trains work with squash merging? Jump to heading
Yes. The squash happens as part of the train’s merge, and the tested result is the squashed commit on top of the cars ahead.
What if the target branch receives a direct push? Jump to heading
Every running car’s pipeline is based on the old target, so GitLab restarts the train. Protect the target branch so that merge requests are the only way in.
Related Jump to heading
- Merge Queues & Required Checks — the parent topic.
- Verifying Signed Commits in GitLab CI — handling merged-results ranges in a signature job.
- Catching Semantic Conflicts in CI — what trains protect against.
- Batching Pull Requests to Cut CI Cost — throughput thinking that applies to trains too.