Merge Request Pipelines, Merged Results Pipelines, Merge Trains, and Pre-Merge Validation: Guided Hands-On Workflow and Core Operations
Create a disposable merge request and compare branch-pipeline evidence with merge-request-pipeline evidence. You will remove a deliberate duplicate-pipeline bug, record the exact MR ref/SHA under test, and simulate merged-result integration locally so the mandatory path remains Free-compatible.
Learning objectives
- Create a disposable merge request whose root configuration intentionally supports merge request pipelines.
- Compare safe branch and merge-request metadata without printing secrets or broad environment dumps.
- Use workflow:rules to avoid duplicate branch and MR pipelines for the same source-branch update.
- Prove the exact commit/ref under test from job and pipeline evidence.
- Simulate source+target integration locally when merged-results pipelines are unavailable on the mandatory Free path.
1. Disposable lab scope and assumptions
Use a throwaway GitLab project such as glci-mr-lab.
Create a default branch main and a feature branch
glci/ch16-feature. Do not use a proprietary repository,
deployment credentials, protected production runners, or real
secrets.
Current assumptions verified 2026-09-11: ordinary merge request pipelines are available on Free/Premium/Ultimate; merged-results pipelines and merge trains are Premium/Ultimate. The mandatory workflow below uses only ordinary MR pipelines plus a local merge simulation.
2. Create synthetic source with a target/source interaction
The lab needs a change that is easy to identify and easy to merge locally.
mkdir -p src
printf 'mode=base\n' > src/app.conf
git add src/app.conf
git commit -m "ch16: base config"
git push origin main
git switch -c glci/ch16-feature
printf 'mode=feature\n' > src/app.conf
printf 'feature=ch16\n' > src/feature.txt
git add src
git commit -m "ch16: feature change"
git push -u origin glci/ch16-feature
Record the feature commit SHA locally with
git rev-parse HEAD. That SHA becomes one prediction to
compare with the MR pipeline’s CI_COMMIT_SHA.
3. Start with a deliberate duplicate-pipeline bug
The first configuration is semantically valid but operationally poor: its job rule has an MR rule plus a broad final rule. A push to a branch with an open MR can therefore result in both a branch pipeline and an MR pipeline.
stages: [verify]
evidence:
image: alpine:3.22.1
stage: verify
script:
- mkdir -p evidence
- printf 'pipeline_id=%s\n' "$CI_PIPELINE_ID" | tee evidence/context.txt
- printf 'pipeline_source=%s\n' "$CI_PIPELINE_SOURCE" | tee -a evidence/context.txt
- printf 'commit_sha=%s\n' "$CI_COMMIT_SHA" | tee -a evidence/context.txt
- printf 'commit_ref=%s\n' "$CI_COMMIT_REF_NAME" | tee -a evidence/context.txt
artifacts:
paths: [evidence/context.txt]
expire_in: 2 days
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
- when: on_success
Push this file and open a merge request from
glci/ch16-feature to main. Record every
pipeline ID created for the push/MR update. The lesson is not to
celebrate two green pipelines; it is to prove why two were created.
4. Compare branch and MR evidence
For each pipeline, record only non-secret predefined metadata. Do not dump the environment. In the MR pipeline, add the MR-specific fields explicitly:
printf 'mr_iid=%s\n' "${CI_MERGE_REQUEST_IID:-not-an-mr}"
printf 'mr_ref=%s\n' "${CI_MERGE_REQUEST_REF_PATH:-not-an-mr}"
printf 'mr_event_type=%s\n' "${CI_MERGE_REQUEST_EVENT_TYPE:-not-an-mr}"
printf 'mr_source_branch=%s\n' "${CI_MERGE_REQUEST_SOURCE_BRANCH_NAME:-not-an-mr}"
printf 'mr_target_branch=%s\n' "${CI_MERGE_REQUEST_TARGET_BRANCH_NAME:-not-an-mr}"
| Field | Branch pipeline expectation | MR pipeline expectation |
|---|---|---|
| CI_PIPELINE_SOURCE | push | merge_request_event |
| CI_COMMIT_SHA | Feature branch commit for the push | Pipeline commit for MR source content |
| CI_MERGE_REQUEST_IID | Empty/unset | MR IID |
| CI_MERGE_REQUEST_REF_PATH | Empty/unset | refs/merge-requests/<iid>/head style ref path |
| CI_MERGE_REQUEST_EVENT_TYPE | Empty/unset | Usually detached for ordinary MR pipeline |
5. Repair creation at the workflow layer
The causal problem is pipeline creation, so repair it at
workflow:rules rather than adding more job-level
exceptions. This allowlist creates MR pipelines for MR updates and
default-branch pipelines after merge, but no feature-branch push
pipeline in parallel.
workflow:
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
- when: never
stages: [verify]
evidence:
image: alpine:3.22.1
stage: verify
script:
- mkdir -p evidence
- printf 'pipeline_id=%s\n' "$CI_PIPELINE_ID" | tee evidence/context.txt
- printf 'source=%s\n' "$CI_PIPELINE_SOURCE" | tee -a evidence/context.txt
- printf 'sha=%s\n' "$CI_COMMIT_SHA" | tee -a evidence/context.txt
- printf 'mr_iid=%s\n' "${CI_MERGE_REQUEST_IID:-none}" | tee -a evidence/context.txt
- printf 'mr_ref=%s\n' "${CI_MERGE_REQUEST_REF_PATH:-none}" | tee -a evidence/context.txt
- printf 'event_type=%s\n' "${CI_MERGE_REQUEST_EVENT_TYPE:-none}" | tee -a evidence/context.txt
artifacts:
paths: [evidence/context.txt]
expire_in: 2 days
Push one more harmless commit to the feature branch. Expect one MR pipeline for that update. Preserve the earlier duplicate pipeline IDs as the “before” evidence rather than deleting them.
6. Prove the exact revision under test
Compare three values:
-
Local feature HEAD:
git rev-parse glci/ch16-feature. -
MR pipeline
CI_COMMIT_SHArecorded in the evidence artifact. - Pipeline details/API SHA for the same pipeline ID.
For an ordinary MR pipeline, the code under test is the source-branch content, so these should represent the same source revision even though GitLab presents it under merge-request pipeline context.
7. Free-path merged-result simulation: test integration without claiming GitLab merged-results
If your account/project does not have merged-results pipelines, you can still teach the integration question locally. Fetch both refs and create a temporary merge commit in a disposable local clone. The output is a simulation, not a GitLab merged-results pipeline and not merge-train evidence.
git fetch origin main glci/ch16-feature
rm -rf .ch16-merge-sim
git worktree add --detach .ch16-merge-sim origin/main
(
cd .ch16-merge-sim
git config user.name "CI Course Simulator"
git config user.email "ci-course@example.invalid"
git merge --no-ff --no-edit origin/glci/ch16-feature
printf 'simulated_merge_sha=%s\n' "$(git rev-parse HEAD)"
printf 'target_parent=%s\n' "$(git rev-parse HEAD^1)"
printf 'source_parent=%s\n' "$(git rev-parse HEAD^2)"
test "$(cat src/app.conf)" = "mode=feature"
)
git worktree remove .ch16-merge-sim
This proves the concept “test source+target integration.” It does not reproduce GitLab’s temporary merged commit identity, project settings, event variables, UI labels, or mergeability engine. Mark all of those as not observed.
9. Fork trust exercise without exposing parent secrets
Create no real fork if you do not need one. Instead, answer the trust question from the documented model:
- Where would a fork MR pipeline run by default?
- Whose CI configuration, runners, and variables would it use?
- What changes if an authorized parent member runs the fork MR pipeline in the parent project?
- Why must CI configuration changes be reviewed before doing so?
If you do create a disposable fork, keep every project variable synthetic and never enable protected-resource access just for the exercise.
10. Evidence packet for the hands-on workflow
Disposable project path + default branch
IID + source/target project/branch
Duplicate branch/MR pipeline IDs and sources
Single MR pipeline ID + source + SHA + MR ref path
Relevant workflow:rules from compiled configuration
Local target/source parents + simulated merge SHA, explicitly labeled simulated
No real protected secrets, no production runner, merged-results/train optional
11. Challenge: select the correct layer
You still see two pipelines after changing a job’s
rules. One has source push, the other
merge_request_event. Which layer should you inspect
first?
Answer after investigation: pipeline creation.
Inspect workflow:rules and any root-level logic that
permits both sources. Do not tune runner concurrency or delete one
pipeline as a “fix.”
12. Cleanup and rollback
- Preserve the evidence packet until the lesson is reviewed.
- Remove the local worktree simulation if it remains.
- Close the disposable merge request.
- Delete only the exact disposable branch/project after confirming its path.
- Do not bulk-cancel unrelated pipelines or delete artifacts by “latest.”
Knowledge check
Why did the first configuration create duplicates?
The final catch-all job rule admitted branch push pipelines while the MR rule admitted MR pipelines. With an open MR, one source update could therefore create both contexts.
Why is workflow:rules a better repair for duplicate pipeline creation than runner tuning?
The error occurs before queueing: two pipelines are being created. Runner capacity cannot correct a pipeline-creation decision.
What proves the exact revision tested by the repaired MR pipeline?
The pipeline ID plus its recorded CI_COMMIT_SHA/ref and the pipeline details/API SHA. A branch name alone is mutable.
What does the local merge worktree prove?
Only the source+target integration concept for those fetched commits. It does not prove GitLab merged-results settings, event variables, synthetic SHA generation, or mergeability behavior.
Why avoid environment dumps in the evidence job?
They can expose CI/CD variables, job tokens, credentials, or other sensitive runtime data. Print only the specific non-secret metadata required.
Version and compatibility note
GitLab and GitLab Runner evolve continuously. Treat version-sensitive YAML, runner/executor behavior, APIs, security features, policy controls, tiers, and deprecations as assumptions to verify against the current official GitLab documentation before production use. Preserve the exact project/ref/SHA, compiled configuration, pipeline/job IDs, runner/tool versions, and external target evidence used for any reproducible lab or incident record.
Official references and version notes
Documentation verification date: 2026-09-11. Merge-request pipeline behavior, protected-resource access, merged-results pipelines, merge trains, predefined variables, and merge checks are version-sensitive. Re-check the deployed GitLab version and project settings before using these patterns in production.
- Merge request pipelines — prerequisites, source-branch behavior, fork pipelines, parent-project execution, and protected resources.
- Merged results pipelines — temporary merged commits and current Premium/Ultimate availability.
- Merge trains — queue semantics, temporary integration state, current parallel limits, and enforcement options.
- Types of pipelines — branch, MR, merged-results, merge-train, parent/child, and multi-project distinctions.
-
Predefined variables reference
— MR ref/source/target variables and
CI_MERGE_REQUEST_EVENT_TYPE. -
CI/CD YAML syntax reference
— authoritative
workflow:rules,rules,changes, and job semantics. - Debugging CI/CD pipelines — CI Lint, compiled configuration, pipeline naming, and evidence-first troubleshooting.
- Protected branches — permissions and CI/CD implications for protected refs.
Keep the academy open
Support free, practical DevOps education.
Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0
Send only Ethereum/ERC-20 compatible assets to this
address.