feature/* → develop (manuel, via MR)
develop → staging (MR automatique + auto-merge)
staging → main (MR automatique, fusion manuelle)
main → rien
push [develop]: Existe-t-il déjà une MR ?
├── oui
│ → ne rien faire
│ → afficher les infos de la MR existante
└── non
│
▼
Créer la Merge Request
## Description
-
## Related issue
Closes #
## Type of change
- [ ] Feature
- [ ] Bugfix
- [ ] Refactoring
- [ ] Documentation
- [ ] CI/CD
- [ ] Security
## Tests
- [ ] Unit tests
- [ ] Integration tests
- [ ] Functional tests
- [ ] Performance tests
- [ ] Smoke tests
## CI
- [ ] Maven Build
- [ ] SonarQube
- [ ] Semgrep
- [ ] Trivy FS
- [ ] Trivy Image
- [ ] Docker Hub
## Checklist
- [ ] Documentation updated
- [ ] No secrets committed
- [ ] Breaking change documented
- [ ] Ready for review
.gitlab/merge_request_templates/Default.md
Les modèles s'écrivent au format Markdown et se configurent directement dans le projet|→ New push sur feature → Ouverture de la MR
|→ template proposé automatiquement + validation de la Merge Request
|→ On ajoute le descriptif
|→ On coche les cases adéquates
|→ Le pipeline se met en route
|→ On merge si tout est vert (approbations)
Nom: gitlab-ci-bot
Expiration: 1 an
Scopes: api
write_repository
read_repository
Il générera alors un glpat token
gitlab-ci-bot : glpat-xxxxxxxxxxxxxxxxxx
| Clé | Protected | Masked | Valeur |
|---|---|---|---|
| GITLAB_TOKEN | glpat-xxxxxxxxxxxxxxxx |
.gitlab/
├── jobs/
│ └── auto-promote.yml
stages:
# ... stages existants
- release
include:
- local: '.gitlab/jobs/auto-promote.yml'
auto-promote:
stage: release
image: alpine:3.22
allow_failure: false
rules:
- if: '$CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == "develop"'
- if: '$CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == "staging"'
- when: never
before_script:
- apk add --no-cache curl jq
script:
- |
set -e
case "$CI_COMMIT_BRANCH" in
develop)
TARGET_BRANCH="staging"
MR_LABELS="automated,promote,staging"
ENABLE_AUTO_MERGE="true"
;;
staging)
TARGET_BRANCH="main"
MR_LABELS="automated,release,production"
ENABLE_AUTO_MERGE="false"
;;
*)
echo "No promotion required."
exit 0
;;
esac
echo "=========================================="
echo "Automatic promotion"
echo "Source : ${CI_COMMIT_BRANCH}"
echo "Target : ${TARGET_BRANCH}"
echo "=========================================="
PROJECT_ID="$CI_PROJECT_ID"
curl --request POST \
--header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
--header "Content-Type: application/json" \
--data "{
\"source_branch\":\"$CI_COMMIT_BRANCH\",
\"target_branch\":\"$TARGET_BRANCH\",
\"title\":\"Promote $CI_COMMIT_BRANCH → $TARGET_BRANCH\",
\"remove_source_branch\":false,
\"squash\":false
}" \
"$CI_API_V4_URL/projects/$PROJECT_ID/merge_requests"
DESCRIPTION=$(cat <<EOF
## Automatic Promotion
**Source branch:** ${CI_COMMIT_BRANCH}
**Target branch:** ${TARGET_BRANCH}
**Commit short-SHA:** ${CI_COMMIT_SHORT_SHA}
**Pipeline:** ${CI_PIPELINE_URL}
**Author:** ${CI_COMMIT_AUTHOR}
---
Generated automatically by GitLab CI.
EOF
)
echo "=========================================="
echo "Automatic promotion"
echo "Source : ${CI_COMMIT_BRANCH}"
echo "Target : ${TARGET_BRANCH}"
echo "=========================================="
echo "Checking for existing merge request..."
EXISTING_MR=$(curl --silent --show-error --fail-with-body \
--header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
"$CI_API_V4_URL/projects/$PROJECT_ID/merge_requests?state=opened&source_branch=$CI_COMMIT_BRANCH&target_branch=$TARGET_BRANCH")
if ! echo "$EXISTING_MR" | jq -e 'type == "array"' >/dev/null; then
echo "ERROR: GitLab API did not return a merge request array."
exit 1
fi
COUNT=$(echo "$EXISTING_MR" | jq 'length')
if [ "$COUNT" -gt 0 ]; then
STATUS="Existing Merge Request"
MR_IID=$(echo "$EXISTING_MR" | jq -r '.[0].iid')
MR_URL=$(echo "$EXISTING_MR" | jq -r '.[0].web_url')
else
STATUS="Merge Request created"
echo "=========================================="
echo "Status : ${STATUS}"
echo "Source : ${CI_COMMIT_BRANCH}"
echo "Target : ${TARGET_BRANCH}"
echo "MR : !${MR_IID}"
echo "URL : ${MR_URL}"
echo "=========================================="
==========================================
Status : Existing Merge Request
Source : develop
Target : staging
MR : !42
URL : https://gitlab.com/user/projet/-/merge_requests/42
==========================================
C'est beaucoup plus pratique pour comprendre ce qui se passe. RESPONSE=$(curl --silent --show-error --fail-with-body \
--request POST \
--header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
--data-urlencode "source_branch=${CI_COMMIT_BRANCH}" \
--data-urlencode "target_branch=${TARGET_BRANCH}" \
--data-urlencode "title=Promote ${CI_COMMIT_BRANCH} → ${TARGET_BRANCH} (${CI_COMMIT_SHORT_SHA})" \
--data-urlencode "description=${DESCRIPTION}" \
--data-urlencode "labels=${MR_LABELS}" \
--data "remove_source_branch=false" \
--data "squash=false" \
"$CI_API_V4_URL/projects/$PROJECT_ID/merge_requests")
if ! echo "$RESPONSE" | jq -e '.iid' >/dev/null; then
echo "ERROR: Failed to create merge request."
exit 1
fi
MR_IID=$(echo "$RESPONSE" | jq -r '.iid')
MR_URL=$(echo "$RESPONSE" | jq -r '.web_url')
fi
Automatic Promotion
Source branch: develop
Target branch: staging
Commit short-SHA: cdf8795b
Pipeline: https://gitlab.com/val7304/flashcards/-/pipelines/2732419157
Author: Valérie Hermans valerie_hermans@outlook.com
------------------------------------------------
Generated automatically by GitLab CI.
Activity **** added
automated promote staging

auto-promote
Passed Started 1 minute ago by
$ case "$CI_COMMIT_BRANCH" in
Source : develop
Target : staging
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 2172 100 2010 100 162 2934 236 --:--:-- --:--:-- --:--:-- 3170
{"id":504836435,"iid":6,"project_id":40456472,"title":"Promote develop → staging", "description":null,"state":"opened","created_at":"2026-07-08T11:03:29.379Z", "updated_at":"2026-07-08T11:03:29.379Z", //...
Merge checks
These checks must pass before merge requests can be merged.
Pipelines must succeed
Merge requests can't be merged if the latest pipeline did not succeed or is still running
Skipped pipelines are considered successful
Introduces the risk of merging changes that do not pass the pipeline.
All threads must be resolved
Allowed to merge: Developers and Maintainers
Allowed to push and merge: No one
echo "=========================================="
echo "Project : ${CI_PROJECT_PATH}"
echo "Status : ${STATUS}"
echo "Source Branch : ${CI_COMMIT_BRANCH}"
echo "Target Branch : ${TARGET_BRANCH}"
echo "Commit : ${CI_COMMIT_SHORT_SHA}"
echo "MR : !${MR_IID}"
echo "URL : ${MR_URL}"
echo "Pipeline : ${CI_PIPELINE_URL}"
echo "Auto-merge : ${ENABLE_AUTO_MERGE}"
echo "=========================================="
if [ "$ENABLE_AUTO_MERGE" != "true" ]; then
echo "Auto-merge: disabled"
echo "Manual merge is required for ${CI_COMMIT_BRANCH} → ${TARGET_BRANCH}."
exit 0
fi
echo "Checking merge request status before enabling auto-merge..."
MAX_ATTEMPTS=24
ATTEMPT=1
while [ "$ATTEMPT" -le "$MAX_ATTEMPTS" ]; do
MR_DETAILS=$(curl --silent --show-error --fail-with-body \
--header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
"$CI_API_V4_URL/projects/$PROJECT_ID/merge_requests/$MR_IID?with_merge_status_recheck=true")
MERGE_STATUS=$(echo "$MR_DETAILS" | jq -r '.detailed_merge_status // empty')
echo "Attempt ${ATTEMPT}/${MAX_ATTEMPTS}: ${MERGE_STATUS}"
case "$MERGE_STATUS" in
checking|unchecked|approvals_syncing)
echo "GitLab is still preparing the merge request."
sleep 10
ATTEMPT=$((ATTEMPT + 1))
;;
ci_still_running|ci_must_pass|can_be_merged)
echo "Pipeline may still be running; enabling auto-merge."
break
;;
not_approved|blocked_status|conflict|cannot_be_merged|not_open)
echo "ERROR: Merge request cannot be auto-merged."
exit 1
;;
*)
echo "ERROR: Unknown merge request status: ${MERGE_STATUS}"
exit 1
;;
esac
done
if [ "$ATTEMPT" -gt "$MAX_ATTEMPTS" ]; then
echo "ERROR: Merge request !${MR_IID} is still being prepared."
exit 1
fi
echo "Enabling auto-merge for !${MR_IID}..."
AUTO_MERGE_RESPONSE_FILE="$(mktemp)"
HTTP_STATUS=$(curl --silent --show-error \
--output "$AUTO_MERGE_RESPONSE_FILE" \
--write-out "%{http_code}" \
--request PUT \
--header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
--data-urlencode "auto_merge=true" \
--data-urlencode "sha=${CI_COMMIT_SHA}" \
"$CI_API_V4_URL/projects/$PROJECT_ID/merge_requests/$MR_IID/merge" || true)
if [ "$HTTP_STATUS" != "200" ]; then
echo "ERROR: Failed to enable auto-merge (HTTP ${HTTP_STATUS})"
exit 1
fi
AUTO_MERGE_ENABLED=$(jq -r '((.merge_when_pipeline_succeeds //
false) or (.auto_merge //
false))' "$AUTO_MERGE_RESPONSE_FILE")
if [ "$AUTO_MERGE_ENABLED" = "true" ]; then
echo "Auto-merge: enabled"
else
echo "ERROR: GitLab returned HTTP 200 but auto-merge is not enabled"
exit 1
fi
rm -f "$AUTO_MERGE_RESPONSE_FILE"
{
"error": "invalid_token",
"error_description": "Token was revoked or is invalid."
}
auto-merge: develop to staging
---------------------------------
Automatic promotion
Source : develop
Target : staging
Project: val7304/flashcards
==========================================
Checking for existing merge request...
==========================================
Project : val7304/flashcards
Status : Merge Request created
Source Branch : develop
Target Branch : staging
Commit : cdf8795b
MR : !227
URL : https://gitlab.com/val7304/flashcards/-/merge_requests/227
Pipeline : https://gitlab.com/val7304/flashcards/-/pipelines/xxxxxxxx
Auto-merge : true
==========================================
Checking merge request status before enabling auto-merge...
Attempt 1/24: checking
GitLab is still preparing the merge request.
Attempt 2/24: ci_still_running
#debug detail status automerge
Pipeline may still be running; enabling auto-merge.
Enabling auto-merge for !227...
HTTP status: 200
GitLab response:
{
"id": 518641251,
"iid": 227,
"project_id": 40456472,
"title": "Promote develop → staging (cdf8795b)",
//..
"user": {
"can_merge": true
}
}
Auto-merge: enabled
OK: 13.9 MiB in 27 packages
$ case "$CI_COMMIT_BRANCH" in
==========================================
Project : val7304/flashcards
Status : Merge Request created
Source Branch : staging
Target Branch : main
Commit : 4b3a22c0
MR : !187
URL : https://gitlab.com/val7304/flashcards/-/merge_requests/187
Pipeline : https://gitlab.com/val7304/flashcards/-/pipelines/...
==========================================
Auto-merge : disabled
Reason : Production promotion requires manual validation.
Push sur develop → Pipeline develop
│
▼
auto-promote
├── crée (ou retrouve) la MR
├── active "Merge when pipeline succeeds"
▼
MR ouverte
│
▼
Pipeline de la MR / staging
│
▼
Tous les checks passent
│
▼
GitLab fusionne automatiquement
│
▼
Commit:
"Merge branch 'develop' into 'staging'"
Created by gitlab-ci-bot
Checking for existing merge request...
//...
URL : https://gitlab.com/val7304/flashcards/-/merge_requests/381
Pipeline : https://gitlab.com/val7304/flashcards/-/pipelines/2874550766
Auto-merge : true
.. ERROR: Job failed: exit code 1




--data "reviewer_ids[]=123"
--data "reviewer_ids[]=456"
feature/* → Merge → suppression automatique de la branche