GitLab CI pipelines can be configured to run exclusively for merge requests, which is surprisingly simple once you know where to look.
Let’s see this in action. Imagine you have a .gitlab-ci.yml file and you want a specific job, say test_coverage, to only run when a merge request is open targeting your main branch.
stages:
- build
- test
- deploy
build_job:
stage: build
script:
- echo "Building the application..."
- make build
rules:
- if: $CI_PIPELINE_SOURCE == "push" # Run for regular pushes
- if: $CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "main" # Run for MRs targeting main
test_coverage:
stage: test
script:
- echo "Running coverage tests..."
- make test-coverage
rules:
- if: $CI_MERGE_REQUEST_EVENT_TYPE == "merged_request" && $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "main" # Only run for merge requests targeting main
deploy_staging:
stage: deploy
script:
- echo "Deploying to staging..."
- make deploy-staging
only:
- main # This job will run on pushes to main
In this example, the test_coverage job uses rules to specify its execution conditions. The key here is if: $CI_MERGE_REQUEST_EVENT_TYPE == "merged_request" && $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "main". This tells GitLab CI to execute this job only when the pipeline is triggered by a merge request event (meaning a merge request is created, updated, or reopened) and the target branch for that merge request is main.
The build_job is a bit more nuanced. It’s configured to run for both regular pushes ($CI_PIPELINE_SOURCE == "push") and for merge requests targeting main. This is useful if you want to ensure your build process is validated on both scenarios. The deploy_staging job, on the other hand, uses the older only syntax and is restricted to running only when commits are pushed directly to the main branch.
The core problem this solves is preventing unnecessary pipeline runs. Imagine a large project with many commits to feature branches. If every commit triggers a full pipeline that includes lengthy integration tests or deployments, you’re wasting CI minutes and developer time. By restricting certain jobs or the entire pipeline to merge requests, you ensure that these resource-intensive tasks only run when code is about to be merged into a main branch, providing a final gate before integration.
Internally, GitLab CI evaluates these rules (or only/except clauses) at the time a pipeline is triggered. When a push event occurs, it checks the rules for each job. If the conditions are met, the job is scheduled. For merge request events, GitLab provides specific predefined CI/CD variables that allow you to inspect the details of the merge request, such as its source branch, target branch, and the event type.
The rules syntax is more powerful and flexible than the older only/except syntax. You can combine multiple conditions using if and when clauses, and even use changes to trigger jobs only when specific files are modified. For instance, you could have a linting job that only runs for merge requests and only if Python files have changed:
lint_code:
stage: test
script:
- echo "Linting Python code..."
- flake8 .
rules:
- if: $CI_MERGE_REQUEST_EVENT_TYPE == "merged_request"
changes:
- "**/*.py"
This level of granularity ensures your CI/CD pipeline is efficient and only performs work that is relevant to the current context, saving compute resources and speeding up feedback loops for developers.
A common point of confusion is the difference between $CI_PIPELINE_SOURCE and $CI_MERGE_REQUEST_EVENT_TYPE. $CI_PIPELINE_SOURCE tells you how the pipeline was triggered (e.g., push, merge_request_event, schedule). $CI_MERGE_REQUEST_EVENT_TYPE is only available when $CI_PIPELINE_SOURCE is merge_request_event and tells you what kind of merge request event occurred (e.g., merged_request, opened, reopened). For simply checking if it’s a merge request, $CI_PIPELINE_SOURCE == "merge_request_event" is often sufficient.
The next logical step after controlling when pipelines run is to control which jobs run based on the merge request’s content or author.