Merge request pipelines vs. branch pipelines: the difference
AI generated
CI/CD
.yml
GitLab · CI/CD · DevOps
Merge request pipelines
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.

16 min read merge request pipelines branch pipelines workflow:rules CI_PIPELINE_SOURCE

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.

11. FAQ: MR pipelines vs. branch pipelines: The essentials at a glance

1What is the basic difference between a branch and a merge request pipeline?
A branch pipeline tests the code exactly as it exists in the branch after the push. A merge request pipeline instead tests a simulated merge commit that combines the feature branch with the current state of the target branch, reflecting the expected state after the merge.
2Why do I see two pipelines running for the same commit?
This happens when both a push to the branch and an open merge request for that branch exist without workflow:rules being configured. GitLab then triggers both the branch pipeline and the merge request pipeline by default.
3How do I distinguish the two pipeline types in rules conditions?
Via the CI_PIPELINE_SOURCE variable, which takes the value push for branch pipelines and merge_request_event for merge request pipelines. This variable is the central basis for corresponding if conditions in rules and workflow:rules.
4What additional variables exist only in merge request pipelines?
Among others, CI_MERGE_REQUEST_TARGET_BRANCH_NAME and CI_MERGE_REQUEST_SOURCE_BRANCH_NAME, which indicate the target and source branch of the merge request. These variables are not set in pure branch pipelines, since no merge request exists there.
5How do I reliably avoid duplicate pipelines?
With a top-level workflow:rules configuration in the .gitlab-ci.yml that allows merge request events and suppresses branch pushes whenever an open merge request already exists for the same branch. GitLab also offers a ready-made template to include for this.
6Can I use pipelines must succeed with pure branch pipelines too?
Technically to some extent, but the association is less reliable with pure branch pipelines, because GitLab cannot always unambiguously determine which pipeline run is authoritative for the current merge request state. For binding quality checks, merge request pipelines are therefore recommended.
7Does the merge ref computation cause noticeable overhead?
For most projects the overhead is negligible. In very large repositories with frequent merge conflicts and many merge requests running in parallel, however, computing the simulated merge ref can become noticeable.
8Do small projects strictly need merge request pipelines?
No, for small projects with a low commit frequency on the target branch or without an active review process, the extra benefit is small. A simple branch pipeline configuration is often entirely sufficient there.
9Does the pipeline type affect caching behavior?
Yes, a merge request pipeline has a different cache context than a branch pipeline due to the simulated merge commit. This should be accounted for with aggressive caching strategies that include the target branch name in the cache key, to avoid unnecessary cache rebuilding.
10Is there a ready-made GitLab template for the recommended workflow:rules pattern?
Yes, GitLab provides a template called Workflows/MergeRequest-Pipelines that can be included via include and already implements the recommended pattern for avoiding duplicate pipelines.