Merge Request Pipeline Example
This example tests a job that checks an MR for the ready label, posts a
comment, and approves the MR.
Happy Path
The MR has the ready label. The job comments and approves.
stages:
- check
check-mr:
stage: check
script:
- |
labels=$(curl -s \
-H "PRIVATE-TOKEN: $CI_JOB_TOKEN" \
"$CI_API_V4_URL/projects/1/merge_requests/$CI_MERGE_REQUEST_IID" \
| grep -o '"ready"' || true)
if [ -z "$labels" ]; then
echo "MR is not ready" >&2
exit 1
fi
- |
curl -s -X POST \
-H "PRIVATE-TOKEN: $CI_JOB_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"body\":\"MR #$CI_MERGE_REQUEST_IID is approved and ready to merge\"}" \
"$CI_API_V4_URL/projects/1/merge_requests/$CI_MERGE_REQUEST_IID/notes"
- |
curl -s -X POST \
-H "PRIVATE-TOKEN: $CI_JOB_TOKEN" \
"$CI_API_V4_URL/projects/1/merge_requests/$CI_MERGE_REQUEST_IID/approve"
- echo "MR approved"
---
.glut:
name: "check-mr approves a ready MR"
setup:
branch: "feature/my-change"
pipeline_source: "merge_request_event"
merge_request:
iid: 42
title: "Add new feature"
target_branch: "main"
draft: false
labels: "ready"
api:
token:
valid: true
scopes:
- "api"
project:
path: "test-group/test-project"
default_branch: "main"
git:
origin:
branch: "feature/my-change"
files:
"Dockerfile": "FROM scratch\n"
assert:
job:
check-mr:
exit-status: 0
stdout:
- "MR approved"
api:
"GET /api/v4/projects/*/merge_requests/*":
called: true
"POST /api/v4/projects/*/merge_requests/*/notes":
called: true
times: 1
body:
body:
contain-substring: "approved and ready"
"POST /api/v4/projects/*/merge_requests/*/approve":
called: true
times: 1
Key points:
- Use
branch, nottag, for MR pipelines. CI_COMMIT_BRANCHis not set in MR pipelines. UseCI_MERGE_REQUEST_SOURCE_BRANCH_NAMEorCI_COMMIT_REF_NAMEif you need the branch name inside the job.CI_MERGE_REQUEST_IIDcomes fromsetup.merge_request.iid. It is a string ("42") in the environment.- Always use
*inassert.apipaths. Never hard-code a project ID or MR IID. git.origin.branchmatchessetup.branch. This ensures anygit checkoutinside the pipeline finds the expected branch name.
Draft MR Path
When the MR is in draft state, the job exits early without approving.
stages:
- check
check-mr:
stage: check
script:
- |
if [ "$CI_MERGE_REQUEST_DRAFT" = "true" ]; then
echo "MR is in draft state, skipping approval" >&2
exit 1
fi
- echo "not reached"
---
.glut:
name: "check-mr rejects a draft MR"
setup:
branch: "feature/my-change"
pipeline_source: "merge_request_event"
merge_request:
iid: 42
title: "Draft: Add new feature"
target_branch: "main"
draft: true
api:
token:
valid: true
scopes:
- "api"
assert:
job:
check-mr:
exit-status: 1
stderr:
- "MR is in draft state"
api:
"POST /api/v4/projects/*/merge_requests/*/approve":
called: false
setup.merge_request.draft: true sets CI_MERGE_REQUEST_DRAFT=true in the
environment. The job reads this variable and exits before calling the approve
endpoint. The called: false assert confirms no approve request was made.