From 4308a39ce49d19847c74d72a87ed64b6d20a09c0 Mon Sep 17 00:00:00 2001 From: Amy Qualls Date: Wed, 4 Jun 2025 09:45:18 -0700 Subject: [PATCH 1/8] Describe using a dry-run merge early in jobs Stubs out the answer to a question provided in an internal-only resource. Let's get this answer out to customers. It needs a lot more detail to be usable, but I can get that detail as part of the merge request review. --- doc/ci/pipelines/merge_request_pipelines.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/doc/ci/pipelines/merge_request_pipelines.md b/doc/ci/pipelines/merge_request_pipelines.md index c598911f5134e0..6eb6c8fa2ac58f 100644 --- a/doc/ci/pipelines/merge_request_pipelines.md +++ b/doc/ci/pipelines/merge_request_pipelines.md @@ -182,3 +182,19 @@ To control access to protected variables and runners: - Expand **Variables** - Under **Access protected resources in merge request pipelines**, select or clear the **Allow merge request pipelines to access protected variables and runners** option. + +## Troubleshooting + +### Stop a pipeline early if it contains merge conflicts + +If a merge request has conflicts with its target branch, the merge cannot happen, but CI/CD +pipelines are still created. + +To preserve resources and prevent a long pipeline that cannot succeed, create a job early in the pipeline. +This job should perform a dry run of the merge. In this command, replace `TARGET` with your target branch: + +```shell +git merge --no-commit --no-ff TARGET +``` + +If the merge fails, fail the job, and fail the pipeline early. -- GitLab From 2f8e0bbbc058de8f0b8aa2f9ccde2f8d982fe5c9 Mon Sep 17 00:00:00 2001 From: Amy Qualls Date: Mon, 28 Jul 2025 14:44:21 -0700 Subject: [PATCH 2/8] Start by writing it as a troubleshooting entry First, let's capture Falko's detailed work. Co-authored-by: Falko Sieverding --- doc/ci/pipelines/merge_request_pipelines.md | 22 +++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/doc/ci/pipelines/merge_request_pipelines.md b/doc/ci/pipelines/merge_request_pipelines.md index 6eb6c8fa2ac58f..4ea4858ec6e0e6 100644 --- a/doc/ci/pipelines/merge_request_pipelines.md +++ b/doc/ci/pipelines/merge_request_pipelines.md @@ -188,13 +188,15 @@ To control access to protected variables and runners: ### Stop a pipeline early if it contains merge conflicts If a merge request has conflicts with its target branch, the merge cannot happen, but CI/CD -pipelines are still created. - -To preserve resources and prevent a long pipeline that cannot succeed, create a job early in the pipeline. -This job should perform a dry run of the merge. In this command, replace `TARGET` with your target branch: - -```shell -git merge --no-commit --no-ff TARGET -``` - -If the merge fails, fail the job, and fail the pipeline early. +pipelines are still created. To preserve resources and prevent a long pipeline that cannot succeed, +create two parallel jobs in the `.pre` stage that runs first. If either check fails, the entire +pipeline fails early: + +- `check-merge-conflict-git`: This check focuses only on Git conflicts. It uses a lightweight `alpine:latest` + image, installs Git, and performs a `git merge --no-commit`. If any code conflicts exist, the job + fails. +- `check-merge-status`: This broader check uses the merge request API to check the value of `detailed_merge_status`, + and provides human-readable error messages. The job fails on Git problems (like merge conflicts) or policy + issues (like lack of approvals, or unresolved discussions). When testing merge requests in draft status + or with incomplete CI/CD pipelines, this check also considers the underlying value of `can_be_merged`. + Work in progress is not forced out of draft status to satisfy this check. -- GitLab From c09b029a944248ed97155cd6fe0590e56cd50273 Mon Sep 17 00:00:00 2001 From: Amy Qualls Date: Mon, 28 Jul 2025 14:47:01 -0700 Subject: [PATCH 3/8] Create the stub of a fail-fast tutorial page This information should be a real, full-fledged tutorial and not just a troubleshooting idea. Let's move the information over to where I'll keep building. Co-authored-by: Falko Sieverding --- doc/tutorials/merge_requests/fail_fast.md | 28 +++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 doc/tutorials/merge_requests/fail_fast.md diff --git a/doc/tutorials/merge_requests/fail_fast.md b/doc/tutorials/merge_requests/fail_fast.md new file mode 100644 index 00000000000000..3935a4f3dc8e0a --- /dev/null +++ b/doc/tutorials/merge_requests/fail_fast.md @@ -0,0 +1,28 @@ +--- +stage: Create +group: Code Review +info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments +description: How the GitLab UI helps you track merge requests from creation to merging. +title: 'Tutorial: Stop long pipelines early if they cannot succeed' +--- + +{{< details >}} + +- Tier: Premium, Ultimate +- Offering: GitLab.com, GitLab Self-Managed, GitLab Dedicated + +{{< /details >}} + +If a merge request has conflicts with its target branch, the merge cannot happen, but CI/CD +pipelines are still created. To preserve resources and prevent a long pipeline that cannot succeed, +create two parallel jobs in the `.pre` stage that runs first. If either check fails, the entire +pipeline fails early: + +- `check-merge-conflict-git`: This check focuses only on Git conflicts. It uses a lightweight `alpine:latest` + image, installs Git, and performs a `git merge --no-commit`. If any code conflicts exist, the job + fails. +- `check-merge-status`: This broader check uses the merge request API to check the value of `detailed_merge_status`, + and provides human-readable error messages. The job fails on Git problems (like merge conflicts) or policy + issues (like lack of approvals, or unresolved discussions). When testing merge requests in draft status + or with incomplete CI/CD pipelines, this check also considers the underlying value of `can_be_merged`. + Work in progress is not forced out of draft status to satisfy this check. -- GitLab From a0abe43a544064f67e70f78f8839aebc31a5dda9 Mon Sep 17 00:00:00 2001 From: Amy Qualls Date: Mon, 28 Jul 2025 14:59:38 -0700 Subject: [PATCH 4/8] Copy code into a subheading Next, let's capture the code I was given. It's a long example, which is why I wanted to put it on a separate tutorial page. Co-authored-by: Falko Sieverding --- doc/tutorials/merge_requests/fail_fast.md | 191 +++++++++++++++++++++- 1 file changed, 190 insertions(+), 1 deletion(-) diff --git a/doc/tutorials/merge_requests/fail_fast.md b/doc/tutorials/merge_requests/fail_fast.md index 3935a4f3dc8e0a..38df95666fd12d 100644 --- a/doc/tutorials/merge_requests/fail_fast.md +++ b/doc/tutorials/merge_requests/fail_fast.md @@ -3,7 +3,7 @@ stage: Create group: Code Review info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments description: How the GitLab UI helps you track merge requests from creation to merging. -title: 'Tutorial: Stop long pipelines early if they cannot succeed' +title: 'Tutorial: Stop long merge pipelines early with a fail-fast CI/CD job' --- {{< details >}} @@ -13,6 +13,12 @@ title: 'Tutorial: Stop long pipelines early if they cannot succeed' {{< /details >}} +You don't want to waste CI/CD minutes on a long pipeline that has no chance of ever succeeding. +If you create a fail-fast job early in your pipeline, the job output provides immediate, actionable feedback to +the developer while saving you time and money. + +## Understand the types of failures + If a merge request has conflicts with its target branch, the merge cannot happen, but CI/CD pipelines are still created. To preserve resources and prevent a long pipeline that cannot succeed, create two parallel jobs in the `.pre` stage that runs first. If either check fails, the entire @@ -26,3 +32,186 @@ pipeline fails early: issues (like lack of approvals, or unresolved discussions). When testing merge requests in draft status or with incomplete CI/CD pipelines, this check also considers the underlying value of `can_be_merged`. Work in progress is not forced out of draft status to satisfy this check. + +## Add jobs to your `.gitlab-ci.yml` + +I achieved this with a two-pronged approach using two parallel jobs in the .pre stage that runs first: + +This combination gives us the best of both worlds: the raw speed and accuracy of a Git dry-run, plus the intelligent, context-aware feedback from the API. + +### Example code for `.gitlab-ci.yml` + +```yaml +#.gitlab-ci.yml + +# ========================================================================== +# 1. WORKFLOW CONTROL +# ========================================================================== +# This section defines the global rules for pipeline creation. It's the +# first thing GitLab evaluates. Its purpose is to ensure pipelines run ONLY +# for the events we care about, preventing wasteful duplicate pipelines. +# +# The logic is as follows: +# - Rule 1: Always create a pipeline for merge request events. +# - Rule 2: Always create a pipeline for commits to the default branch (e.g., 'main' or 'master'). +# - Rule 3: Always create a pipeline for tags (for releases). +# - Rule 4: NEVER create a branch pipeline if a merge request is already open for that branch. +# This is the key rule to prevent duplicate pipelines. +# - Rule 5: A fallback to allow manual pipeline runs from the UI ('web') and for scheduled pipelines. +# +# For more details, see: https://docs.gitlab.com/ee/ci/yaml/workflow.html +#.gitlab-ci.yml +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_PIPELINE_SOURCE == 'web' + - if: $CI_PIPELINE_SOURCE == 'schedule' + +# ========================================================================== +# 2. STAGES +# ========================================================================== +stages: + - ❄️ validate + - 🛠 build + - 🚂 test + - ⚙️ Run + - 🚀 staging + - 🐍 deploy + - 🛑 Stop + - 📚 cleanup + +# ========================================================================== +# 3. JOB TEMPLATES & DEFAULTS +# ========================================================================== +.default_rules: + rules: + - if: $CI_PIPELINE_SOURCE == 'merge_request_event' + - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH + +# ========================================================================== +# 4. VALIDATION JOBS +# ========================================================================== +# Job 1: Polls the GitLab API for a detailed merge status. +check-merge-status: + stage: .pre + image: alpine:latest + extends: .default_rules + before_script: + - apk add --no-cache curl jq diffutils + script: + - | + set -euo pipefail + echo "🔍 [API Check] Checking merge request !${CI_MERGE_REQUEST_IID} for mergeability..." + + # --- Configuration --- + POLL_INTERVAL=10 + MAX_ATTEMPTS=18 # 18 attempts * 10s = 3 minutes timeout + API_URL="${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/merge_requests/${CI_MERGE_REQUEST_IID}" + + # --- Initial API Call --- + echo "Fetching initial MR data and triggering a status recheck..." + PREVIOUS_RESPONSE=$(curl --silent --show-error --fail \ + -H "JOB-TOKEN: ${CI_JOB_TOKEN}" "${API_URL}?with_merge_status_recheck=true" | jq --sort-keys .) + + echo "------------------- Initial Merge Request State -------------------" + echo "${PREVIOUS_RESPONSE}" + echo "---------------------------------------------------------------------" + + # --- Polling Loop --- + for i in $(seq 1 $MAX_ATTEMPTS); do + if [ $i -gt 1 ]; then + sleep ${POLL_INTERVAL} + echo -e "\nAttempt ${i}/${MAX_ATTEMPTS}: Re-querying MR status..." + CURRENT_RESPONSE=$(curl --silent --show-error --fail \ + -H "JOB-TOKEN: ${CI_JOB_TOKEN}" "${API_URL}" | jq --sort-keys .) + + echo "🔄 Diffs from previous state:" + diff -u <(echo "${PREVIOUS_RESPONSE}") <(echo "${CURRENT_RESPONSE}") || true + + PREVIOUS_RESPONSE="${CURRENT_RESPONSE}" + else + echo -e "\nAttempt 1/${MAX_ATTEMPTS}: Analyzing initial MR status..." + CURRENT_RESPONSE="${PREVIOUS_RESPONSE}" + fi + + DETAILED_STATUS=$(echo "${CURRENT_RESPONSE}" | jq -r '.detailed_merge_status') + echo "🔖 Current detailed_merge_status: ${DETAILED_STATUS}" + + case "${DETAILED_STATUS}" in + "mergeable") + echo "✅ [API Check] Success: MR is mergeable. Pipeline can proceed." + exit 0 + ;; + "conflict" | "need_rebase") + echo "❌ [API Check] Fatal Error: MR has a hard conflict. To fix, please '${DETAILED_STATUS}' the source branch." + exit 1 + ;; + "discussions_not_resolved" | "not_approved" | "requested_changes" | "merge_request_blocked" | "not_open" | "security_policy_violations" | "jira_association_missing" | "locked_paths" | "locked_lfs_files" | "title_regex" | "commits_status") + echo "❌ [API Check] Policy Error: MR is blocked by a project policy: '${DETAILED_STATUS}'. Please resolve the issue in the MR view." + exit 1 + ;; + "draft_status" | "ci_still_running") + SIMPLE_STATUS=$(echo "${CURRENT_RESPONSE}" | jq -r '.merge_status') + if [ "${SIMPLE_STATUS}" == "can_be_merged" ]; then + echo "✅ [API Check] Success: Status is '${DETAILED_STATUS}', but MR is otherwise mergeable. Continuing." + exit 0 + else + echo "⏳ [API Check] Status is '${DETAILED_STATUS}' and not yet mergeable. Continuing to poll..." + fi + ;; + *) # All other statuses are asynchronous/transitional + echo "⏳ [API Check] Status is '${DETAILED_STATUS}'. This is a transitional state. Waiting for next poll..." + ;; + esac + done + + echo "⏰ [API Check] Error: Timed out waiting for a terminal merge status." + exit 1 + rules: + - if: $CI_PIPELINE_SOURCE == 'merge_request_event' + +# Job 2: Uses Git directly to check for merge conflicts. +check-merge-conflict-git: + stage: .pre + image: alpine:latest + extends: .default_rules + before_script: + - apk add --no-cache git + script: + - | + set -euo pipefail + echo "🔍 [Git Check] Performing a local merge test..." + + git config --global user.name "GitLab CI" + git config --global user.email "gitlab-ci@example.com" + + echo "Fetching target branch '${CI_MERGE_REQUEST_TARGET_BRANCH_NAME}'..." + git fetch origin "$CI_MERGE_REQUEST_TARGET_BRANCH_NAME" + + echo "Attempting to merge target into source branch locally..." + git merge --no-commit --no-ff "origin/$CI_MERGE_REQUEST_TARGET_BRANCH_NAME" + + echo "✅ [Git Check] Success: No git conflicts detected. The branches can be merged." + rules: + - if: $CI_PIPELINE_SOURCE == 'merge_request_event' + +# Example subsequent job that will not run if the above job fails +run-tests: + stage: 🚂 test + extends: .default_rules # Apply the default rules here as well + script: + - | + echo "--- 🔍 Inspecting CI/CD Variables ---" + echo "Project ID: $CI_PROJECT_ID" + echo "Merge Request IID: $CI_MERGE_REQUEST_IID" + echo "Merge Request Title: '$CI_MERGE_REQUEST_TITLE'" + echo "Source Branch: $CI_MERGE_REQUEST_SOURCE_BRANCH_NAME" + echo "Target Branch: $CI_MERGE_REQUEST_TARGET_BRANCH_NAME" + echo "GitLab API v4 URL: $CI_API_V4_URL" + echo "CI Job Token: Is a secret" + echo "------------------------------------" +``` -- GitLab From a441ff5b93aac6e44d3c31da4b46d12765499984 Mon Sep 17 00:00:00 2001 From: Amy Qualls Date: Mon, 28 Jul 2025 15:14:44 -0700 Subject: [PATCH 5/8] Rewrite subheadings, include code Introduces the code carefully, so as not to overwhelm a new user. Co-authored-by: Falko Sieverding --- doc/tutorials/merge_requests/fail_fast.md | 55 ++++++++++++----------- 1 file changed, 30 insertions(+), 25 deletions(-) diff --git a/doc/tutorials/merge_requests/fail_fast.md b/doc/tutorials/merge_requests/fail_fast.md index 38df95666fd12d..0b87ae86b052e3 100644 --- a/doc/tutorials/merge_requests/fail_fast.md +++ b/doc/tutorials/merge_requests/fail_fast.md @@ -14,31 +14,35 @@ title: 'Tutorial: Stop long merge pipelines early with a fail-fast CI/CD job' {{< /details >}} You don't want to waste CI/CD minutes on a long pipeline that has no chance of ever succeeding. -If you create a fail-fast job early in your pipeline, the job output provides immediate, actionable feedback to -the developer while saving you time and money. +When you create fail-fast checks early in your pipeline, developers don't have to wait for hours to +learn about common pipeline failures. ## Understand the types of failures -If a merge request has conflicts with its target branch, the merge cannot happen, but CI/CD -pipelines are still created. To preserve resources and prevent a long pipeline that cannot succeed, -create two parallel jobs in the `.pre` stage that runs first. If either check fails, the entire -pipeline fails early: +Merge request pipelines can fail for many reasons. Two types of failures are common, and can be +detected quickly: -- `check-merge-conflict-git`: This check focuses only on Git conflicts. It uses a lightweight `alpine:latest` +- Structural problems in Git, such as merge conflicts or needed rebases, which prevent merging. +- Policy problems, which prevent merging until the project's merge policies are satisfied. + +The example script on this page creates two parallel jobs in the `.pre` stage that runs first in your +pipeline. If either check fails, the entire pipeline fails early. The checks combine the raw speed +and accuracy of a Git dry-run, plus the intelligent, context-aware feedback from the API. + +## Add jobs to your `.gitlab-ci.yml` + +Edit the jobs in this example `.gitlab-ci.yml` to meet your project's needs. The example provides +two jobs: + +- The `check-merge-conflict-git` check focuses only on Git conflicts. It uses a lightweight `alpine:latest` image, installs Git, and performs a `git merge --no-commit`. If any code conflicts exist, the job fails. -- `check-merge-status`: This broader check uses the merge request API to check the value of `detailed_merge_status`, +- The broader `check-merge-status` check uses the merge request API to check the value of `detailed_merge_status`, and provides human-readable error messages. The job fails on Git problems (like merge conflicts) or policy issues (like lack of approvals, or unresolved discussions). When testing merge requests in draft status or with incomplete CI/CD pipelines, this check also considers the underlying value of `can_be_merged`. Work in progress is not forced out of draft status to satisfy this check. -## Add jobs to your `.gitlab-ci.yml` - -I achieved this with a two-pronged approach using two parallel jobs in the .pre stage that runs first: - -This combination gives us the best of both worlds: the raw speed and accuracy of a Git dry-run, plus the intelligent, context-aware feedback from the API. - ### Example code for `.gitlab-ci.yml` ```yaml @@ -51,15 +55,16 @@ This combination gives us the best of both worlds: the raw speed and accuracy of # first thing GitLab evaluates. Its purpose is to ensure pipelines run ONLY # for the events we care about, preventing wasteful duplicate pipelines. # -# The logic is as follows: +# The logic: # - Rule 1: Always create a pipeline for merge request events. -# - Rule 2: Always create a pipeline for commits to the default branch (e.g., 'main' or 'master'). +# - Rule 2: Always create a pipeline for commits to the default branch (like 'main'). # - Rule 3: Always create a pipeline for tags (for releases). # - Rule 4: NEVER create a branch pipeline if a merge request is already open for that branch. -# This is the key rule to prevent duplicate pipelines. +# This rule is the key to prevent duplicate pipelines. # - Rule 5: A fallback to allow manual pipeline runs from the UI ('web') and for scheduled pipelines. # # For more details, see: https://docs.gitlab.com/ee/ci/yaml/workflow.html + #.gitlab-ci.yml workflow: rules: @@ -95,7 +100,7 @@ stages: # ========================================================================== # 4. VALIDATION JOBS # ========================================================================== -# Job 1: Polls the GitLab API for a detailed merge status. +# Job 1: Polls the GitLab API for the detailed merge status. check-merge-status: stage: .pre image: alpine:latest @@ -164,7 +169,7 @@ check-merge-status: fi ;; *) # All other statuses are asynchronous/transitional - echo "⏳ [API Check] Status is '${DETAILED_STATUS}'. This is a transitional state. Waiting for next poll..." + echo "⏳ [API Check] Status is '${DETAILED_STATUS}'. This state is transitional. Waiting for next poll..." ;; esac done @@ -195,7 +200,7 @@ check-merge-conflict-git: echo "Attempting to merge target into source branch locally..." git merge --no-commit --no-ff "origin/$CI_MERGE_REQUEST_TARGET_BRANCH_NAME" - echo "✅ [Git Check] Success: No git conflicts detected. The branches can be merged." + echo "✅ [Git Check] Success: No Git conflicts detected. The branches can be merged." rules: - if: $CI_PIPELINE_SOURCE == 'merge_request_event' @@ -207,11 +212,11 @@ run-tests: - | echo "--- 🔍 Inspecting CI/CD Variables ---" echo "Project ID: $CI_PROJECT_ID" - echo "Merge Request IID: $CI_MERGE_REQUEST_IID" - echo "Merge Request Title: '$CI_MERGE_REQUEST_TITLE'" - echo "Source Branch: $CI_MERGE_REQUEST_SOURCE_BRANCH_NAME" - echo "Target Branch: $CI_MERGE_REQUEST_TARGET_BRANCH_NAME" + echo "Merge request IID: $CI_MERGE_REQUEST_IID" + echo "Merge request title: '$CI_MERGE_REQUEST_TITLE'" + echo "Source branch: $CI_MERGE_REQUEST_SOURCE_BRANCH_NAME" + echo "Target branch: $CI_MERGE_REQUEST_TARGET_BRANCH_NAME" echo "GitLab API v4 URL: $CI_API_V4_URL" - echo "CI Job Token: Is a secret" + echo "CI job token: Is a secret" echo "------------------------------------" ``` -- GitLab From 63889026b691dd0bf961a3ffd4c1bcb215264ee1 Mon Sep 17 00:00:00 2001 From: Amy Qualls Date: Mon, 28 Jul 2025 15:19:08 -0700 Subject: [PATCH 6/8] Smooth out troubleshooting, point to tutorial Let's remove the extra information from the troubleshooting section, and point to this new tutorial instead. --- doc/ci/pipelines/merge_request_pipelines.md | 22 +++++++-------------- doc/tutorials/merge_requests/fail_fast.md | 2 +- 2 files changed, 8 insertions(+), 16 deletions(-) diff --git a/doc/ci/pipelines/merge_request_pipelines.md b/doc/ci/pipelines/merge_request_pipelines.md index 4ea4858ec6e0e6..3701bbcd0dde95 100644 --- a/doc/ci/pipelines/merge_request_pipelines.md +++ b/doc/ci/pipelines/merge_request_pipelines.md @@ -185,18 +185,10 @@ To control access to protected variables and runners: ## Troubleshooting -### Stop a pipeline early if it contains merge conflicts - -If a merge request has conflicts with its target branch, the merge cannot happen, but CI/CD -pipelines are still created. To preserve resources and prevent a long pipeline that cannot succeed, -create two parallel jobs in the `.pre` stage that runs first. If either check fails, the entire -pipeline fails early: - -- `check-merge-conflict-git`: This check focuses only on Git conflicts. It uses a lightweight `alpine:latest` - image, installs Git, and performs a `git merge --no-commit`. If any code conflicts exist, the job - fails. -- `check-merge-status`: This broader check uses the merge request API to check the value of `detailed_merge_status`, - and provides human-readable error messages. The job fails on Git problems (like merge conflicts) or policy - issues (like lack of approvals, or unresolved discussions). When testing merge requests in draft status - or with incomplete CI/CD pipelines, this check also considers the underlying value of `can_be_merged`. - Work in progress is not forced out of draft status to satisfy this check. +### Stop a pipeline early for Git or policy problems + +If a merge request has Git or policy problems, the merge cannot happen, but CI/CD +pipelines are still created. To preserve resources and prevent a long pipeline that can never succeed, +consider creating jobs that run at the beginning of your CI/CD pipeline, as described in +[Tutorial: Stop long merge pipelines early with fail-fast CI/CD jobs](../../tutorials/merge_requests/fail_fast.md). +Configured properly, these jobs fail quickly for common problems. diff --git a/doc/tutorials/merge_requests/fail_fast.md b/doc/tutorials/merge_requests/fail_fast.md index 0b87ae86b052e3..990c05f4de5afd 100644 --- a/doc/tutorials/merge_requests/fail_fast.md +++ b/doc/tutorials/merge_requests/fail_fast.md @@ -3,7 +3,7 @@ stage: Create group: Code Review info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments description: How the GitLab UI helps you track merge requests from creation to merging. -title: 'Tutorial: Stop long merge pipelines early with a fail-fast CI/CD job' +title: 'Tutorial: Stop long merge pipelines early with fail-fast CI/CD jobs' --- {{< details >}} -- GitLab From 6b0980cdc7c6dfd14efd2330e2dc5ee677d90722 Mon Sep 17 00:00:00 2001 From: Amy Qualls Date: Mon, 28 Jul 2025 15:33:46 -0700 Subject: [PATCH 7/8] Add in error messaging and crosslink Show the error messages. Also, point to the part of the merge request API that lists all of the available merge statuses. --- doc/tutorials/merge_requests/fail_fast.md | 69 +++++++++++++++++- .../img/script_failure_v18_3.png | Bin 0 -> 8160 bytes 2 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 doc/tutorials/merge_requests/img/script_failure_v18_3.png diff --git a/doc/tutorials/merge_requests/fail_fast.md b/doc/tutorials/merge_requests/fail_fast.md index 990c05f4de5afd..8d0e7e339d2328 100644 --- a/doc/tutorials/merge_requests/fail_fast.md +++ b/doc/tutorials/merge_requests/fail_fast.md @@ -43,6 +43,9 @@ two jobs: or with incomplete CI/CD pipelines, this check also considers the underlying value of `can_be_merged`. Work in progress is not forced out of draft status to satisfy this check. +The merge request API documentation includes a full list of +[possible values for `detailed_merge_status`](../../api/merge_requests.md#merge-status). + ### Example code for `.gitlab-ci.yml` ```yaml @@ -130,7 +133,7 @@ check-merge-status: for i in $(seq 1 $MAX_ATTEMPTS); do if [ $i -gt 1 ]; then sleep ${POLL_INTERVAL} - echo -e "\nAttempt ${i}/${MAX_ATTEMPTS}: Re-querying MR status..." + echo -e "\nAttempt ${i}/${MAX_ATTEMPTS}: Re-querying merge request status..." CURRENT_RESPONSE=$(curl --silent --show-error --fail \ -H "JOB-TOKEN: ${CI_JOB_TOKEN}" "${API_URL}" | jq --sort-keys .) @@ -220,3 +223,67 @@ run-tests: echo "CI job token: Is a secret" echo "------------------------------------" ``` + +### Failure examples from these jobs + +The script creates two jobs. If one or both fail, you might see this on your merge request's +**Pipelines** page: + +![A pipeline that has failed early because of two job failures.](img/script_failure_v18_3.png) + +Failures in the `check-merge-conflict-git` job can look like this: + +```plaintext +$ set -euo pipefail # collapsed multi-line command +🔍 [Git Check] Performing a local merge test... +Fetching target branch 'main'... +From https://gitlab.com/myproject + * branch main -> FETCH_HEAD + * [new branch] main -> origin/main +Attempting to merge target into source branch locally... +Auto-merging content/example.rb +CONFLICT (content): Merge conflict in content/example.rb +Automatic merge failed; fix conflicts and then commit the result. +Cleaning up project directory and file based variables +00:00 +ERROR: Job failed: exit code 1 +``` + +Failures in the `check-merge-status` job can look like this: + +```plaintext +--------------------------------------------------------------------- +Attempt 1/18: Analyzing initial MR status... +🔖 Current detailed_merge_status: approvals_syncing +⏳ [API Check] Status is 'approvals_syncing'. This is a transitional state. Waiting for next poll... +Attempt 2/18: Re-querying merge request status... +🔄 Diffs from previous state: +--- /dev/fd/64 2025-07-27 12:57:20.457034196 +0000 ++++ /dev/fd/65 2025-07-27 12:57:20.458034185 +0000 +@@ -38,7 +38,7 @@ + "closed_by": null, + "created_at": "2025-07-27T12:32:05.613Z", + "description": "Experiment merge request\n\nCloses #2", +- "detailed_merge_status": "approvals_syncing", ++ "detailed_merge_status": "draft_status", + "diff_refs": { + "base_sha": "00000000", + "head_sha": "11111111", +🔖 Current detailed_merge_status: draft_status +⏳ [API Check] Status is 'draft_status' and not yet mergeable. Continuing to poll... +Attempt 3/18: Re-querying merge request status... +🔄 Diffs from previous state: +🔖 Current detailed_merge_status: draft_status +⏳ [API Check] Status is 'draft_status' and not yet mergeable. Continuing to poll... + +[...] + +Attempt 18/18: Re-querying merge request status... +🔄 Diffs from previous state: +🔖 Current detailed_merge_status: draft_status +⏳ [API Check] Status is 'draft_status' and not yet mergeable. Continuing to poll... +⏰ [API Check] Error: Timed out waiting for a terminal merge status. +Cleaning up project directory and file based variables +00:01 +ERROR: Job failed: exit code 1 +``` diff --git a/doc/tutorials/merge_requests/img/script_failure_v18_3.png b/doc/tutorials/merge_requests/img/script_failure_v18_3.png new file mode 100644 index 0000000000000000000000000000000000000000..dd3d84bcb4739e49a00063d96cce2d2a6e6022bf GIT binary patch literal 8160 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 F1OOpiF9ZMp literal 0 HcmV?d00001 -- GitLab From d7a56212e19aa49c5bcf74b42a7cacd7d507c4e8 Mon Sep 17 00:00:00 2001 From: Amy Qualls Date: Wed, 30 Jul 2025 14:04:30 -0700 Subject: [PATCH 8/8] Apply 1 suggestion(s) to 1 file(s) Co-authored-by: Falko Sieverding --- doc/tutorials/merge_requests/fail_fast.md | 121 ++++++++++++++++------ 1 file changed, 88 insertions(+), 33 deletions(-) diff --git a/doc/tutorials/merge_requests/fail_fast.md b/doc/tutorials/merge_requests/fail_fast.md index 8d0e7e339d2328..d83ed5c0a5967c 100644 --- a/doc/tutorials/merge_requests/fail_fast.md +++ b/doc/tutorials/merge_requests/fail_fast.md @@ -120,6 +120,63 @@ check-merge-status: MAX_ATTEMPTS=18 # 18 attempts * 10s = 3 minutes timeout API_URL="${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/merge_requests/${CI_MERGE_REQUEST_IID}" + # --- Initial API Call --- +# ========================================================================== +# 1. WORKFLOW CONTROL +# ========================================================================== +# This section defines the global rules for pipeline creation. It's the +# first thing GitLab evaluates. Its purpose is to ensure pipelines run ONLY +# for the events we care about, preventing wasteful duplicate pipelines. +# +# The logic is as follows: +# - Rule 1: Always create a pipeline for merge request events. +# - Rule 2: Always create a pipeline for commits to the default branch (e.g., 'main' or 'master'). +# - Rule 3: Always create a pipeline for tags (for releases). +# - Rule 4: NEVER create a branch pipeline if a merge request is already open for that branch. +# This is the key rule to prevent duplicate pipelines. +# - Rule 5: A fallback to allow manual pipeline runs from the UI ('web') and for scheduled pipelines. +# +# For more details, see: https://docs.gitlab.com/ee/ci/yaml/workflow.html +#.gitlab-ci.yml +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_PIPELINE_SOURCE == 'web' + - if: $CI_PIPELINE_SOURCE == 'schedule' + +# ========================================================================== +# 2. STAGES +# ========================================================================== +stages: + - 🚂 test + +# ========================================================================== +# 3. VALIDATION JOBS +# ========================================================================== +# Job 1: Polls the GitLab API for a detailed merge status. +check-merge-status: + stage: .pre + image: alpine:latest + variables: + # We do not want to clone the repo and use only the MR Api + GIT_STRATEGY: none + before_script: + - apk add --no-cache curl jq diffutils + script: + - | + set -euo pipefail + echo "🔍 [API Check] Checking merge request !${CI_MERGE_REQUEST_IID} for mergeability..." + + # --- Configuration --- + POLL_INTERVAL=10 + TIMEOUT=180 + MAX_ATTEMPTS=$(( ${TIMEOUT} / ${POLL_INTERVAL} )) + API_URL="${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/merge_requests/${CI_MERGE_REQUEST_IID}" + # --- Initial API Call --- echo "Fetching initial MR data and triggering a status recheck..." PREVIOUS_RESPONSE=$(curl --silent --show-error --fail \ @@ -128,40 +185,40 @@ check-merge-status: echo "------------------- Initial Merge Request State -------------------" echo "${PREVIOUS_RESPONSE}" echo "---------------------------------------------------------------------" - + # --- Polling Loop --- for i in $(seq 1 $MAX_ATTEMPTS); do - if [ $i -gt 1 ]; then + if [ $i -gt 1 ]; then sleep ${POLL_INTERVAL} - echo -e "\nAttempt ${i}/${MAX_ATTEMPTS}: Re-querying merge request status..." + echo -e "\nAttempt ${i}/${MAX_ATTEMPTS}: Re-querying MR status..." CURRENT_RESPONSE=$(curl --silent --show-error --fail \ - -H "JOB-TOKEN: ${CI_JOB_TOKEN}" "${API_URL}" | jq --sort-keys .) - + -H "JOB-TOKEN: ${CI_JOB_TOKEN}" "${API_URL}" | jq --sort-keys .) + echo "🔄 Diffs from previous state:" diff -u <(echo "${PREVIOUS_RESPONSE}") <(echo "${CURRENT_RESPONSE}") || true - + PREVIOUS_RESPONSE="${CURRENT_RESPONSE}" - else + else echo -e "\nAttempt 1/${MAX_ATTEMPTS}: Analyzing initial MR status..." CURRENT_RESPONSE="${PREVIOUS_RESPONSE}" - fi + fi - DETAILED_STATUS=$(echo "${CURRENT_RESPONSE}" | jq -r '.detailed_merge_status') - echo "🔖 Current detailed_merge_status: ${DETAILED_STATUS}" + DETAILED_STATUS=$(echo "${CURRENT_RESPONSE}" | jq -r '.detailed_merge_status') + echo "🔖 Current detailed_merge_status: ${DETAILED_STATUS}" - case "${DETAILED_STATUS}" in + case "${DETAILED_STATUS}" in "mergeable") - echo "✅ [API Check] Success: MR is mergeable. Pipeline can proceed." - exit 0 - ;; + echo "✅ [API Check] Success: MR is mergeable. Pipeline can proceed." + exit 0 + ;; "conflict" | "need_rebase") - echo "❌ [API Check] Fatal Error: MR has a hard conflict. To fix, please '${DETAILED_STATUS}' the source branch." - exit 1 - ;; + echo "❌ [API Check] Fatal Error: MR has a hard conflict. To fix, please '${DETAILED_STATUS}' the source branch." + exit 1 + ;; "discussions_not_resolved" | "not_approved" | "requested_changes" | "merge_request_blocked" | "not_open" | "security_policy_violations" | "jira_association_missing" | "locked_paths" | "locked_lfs_files" | "title_regex" | "commits_status") - echo "❌ [API Check] Policy Error: MR is blocked by a project policy: '${DETAILED_STATUS}'. Please resolve the issue in the MR view." - exit 1 - ;; + echo "❌ [API Check] Policy Error: MR is blocked by a project policy: '${DETAILED_STATUS}'. Please resolve the issue in the MR view." + exit 1 + ;; "draft_status" | "ci_still_running") SIMPLE_STATUS=$(echo "${CURRENT_RESPONSE}" | jq -r '.merge_status') if [ "${SIMPLE_STATUS}" == "can_be_merged" ]; then @@ -172,7 +229,7 @@ check-merge-status: fi ;; *) # All other statuses are asynchronous/transitional - echo "⏳ [API Check] Status is '${DETAILED_STATUS}'. This state is transitional. Waiting for next poll..." + echo "⏳ [API Check] Status is '${DETAILED_STATUS}'. This is a transitional state. Waiting for next poll..." ;; esac done @@ -186,41 +243,39 @@ check-merge-status: check-merge-conflict-git: stage: .pre image: alpine:latest - extends: .default_rules before_script: - apk add --no-cache git script: - | set -euo pipefail echo "🔍 [Git Check] Performing a local merge test..." - + git config --global user.name "GitLab CI" git config --global user.email "gitlab-ci@example.com" - + echo "Fetching target branch '${CI_MERGE_REQUEST_TARGET_BRANCH_NAME}'..." git fetch origin "$CI_MERGE_REQUEST_TARGET_BRANCH_NAME" - + echo "Attempting to merge target into source branch locally..." git merge --no-commit --no-ff "origin/$CI_MERGE_REQUEST_TARGET_BRANCH_NAME" - - echo "✅ [Git Check] Success: No Git conflicts detected. The branches can be merged." + + echo "✅ [Git Check] Success: No git conflicts detected. The branches can be merged." rules: - if: $CI_PIPELINE_SOURCE == 'merge_request_event' # Example subsequent job that will not run if the above job fails run-tests: stage: 🚂 test - extends: .default_rules # Apply the default rules here as well script: - | echo "--- 🔍 Inspecting CI/CD Variables ---" echo "Project ID: $CI_PROJECT_ID" - echo "Merge request IID: $CI_MERGE_REQUEST_IID" - echo "Merge request title: '$CI_MERGE_REQUEST_TITLE'" - echo "Source branch: $CI_MERGE_REQUEST_SOURCE_BRANCH_NAME" - echo "Target branch: $CI_MERGE_REQUEST_TARGET_BRANCH_NAME" + echo "Merge Request IID: $CI_MERGE_REQUEST_IID" + echo "Merge Request Title: '$CI_MERGE_REQUEST_TITLE'" + echo "Source Branch: $CI_MERGE_REQUEST_SOURCE_BRANCH_NAME" + echo "Target Branch: $CI_MERGE_REQUEST_TARGET_BRANCH_NAME" echo "GitLab API v4 URL: $CI_API_V4_URL" - echo "CI job token: Is a secret" + echo "CI Job Token: Is a secret" echo "------------------------------------" ``` -- GitLab