Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
		
							parent
							
								
									7b78125a38
								
							
						
					
					
						commit
						ef615776bf
					
				|  | @ -519,10 +519,26 @@ rspec:coverage: | |||
|     paths: | ||||
|       - coverage/index.html | ||||
|       - coverage/assets/ | ||||
|       - coverage/lcov/ | ||||
|       - tmp/memory_test/ | ||||
|     reports: | ||||
|       cobertura: coverage/coverage.xml | ||||
| 
 | ||||
| rspec:undercoverage: | ||||
|   extends: | ||||
|     - .coverage-base | ||||
|     - .rails:rules:rspec-undercoverage | ||||
|   stage: post-test | ||||
|   needs: ["rspec:coverage"] | ||||
|   script: | ||||
|     - if [ -n "$CI_MERGE_REQUEST_SOURCE_BRANCH_SHA" ]; then | ||||
|         echo "Checking out \$CI_MERGE_REQUEST_SOURCE_BRANCH_SHA ($CI_MERGE_REQUEST_SOURCE_BRANCH_SHA) instead of \$CI_COMMIT_SHA (merge result commit $CI_COMMIT_SHA) so we can use $CI_MERGE_REQUEST_DIFF_BASE_SHA for undercoverage in this merged result pipeline"; | ||||
|         git checkout -f ${CI_MERGE_REQUEST_SOURCE_BRANCH_SHA}; | ||||
|       else | ||||
|         echo "Using \$CI_COMMIT_SHA ($CI_COMMIT_SHA) for this non-merge result pipeline."; | ||||
|       fi; | ||||
|     - run_timed_command "scripts/undercoverage" | ||||
| 
 | ||||
| rspec:feature-flags: | ||||
|   extends: | ||||
|     - .coverage-base | ||||
|  |  | |||
|  | @ -67,6 +67,9 @@ | |||
| .if-merge-request-labels-run-review-app: &if-merge-request-labels-run-review-app | ||||
|   if: '$CI_MERGE_REQUEST_LABELS =~ /pipeline:run-review-app/' | ||||
| 
 | ||||
| .if-merge-request-labels-skip-undercoverage: &if-merge-request-labels-skip-undercoverage | ||||
|   if: '$CI_MERGE_REQUEST_LABELS =~ /pipeline:skip-undercoverage/' | ||||
| 
 | ||||
| .if-security-merge-request: &if-security-merge-request | ||||
|   if: '$CI_PROJECT_NAMESPACE == "gitlab-org/security" && $CI_MERGE_REQUEST_IID' | ||||
| 
 | ||||
|  | @ -1359,6 +1362,17 @@ | |||
|     - <<: *if-merge-request-labels-run-all-rspec | ||||
|       when: always | ||||
| 
 | ||||
| .rails:rules:rspec-undercoverage: | ||||
|   rules: | ||||
|     - <<: *if-not-ee | ||||
|       when: never | ||||
|     - <<: *if-merge-request-labels-skip-undercoverage | ||||
|       allow_failure: true | ||||
|     - <<: *if-merge-request-labels-run-all-rspec | ||||
|     - <<: *if-merge-request-approved | ||||
|     - <<: *if-merge-request | ||||
|       changes: *backend-patterns | ||||
| 
 | ||||
| .rails:rules:default-branch-schedule-nightly--code-backstage: | ||||
|   rules: | ||||
|     - <<: *if-default-branch-schedule-nightly | ||||
|  |  | |||
|  | @ -1 +1 @@ | |||
| 4dd8bfe1307ffcc5a2a3f4eb70da7977a7c1d915 | ||||
| a191a5d10f0772ae2ed6ec869001ddde6d277827 | ||||
|  |  | |||
							
								
								
									
										2
									
								
								Gemfile
								
								
								
								
							
							
						
						
									
										2
									
								
								Gemfile
								
								
								
								
							|  | @ -405,7 +405,9 @@ end | |||
| 
 | ||||
| group :development, :test, :coverage do | ||||
|   gem 'simplecov', '~> 0.18.5', require: false | ||||
|   gem 'simplecov-lcov', '~> 0.8.0', require: false | ||||
|   gem 'simplecov-cobertura', '~> 1.3.1', require: false | ||||
|   gem 'undercover', '~> 0.4.4', require: false | ||||
| end | ||||
| 
 | ||||
| # Gems required in omnibus-gitlab pipeline | ||||
|  |  | |||
|  | @ -645,6 +645,8 @@ GEM | |||
|       concurrent-ruby (~> 1.0) | ||||
|     i18n_data (0.8.0) | ||||
|     icalendar (2.4.1) | ||||
|     imagen (0.1.8) | ||||
|       parser (>= 2.5, != 2.5.1.1) | ||||
|     invisible_captcha (1.1.0) | ||||
|       rails (>= 4.2) | ||||
|     ipaddress (0.8.3) | ||||
|  | @ -1197,6 +1199,7 @@ GEM | |||
|     simplecov-cobertura (1.3.1) | ||||
|       simplecov (~> 0.8) | ||||
|     simplecov-html (0.12.3) | ||||
|     simplecov-lcov (0.8.0) | ||||
|     sixarm_ruby_unaccent (1.2.0) | ||||
|     slack-messenger (2.3.4) | ||||
|     snowplow-tracker (0.6.1) | ||||
|  | @ -1312,6 +1315,10 @@ GEM | |||
|       concurrent-ruby (~> 1.0) | ||||
|     u2f (0.2.1) | ||||
|     uber (0.1.0) | ||||
|     undercover (0.4.4) | ||||
|       imagen (>= 0.1.8) | ||||
|       rainbow (>= 2.1, < 4.0) | ||||
|       rugged (>= 0.27, < 1.3) | ||||
|     unf (0.1.4) | ||||
|       unf_ext | ||||
|     unf_ext (0.0.7.7) | ||||
|  | @ -1626,6 +1633,7 @@ DEPENDENCIES | |||
|   simple_po_parser (~> 1.1.2) | ||||
|   simplecov (~> 0.18.5) | ||||
|   simplecov-cobertura (~> 1.3.1) | ||||
|   simplecov-lcov (~> 0.8.0) | ||||
|   slack-messenger (~> 2.3.4) | ||||
|   snowplow-tracker (~> 0.6.1) | ||||
|   solargraph (~> 0.43) | ||||
|  | @ -1648,6 +1656,7 @@ DEPENDENCIES | |||
|   toml-rb (~> 2.0) | ||||
|   truncato (~> 0.7.11) | ||||
|   u2f (~> 0.2.1) | ||||
|   undercover (~> 0.4.4) | ||||
|   unf (~> 0.1.4) | ||||
|   unleash (~> 3.2.2) | ||||
|   valid_email (~> 0.1) | ||||
|  |  | |||
|  | @ -0,0 +1,21 @@ | |||
| import { mergeAttributes, Node } from '@tiptap/core'; | ||||
| import { PARSE_HTML_PRIORITY_HIGHEST } from '../constants'; | ||||
| 
 | ||||
| export default Node.create({ | ||||
|   name: 'footnoteDefinition', | ||||
| 
 | ||||
|   content: 'paragraph', | ||||
| 
 | ||||
|   group: 'block', | ||||
| 
 | ||||
|   parseHTML() { | ||||
|     return [ | ||||
|       { tag: 'section.footnotes li' }, | ||||
|       { tag: '.footnote-backref', priority: PARSE_HTML_PRIORITY_HIGHEST, ignore: true }, | ||||
|     ]; | ||||
|   }, | ||||
| 
 | ||||
|   renderHTML({ HTMLAttributes }) { | ||||
|     return ['li', mergeAttributes(HTMLAttributes), 0]; | ||||
|   }, | ||||
| }); | ||||
|  | @ -0,0 +1,37 @@ | |||
| import { Node, mergeAttributes } from '@tiptap/core'; | ||||
| import { PARSE_HTML_PRIORITY_HIGHEST } from '../constants'; | ||||
| 
 | ||||
| export default Node.create({ | ||||
|   name: 'footnoteReference', | ||||
| 
 | ||||
|   inline: true, | ||||
| 
 | ||||
|   group: 'inline', | ||||
| 
 | ||||
|   atom: true, | ||||
| 
 | ||||
|   draggable: true, | ||||
| 
 | ||||
|   selectable: true, | ||||
| 
 | ||||
|   addAttributes() { | ||||
|     return { | ||||
|       footnoteId: { | ||||
|         default: null, | ||||
|         parseHTML: (element) => element.querySelector('a').getAttribute('id'), | ||||
|       }, | ||||
|       footnoteNumber: { | ||||
|         default: null, | ||||
|         parseHTML: (element) => element.textContent, | ||||
|       }, | ||||
|     }; | ||||
|   }, | ||||
| 
 | ||||
|   parseHTML() { | ||||
|     return [{ tag: 'sup.footnote-ref', priority: PARSE_HTML_PRIORITY_HIGHEST }]; | ||||
|   }, | ||||
| 
 | ||||
|   renderHTML({ HTMLAttributes: { footnoteNumber, footnoteId, ...HTMLAttributes } }) { | ||||
|     return ['sup', mergeAttributes(HTMLAttributes), footnoteNumber]; | ||||
|   }, | ||||
| }); | ||||
|  | @ -0,0 +1,19 @@ | |||
| import { mergeAttributes, Node } from '@tiptap/core'; | ||||
| 
 | ||||
