diff options
Diffstat (limited to 'doc/ci/yaml')
-rw-r--r-- | doc/ci/yaml/artifacts_reports.md | 4 | ||||
-rw-r--r-- | doc/ci/yaml/includes.md | 110 | ||||
-rw-r--r-- | doc/ci/yaml/index.md | 97 | ||||
-rw-r--r-- | doc/ci/yaml/script.md | 58 | ||||
-rw-r--r-- | doc/ci/yaml/yaml_optimization.md | 51 |
5 files changed, 219 insertions, 101 deletions
diff --git a/doc/ci/yaml/artifacts_reports.md b/doc/ci/yaml/artifacts_reports.md index e12786f06ce..38b0ab1f0a3 100644 --- a/doc/ci/yaml/artifacts_reports.md +++ b/doc/ci/yaml/artifacts_reports.md @@ -115,8 +115,8 @@ collected code quality report uploads to GitLab as an artifact. GitLab can display the results of one or more reports in: -- The merge request [code quality widget](../testing/code_quality.md#code-quality-widget). -- The merge request [diff annotations](../testing/code_quality.md#code-quality-in-diff-view). +- The merge request [code quality widget](../testing/code_quality.md#merge-request-widget). +- The merge request [diff annotations](../testing/code_quality.md#merge-request-changes-view). - The [full report](../testing/metrics_reports.md). ## `artifacts:reports:container_scanning` **(ULTIMATE)** diff --git a/doc/ci/yaml/includes.md b/doc/ci/yaml/includes.md index daf2e653250..b1f43a4679b 100644 --- a/doc/ci/yaml/includes.md +++ b/doc/ci/yaml/includes.md @@ -56,7 +56,7 @@ You can include an array of configuration files: - template: Auto-DevOps.gitlab-ci.yml ``` -- You can define an array that combines both default and specific `include` type: +- You can define an array that combines both default and specific `include` types: ```yaml include: @@ -290,9 +290,9 @@ smoke-test-job: In `include` sections in your `.gitlab-ci.yml` file, you can use: -- [Project variables](../variables/index.md#add-a-cicd-variable-to-a-project). -- [Group variables](../variables/index.md#add-a-cicd-variable-to-a-group). -- [Instance variables](../variables/index.md#add-a-cicd-variable-to-an-instance). +- [Project variables](../variables/index.md#for-a-project). +- [Group variables](../variables/index.md#for-a-group). +- [Instance variables](../variables/index.md#for-an-instance). - Project [predefined variables](../variables/predefined_variables.md). - In GitLab 14.2 and later, the `$CI_COMMIT_REF_NAME` [predefined variable](../variables/predefined_variables.md). @@ -333,52 +333,82 @@ see this [CI/CD variable demo](https://youtu.be/4XR8gw3Pkos). ## Use `rules` with `include` > - Introduced in GitLab 14.2 [with a flag](../../administration/feature_flags.md) named `ci_include_rules`. Disabled by default. -> - [Enabled on GitLab.com](https://gitlab.com/gitlab-org/gitlab/-/issues/337507) in GitLab 14.3. -> - [Enabled on self-managed](https://gitlab.com/gitlab-org/gitlab/-/issues/337507) GitLab 14.3. -> - [Feature flag `ci_include_rules` removed](https://gitlab.com/gitlab-org/gitlab/-/issues/337507) in GitLab 14.4. -> - [Generally available](https://gitlab.com/gitlab-org/gitlab/-/issues/337507) in GitLab 14.4. +> - [Enabled on GitLab.com and self-managed](https://gitlab.com/gitlab-org/gitlab/-/issues/337507) in GitLab 14.3. +> - [Generally available](https://gitlab.com/gitlab-org/gitlab/-/issues/337507) in GitLab 14.4. Feature flag `ci_include_rules` removed. > - [Support for `exists` keyword added](https://gitlab.com/gitlab-org/gitlab/-/issues/341511) in GitLab 14.5. You can use [`rules`](index.md#rules) with `include` to conditionally include other configuration files. -You can only use the following rules with `include` (and only with [certain variables](#use-variables-with-include)): +You can only use `rules` with [certain variables](#use-variables-with-include), and +these keywords: -- [`if` rules](index.md#rulesif). For example: +- [`rules:if`](index.md#rulesif). +- [`rules:exists`](index.md#rulesexists). - ```yaml - include: - - local: builds.yml - rules: - - if: $INCLUDE_BUILDS == "true" - - local: deploys.yml - rules: - - if: $CI_COMMIT_BRANCH == "main" - - test: - stage: test - script: exit 0 - ``` +You cannot use [`needs:`](index.md#needs) to create a job dependency that points to +a job added with `include:local:rules`. When the configuration is validated, +GitLab returns `undefined need: <job-name>`. [Issue 345377](https://gitlab.com/gitlab-org/gitlab/-/issues/345377) +proposes improving this behavior. -- [`exists` rules](index.md#rulesexists). For example: +### `include` with `rules:if` - ```yaml - include: - - local: builds.yml - rules: - - exists: - - file.md - - test: - stage: test - script: exit 0 - ``` +Use [`rules:if`](index.md#rulesif) to conditionally include other configuration files +based on the status of CI/CD variables. For example: -`rules` keyword `changes` is not supported. +```yaml +include: + - local: builds.yml + rules: + - if: $INCLUDE_BUILDS == "true" + - local: deploys.yml + rules: + - if: $CI_COMMIT_BRANCH == "main" + +test: + stage: test + script: exit 0 +``` -You cannot use [`needs:`](index.md#needs) to create a job dependency that points to -a job added with `include:local:rules`. When the configuration is checked for validity, -GitLab returns `undefined need: <job-name>`. An [issue exists](https://gitlab.com/gitlab-org/gitlab/-/issues/345377) -to improve this behavior. +### `include` with `rules:exists` + +Use [`rules:exists`](index.md#rulesexists) to conditionally include other configuration files +based on the existence of files. For example: + +```yaml +include: + - local: builds.yml + rules: + - exists: + - file.md + +test: + stage: test + script: exit 0 +``` + +In this example, GitLab checks for the existence of `file.md` in the current project. + +There is a known issue if you configure `include` with `rules:exists` to add a configuration file +from a different project. GitLab checks for the existence of the file in the _other_ project. +For example: + +```yaml +include: +- project: my-group/my-project-2 + ref: main + file: test-file.yml + rules: + - exists: + - file.md + +test: + stage: test + script: exit 0 +``` + +In this example, GitLab checks for the existence of `test-file.yml` in `my-group/my-project-2`, +not the current project. Follow [issue 386040](https://gitlab.com/gitlab-org/gitlab/-/issues/386040) +for information about work to improve this behavior. ## Use `include:local` with wildcard file paths diff --git a/doc/ci/yaml/index.md b/doc/ci/yaml/index.md index dffe409b193..3796eed54e1 100644 --- a/doc/ci/yaml/index.md +++ b/doc/ci/yaml/index.md @@ -171,7 +171,7 @@ the time limit to resolve all files is 30 seconds. #### `include:local` -Use `include:local` to include a file that is in the same repository as the `.gitlab-ci.yml` file. +Use `include:local` to include a file that is in the same repository as the project running the pipeline. Use `include:local` instead of symbolic links. **Keyword type**: Global keyword. @@ -397,10 +397,7 @@ Use [`workflow`](workflow.md) to control pipeline behavior. > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/372538) in GitLab 15.5 [with a flag](../../administration/feature_flags.md) named `pipeline_name`. Disabled by default. > - [Enabled on GitLab.com and self-managed](https://gitlab.com/gitlab-org/gitlab/-/issues/376095) in GitLab 15.7. - -FLAG: -On self-managed GitLab, by default this feature is available. To hide the feature, -ask an administrator to [disable the feature flag](../../administration/feature_flags.md) named `pipeline_name`. +> - [Generally available](https://gitlab.com/gitlab-org/gitlab/-/issues/376095) in GitLab 15.8. Feature flag `pipeline_name` removed. You can use `name` in `workflow:` to define a name for pipelines. @@ -669,6 +666,7 @@ In this example, `job1` and `job2` run in parallel: **Additional details**: - You can use `allow_failure` as a subkey of [`rules`](#rulesallow_failure). +- If `allow_failure: true` is set, the job is always considered successful, and later jobs with [`when: on_failure`](#when) don't start if this job fails. - You can use `allow_failure: false` with a manual job to create a [blocking manual job](../jobs/job_control.md#types-of-manual-jobs). A blocked pipeline does not run any jobs in later stages until the manual job is started and completes successfully. @@ -1003,7 +1001,7 @@ rspec: - Combining reports in parent pipelines using [artifacts from child pipelines](#needspipelinejob) is not supported. Track progress on adding support in [this issue](https://gitlab.com/gitlab-org/gitlab/-/issues/215725). -- To be able to browse the report output files, include the [`artifacts:paths`](#artifactspaths) keyword. This will upload and store the artifact twice. +- To be able to browse the report output files, include the [`artifacts:paths`](#artifactspaths) keyword. This uploads and stores the artifact twice. - The test reports are collected regardless of the job results (success or failure). You can use [`artifacts:expire_in`](#artifactsexpire_in) to set up an expiration date for artifacts reports. @@ -1103,10 +1101,16 @@ job: ### `cache` +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/330047) in GitLab 15.0, caches are not shared between protected and unprotected branches. + Use `cache` to specify a list of files and directories to cache between jobs. You can only use paths that are in the local working copy. -Caching is shared between pipelines and jobs. Caches are restored before [artifacts](#artifacts). +Caches are: + +- Shared between pipelines and jobs. +- By default, not shared between [protected](../../user/project/protected_branches.md) and unprotected branches. +- Restored before [artifacts](#artifacts). Learn more about caches in [Caching in GitLab CI/CD](../caching/index.md). @@ -1142,6 +1146,10 @@ rspec: - .config ``` +**Additional details**: + +- The `cache:paths` keyword includes files even if they are untracked or in your `.gitignore` file. + **Related topics**: - See the [common `cache` use cases](../caching/index.md#common-use-cases-for-caches) for more @@ -1289,6 +1297,11 @@ Untracked files include files that are: - Ignored due to [`.gitignore` configuration](https://git-scm.com/docs/gitignore). - Created, but not added to the checkout with [`git add`](https://git-scm.com/docs/git-add). +Caching untracked files can create unexpectedly large caches if the job downloads: + +- Dependencies, like gems or node modules, which are usually untracked. +- [Artifacts](#artifacts) from a different job. Files extracted from the artifacts are untracked by default. + **Keyword type**: Job keyword. You can use it only as part of a job or in the [`default` section](#default). @@ -1307,8 +1320,9 @@ rspec: **Additional details**: -- You can combine `cache:untracked` with `cache:paths` to cache all untracked files - as well as files in the configured paths. This is useful for including files that are not tracked because of a `.gitignore` configuration. For example: +- You can combine `cache:untracked` with `cache:paths` to cache all untracked files, as well as files in the configured paths. + Use `cache:paths` to cache any specific files, including tracked files, or files that are outside of the working directory, + and use `cache: untracked` to also cache all untracked files. For example: ```yaml rspec: @@ -1319,6 +1333,36 @@ rspec: - binaries/ ``` + In this example, the job caches all untracked files in the repository, as well as all the files in `binaries/`. + If there are untracked files in `binaries/`, they are covered by both keywords. + +#### `cache:unprotect` + +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/362114) in GitLab 15.8. + +Use `cache:unprotect` to set a cache to be shared between [protected](../../user/project/protected_branches.md) +and unprotected branches. + +WARNING: +When set to `true`, users without access to protected branches can read and write to +cache keys used by protected branches. + +**Keyword type**: Job keyword. You can use it only as part of a job or in the +[`default` section](#default). + +**Possible inputs**: + +- `true` or `false` (default). + +**Example of `cache:untracked`**: + +```yaml +rspec: + script: test + cache: + unprotect: true +``` + #### `cache:when` > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/18969) in GitLab 13.5 and GitLab Runner v13.5.0. @@ -2820,6 +2864,8 @@ You must: ### `parallel` +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/336576) in GitLab 15.9, the maximum value for `parallel` is increased from 50 to 200. + Use `parallel` to run a job multiple times in parallel in a single pipeline. Multiple runners must exist, or a single runner must be configured to run multiple jobs concurrently. @@ -2830,7 +2876,7 @@ Parallel jobs are named sequentially from `job_name 1/N` to `job_name N/N`. **Possible inputs**: -- A numeric value from `2` to `50`. +- A numeric value from `2` to `200`. **Example of `parallel`**: @@ -2855,6 +2901,7 @@ This example creates 5 jobs that run in parallel, named `test 1/5` to `test 5/5` > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/15356) in GitLab 13.3. > - The job naming style was [improved in GitLab 13.4](https://gitlab.com/gitlab-org/gitlab/-/issues/230452). +> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/336576) in GitLab 15.9, the maximum number of permutations is increased from 50 to 200. Use `parallel:matrix` to run a job multiple times in parallel in a single pipeline, but with different variable values for each instance of the job. @@ -2867,7 +2914,7 @@ Multiple runners must exist, or a single runner must be configured to run multip - The variable names can use only numbers, letters, and underscores (`_`). - The values must be either a string, or an array of strings. -- The number of permutations cannot exceed 50. +- The number of permutations cannot exceed 200. **Example of `parallel:matrix`**: @@ -3242,7 +3289,9 @@ Use `retry:when` with `retry:max` to retry jobs for only specific failure cases. - `always`: Retry on any failure (default). - `unknown_failure`: Retry when the failure reason is unknown. -- `script_failure`: Retry when the script failed. +- `script_failure`: Retry when: + - The script failed. + - The runner failed to pull the Docker image. For `docker`, `docker+machine`, `kubernetes` [executors](https://docs.gitlab.com/runner/executors/). - `api_failure`: Retry on API failure. - `stuck_or_timeout_failure`: Retry when the job got stuck or timed out. - `runner_system_failure`: Retry if there is a runner system failure (for example, job setup failed). @@ -3332,8 +3381,8 @@ Use `rules:if` clauses to specify when to add a job to a pipeline: - If an `if` statement is true, but it's combined with `when: never`, do not add the job to the pipeline. - If no `if` statements are true, do not add the job to the pipeline. -`if` clauses are evaluated based on the values of [predefined CI/CD variables](../variables/predefined_variables.md) -or [custom CI/CD variables](../variables/index.md#custom-cicd-variables), with +`if` clauses are evaluated based on the values of [CI/CD variables](../variables/index.md) +or [predefined CI/CD variables](../variables/predefined_variables.md), with [some exceptions](../variables/where_variables_can_be_used.md#gitlab-ciyml-file). **Keyword type**: Job-specific and pipeline-specific. You can use it as part of a job @@ -3451,7 +3500,7 @@ any subkeys. All additional details and related topics are the same. **Possible inputs**: -- An array of file paths. In GitLab 13.6 and later, [file paths can include variables](../jobs/job_control.md#variables-in-ruleschanges). +- An array of file paths. [File paths can include variables](../jobs/job_control.md#variables-in-ruleschanges). **Example of `rules:changes:paths`**: @@ -3657,7 +3706,7 @@ Use `secrets` to specify [CI/CD secrets](../secrets/index.md) to: - Retrieve from an external secrets provider. - Make available in the job as [CI/CD variables](../variables/index.md) - ([`file` type](../variables/index.md#cicd-variable-types) by default). + ([`file` type](../variables/index.md#use-file-type-cicd-variables) by default). This keyword must be used with `secrets:vault`. @@ -3716,7 +3765,7 @@ job: > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/250695) in GitLab 14.1 and GitLab Runner 14.1. Use `secrets:file` to configure the secret to be stored as either a -[`file` or `variable` type CI/CD variable](../variables/index.md#cicd-variable-types) +[`file` or `variable` type CI/CD variable](../variables/index.md#use-file-type-cicd-variables) By default, the secret is passed to the job as a `file` type CI/CD variable. The value of the secret is stored in the file and the variable contains the path to the file. @@ -4252,8 +4301,8 @@ child3: ### `variables` -[CI/CD variables](../variables/index.md) are configurable values that are passed to jobs. -Use `variables` to create [custom variables](../variables/index.md#custom-cicd-variables). +Use `variables` to define [CI/CD variables](../variables/index.md#define-a-cicd-variable-in-the-gitlab-ciyml-file), +which are configurable values that are passed to jobs. Variables are always available in `script`, `before_script`, and `after_script` commands. You can also use variables as inputs in some job keywords. @@ -4298,7 +4347,7 @@ deploy_review_job: - All YAML-defined variables are also set to any linked [Docker service containers](../services/index.md). - YAML-defined variables are meant for non-sensitive project configuration. Store sensitive information - in [protected variables](../variables/index.md#protected-cicd-variables) or [CI/CD secrets](../secrets/index.md). + in [protected variables](../variables/index.md#protect-a-cicd-variable) or [CI/CD secrets](../secrets/index.md). - [Manual pipeline variables](../variables/index.md#override-a-defined-cicd-variable) and [scheduled pipeline variables](../pipelines/schedules.md#add-a-pipeline-schedule) are not passed to downstream pipelines by default. Use [trigger:forward](#triggerforward) @@ -4306,7 +4355,6 @@ deploy_review_job: **Related topics**: -- You can use [YAML anchors for variables](yaml_optimization.md#yaml-anchors-for-variables). - [Predefined variables](../variables/predefined_variables.md) are variables the runner automatically creates and makes available in the job. - You can [configure runner behavior with variables](../runners/configure_runners.md#configure-runner-behavior-with-variables). @@ -4348,10 +4396,7 @@ variables: > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/353991) in GitLab 15.6 [with a flag](../../administration/feature_flags.md) named `ci_raw_variables_in_yaml_config`. Disabled by default. > - [Enabled on GitLab.com](https://gitlab.com/gitlab-org/gitlab/-/issues/375034) in GitLab 15.6. > - [Enabled on self-managed](https://gitlab.com/gitlab-org/gitlab/-/issues/375034) in GitLab 15.7. - -FLAG: -On self-managed GitLab, by default this feature is available. To hide the feature per project, -ask an administrator to [disable the feature flag](../../administration/feature_flags.md) named `ci_raw_variables_in_yaml_config`. +> - [Generally available](https://gitlab.com/gitlab-org/gitlab/-/issues/375034) in GitLab 15.8. Feature flag `ci_raw_variables_in_yaml_config` removed. Use the `expand` keyword to configure a variable to be expandable or not. @@ -4394,7 +4439,7 @@ the default value is `when: on_success`. or have `allow_failure: true`. - `manual`: Run the job only when [triggered manually](../jobs/job_control.md#create-a-job-that-must-be-run-manually). - `always`: Run the job regardless of the status of jobs in earlier stages. Can also be used in `workflow:rules`. -- `on_failure`: Run the job only when at least one job in an earlier stage fails. +- `on_failure`: Run the job only when at least one job in an earlier stage fails. A job with `allow_failure: true` is always considered successful. - `delayed`: [Delay the execution of a job](../jobs/job_control.md#run-a-job-after-a-delay) for a specified duration. - `never`: Don't run the job. Can only be used in a [`rules`](#rules) section or `workflow: rules`. diff --git a/doc/ci/yaml/script.md b/doc/ci/yaml/script.md index 1d3186a4047..1c31191c2f4 100644 --- a/doc/ci/yaml/script.md +++ b/doc/ci/yaml/script.md @@ -203,7 +203,7 @@ job: - echo -e "\e[31mThis text is red,\e[0m but this text isn't\e[31m however this text is red again." ``` -You can define the color codes in Shell environment variables, or even [custom CI/CD variables](../variables/index.md#custom-cicd-variables), +You can define the color codes in Shell environment variables, or even [CI/CD variables](../variables/index.md#define-a-cicd-variable-in-the-gitlab-ciyml-file), which makes the commands easier to read and reusable. For example, using the same example as above and environment variables defined in a `before_script`: @@ -284,3 +284,59 @@ job-fails: - (invalid-command xyz && invalid-command abc) - echo "The job failed already, and this is not executed." ``` + +### Multiline commands not preserved by folded YAML multiline block scalar + +If you use the `- >` folded YAML multiline block scalar to split long commands, +additional indentation causes the lines to be processed as individual commands. + +For example: + +```yaml +script: + - > + RESULT=$(curl --silent + --header + "Authorization: Bearer $CI_JOB_TOKEN" + "${CI_API_V4_URL}/job" + ) +``` + +This fails as the indentation causes the line breaks to be preserved: + +<!-- vale gitlab.CurlStringsQuoted = NO --> + +```plaintext +$ RESULT=$(curl --silent # collapsed multi-line command +curl: no URL specified! +curl: try 'curl --help' or 'curl --manual' for more information +/bin/bash: line 149: --header: command not found +/bin/bash: line 150: https://gitlab.example.com/api/v4/job: No such file or directory +``` + +<!-- vale gitlab.CurlStringsQuoted = YES --> + +Resolve this by either: + +- Removing the extra indentation: + + ```yaml + script: + - > + RESULT=$(curl --silent + --header + "Authorization: Bearer $CI_JOB_TOKEN" + "${CI_API_V4_URL}/job" + ) + ``` + +- Modifying the script so the extra line breaks are handled, for example using shell line continuation: + + ```yaml + script: + - > + RESULT=$(curl --silent \ + --header \ + "Authorization: Bearer $CI_JOB_TOKEN" \ + "${CI_API_V4_URL}/job") + ``` diff --git a/doc/ci/yaml/yaml_optimization.md b/doc/ci/yaml/yaml_optimization.md index 5344a999b95..07019a2776f 100644 --- a/doc/ci/yaml/yaml_optimization.md +++ b/doc/ci/yaml/yaml_optimization.md @@ -13,7 +13,7 @@ files by using: - YAML-specific features like [anchors (`&`)](#anchors), aliases (`*`), and map merging (`<<`). Read more about the various [YAML features](https://learnxinyminutes.com/docs/yaml/). - The [`extends` keyword](#use-extends-to-reuse-configuration-sections), - which is more flexible and readable. We recommend you use `extends` where possible. + which is more flexible and readable. You should use `extends` where possible. ## Anchors @@ -21,10 +21,20 @@ YAML has a feature called 'anchors' that you can use to duplicate content across your document. Use anchors to duplicate or inherit properties. Use anchors with [hidden jobs](../jobs/index.md#hide-jobs) -to provide templates for your jobs. When there are duplicate keys, GitLab -performs a reverse deep merge based on the keys. +to provide templates for your jobs. When there are duplicate keys, the latest included key wins, overriding the other keys. -You can use YAML anchors to merge YAML arrays. +In certain cases (see [YAML anchors for scripts](#yaml-anchors-for-scripts)), you can use YAML anchors to build arrays with multiple components defined elsewhere. For example: + +```yaml +.default_scripts: &default_scripts + - ./default-script1.sh + - ./default-script2.sh + +job1: + script: + - *default_scripts + - ./job-script.sh +``` You can't use YAML anchors across multiple files when using the [`include`](index.md#include) keyword. Anchors are only valid in the file they were defined in. To reuse configuration @@ -43,12 +53,12 @@ with their own custom `script` defined: - redis test1: - <<: *job_configuration # Merge the contents of the 'job_configuration' alias + <<: *job_configuration # Add the contents of the 'job_configuration' alias script: - test1 project test2: - <<: *job_configuration # Merge the contents of the 'job_configuration' alias + <<: *job_configuration # Add the contents of the 'job_configuration' alias script: - test2 project ``` @@ -189,30 +199,6 @@ job2: - *some-script-after ``` -### YAML anchors for variables - -Use [YAML anchors](#anchors) with `variables` to repeat assignment -of variables across multiple jobs. You can also use YAML anchors when a job -requires a specific `variables` block that would otherwise override the global variables. - -The following example shows how override the `GIT_STRATEGY` variable without affecting -the use of the `SAMPLE_VARIABLE` variable: - -```yaml -# global variables -variables: &global-variables - SAMPLE_VARIABLE: sample_variable_value - ANOTHER_SAMPLE_VARIABLE: another_sample_variable_value - -# a job that must set the GIT_STRATEGY variable, yet depend on global variables -job_no_git_strategy: - stage: cleanup - variables: - <<: *global-variables - GIT_STRATEGY: none - script: echo $SAMPLE_VARIABLE -``` - ## Use `extends` to reuse configuration sections You can use the [`extends` keyword](index.md#extends) to reuse configuration in @@ -331,8 +317,9 @@ to the contents of the `script`: ### Merge details You can use `extends` to merge hashes but not arrays. -The algorithm used for merge is "closest scope wins," so -keys from the last member always override anything defined on other +The algorithm used for merge is "closest scope wins". When there are +duplicate keys, GitLab performs a reverse deep merge based on the keys. +Keys from the last member always override anything defined on other levels. For example: ```yaml |