vs. branch pipelines: the difference
Anyone who suddenly sees two pipelines for the same commit in GitLab CI has usually run into the difference between branch pipelines and merge request pipelines: the former test the actual push state of a branch, the latter simulate what the code would look like after a merge. This article explains the difference in detail and shows how workflow:rules reliably prevents duplicate pipelines.
Table of Contents
- 1. What a branch pipeline actually tests
- 2. How merge request pipelines test the result state
- 3. CI_PIPELINE_SOURCE as the central distinction
- 4. The problem of duplicate pipelines without workflow:rules
- 5. workflow:rules as a clean solution
- 6. Merge widget and pipeline success requirements
- 7. Performance aspect: merge ref computation and caching
- 8. When pure branch pipelines are enough
- 9. Conclusion: two pipeline types for two different questions
- 10. Summary
- 11. FAQ
1. What a branch pipeline actually tests
A branch pipeline, internally referred to by GitLab as a push pipeline, starts automatically on every push to a branch and tests exactly the state of the code as it exists in that branch at that moment. This is the default behavior many developers know from GitLab, and it answers a clear question: does the code work exactly as it currently stands in the branch. For feature branches without an active merge request, this is often the only sensible question, because it is not yet decided which target branch the code will eventually be merged into.
The crucial point is that a branch pipeline says nothing about how the code behaves after being merged with the target branch. If changes land on the target branch, usually main or develop, between the last push to the feature branch and the actual merge, conflicts or subtle incompatibilities can arise that a pure branch pipeline never catches, because it completely ignores the target branch during its test run. This exact gap is closed by merge request pipelines.
2. How merge request pipelines test the result state
A merge request pipeline is triggered as soon as a merge request is opened or updated, and it tests not the pure branch state but a simulated merge commit formed from the current state of the feature branch and the current state of the target branch. Internally, GitLab creates a temporary merge ref that combines both states and runs the pipeline against that simulated state, not against the plain feature branch. This means a merge request pipeline answers the actually relevant question: does the code work after it is really merged, including all changes that landed on the target branch in the meantime.
This property makes merge request pipelines especially valuable in teams with a high commit frequency on the target branch, because the likelihood increases there that the target branch and the feature branch drift apart in the meantime. An additional practical advantage is that merge request pipelines are directly visible in the merge request interface, including diff annotations for failed tests, and the option to configure the merge widget so a merge is only allowed once the merge request pipeline has succeeded, which is not guaranteed with pure branch pipelines without extra configuration.
test_mr:
stage: test
script:
- vendor/bin/phpunit
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
3. CI_PIPELINE_SOURCE as the central distinction
Technically, the two pipeline types are distinguished by the predefined variable CI_PIPELINE_SOURCE, which takes the value push for a branch pipeline and merge_request_event for a merge request pipeline. This variable is the central basis for all rules and workflow:rules conditions that control job behavior depending on pipeline type. Additionally, merge request pipelines expose further variables such as CI_MERGE_REQUEST_TARGET_BRANCH_NAME and CI_MERGE_REQUEST_SOURCE_BRANCH_NAME, which are not set at all on pure branch pipelines, simply because no merge request exists there for them to refer to.
These additional variables are useful for jobs that should behave differently depending on which target branch a merge is aimed at, for example applying stricter test requirements for a merge request targeting main than for a merge request between two feature branches. Referencing these variables in a branch pipeline simply returns an empty value, which is a common source of unexpected job behavior when teams use both pipeline types in parallel without accounting for the difference in variable availability.
4. The problem of duplicate pipelines without workflow:rules
Without explicit configuration, two complete pipelines can potentially run for the same commit at once: a branch pipeline, because a push happened, and a merge request pipeline, because an open merge request already exists for that branch. Both pipelines run essentially the same jobs, doubling CI minute consumption and cluttering both the commit view and the merge request view with two parallel, often slightly different status icons, which regularly confuses developers about which of the two statuses actually matters.
This behavior is not a bug but the logical consequence of GitLab triggering both pipeline types independently unless configured otherwise. For projects that do not yet use merge request pipelines, the problem goes unnoticed, but as soon as a team switches from pure branch pipelines to merge request pipelines without simultaneously suppressing the branch pipeline for branches with an open merge request, CI resource consumption effectively doubles overnight, often unnoticed until someone takes a closer look at the CI minutes billing.
5. workflow:rules as a clean solution
GitLab's recommended solution is a top-level workflow:rules configuration in the .gitlab-ci.yml that determines under which conditions a pipeline should be started at all, before individual job rules are evaluated. The common pattern states: if a merge request exists for the current branch, only the merge request pipeline should run and the branch pipeline should be suppressed; if no merge request exists, for example on a fresh feature branch without an open merge request, the branch pipeline should continue to run normally, so developers still get immediate feedback on their push even without a merge request.
This logic can be expressed with a combination of $CI_PIPELINE_SOURCE and the $CI_OPEN_MERGE_REQUESTS variable, or more directly with a rule order that allows merge request events first and branch pushes only when no matching merge request exists. GitLab now also offers a preconfigured template called Workflows/MergeRequest-Pipelines that can be included via include and already implements exactly this recommended pattern, so teams do not have to write the exact condition logic from scratch every time.
workflow:
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
- if: '$CI_COMMIT_TAG'
- if: '$CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS'
when: never
- if: '$CI_COMMIT_BRANCH'
6. Merge widget and pipeline success requirements
An important practical advantage of merge request pipelines is their tight integration with the merge widget in the merge request itself. In the project settings under Merge requests, the Pipelines must succeed option can be enabled, which only allows a merge once the associated merge request pipeline has completed successfully. This coupling only works reliably with real merge request pipelines, because GitLab cannot always unambiguously determine, with pure branch pipelines, which pipeline run is actually authoritative for the current merge request state, especially when time has passed between the last push and the merge attempt.
In addition, merge request pipelines can be combined with approval rules, for example requiring both a successful pipeline and a certain number of code owner approvals before a merge request can be merged. This combination of technical assurance via the pipeline and human assurance via reviewers is a central building block in teams with strict quality requirements, such as security-critical software or projects with many concurrently working contributors, to prevent flawed code from reaching the target branch unreviewed.
7. Performance aspect: merge ref computation and caching
An often overlooked aspect is that computing the simulated merge ref for a merge request pipeline adds overhead compared to a pure branch pipeline, because GitLab has to check before the actual pipeline start whether the feature branch and target branch can be merged without conflict. For most projects this overhead is negligibly small, but it can become noticeable in very large repositories with a long history and frequent merge conflicts, especially when many merge requests are tested simultaneously against the same, rapidly changing target branch.
For caching strategies it also matters that a merge request pipeline typically has a different cache key context than a branch pipeline, because the underlying simulated commit technically changes with every update of the target branch, even if the feature branch itself remains unchanged. Teams using aggressive caching strategies with the target branch name in the cache key should account for this, to avoid the cache being constantly rebuilt even though the code relevant to the cache has not actually changed.
8. When pure branch pipelines are enough
Not every project necessarily needs merge request pipelines. For small internal tools, projects with a very low commit frequency on the target branch, or solo developers without a review process, the extra benefit of merge result simulation is small, because the feature branch and target branch rarely diverge far enough for a pure branch test to no longer reflect reality. In such cases, a simple branch pipeline configuration is often entirely sufficient and avoids the added complexity of workflow:rules and merge-request-specific variables.
The picture is different for teams with several active contributors and a target branch updated multiple times a day, for example through other merge requests being merged in parallel. There, the likelihood increases significantly that a feature branch works fine at the time of its last push but breaks after the actual merge due to intervening changes on the target branch, and this exact scenario is the main reason merge request pipelines prevail in active teams over the long run, despite the additional configuration effort with workflow:rules.
9. Conclusion: two pipeline types for two different questions
Branch pipelines and merge request pipelines answer different questions, both of which have their place: does the code work as it stands in the branch, versus does the code work as it would look after the merge. For teams with an active merge request workflow and several contributors, the combination of merge request pipelines and a clean workflow:rules configuration that suppresses duplicate branch pipelines is the recommended default approach, because it delivers both more realistic tests and lower CI resource consumption.
The table below contrasts both pipeline types along the most important practical criteria, as a quick reference for your own configuration decision.
| Criterion | Branch pipeline | Merge request pipeline | Practical note |
|---|---|---|---|
| State tested | pure push state of the branch | simulated merge commit with target branch | MR pipeline closer to reality after merge |
| CI_PIPELINE_SOURCE | push | merge_request_event | basis for rules distinction |
| Merge widget integration | limited | full, including pipelines-must-succeed | use MR pipeline for binding quality checks |
| Without config while MR is open | runs additionally, often unwanted | runs additionally, often unwanted | use workflow:rules to avoid duplicate runs |
| Available variables | CI_COMMIT_BRANCH etc. | plus CI_MERGE_REQUEST_* variables | use MR variables for target-branch-dependent logic |
Mironsoft
CI/CD pipelines, zero-downtime deployments and release automation
Deployments that run without downtime and without the nail-biting?
We review existing GitLab pipelines for fragile deployment steps and missing safeguards, then build a release process with zero-downtime deployments, automated checks and a rollback you can actually trust in an emergency.
Pipeline Review
Checking an existing .gitlab-ci.yml for fragility, missing stages and security gaps.
Zero-Downtime Deployment
Building symlink releases, health checks and rollback strategies for Magento stores.
CI/CD Automation
Connecting tests, security scans and deployments into one reliable pipeline.
10. Summary
MR pipelines vs. branch pipelines: The essentials at a glance
Branch pipeline tests
The pure push state of a branch, regardless of whether and where it will later be merged.
Merge request pipeline tests
A simulated merge commit built from the feature branch and the current target branch state.
Avoiding duplicate pipelines
A top-level workflow:rules configuration suppresses branch pipelines whenever an open merge request exists.
Merge widget advantage
Only merge request pipelines can be reliably combined with pipelines-must-succeed and approval rules.