| export default Node.create({ | ||||
|   name: 'footnotesSection', | ||||
| 
 | ||||
|   content: 'footnoteDefinition+', | ||||
| 
 | ||||
|   group: 'block', | ||||
| 
 | ||||
|   isolating: true, | ||||
| 
 | ||||
|   parseHTML() { | ||||
|     return [{ tag: 'section.footnotes > ol' }]; | ||||
|   }, | ||||
| 
 | ||||
|   renderHTML({ HTMLAttributes }) { | ||||
|     return ['ol', mergeAttributes(HTMLAttributes, { class: 'footnotes gl-font-sm' }), 0]; | ||||
|   }, | ||||
| }); | ||||
|  | @ -19,6 +19,9 @@ import Dropcursor from '../extensions/dropcursor'; | |||
| import Emoji from '../extensions/emoji'; | ||||
| import Figure from '../extensions/figure'; | ||||
| import FigureCaption from '../extensions/figure_caption'; | ||||
| import FootnoteDefinition from '../extensions/footnote_definition'; | ||||
| import FootnoteReference from '../extensions/footnote_reference'; | ||||
| import FootnotesSection from '../extensions/footnotes_section'; | ||||
| import Frontmatter from '../extensions/frontmatter'; | ||||
| import Gapcursor from '../extensions/gapcursor'; | ||||
| import HardBreak from '../extensions/hard_break'; | ||||
|  | @ -94,6 +97,9 @@ export const createContentEditor = ({ | |||
|     Emoji, | ||||
|     Figure, | ||||
|     FigureCaption, | ||||
|     FootnoteDefinition, | ||||
|     FootnoteReference, | ||||
|     FootnotesSection, | ||||
|     Frontmatter, | ||||
|     Gapcursor, | ||||
|     HardBreak, | ||||
|  |  | |||
|  | @ -17,6 +17,9 @@ import Division from '../extensions/division'; | |||
| import Emoji from '../extensions/emoji'; | ||||
| import Figure from '../extensions/figure'; | ||||
| import FigureCaption from '../extensions/figure_caption'; | ||||
| import FootnotesSection from '../extensions/footnotes_section'; | ||||
| import FootnoteDefinition from '../extensions/footnote_definition'; | ||||
| import FootnoteReference from '../extensions/footnote_reference'; | ||||
| import Frontmatter from '../extensions/frontmatter'; | ||||
| import HardBreak from '../extensions/hard_break'; | ||||
| import Heading from '../extensions/heading'; | ||||
|  | @ -156,6 +159,15 @@ const defaultSerializerConfig = { | |||
| 
 | ||||
|       state.write(`:${name}:`); | ||||
|     }, | ||||
|     [FootnoteDefinition.name]: (state, node) => { | ||||
|       state.renderInline(node); | ||||
|     }, | ||||
|     [FootnoteReference.name]: (state, node) => { | ||||
|       state.write(`[^${node.attrs.footnoteNumber}]`); | ||||
|     }, | ||||
|     [FootnotesSection.name]: (state, node) => { | ||||
|       state.renderList(node, '', (index) => `[^${index + 1}]: `); | ||||
|     }, | ||||
|     [Frontmatter.name]: (state, node) => { | ||||
|       const { language } = node.attrs; | ||||
|       const syntax = { | ||||
|  |  | |||
|  | @ -63,7 +63,7 @@ export default { | |||
|           class="ide-sidebar-link js-ide-review-mode" | ||||
|           @click.prevent="changedActivityView($event, $options.leftSidebarViews.review.name)" | ||||
|         > | ||||
|           <gl-icon name="file-modified" /> | ||||
|           <gl-icon name="review-list" /> | ||||
|         </button> | ||||
|       </li> | ||||
|       <li> | ||||
|  |  | |||
|  | @ -0,0 +1,27 @@ | |||
| import InputCopyToggleVisibility from './input_copy_toggle_visibility.vue'; | ||||
| 
 | ||||
| export default { | ||||
|   component: InputCopyToggleVisibility, | ||||
|   title: 'vue_shared/components/form/input_copy_toggle_visibility', | ||||
| }; | ||||
| 
 | ||||
| const defaultProps = { | ||||
|   value: 'hR8x1fuJbzwu5uFKLf9e', | ||||
|   formInputGroupProps: { class: 'gl-form-input-xl' }, | ||||
| }; | ||||
| 
 | ||||
| const Template = (args, { argTypes }) => ({ | ||||
|   components: { InputCopyToggleVisibility }, | ||||
|   props: Object.keys(argTypes), | ||||
|   template: `<input-copy-toggle-visibility
 | ||||
|       :value="value"  | ||||
|       :initial-visibility="initialVisibility" | ||||
|       :show-toggle-visibility-button="showToggleVisibilityButton" | ||||
|       :show-copy-button="showCopyButton" | ||||
|       :form-input-group-props="formInputGroupProps" | ||||
|       :copy-button-title="copyButtonTitle" | ||||
|     />`, | ||||
| }); | ||||
| 
 | ||||
| export const Default = Template.bind({}); | ||||
| Default.args = defaultProps; | ||||
|  | @ -0,0 +1,124 @@ | |||
| <script> | ||||
| import { GlFormInputGroup, GlFormGroup, GlButton, GlTooltipDirective } from '@gitlab/ui'; | ||||
| 
 | ||||
| import { __ } from '~/locale'; | ||||
| import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; | ||||
| 
 | ||||
| export default { | ||||
|   name: 'InputCopyToggleVisibility', | ||||
|   i18n: { | ||||
|     toggleVisibilityLabelHide: __('Click to hide'), | ||||
|     toggleVisibilityLabelReveal: __('Click to reveal'), | ||||
|   }, | ||||
|   components: { | ||||
|     GlFormInputGroup, | ||||
|     GlFormGroup, | ||||
|     GlButton, | ||||
|     ClipboardButton, | ||||
|   }, | ||||
|   directives: { | ||||
|     GlTooltip: GlTooltipDirective, | ||||
|   }, | ||||
|   props: { | ||||
|     value: { | ||||
|       type: String, | ||||
|       required: false, | ||||
|       default: '', | ||||
|     }, | ||||
|     initialVisibility: { | ||||
|       type: Boolean, | ||||
|       required: false, | ||||
|       default: false, | ||||
|     }, | ||||
|     showToggleVisibilityButton: { | ||||
|       type: Boolean, | ||||
|       required: false, | ||||
|       default: true, | ||||
|     }, | ||||
|     showCopyButton: { | ||||
|       type: Boolean, | ||||
|       required: false, | ||||
|       default: true, | ||||
|     }, | ||||
|     copyButtonTitle: { | ||||
|       type: String, | ||||
|       required: false, | ||||
|       default: __('Copy'), | ||||
|     }, | ||||
|     formInputGroupProps: { | ||||
|       type: Object, | ||||
|       required: false, | ||||
|       default() { | ||||
|         return {}; | ||||
|       }, | ||||
|     }, | ||||
|   }, | ||||
|   data() { | ||||
|     return { | ||||
|       valueIsVisible: this.initialVisibility, | ||||
|     }; | ||||
|   }, | ||||
|   computed: { | ||||
|     toggleVisibilityLabel() { | ||||
|       return this.valueIsVisible | ||||
|         ? this.$options.i18n.toggleVisibilityLabelHide | ||||
|         : this.$options.i18n.toggleVisibilityLabelReveal; | ||||
|     }, | ||||
|     toggleVisibilityIcon() { | ||||
|       return this.valueIsVisible ? 'eye-slash' : 'eye'; | ||||
|     }, | ||||
|     computedValueIsVisible() { | ||||
|       return !this.showToggleVisibilityButton || this.valueIsVisible; | ||||
|     }, | ||||
|     displayedValue() { | ||||
|       return this.computedValueIsVisible ? this.value : '*'.repeat(this.value.length || 20); | ||||
|     }, | ||||
|   }, | ||||
|   methods: { | ||||
|     handleToggleVisibilityButtonClick() { | ||||
|       this.valueIsVisible = !this.valueIsVisible; | ||||
| 
 | ||||
|       this.$emit('visibility-change', this.valueIsVisible); | ||||
|     }, | ||||
|     handleCopyButtonClick() { | ||||
|       this.$emit('copy'); | ||||
|     }, | ||||
|     handleFormInputCopy(event) { | ||||
|       if (this.computedValueIsVisible) { | ||||
|         return; | ||||
|       } | ||||
| 
 | ||||
|       event.clipboardData.setData('text/plain', this.value); | ||||
|       event.preventDefault(); | ||||
|     }, | ||||
|   }, | ||||
| }; | ||||
| </script> | ||||
| <template> | ||||
|   <gl-form-group v-bind="$attrs"> | ||||
|     <gl-form-input-group | ||||
|       :value="displayedValue" | ||||
|       input-class="gl-font-monospace! gl-cursor-default!" | ||||
|       select-on-click | ||||
|       readonly | ||||
|       v-bind="formInputGroupProps" | ||||
|       @copy="handleFormInputCopy" | ||||
|     > | ||||
|       <template v-if="showToggleVisibilityButton || showCopyButton" #append> | ||||
|         <gl-button | ||||
|           v-if="showToggleVisibilityButton" | ||||
|           v-gl-tooltip.hover="toggleVisibilityLabel" | ||||
|           :aria-label="toggleVisibilityLabel" | ||||
|           :icon="toggleVisibilityIcon" | ||||
|           @click="handleToggleVisibilityButtonClick" | ||||
|         /> | ||||
|         <clipboard-button | ||||
|           v-if="showCopyButton" | ||||
|           :text="value" | ||||
|           :title="copyButtonTitle" | ||||
|           @click="handleCopyButtonClick" | ||||
|         /> | ||||
|       </template> | ||||
|     </gl-form-input-group> | ||||
|   </gl-form-group> | ||||
| </template> | ||||
|  | @ -1084,16 +1084,6 @@ module Ci | |||
|       runner&.instance_type? | ||||
|     end | ||||
| 
 | ||||
|     def job_variables_attributes | ||||
|       strong_memoize(:job_variables_attributes) do | ||||
|         job_variables.map do |variable| | ||||
|           variable.attributes.except('id', 'job_id', 'encrypted_value', 'encrypted_value_iv').tap do |attrs| | ||||
|             attrs[:value] = variable.value | ||||
|           end | ||||
|         end | ||||
|       end | ||||
|     end | ||||
| 
 | ||||
|     protected | ||||
| 
 | ||||
|     def run_status_commit_hooks! | ||||
|  |  | |||
|  | @ -7,7 +7,7 @@ module Ci | |||
|          allow_failure stage stage_id stage_idx trigger_request | ||||
|          yaml_variables when environment coverage_regex | ||||
|          description tag_list protected needs_attributes | ||||
|          resource_group scheduling_type job_variables_attributes].freeze | ||||
|          resource_group scheduling_type].freeze | ||||
|     end | ||||
| 
 | ||||
|     def self.extra_accessors | ||||
|  |  | |||
|  | @ -1,8 +0,0 @@ | |||
| --- | ||||
| name: drop_detached_partitions | ||||
| introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/67056 | ||||
| rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/337155 | ||||
| milestone: '14.2' | ||||
| type: development | ||||
| group: group::database | ||||
| default_enabled: false | ||||
|  | @ -1,8 +0,0 @@ | |||
| --- | ||||
| name: partition_pruning | ||||
| introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/67056 | ||||
| rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/337153 | ||||
| milestone: '14.2' | ||||
| type: development | ||||
| group: group::database | ||||
| default_enabled: false | ||||
|  | @ -10,5 +10,4 @@ link: https://docs.gitlab.com/ee/development/documentation/styleguide/index.html | |||
| level: suggestion | ||||
| ignorecase: true | ||||
| swap: | ||||
|   button: 'if possible, rewrite to not use' | ||||
|   area: 'use "section" instead of' | ||||
|   button: 'if possible, rewrite to remove' | ||||
|  |  | |||
|  | @ -252,6 +252,7 @@ Example response: | |||
|   "finished_at": "2016-01-11T10:15:10.506Z", | ||||
|   "duration": 97.0, | ||||
|   "status": "failed", | ||||
|   "failure_reason": "script_failure", | ||||
|   "tag": false, | ||||
|   "web_url": "https://example.com/foo/bar/-/jobs/42", | ||||
|   "user": null | ||||
|  |  | |||
|  | @ -71,6 +71,7 @@ Example of response | |||
|     "runner": null, | ||||
|     "stage": "test", | ||||
|     "status": "failed", | ||||
|     "failure_reason": "script_failure", | ||||
|     "tag": false, | ||||
|     "web_url": "https://example.com/foo/bar/-/jobs/7", | ||||
|     "user": { | ||||
|  | @ -126,6 +127,7 @@ Example of response | |||
|     "runner": null, | ||||
|     "stage": "test", | ||||
|     "status": "failed", | ||||
|     "failure_reason": "stuck_or_timeout_failure", | ||||
|     "tag": false, | ||||
|     "web_url": "https://example.com/foo/bar/-/jobs/6", | ||||
|     "user": { | ||||
|  | @ -207,6 +209,7 @@ Example of response | |||
|     "runner": null, | ||||
|     "stage": "test", | ||||
|     "status": "failed", | ||||
|     "failure_reason": "stuck_or_timeout_failure", | ||||
|     "tag": false, | ||||
|     "web_url": "https://example.com/foo/bar/-/jobs/6", | ||||
|     "user": { | ||||
|  | @ -271,6 +274,7 @@ Example of response | |||
|     "runner": null, | ||||
|     "stage": "test", | ||||
|     "status": "failed", | ||||
|     "failure_reason": "script_failure", | ||||
|     "tag": false, | ||||
|     "web_url": "https://example.com/foo/bar/-/jobs/7", | ||||
|     "user": { | ||||
|  | @ -443,6 +447,7 @@ Example of response | |||
|   "runner": null, | ||||
|   "stage": "test", | ||||
|   "status": "failed", | ||||
|   "failure_reason": "script_failure", | ||||
|   "tag": false, | ||||
|   "web_url": "https://example.com/foo/bar/-/jobs/8", | ||||
|   "user": { | ||||
|  |  | |||
|  | @ -145,6 +145,6 @@ PUT /projects/:id/external_status_checks/:check_id | |||
| | `external_url`         | string           | no       | URL of external status check resource          | | ||||
| | `protected_branch_ids` | `array<Integer>` | no       | IDs of protected branches to scope the rule by | | ||||
| 
 | ||||
| ## Related links | ||||
| ## Related topics | ||||
| 
 | ||||
| - [External status checks](../user/project/merge_requests/status_checks.md). | ||||
|  |  | |||
|  | @ -11,7 +11,7 @@ The GitLab API v3 was [removed](https://gitlab.com/gitlab-org/gitlab-foss/-/issu | |||
| 
 | ||||
| For information about the current version of the GitLab API, read the [API documentation](index.md). | ||||
| 
 | ||||
| ## Related links | ||||
| ## Related topics | ||||
| 
 | ||||
| - [GitLab v3 API documentation](https://gitlab.com/gitlab-org/gitlab-foss/-/blob/8-16-stable/doc/api/index.md) | ||||
| - [Migration guide](https://gitlab.com/gitlab-org/gitlab-foss/-/blob/11-0-stable/doc/api/v3_to_v4.md) from | ||||
|  |  | |||
|  | @ -199,8 +199,10 @@ use `include:file`. You can use `include:file` in combination with `include:proj | |||
| 
 | ||||
| **Keyword type**: Global keyword. | ||||
| 
 | ||||
| **Possible inputs**: A full path, relative to the root directory (`/`). | ||||
| The YAML file must have the extension `.yml` or `.yaml`. | ||||
| **Possible inputs**: | ||||
| 
 | ||||
| - A full path, relative to the root directory (`/`). The YAML file must have the | ||||
|   extension `.yml` or `.yaml`. | ||||
| 
 | ||||
| **Example of `include:file`**: | ||||
| 
 | ||||
|  | @ -255,10 +257,10 @@ Use `include:remote` with a full URL to include a file from a different location | |||
| 
 | ||||
| **Keyword type**: Global keyword. | ||||
| 
 | ||||
| **Possible inputs**: A public URL accessible by an HTTP/HTTPS `GET` request. | ||||
| Authentication with the remote URL is not supported. | ||||
| **Possible inputs**: | ||||
| 
 | ||||
| The YAML file must have the extension `.yml` or `.yaml`. | ||||
| - A public URL accessible by an HTTP/HTTPS `GET` request. Authentication with the | ||||
|   remote URL is not supported. The YAML file must have the extension `.yml` or `.yaml`. | ||||
| 
 | ||||
| **Example of `include:remote`**: | ||||
| 
 | ||||
|  | @ -281,7 +283,9 @@ Use `include:template` to include [`.gitlab-ci.yml` templates](https://gitlab.co | |||
| 
 | ||||
| **Keyword type**: Global keyword. | ||||
| 
 | ||||
| **Possible inputs**: [`.gitlab-ci.yml` templates](https://gitlab.com/gitlab-org/gitlab/-/tree/master/lib/gitlab/ci/templates). | ||||
| **Possible inputs**: | ||||
| 
 | ||||
| - [`.gitlab-ci.yml` templates](https://gitlab.com/gitlab-org/gitlab/-/tree/master/lib/gitlab/ci/templates). | ||||
| 
 | ||||
| **Example of `include:template`**: | ||||
| 
 | ||||
|  | @ -556,7 +560,9 @@ The default value for `allow_failure` is: | |||
| 
 | ||||
| **Keyword type**: Job keyword. You can use it only as part of a job. | ||||
| 
 | ||||
| **Possible inputs**: `true` or `false`. | ||||
| **Possible inputs**: | ||||
| 
 | ||||
| - `true` or `false`. | ||||
| 
 | ||||
| **Example of `allow_failure`**: | ||||
| 
 | ||||
|  | @ -894,7 +900,9 @@ included templates in jobs. | |||
| **Keyword type**: Job keyword. You can use it only as part of a job or in the | ||||
| [`default` section](#default). | ||||
| 
 | ||||
| **Possible inputs**: See list of available [artifacts reports types](artifacts_reports.md). | ||||
| **Possible inputs**: | ||||
| 
 | ||||
| - See list of available [artifacts reports types](artifacts_reports.md). | ||||
| 
 | ||||
| **Example of `artifacts:reports`**: | ||||
| 
 | ||||
|  | @ -1024,14 +1032,15 @@ Use the `cache:paths` keyword to choose which files or directories to cache. | |||
| **Keyword type**: Job keyword. You can use it only as part of a job or in the | ||||
| [`default` section](#default). | ||||
| 
 | ||||
| **Possible inputs**: An array of paths relative to the project directory (`$CI_PROJECT_DIR`). | ||||
| You can use wildcards that use [glob](https://en.wikipedia.org/wiki/Glob_(programming)) | ||||
| patterns: | ||||
| **Possible inputs**: | ||||
| 
 | ||||
| - In [GitLab Runner 13.0 and later](https://gitlab.com/gitlab-org/gitlab-runner/-/issues/2620), | ||||
| [`doublestar.Glob`](https://pkg.go.dev/github.com/bmatcuk/doublestar@v1.2.2?tab=doc#Match). | ||||
| - In GitLab Runner 12.10 and earlier, | ||||
| [`filepath.Match`](https://pkg.go.dev/path/filepath#Match). | ||||
| - An array of paths relative to the project directory (`$CI_PROJECT_DIR`). | ||||
|   You can use wildcards that use [glob](https://en.wikipedia.org/wiki/Glob_(programming)) | ||||
|   patterns: | ||||
|   - In [GitLab Runner 13.0 and later](https://gitlab.com/gitlab-org/gitlab-runner/-/issues/2620), | ||||
|   [`doublestar.Glob`](https://pkg.go.dev/github.com/bmatcuk/doublestar@v1.2.2?tab=doc#Match). | ||||
|   - In GitLab Runner 12.10 and earlier, | ||||
|   [`filepath.Match`](https://pkg.go.dev/path/filepath#Match). | ||||
| 
 | ||||
| **Example of `cache:paths`**: | ||||
| 
 | ||||
|  | @ -1114,7 +1123,9 @@ which speeds up subsequent pipeline runs. | |||
| **Keyword type**: Job keyword. You can use it only as part of a job or in the | ||||
| [`default` section](#default). | ||||
| 
 | ||||
| **Possible inputs**: An array of one or two file paths. | ||||
| **Possible inputs**: | ||||
| 
 | ||||
| - An array of one or two file paths. | ||||
| 
 | ||||
| **Example of `cache:key:files`**: | ||||
| 
 | ||||
|  | @ -1190,7 +1201,9 @@ Use `untracked: true` to cache all files that are untracked in your Git reposito | |||
| **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). | ||||
| **Possible inputs**: | ||||
| 
 | ||||
| - `true` or `false` (default). | ||||
| 
 | ||||
| **Example of `cache:untracked`**: | ||||
| 
 | ||||
|  | @ -1303,7 +1316,9 @@ line in the job output matches the regular expression. | |||
| To extract the code coverage value in the matching line, GitLab uses this | ||||
| regular expression: `\d+(\.\d+)?`. | ||||
| 
 | ||||
| **Possible inputs**: A regular expression. Must start and end with `/`. | ||||
| **Possible inputs**: | ||||
| 
 | ||||
| - A regular expression. Must start and end with `/`. | ||||
| 
 | ||||
| **Example of `coverage`**: | ||||
| 
 | ||||
|  | @ -1811,7 +1826,9 @@ where each shell token is a separate string in the array. | |||
| **Keyword type**: Job keyword. You can use it only as part of a job or in the | ||||
| [`default` section](#default). | ||||
| 
 | ||||
| **Possible inputs**: A string. | ||||
| **Possible inputs**: | ||||
| 
 | ||||
| - A string. | ||||
| 
 | ||||
| **Example of `image:entrypoint`**: | ||||
| 
 | ||||
|  | @ -1918,7 +1935,9 @@ You can't cancel subsequent jobs after a job with `interruptible: false` starts. | |||
| **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). | ||||
| **Possible inputs**: | ||||
| 
 | ||||
| - `true` or `false` (default). | ||||
| 
 | ||||
| **Example of `interruptible`**: | ||||
| 
 | ||||
|  | @ -2411,7 +2430,9 @@ to a pipeline, based on the status of [CI/CD variables](../variables/index.md). | |||
| 
 | ||||
| **Keyword type**: Job keyword. You can use it only as part of a job. | ||||
| 
 | ||||
| **Possible inputs**: An array of [CI/CD variable expressions](../jobs/job_control.md#cicd-variable-expressions). | ||||
| **Possible inputs**: | ||||
| 
 | ||||
| - An array of [CI/CD variable expressions](../jobs/job_control.md#cicd-variable-expressions). | ||||
| 
 | ||||
| **Example of `only:variables`**: | ||||
| 
 | ||||
|  | @ -2490,7 +2511,9 @@ when the Kubernetes service is active in the project. | |||
| 
 | ||||
| **Keyword type**: Job-specific. You can use it only as part of a job. | ||||
| 
 | ||||
| **Possible inputs**: The `kubernetes` strategy accepts only the `active` keyword. | ||||
| **Possible inputs**: | ||||
| 
 | ||||
| - The `kubernetes` strategy accepts only the `active` keyword. | ||||
| 
 | ||||
| **Example of `only:kubernetes`**: | ||||
| 
 | ||||
|  | @ -2546,7 +2569,9 @@ Parallel jobs are named sequentially from `job_name 1/N` to `job_name N/N`. | |||
| 
 | ||||
| **Keyword type**: Job keyword. You can use it only as part of a job. | ||||
| 
 | ||||
| **Possible inputs**: A numeric value from `2` to `50`. | ||||
| **Possible inputs**: | ||||
| 
 | ||||
| - A numeric value from `2` to `50`. | ||||
| 
 | ||||
| **Example of `parallel`**: | ||||
| 
 | ||||
|  | @ -2579,7 +2604,9 @@ Multiple runners must exist, or a single runner must be configured to run multip | |||
| 
 | ||||
| **Keyword type**: Job keyword. You can use it only as part of a job. | ||||
| 
 | ||||
| **Possible inputs**: A numeric value from `2` to `50`. | ||||
| **Possible inputs**: | ||||
| 
 | ||||
| - A numeric value from `2` to `50`. | ||||
| 
 | ||||
| **Example of `parallel:matrix`**: | ||||
| 
 | ||||
|  | @ -2699,7 +2726,9 @@ New tags use the SHA associated with the pipeline. | |||
| 
 | ||||
| **Keyword type**: Job keyword. You can use it only as part of a job. | ||||
| 
 | ||||
| **Possible inputs**: A tag name. Can use [CI/CD variables](../variables/index.md). | ||||
| **Possible inputs**: | ||||
| 
 | ||||
| - A tag name. Can use [CI/CD variables](../variables/index.md). | ||||
| 
 | ||||
| **Example of `release:tag_name`**: | ||||
| 
 | ||||
|  | @ -2738,7 +2767,9 @@ The release name. If omitted, it is populated with the value of `release: tag_na | |||
| 
 | ||||
| **Keyword type**: Job keyword. You can use it only as part of a job. | ||||
| 
 | ||||
| **Possible inputs**: A text string. | ||||
| **Possible inputs**: | ||||
| 
 | ||||
| - A text string. | ||||
| 
 | ||||
| **Example of `release:name`**: | ||||
| 
 | ||||
|  | @ -2843,8 +2874,10 @@ can be deployed to, but only one deployment can occur per device at any given ti | |||
| 
 | ||||
| **Keyword type**: Job keyword. You can use it only as part of a job. | ||||
| 
 | ||||
| **Possible inputs**: Only letters, digits, `-`, `_`, `/`, `$`, `{`, `}`, `.`, and spaces. | ||||
| It can't start or end with `/`. | ||||
| **Possible inputs**: | ||||
| 
 | ||||
| - Only letters, digits, `-`, `_`, `/`, `$`, `{`, `}`, `.`, and spaces. | ||||
|   It can't start or end with `/`. | ||||
| 
 | ||||
| **Example of `resource_group`**: | ||||
| 
 | ||||
|  | @ -2875,7 +2908,9 @@ to select which failures to retry on. | |||
| **Keyword type**: Job keyword. You can use it only as part of a job or in the | ||||
| [`default` section](#default). | ||||
| 
 | ||||
| **Possible inputs**: `0` (default), `1`, or `2`. | ||||
| **Possible inputs**: | ||||
| 
 | ||||
| - `0` (default), `1`, or `2`. | ||||
| 
 | ||||
| **Example of `retry`**: | ||||
| 
 | ||||
|  | @ -2894,7 +2929,9 @@ Use `retry:when` with `retry:max` to retry jobs for only specific failure cases. | |||
| **Keyword type**: Job keyword. You can use it only as part of a job or in the | ||||
| [`default` section](#default). | ||||
| 
 | ||||
| **Possible inputs**: A single failure type, or an array of one or more failure types: | ||||
| **Possible inputs**: | ||||
| 
 | ||||
| - A single failure type, or an array of one or more failure types: | ||||
| 
 | ||||
| <!-- | ||||
|   If you change any of the values below, make sure to update the `RETRY_WHEN_IN_DOCUMENTATION` | ||||
|  | @ -3002,7 +3039,9 @@ or [custom CI/CD variables](../variables/index.md#custom-cicd-variables). | |||
| **Keyword type**: Job-specific and pipeline-specific. You can use it as part of a job | ||||
| to configure the job behavior, or with [`workflow`](#workflow) to configure the pipeline behavior. | ||||
| 
 | ||||
| **Possible inputs**: A [CI/CD variable expression](../jobs/job_control.md#cicd-variable-expressions). | ||||
| **Possible inputs**: | ||||
| 
 | ||||
| - A [CI/CD variable expression](../jobs/job_control.md#cicd-variable-expressions). | ||||
| 
 | ||||
| **Example of `rules:if`**: | ||||
| 
 | ||||
|  | @ -3048,8 +3087,9 @@ branch or merge request pipelines. | |||
| 
 | ||||
| **Keyword type**: Job keyword. You can use it only as part of a job. | ||||
| 
 | ||||
| **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). | ||||
| **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). | ||||
| 
 | ||||
| **Example of `rules:changes`**: | ||||
| 
 | ||||
|  | @ -3083,8 +3123,10 @@ Use `exists` to run a job when certain files exist in the repository. | |||
| 
 | ||||
| **Keyword type**: Job keyword. You can use it only as part of a job. | ||||
| 
 | ||||
| **Possible inputs**: An array of file paths. Paths are relative to the project directory (`$CI_PROJECT_DIR`) | ||||
| and can't directly link outside it. File paths can use glob patterns. | ||||
| **Possible inputs**: | ||||
| 
 | ||||
| - An array of file paths. Paths are relative to the project directory (`$CI_PROJECT_DIR`) | ||||
|   and can't directly link outside it. File paths can use glob patterns. | ||||
| 
 | ||||
| **Example of `rules:exists`**: | ||||
| 
 | ||||
|  | @ -3122,7 +3164,9 @@ job to run before continuing. | |||
| 
 | ||||
| **Keyword type**: Job keyword. You can use it only as part of a job. | ||||
| 
 | ||||
| **Possible inputs**: `true` or `false`. Defaults to `false` if not defined. | ||||
| **Possible inputs**: | ||||
| 
 | ||||
| - `true` or `false`. Defaults to `false` if not defined. | ||||
| 
 | ||||
| **Example of `rules:allow_failure`**: | ||||
| 
 | ||||
|  | @ -3151,7 +3195,9 @@ Use [`variables`](#variables) in `rules` to define variables for specific condit | |||
| 
 | ||||
| **Keyword type**: Job-specific. You can use it only as part of a job. | ||||
| 
 | ||||
| **Possible inputs**: A hash of variables in the format `VARIABLE-NAME: value`. | ||||
| **Possible inputs**: | ||||
| 
 | ||||
| - A hash of variables in the format `VARIABLE-NAME: value`. | ||||
| 
 | ||||
| **Example of `rules:variables`**: | ||||
| 
 | ||||
|  | @ -3286,7 +3332,9 @@ the secret value directly in the variable. | |||
| 
 | ||||
| **Keyword type**: Job keyword. You can use it only as part of a job. | ||||
| 
 | ||||
| **Possible inputs**: `true` (default) or `false`. | ||||
| **Possible inputs**: | ||||
| 
 | ||||
| - `true` (default) or `false`. | ||||
| 
 | ||||
| **Example of `secrets:file`**: | ||||
| 
 | ||||
|  | @ -3682,7 +3730,9 @@ Must be used with `value`, for the variable value. | |||
| 
 | ||||
| **Keyword type**: Global keyword. You cannot set job-level variables to be pre-filled when you run a pipeline manually. | ||||
| 
 | ||||
| **Possible inputs**: A string. | ||||
| **Possible inputs**: | ||||
| 
 | ||||
| - A string. | ||||
| 
 | ||||
| **Example of `variables:description`**: | ||||
| 
 | ||||
|  |  | |||
|  | @ -134,7 +134,7 @@ After configuring GitLab for the two databases, create the new CI/CD database: | |||
|    and run any pending migrations: | ||||
| 
 | ||||
|     ```shell | ||||
|     bundle exec rails rails db:create db:schema:load:ci db:migrate | ||||
|     bundle exec rails db:create db:schema:load:ci db:migrate | ||||
|     ``` | ||||
| 
 | ||||
| 1. Restart GDK: | ||||
|  |  | |||
|  | @ -220,6 +220,20 @@ The `* as-if-jh` jobs are run in addition to the regular EE-context jobs. The `j | |||
| The intent is to ensure that a change doesn't introduce a failure after the `gitlab-org/gitlab` project is synced to | ||||
| the `gitlab-jh/gitlab` project. | ||||
| 
 | ||||
| ## `undercover` RSpec test | ||||
| 
 | ||||
| > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/74859) in GitLab 14.6. | ||||
| 
 | ||||
| The `rspec:undercoverage` job runs [`undercover`](https://rubygems.org/gems/undercover) | ||||
| to detect, and fail if any changes introduced in the merge request has zero coverage. | ||||
| 
 | ||||
| The `rsepc:undercoverage` job obtains coverage data from the `rspec:coverage` | ||||
| job. | ||||
| 
 | ||||
| In the event of an emergency, or false positive from this job, add the | ||||
| `pipeline:skip-undercoverage` label to the merge request to allow this job to | ||||
| fail. | ||||
| 
 | ||||
| ## PostgreSQL versions testing | ||||
| 
 | ||||
| Our test suite runs against PG12 as GitLab.com runs on PG12 and | ||||
|  |  | |||
|  | @ -46,6 +46,6 @@ project, group, or instance level: | |||
| When the integration sends data, you can view it in the [CI Visibility](https://app.datadoghq.com/ci) | ||||
| section of your Datadog account. | ||||
| 
 | ||||
| ## Related links | ||||
| ## Related topics | ||||
| 
 | ||||
| - [Datadog's CI Visibility](https://docs.datadoghq.com/continuous_integration/) documentation. | ||||
|  |  | |||
|  | @ -74,7 +74,7 @@ into a different branch (`stable`): | |||
|    git cherry-pick <SHA> | ||||
|    ``` | ||||
| 
 | ||||
| ## Related links | ||||
| ## Related topics | ||||
| 
 | ||||
| - Cherry-pick commits with [the Commits API](../../api/commits.md#cherry-pick-a-commit) | ||||
| - Git documentation [for cherry-picks](https://git-scm.com/docs/git-cherry-pick) | ||||
|  |  | |||
|  | @ -88,7 +88,7 @@ sees in the project's search results respectively. | |||
| |:---------------------------------------------------------------------------------------|:---------------------------------------------------------------------------------| | ||||
| |  |  | | ||||
| 
 | ||||
| ## Related links | ||||
| ## Related topics | ||||
| 
 | ||||
| - [Merge requests for confidential issues](../merge_requests/confidential.md) | ||||
| - [Make an epic confidential](../../group/epics/manage_epics.md#make-an-epic-confidential) | ||||
|  |  | |||
|  | @ -105,7 +105,7 @@ Without the approvals, the work cannot merge. Required approvals enable multiple | |||
| - [Require approval from a security team](../../../application_security/index.md#security-approvals-in-merge-requests) | ||||
|   before merging code that could introduce a vulnerability. **(ULTIMATE)** | ||||
| 
 | ||||
| ## Related links | ||||
| ## Related topics | ||||
| 
 | ||||
| - [Merge request approvals API](../../../../api/merge_request_approvals.md) | ||||
| - [Instance-level approval rules](../../../admin_area/merge_requests_approvals.md) for self-managed installations | ||||
|  |  | |||
|  | @ -157,7 +157,7 @@ You can also enforce merge request approval settings: | |||
| If the settings are inherited by a group or project, they cannot be changed in the group or project | ||||
| that inherited them. | ||||
| 
 | ||||
| ## Related links | ||||
| ## Related topics | ||||
| 
 | ||||
| - [Instance-level merge request approval settings](../../../admin_area/merge_requests_approvals.md) | ||||
| - [Compliance report](../../../compliance/compliance_report/index.md) | ||||
|  |  | |||
|  | @ -79,7 +79,7 @@ merge request is from a fork: | |||
| 1. (Optional) Select **Start a new merge request** if you're ready to create a merge request. | ||||
| 1. Click **Cherry-pick**. | ||||
| 
 | ||||
| ## Related links | ||||
| ## Related topics | ||||
| 
 | ||||
| - The [Commits API](../../../api/commits.md) enables you to add custom messages | ||||
|   to changes you cherry-pick through the API. | ||||
|  |  | |||
|  | @ -70,7 +70,7 @@ Open a merge request | |||
| - You are satisfied the problem is resolved in your private fork. | ||||
| - You are ready to make the confidential commits public. | ||||
| 
 | ||||
| ## Related links | ||||
| ## Related topics | ||||
| 
 | ||||
| - [Confidential issues](../issues/confidential_issues.md) | ||||
| - [Make an epic confidential](../../group/epics/manage_epics.md#make-an-epic-confidential) | ||||
|  |  | |||
|  | @ -144,6 +144,6 @@ to your branch to address your reviewers' requests. | |||
| WARNING: | ||||
| Suggestions applied from multiple authors creates a commit authored by the user applying the suggestions. | ||||
| 
 | ||||
| ## Related links | ||||
| ## Related topics | ||||
| 
 | ||||
| - [Suggestions API](../../../../api/suggestions.md) | ||||
|  |  | |||
|  | @ -181,6 +181,6 @@ You should: | |||
| - Check the [GitLab status page](https://status.gitlab.com/) if the problem persists, | ||||
|   to see if there is a wider outage. | ||||
| 
 | ||||
| ## Related links | ||||
| ## Related topics | ||||
| 
 | ||||
| - [External status checks API](../../../api/status_checks.md) | ||||
|  |  | |||
|  | @ -248,9 +248,19 @@ When you [rename a user](../../profile/index.md#change-your-username), | |||
| - The redirects are available as long as the original path is not claimed by | ||||
|   another group, user, or project. | ||||
| 
 | ||||
| ## Related links | ||||
| ## Related topics | ||||
| 
 | ||||
| - [GitLab Workflow VS Code extension](vscode.md) | ||||
| - [GitLab Workflow VS Code extension](vscode.md). | ||||
| - To lock files and prevent change conflicts, use [file locking](../file_lock.md). | ||||
| - [Repository API](../../../api/repositories.md). | ||||
| - [Find files](file_finder.md) in a repository. | ||||
| - [Branches](branches/index.md). | ||||
| - [File templates](web_editor.md#template-dropdowns). | ||||
| - [Create a directory](web_editor.md#create-a-directory). | ||||
| - [Start a merge request](web_editor.md#tips). | ||||
| - [Find file history](git_history.md). | ||||
| - [Identify changes by line (Git blame)](git_blame.md). | ||||
| - [Use Jupyter notebooks with GitLab](jupyter_notebooks/index.md). | ||||
| 
 | ||||
| ## Troubleshooting | ||||
| 
 | ||||
|  | @ -287,16 +297,3 @@ The same approach should also allow misidentified file types to be fixed. | |||
|    ``` | ||||
| 
 | ||||
|   `*.txt` files have an entry in the heuristics file. This example prevents parsing of these files. | ||||
| 
 | ||||
| ## Related topics | ||||
| 
 | ||||
| - To lock files and prevent change conflicts, use [file locking](../file_lock.md). | ||||
| - [Repository API](../../../api/repositories.md). | ||||
| - [Find files](file_finder.md) in a repository. | ||||
| - [Branches](branches/index.md). | ||||
| - [File templates](web_editor.md#template-dropdowns). | ||||
| - [Create a directory](web_editor.md#create-a-directory). | ||||
| - [Start a merge request](web_editor.md#tips). | ||||
| - [Find file history](git_history.md). | ||||
| - [Identify changes by line (Git blame)](git_blame.md). | ||||
| - [Use Jupyter notebooks with GitLab](jupyter_notebooks/index.md). | ||||
|  |  | |||
|  | @ -123,7 +123,7 @@ To do so: | |||
| 
 | ||||
| With this option enabled, `75h` is displayed instead of `1w 4d 3h`. | ||||
| 
 | ||||
| ## Related links | ||||
| ## Related topics | ||||
| 
 | ||||
| - [Time tracking solutions page](https://about.gitlab.com/solutions/time-tracking/) | ||||
| - Time tracking GraphQL references: | ||||
|  |  | |||
|  | @ -13,6 +13,7 @@ module API | |||
|         expose :user, with: ::API::Entities::User | ||||
|         expose :commit, with: ::API::Entities::Commit | ||||
|         expose :pipeline, with: ::API::Entities::Ci::PipelineBasic | ||||
|         expose :failure_reason, if: -> (job) { job.failed? } | ||||
| 
 | ||||
|         expose :web_url do |job, _options| | ||||
|           Gitlab::Routing.url_helpers.project_job_url(job.project, job) | ||||
|  |  | |||
|  | @ -4,8 +4,6 @@ module Gitlab | |||
|     module Partitioning | ||||
|       class DetachedPartitionDropper | ||||
|         def perform | ||||
|           return unless Feature.enabled?(:drop_detached_partitions, default_enabled: :yaml) | ||||
| 
 | ||||
|           Gitlab::AppLogger.info(message: "Checking for previously detached partitions to drop") | ||||
| 
 | ||||
|           Postgresql::DetachedPartition.ready_to_drop.find_each do |detached_partition| | ||||
|  |  | |||
|  | @ -25,11 +25,9 @@ module Gitlab | |||
|             partitions_to_create = missing_partitions | ||||
|             create(partitions_to_create) unless partitions_to_create.empty? | ||||
| 
 | ||||
|             if Feature.enabled?(:partition_pruning, default_enabled: :yaml) | ||||
|             partitions_to_detach = extra_partitions | ||||
|             detach(partitions_to_detach) unless partitions_to_detach.empty? | ||||
|           end | ||||
|           end | ||||
|         rescue StandardError => e | ||||
|           Gitlab::AppLogger.error(message: "Failed to create / detach partition(s)", | ||||
|                                   table_name: model.table_name, | ||||
|  |  | |||
|  | @ -19,7 +19,7 @@ module QA | |||
|       end | ||||
| 
 | ||||
|       def image | ||||
|         @image || 'gitlab/gitlab-runner:alpine' | ||||
|         @image || 'registry.gitlab.com/gitlab-org/gitlab-runner:alpine' | ||||
|       end | ||||
| 
 | ||||
|       def executor | ||||
|  |  | |||
|  | @ -0,0 +1,3 @@ | |||
| #!/usr/bin/env bash | ||||
| 
 | ||||
| bundle exec undercover -c "${CI_MERGE_REQUEST_DIFF_BASE_SHA:-$(git merge-base origin/master HEAD)}" | ||||
|  | @ -429,6 +429,38 @@ | |||
|     </figcaption> | ||||
|     </figure> | ||||
| 
 | ||||
| - name: footnotes | ||||
|   substitutions: | ||||
|     # NOTE: We don't care about verifying specific attribute values here, that should be the | ||||
|     #       responsibility of unit tests. These tests are about the structure of the HTML. | ||||
|     fn_href_substitution: | ||||
|       - regex: '(href)(=")(.+?)(")' | ||||
|         replacement: '\1\2REF\4' | ||||
|     footnote_id_substitution: | ||||
|       - regex: '(id)(=")(.+?)(")' | ||||
|         replacement: '\1\2ID\4' | ||||
| 
 | ||||
|   pending: | ||||
|     backend: https://gitlab.com/gitlab-org/gitlab/-/issues/346591 | ||||
|   markdown: |- | ||||
|     A footnote reference tag looks like this: [^1] | ||||
| 
 | ||||
|     This reference tag is a mix of letters and numbers. [^2] | ||||
| 
 | ||||
|     [^1]: This is the text inside a footnote. | ||||
|     [^2]: This is another footnote. | ||||
|   html: |- | ||||
|     <p data-sourcepos="1:1-1:46" dir="auto">A footnote reference tag looks like this: <sup class="footnote-ref"><a href="#fn-1-2717" id="fnref-1-2717" data-footnote-ref="">1</a></sup></p> | ||||
|     <p data-sourcepos="3:1-3:56" dir="auto">This reference tag is a mix of letters and numbers. <sup class="footnote-ref"><a href="#fn-2-2717" id="fnref-2-2717" data-footnote-ref="">2</a></sup></p> | ||||
|     <section class="footnotes" data-footnotes><ol> | ||||
|     <li id="fn-1-2717"> | ||||
|     <p data-sourcepos="5:7-5:41">This is the text inside a footnote. <a href="#fnref-1-2717" aria-label="Back to content" class="footnote-backref" data-footnote-backref=""><gl-emoji title="leftwards arrow with hook" data-name="leftwards_arrow_with_hook" data-unicode-version="1.1">↩</gl-emoji></a></p> | ||||
|     </li> | ||||
|     <li id="fn-2-2717"> | ||||
|     <p data-sourcepos="6:7-6:31">This is another footnote. <a href="#fnref-2-2717" aria-label="Back to content" class="footnote-backref" data-footnote-backref=""><gl-emoji title="leftwards arrow with hook" data-name="leftwards_arrow_with_hook" data-unicode-version="1.1">↩</gl-emoji></a></p> | ||||
|     </li> | ||||
|     </ol></section> | ||||
| 
 | ||||
| - name: frontmatter_json | ||||
|   markdown: |- | ||||
|     ;;; | ||||
|  |  | |||
|  | @ -11,6 +11,9 @@ import Division from '~/content_editor/extensions/division'; | |||
| import Emoji from '~/content_editor/extensions/emoji'; | ||||
| import Figure from '~/content_editor/extensions/figure'; | ||||
| import FigureCaption from '~/content_editor/extensions/figure_caption'; | ||||
| import FootnoteDefinition from '~/content_editor/extensions/footnote_definition'; | ||||
| import FootnoteReference from '~/content_editor/extensions/footnote_reference'; | ||||
| import FootnotesSection from '~/content_editor/extensions/footnotes_section'; | ||||
| import HardBreak from '~/content_editor/extensions/hard_break'; | ||||
| import Heading from '~/content_editor/extensions/heading'; | ||||
| import HorizontalRule from '~/content_editor/extensions/horizontal_rule'; | ||||
|  | @ -46,6 +49,9 @@ const tiptapEditor = createTestEditor({ | |||
|     DetailsContent, | ||||
|     Division, | ||||
|     Emoji, | ||||
|     FootnoteDefinition, | ||||
|     FootnoteReference, | ||||
|     FootnotesSection, | ||||
|     Figure, | ||||
|     FigureCaption, | ||||
|     HardBreak, | ||||
|  | @ -81,6 +87,9 @@ const { | |||
|     descriptionItem, | ||||
|     descriptionList, | ||||
|     emoji, | ||||
|     footnoteDefinition, | ||||
|     footnoteReference, | ||||
|     footnotesSection, | ||||
|     figure, | ||||
|     figureCaption, | ||||
|     heading, | ||||
|  | @ -117,6 +126,9 @@ const { | |||
|     emoji: { markType: Emoji.name }, | ||||
|     figure: { nodeType: Figure.name }, | ||||
|     figureCaption: { nodeType: FigureCaption.name }, | ||||
|     footnoteDefinition: { nodeType: FootnoteDefinition.name }, | ||||
|     footnoteReference: { nodeType: FootnoteReference.name }, | ||||
|     footnotesSection: { nodeType: FootnotesSection.name }, | ||||
|     hardBreak: { nodeType: HardBreak.name }, | ||||
|     heading: { nodeType: Heading.name }, | ||||
|     horizontalRule: { nodeType: HorizontalRule.name }, | ||||
|  | @ -1105,4 +1117,22 @@ there | |||
|       `.trim(),
 | ||||
|     ); | ||||
|   }); | ||||
| 
 | ||||
|   it('correctly serializes footnotes', () => { | ||||
|     expect( | ||||
|       serialize( | ||||
|         paragraph( | ||||
|           'Oranges are orange ', | ||||
|           footnoteReference({ footnoteId: '1', footnoteNumber: '1' }), | ||||
|         ), | ||||
|         footnotesSection(footnoteDefinition(paragraph('Oranges are fruits'))), | ||||
|       ), | ||||
|     ).toBe( | ||||
|       ` | ||||
| Oranges are orange [^1] | ||||
| 
 | ||||
| [^1]: Oranges are fruits | ||||
|       `.trim(),
 | ||||
|     ); | ||||
|   }); | ||||
| }); | ||||
|  |  | |||
|  | @ -0,0 +1,220 @@ | |||
| import { merge } from 'lodash'; | ||||
| import { GlFormInputGroup } from '@gitlab/ui'; | ||||
| 
 | ||||
| import InputCopyToggleVisibility from '~/vue_shared/components/form/input_copy_toggle_visibility.vue'; | ||||
| import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; | ||||
| import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; | ||||
| 
 | ||||
| import { mountExtended } from 'helpers/vue_test_utils_helper'; | ||||
| 
 | ||||
| describe('InputCopyToggleVisibility', () => { | ||||
|   let wrapper; | ||||
| 
 | ||||
|   afterEach(() => { | ||||
|     wrapper.destroy(); | ||||
|   }); | ||||
| 
 | ||||
|   const valueProp = 'hR8x1fuJbzwu5uFKLf9e'; | ||||
| 
 | ||||
|   const createComponent = (options = {}) => { | ||||
|     wrapper = mountExtended( | ||||
|       InputCopyToggleVisibility, | ||||
|       merge({}, options, { | ||||
|         directives: { | ||||
|           GlTooltip: createMockDirective(), | ||||
|         }, | ||||
|       }), | ||||
|     ); | ||||
|   }; | ||||
| 
 | ||||
|   const findFormInputGroup = () => wrapper.findComponent(GlFormInputGroup); | ||||
|   const findFormInput = () => findFormInputGroup().find('input'); | ||||
|   const findRevealButton = () => | ||||
|     wrapper.findByRole('button', { | ||||
|       name: InputCopyToggleVisibility.i18n.toggleVisibilityLabelReveal, | ||||
|     }); | ||||
|   const findHideButton = () => | ||||
|     wrapper.findByRole('button', { | ||||
|       name: InputCopyToggleVisibility.i18n.toggleVisibilityLabelHide, | ||||
|     }); | ||||
|   const findCopyButton = () => wrapper.findComponent(ClipboardButton); | ||||
|   const createCopyEvent = () => { | ||||
|     const event = new Event('copy', { cancelable: true }); | ||||
|     Object.assign(event, { preventDefault: jest.fn(), clipboardData: { setData: jest.fn() } }); | ||||
| 
 | ||||
|     return event; | ||||
|   }; | ||||
| 
 | ||||
|   const itDoesNotModifyCopyEvent = () => { | ||||
|     it('does not modify copy event', () => { | ||||
|       const event = createCopyEvent(); | ||||
| 
 | ||||
|       findFormInput().element.dispatchEvent(event); | ||||
| 
 | ||||
|       expect(event.clipboardData.setData).not.toHaveBeenCalled(); | ||||
|       expect(event.preventDefault).not.toHaveBeenCalled(); | ||||
|     }); | ||||
|   }; | ||||
| 
 | ||||
|   describe('when `value` prop is passed', () => { | ||||
|     beforeEach(() => { | ||||
|       createComponent({ | ||||
|         propsData: { | ||||
|           value: valueProp, | ||||
|         }, | ||||
|       }); | ||||
|     }); | ||||
| 
 | ||||
|     it('displays value as hidden', () => { | ||||
|       expect(findFormInputGroup().props('value')).toBe('********************'); | ||||
|     }); | ||||
| 
 | ||||
|     it('saves actual value to clipboard when manually copied', () => { | ||||
|       const event = createCopyEvent(); | ||||
|       findFormInput().element.dispatchEvent(event); | ||||
| 
 | ||||
|       expect(event.clipboardData.setData).toHaveBeenCalledWith('text/plain', valueProp); | ||||
|       expect(event.preventDefault).toHaveBeenCalled(); | ||||
|     }); | ||||
| 
 | ||||
|     describe('visibility toggle button', () => { | ||||
|       it('renders a reveal button', () => { | ||||
|         const revealButton = findRevealButton(); | ||||
| 
 | ||||
|         expect(revealButton.exists()).toBe(true); | ||||
| 
 | ||||
|         const tooltip = getBinding(revealButton.element, 'gl-tooltip'); | ||||
| 
 | ||||
|         expect(tooltip.value).toBe(InputCopyToggleVisibility.i18n.toggleVisibilityLabelReveal); | ||||
|       }); | ||||
| 
 | ||||
|       describe('when clicked', () => { | ||||
|         beforeEach(async () => { | ||||
|           await findRevealButton().trigger('click'); | ||||
|         }); | ||||
| 
 | ||||
|         it('displays value', () => { | ||||
|           expect(findFormInputGroup().props('value')).toBe(valueProp); | ||||
|         }); | ||||
| 
 | ||||
|         it('renders a hide button', () => { | ||||
|           const hideButton = findHideButton(); | ||||
| 
 | ||||
|           expect(hideButton.exists()).toBe(true); | ||||
| 
 | ||||
|           const tooltip = getBinding(hideButton.element, 'gl-tooltip'); | ||||
| 
 | ||||
|           expect(tooltip.value).toBe(InputCopyToggleVisibility.i18n.toggleVisibilityLabelHide); | ||||
|         }); | ||||
| 
 | ||||
|         it('emits `visibility-change` event', () => { | ||||
|           expect(wrapper.emitted('visibility-change')[0]).toEqual([true]); | ||||
|         }); | ||||
|       }); | ||||
|     }); | ||||
| 
 | ||||
|     describe('copy button', () => { | ||||
|       it('renders button with correct props passed', () => { | ||||
|         expect(findCopyButton().props()).toMatchObject({ | ||||
|           text: valueProp, | ||||
|           title: 'Copy', | ||||
|         }); | ||||
|       }); | ||||
| 
 | ||||
|       describe('when clicked', () => { | ||||
|         beforeEach(async () => { | ||||
|           await findCopyButton().trigger('click'); | ||||
|         }); | ||||
| 
 | ||||
|         it('emits `copy` event', () => { | ||||
|           expect(wrapper.emitted('copy')[0]).toEqual([]); | ||||
|         }); | ||||
|       }); | ||||
|     }); | ||||
|   }); | ||||
| 
 | ||||
|   describe('when `value` prop is not passed', () => { | ||||
|     beforeEach(() => { | ||||
|       createComponent(); | ||||
|     }); | ||||
| 
 | ||||
|     it('displays value as hidden with 20 asterisks', () => { | ||||
|       expect(findFormInputGroup().props('value')).toBe('********************'); | ||||
|     }); | ||||
|   }); | ||||
| 
 | ||||
|   describe('when `initialVisibility` prop is `true`', () => { | ||||
|     beforeEach(() => { | ||||
|       createComponent({ | ||||
|         propsData: { | ||||
|           value: valueProp, | ||||
|           initialVisibility: true, | ||||
|         }, | ||||
|       }); | ||||
|     }); | ||||
| 
 | ||||
|     it('displays value', () => { | ||||
|       expect(findFormInputGroup().props('value')).toBe(valueProp); | ||||
|     }); | ||||
| 
 | ||||
|     itDoesNotModifyCopyEvent(); | ||||
|   }); | ||||
| 
 | ||||
|   describe('when `showToggleVisibilityButton` is `false`', () => { | ||||
|     beforeEach(() => { | ||||
|       createComponent({ | ||||
|         propsData: { | ||||
|           value: valueProp, | ||||
|           showToggleVisibilityButton: false, | ||||
|         }, | ||||
|       }); | ||||
|     }); | ||||
| 
 | ||||
|     it('does not render visibility toggle button', () => { | ||||
|       expect(findRevealButton().exists()).toBe(false); | ||||
|       expect(findHideButton().exists()).toBe(false); | ||||
|     }); | ||||
| 
 | ||||
|     it('displays value', () => { | ||||
|       expect(findFormInputGroup().props('value')).toBe(valueProp); | ||||
|     }); | ||||
| 
 | ||||
|     itDoesNotModifyCopyEvent(); | ||||
|   }); | ||||
| 
 | ||||
|   describe('when `showCopyButton` is `false`', () => { | ||||
|     beforeEach(() => { | ||||
|       createComponent({ | ||||
|         propsData: { | ||||
|           showCopyButton: false, | ||||
|         }, | ||||
|       }); | ||||
|     }); | ||||
| 
 | ||||
|     it('does not render copy button', () => { | ||||
|       expect(findCopyButton().exists()).toBe(false); | ||||
|     }); | ||||
|   }); | ||||
| 
 | ||||
|   it('passes `formInputGroupProps` prop to `GlFormInputGroup`', () => { | ||||
|     createComponent({ | ||||
|       propsData: { | ||||
|         formInputGroupProps: { | ||||
|           label: 'Foo bar', | ||||
|         }, | ||||
|       }, | ||||
|     }); | ||||
| 
 | ||||
|     expect(findFormInputGroup().props('label')).toBe('Foo bar'); | ||||
|   }); | ||||
| 
 | ||||
|   it('passes `copyButtonTitle` prop to `ClipboardButton`', () => { | ||||
|     createComponent({ | ||||
|       propsData: { | ||||
|         copyButtonTitle: 'Copy token', | ||||
|       }, | ||||
|     }); | ||||
| 
 | ||||
|     expect(findCopyButton().props('title')).toBe('Copy token'); | ||||
|   }); | ||||
| }); | ||||
|  | @ -90,18 +90,6 @@ RSpec.describe Gitlab::Database::Partitioning::DetachedPartitionDropper do | |||
|         expect(table_oid('test_partition')).to be_nil | ||||
|       end | ||||
| 
 | ||||
|       context 'when the drop_detached_partitions feature flag is disabled' do | ||||
|         before do | ||||
|           stub_feature_flags(drop_detached_partitions: false) | ||||
|         end | ||||
| 
 | ||||
|         it 'does not drop the partition' do | ||||
|           dropper.perform | ||||
| 
 | ||||
|           expect(table_oid('test_partition')).not_to be_nil | ||||
|         end | ||||
|       end | ||||
| 
 | ||||
|       context 'removing foreign keys' do | ||||
|         it 'removes foreign keys from the table before dropping it' do | ||||
|           expect(dropper).to receive(:drop_detached_partition).and_wrap_original do |drop_method, partition_name| | ||||
|  |  | |||
|  | @ -101,11 +101,6 @@ RSpec.describe Gitlab::Database::Partitioning::PartitionManager do | |||
|       ] | ||||
|     end | ||||
| 
 | ||||
|     context 'with the partition_pruning feature flag enabled' do | ||||
|       before do | ||||
|         stub_feature_flags(partition_pruning: true) | ||||
|       end | ||||
| 
 | ||||
|     it 'detaches each extra partition' do | ||||
|       extra_partitions.each { |p| expect(manager).to receive(:detach_one_partition).with(p) } | ||||
| 
 | ||||
|  | @ -113,19 +108,6 @@ RSpec.describe Gitlab::Database::Partitioning::PartitionManager do | |||
|     end | ||||
|   end | ||||
| 
 | ||||
|     context 'with the partition_pruning feature flag disabled' do | ||||
|       before do | ||||
|         stub_feature_flags(partition_pruning: false) | ||||
|       end | ||||
| 
 | ||||
|       it 'returns immediately' do | ||||
|         expect(manager).not_to receive(:detach) | ||||
| 
 | ||||
|         sync_partitions | ||||
|       end | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|   describe '#detach_partitions' do | ||||
|     around do |ex| | ||||
|       travel_to(Date.parse('2021-06-23')) do | ||||
|  |  | |||
|  | @ -428,6 +428,26 @@ RSpec.describe API::Ci::Jobs do | |||
|       end | ||||
|     end | ||||
| 
 | ||||
|     context 'when job succeeded' do | ||||
|       it 'does not return failure_reason' do | ||||
|         get api("/projects/#{project.id}/jobs/#{job.id}", api_user) | ||||
| 
 | ||||
|         expect(json_response).not_to include('failure_reason') | ||||
|       end | ||||
|     end | ||||
| 
 | ||||
|     context 'when job failed' do | ||||
|       let(:job) do | ||||
|         create(:ci_build, :failed, :tags, pipeline: pipeline) | ||||
|       end | ||||
| 
 | ||||
|       it 'returns failure_reason' do | ||||
|         get api("/projects/#{project.id}/jobs/#{job.id}", api_user) | ||||
| 
 | ||||
|         expect(json_response).to include('failure_reason') | ||||
|       end | ||||
|     end | ||||
| 
 | ||||
|     context 'when trace artifact record exists with no stored file', :skip_before_request do | ||||
|       before do | ||||
|         create(:ci_job_artifact, :unarchived_trace_artifact, job: job, project: job.project) | ||||
|  |  | |||
|  | @ -125,14 +125,6 @@ RSpec.describe Ci::RetryBuildService do | |||
|         expect(new_build.needs_attributes).to match(build.needs_attributes) | ||||
|         expect(new_build.needs).not_to match(build.needs) | ||||
|       end | ||||
| 
 | ||||
|       it 'clones only the job variables attributes' do | ||||
|         expect(new_build.job_variables.exists?).to be_truthy | ||||
|         expect(build.job_variables.exists?).to be_truthy | ||||
| 
 | ||||
|         expect(new_build.job_variables_attributes).to match(build.job_variables_attributes) | ||||
|         expect(new_build.job_variables).not_to match(build.job_variables) | ||||
|       end | ||||
|     end | ||||
| 
 | ||||
|     describe 'reject accessors' do | ||||
|  | @ -155,7 +147,7 @@ RSpec.describe Ci::RetryBuildService do | |||
|         Ci::Build.attribute_names.map(&:to_sym) + | ||||
|         Ci::Build.attribute_aliases.keys.map(&:to_sym) + | ||||
|         Ci::Build.reflect_on_all_associations.map(&:name) + | ||||
|         [:tag_list, :needs_attributes, :job_variables_attributes] - | ||||
|         [:tag_list, :needs_attributes] - | ||||
|         # ee-specific accessors should be tested in ee/spec/services/ci/retry_build_service_spec.rb instead | ||||
|         described_class.extra_accessors - | ||||
|         [:dast_site_profiles_build, :dast_scanner_profiles_build] # join tables | ||||
|  |  | |||
|  | @ -2,6 +2,7 @@ | |||
| 
 | ||||
| require 'simplecov' | ||||
| require 'simplecov-cobertura' | ||||
| require 'simplecov-lcov' | ||||
| require_relative '../lib/gitlab/utils' | ||||
| 
 | ||||
| module SimpleCovEnv | ||||
|  | @ -18,10 +19,13 @@ module SimpleCovEnv | |||
|   end | ||||
| 
 | ||||
|   def configure_formatter | ||||
|     SimpleCov::Formatter::LcovFormatter.config.report_with_single_file = true | ||||
| 
 | ||||
|     SimpleCov.formatters = SimpleCov::Formatter::MultiFormatter.new([ | ||||
|       SimpleCov::Formatter::SimpleFormatter, | ||||
|       SimpleCov::Formatter::HTMLFormatter, | ||||
|       SimpleCov::Formatter::CoberturaFormatter | ||||
|       SimpleCov::Formatter::CoberturaFormatter, | ||||
|       SimpleCov::Formatter::LcovFormatter | ||||
|     ]) | ||||
|   end | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue