diff options
38 files changed, 336 insertions, 78 deletions
diff --git a/app/assets/javascripts/pipelines/components/pipeline_mini_graph/pipeline_mini_graph.vue b/app/assets/javascripts/pipelines/components/pipeline_mini_graph/pipeline_mini_graph.vue index 83cfa0b7f87..993fa121d89 100644 --- a/app/assets/javascripts/pipelines/components/pipeline_mini_graph/pipeline_mini_graph.vue +++ b/app/assets/javascripts/pipelines/components/pipeline_mini_graph/pipeline_mini_graph.vue @@ -85,6 +85,7 @@ export default { :stages-class="stagesClass" data-testid="pipeline-stages" @pipelineActionRequestComplete="onPipelineActionRequestComplete" + @miniGraphStageClick="$emit('miniGraphStageClick')" /> <gl-icon v-if="hasDownstreamPipelines" diff --git a/app/assets/javascripts/pipelines/components/pipeline_mini_graph/pipeline_stage.vue b/app/assets/javascripts/pipelines/components/pipeline_mini_graph/pipeline_stage.vue index d7e55d36ff6..a68797a7235 100644 --- a/app/assets/javascripts/pipelines/components/pipeline_mini_graph/pipeline_stage.vue +++ b/app/assets/javascripts/pipelines/components/pipeline_mini_graph/pipeline_stage.vue @@ -77,6 +77,10 @@ export default { this.isDropdownOpen = true; this.isLoading = true; this.fetchJobs(); + + // used for tracking and is separate from event hub + // to avoid complexity with mixin + this.$emit('miniGraphStageClick'); }, fetchJobs() { axios diff --git a/app/assets/javascripts/pipelines/components/pipeline_mini_graph/pipeline_stages.vue b/app/assets/javascripts/pipelines/components/pipeline_mini_graph/pipeline_stages.vue index 71de8928748..e965dc5e6b0 100644 --- a/app/assets/javascripts/pipelines/components/pipeline_mini_graph/pipeline_stages.vue +++ b/app/assets/javascripts/pipelines/components/pipeline_mini_graph/pipeline_stages.vue @@ -48,6 +48,7 @@ export default { :update-dropdown="updateDropdown" :is-merge-train="isMergeTrain" @pipelineActionRequestComplete="onPipelineActionRequestComplete" + @miniGraphStageClick="$emit('miniGraphStageClick')" /> </div> </div> diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table.vue index 6f077e4c138..f6e46c090d3 100644 --- a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table.vue +++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table.vue @@ -1,8 +1,10 @@ <script> import { GlTableLite, GlTooltipDirective } from '@gitlab/ui'; import { s__, __ } from '~/locale'; +import Tracking from '~/tracking'; import PipelineMiniGraph from '~/pipelines/components/pipeline_mini_graph/pipeline_mini_graph.vue'; import eventHub from '../../event_hub'; +import { TRACKING_CATEGORIES } from '../../constants'; import PipelineOperations from './pipeline_operations.vue'; import PipelineStopModal from './pipeline_stop_modal.vue'; import PipelineTriggerer from './pipeline_triggerer.vue'; @@ -68,6 +70,7 @@ export default { directives: { GlTooltip: GlTooltipDirective, }, + mixins: [Tracking.mixin()], props: { pipelines: { type: Array, @@ -124,6 +127,9 @@ export default { onPipelineActionRequestComplete() { eventHub.$emit('refreshPipelinesTable'); }, + trackPipelineMiniGraph() { + this.track('click_minigraph', { label: TRACKING_CATEGORIES.table }); + }, }, TBODY_TR_ATTR: { 'data-testid': 'pipeline-table-row', @@ -174,6 +180,7 @@ export default { :update-dropdown="updateGraphDropdown" :upstream-pipeline="item.triggered_by" @pipelineActionRequestComplete="onPipelineActionRequestComplete" + @miniGraphStageClick="trackPipelineMiniGraph" /> </template> diff --git a/app/assets/javascripts/projects/settings_service_desk/components/service_desk_setting.vue b/app/assets/javascripts/projects/settings_service_desk/components/service_desk_setting.vue index 8a9a0b541f3..452e7a4fd21 100644 --- a/app/assets/javascripts/projects/settings_service_desk/components/service_desk_setting.vue +++ b/app/assets/javascripts/projects/settings_service_desk/components/service_desk_setting.vue @@ -176,7 +176,7 @@ export default { </template> </gl-form-input-group> <template v-if="email && hasCustomEmail" #description> - <span class="gl-mt-2 d-inline-block"> + <span class="gl-mt-2 gl-display-inline-block"> <gl-sprintf :message="__('Emails sent to %{email} are also supported.')"> <template #email> <code>{{ incomingEmail }}</code> @@ -190,7 +190,11 @@ export default { </template> </gl-form-group> - <gl-form-group :label="__('Email address suffix')" :state="!projectKeyError"> + <gl-form-group + :label="__('Email address suffix')" + :state="!projectKeyError" + data-testid="suffix-form-group" + > <gl-form-input v-if="hasProjectKeySupport" id="service-desk-project-suffix" @@ -216,22 +220,24 @@ export default { </gl-sprintf> </template> <template v-else #description> - <gl-sprintf - :message=" - __( - 'To add a custom suffix, set up a Service Desk email address. %{linkStart}Learn more.%{linkEnd}', - ) - " - > - <template #link="{ content }"> - <gl-link - :href="customEmailAddressHelpUrl" - target="_blank" - class="gl-text-blue-600 font-size-inherit" - >{{ content }} - </gl-link> - </template> - </gl-sprintf> + <span class="gl-text-gray-900"> + <gl-sprintf + :message=" + __( + 'To add a custom suffix, set up a Service Desk email address. %{linkStart}Learn more.%{linkEnd}', + ) + " + > + <template #link="{ content }"> + <gl-link + :href="customEmailAddressHelpUrl" + target="_blank" + class="gl-text-blue-600 font-size-inherit" + >{{ content }} + </gl-link> + </template> + </gl-sprintf> + </span> </template> <template v-if="hasProjectKeySupport && projectKeyError" #invalid-feedback> @@ -266,7 +272,27 @@ export default { /> <template v-if="hasProjectKeySupport" #description> - {{ __('Emails sent from Service Desk have this name.') }} + {{ __('Name to be used as the sender for emails from Service Desk.') }} + </template> + <template v-else #description> + <span class="gl-text-gray-900"> + <gl-sprintf + :message=" + __( + 'To add display name, set up a Service Desk email address. %{linkStart}Learn more.%{linkEnd}', + ) + " + > + <template #link="{ content }"> + <gl-link + :href="customEmailAddressHelpUrl" + target="_blank" + class="gl-text-blue-600 font-size-inherit" + >{{ content }} + </gl-link> + </template> + </gl-sprintf> + </span> </template> </gl-form-group> diff --git a/app/controllers/concerns/web_hooks/hook_log_actions.rb b/app/controllers/concerns/web_hooks/hook_log_actions.rb index 7c9218ddcd4..f3378d7c857 100644 --- a/app/controllers/concerns/web_hooks/hook_log_actions.rb +++ b/app/controllers/concerns/web_hooks/hook_log_actions.rb @@ -16,6 +16,7 @@ module WebHooks end def show + hide_search_settings end def retry @@ -35,5 +36,9 @@ module WebHooks result = hook.execute(hook_log.request_data, hook_log.trigger) set_hook_execution_notice(result) end + + def hide_search_settings + @hide_search_settings ||= true + end end end diff --git a/app/models/projects/build_artifacts_size_refresh.rb b/app/models/projects/build_artifacts_size_refresh.rb index dee4afdefa6..e66e1d5b42f 100644 --- a/app/models/projects/build_artifacts_size_refresh.rb +++ b/app/models/projects/build_artifacts_size_refresh.rb @@ -2,6 +2,7 @@ module Projects class BuildArtifactsSizeRefresh < ApplicationRecord + include AfterCommitQueue include BulkInsertSafe STALE_WINDOW = 2.hours @@ -52,6 +53,8 @@ module Projects scope :remaining, -> { with_state(:created, :pending).or(stale) } scope :processing_queue, -> { remaining.order(state: :desc) } + after_destroy :schedule_namespace_aggregation_worker + def self.enqueue_refresh(projects) now = Time.zone.now @@ -93,5 +96,13 @@ module Projects def started? !created? end + + private + + def schedule_namespace_aggregation_worker + run_after_commit do + Namespaces::ScheduleAggregationWorker.perform_async(project.namespace_id) + end + end end end diff --git a/app/models/repository.rb b/app/models/repository.rb index 26c3b01a46e..63fbb86544c 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -796,7 +796,7 @@ class Repository def create_dir(user, path, **options) options[:actions] = [{ action: :create_dir, file_path: path }] - multi_action(user, **options) + commit_files(user, **options) end def create_file(user, path, content, **options) @@ -808,7 +808,7 @@ class Repository options[:actions].push({ action: :chmod, file_path: path, execute_filemode: execute_filemode }) end - multi_action(user, **options) + commit_files(user, **options) end def update_file(user, path, content, **options) @@ -823,13 +823,13 @@ class Repository options[:actions].push({ action: :chmod, file_path: path, execute_filemode: execute_filemode }) end - multi_action(user, **options) + commit_files(user, **options) end def delete_file(user, path, **options) options[:actions] = [{ action: :delete, file_path: path }] - multi_action(user, **options) + commit_files(user, **options) end def with_cache_hooks @@ -843,14 +843,14 @@ class Repository result.newrev end - def multi_action(user, **options) + def commit_files(user, **options) start_project = options.delete(:start_project) if start_project options[:start_repository] = start_project.repository.raw_repository end - with_cache_hooks { raw.multi_action(user, **options) } + with_cache_hooks { raw.commit_files(user, **options) } end def merge(user, source_sha, merge_request, message) diff --git a/app/models/snippet_repository.rb b/app/models/snippet_repository.rb index 5ac159d9615..a959ad4d548 100644 --- a/app/models/snippet_repository.rb +++ b/app/models/snippet_repository.rb @@ -31,7 +31,7 @@ class SnippetRepository < ApplicationRecord options[:actions] = transform_file_entries(files) - capture_git_error { repository.multi_action(user, **options) } + capture_git_error { repository.commit_files(user, **options) } ensure Gitlab::ExclusiveLease.cancel(lease_key, uuid) end diff --git a/app/services/design_management/copy_design_collection/copy_service.rb b/app/services/design_management/copy_design_collection/copy_service.rb index 886077191ab..3bc30f62a81 100644 --- a/app/services/design_management/copy_design_collection/copy_service.rb +++ b/app/services/design_management/copy_design_collection/copy_service.rb @@ -143,7 +143,7 @@ module DesignManagement gitaly_actions = version.actions.map do |action| design = action.design # Map the raw Action#event enum value to a Gitaly "action" for the - # `Repository#multi_action` call. + # `Repository#commit_files` call. gitaly_action_name = @event_enum_map[action.event_before_type_cast] # `content` will be the LfsPointer file and not the design file, # and can be nil for deletions. @@ -157,7 +157,7 @@ module DesignManagement }.compact end - sha = target_repository.multi_action( + sha = target_repository.commit_files( git_user, branch_name: temporary_branch, message: commit_message(version), diff --git a/app/services/design_management/runs_design_actions.rb b/app/services/design_management/runs_design_actions.rb index ee6aa9286d3..267ed6bf29f 100644 --- a/app/services/design_management/runs_design_actions.rb +++ b/app/services/design_management/runs_design_actions.rb @@ -15,7 +15,7 @@ module DesignManagement def run_actions(actions, skip_system_notes: false) raise NoActions if actions.empty? - sha = repository.multi_action(current_user, + sha = repository.commit_files(current_user, branch_name: target_branch, message: commit_message, actions: actions.map(&:gitaly_action)) diff --git a/app/services/files/multi_service.rb b/app/services/files/multi_service.rb index 65af4dd5a28..dd09ecafb4f 100644 --- a/app/services/files/multi_service.rb +++ b/app/services/files/multi_service.rb @@ -38,7 +38,7 @@ module Files end def commit_actions!(actions) - repository.multi_action( + repository.commit_files( current_user, message: @commit_message, branch_name: @branch_name, diff --git a/doc/api/groups.md b/doc/api/groups.md index b8545f57cc7..d051fec06a7 100644 --- a/doc/api/groups.md +++ b/doc/api/groups.md @@ -989,6 +989,7 @@ PUT /groups/:id | `unique_project_download_limit_interval_in_seconds` **(ULTIMATE)** | integer | no | Time period during which a user can download a maximum amount of projects before they are banned. Available only on top-level groups. Default: 0, Maximum: 864,000 seconds (10 days). | | `unique_project_download_limit_allowlist` **(ULTIMATE)** | array of strings | no | List of usernames excluded from the unique project download limit. Available only on top-level groups. Default: `[]`, Maximum: 100 usernames. | | `auto_ban_user_on_excessive_projects_download` **(ULTIMATE)** | boolean | no | When enabled, users will get automatically banned from the group when they download more than the maximum number of unique projects in the time period specified by `unique_project_download_limit` and `unique_project_download_limit_interval_in_seconds` respectively. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/94159) in GitLab 15.4 | +| `ip_restriction_ranges` **(PREMIUM)** | string | no | Comman separated IP addresses or subnet masks value to restrict group access, [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/351493) in GitLab 15.4 | NOTE: The `projects` and `shared_projects` attributes in the response are deprecated and [scheduled for removal in API v5](https://gitlab.com/gitlab-org/gitlab/-/issues/213797). diff --git a/doc/development/contributing/style_guides.md b/doc/development/contributing/style_guides.md index 38f9de33c93..2e696cf517b 100644 --- a/doc/development/contributing/style_guides.md +++ b/doc/development/contributing/style_guides.md @@ -146,13 +146,20 @@ reduces the aforementioned [bike-shedding](https://en.wiktionary.org/wiki/bikesh To that end, we encourage creation of new RuboCop rules in the codebase. -We currently maintain Cops across several Ruby code bases, and not all of them are +We maintain Cops across several Ruby code bases, and not all of them are specific to the GitLab application. When creating a new cop that could be applied to multiple applications, we encourage you to add it to our [GitLab Styles](https://gitlab.com/gitlab-org/gitlab-styles) gem. If the Cop targets rules that only apply to the main GitLab application, it should be added to [GitLab](https://gitlab.com/gitlab-org/gitlab) instead. +#### RuboCop node pattern + +When creating [node patterns](https://docs.rubocop.org/rubocop-ast/node_pattern.html) to match +Ruby's AST, you can use [`scripts/rubocop-parse`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/scripts/rubocop-parse) +to display the AST of a Ruby expression, in order to help you create the matcher. +See also [!97024](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/97024). + ### Resolving RuboCop exceptions When the number of RuboCop exceptions exceed the default [`exclude-limit` of 15](https://docs.rubocop.org/rubocop/1.2/usage/basic_usage.html#command-line-flags), diff --git a/doc/raketasks/backup_gitlab.md b/doc/raketasks/backup_gitlab.md index e80fea21064..48230b28eb7 100644 --- a/doc/raketasks/backup_gitlab.md +++ b/doc/raketasks/backup_gitlab.md @@ -35,6 +35,11 @@ WARNING: The backup command requires [additional parameters](backup_restore.md#back-up-and-restore-for-installations-using-pgbouncer) when your installation is using PgBouncer, for either performance reasons or when using it with a Patroni cluster. +WARNING: +The backup command doesn't verify if another backup is already running, as described in +[issue 362593](https://gitlab.com/gitlab-org/gitlab/-/issues/362593). We strongly recommend +you make sure that all backups are complete before starting a new one. + Depending on your version of GitLab, use the following command if you installed GitLab using the Omnibus package: diff --git a/doc/update/index.md b/doc/update/index.md index 87bb59f5718..dd3a62570db 100644 --- a/doc/update/index.md +++ b/doc/update/index.md @@ -472,9 +472,14 @@ and [Helm Chart deployments](https://docs.gitlab.com/charts/). They come with ap ### 15.3.3 -In GitLab 15.3.3, [SAML Group Links](../api/groups.md#saml-group-links) API `access_level` attribute type changed to `integer`. See +- In GitLab 15.3.3, [SAML Group Links](../api/groups.md#saml-group-links) API `access_level` attribute type changed to `integer`. See [valid access levels](../api/members.md#valid-access-levels) documentation. +### 15.3.0 + +- [Incorrect deletion of object storage files on Geo secondary sites]((https://gitlab.com/gitlab-org/gitlab/-/issues/371397)) can occur in certain situations. See [Geo: Incorrect object storage LFS file deletion on secondary site issue in GitLab 15.0.0 to 15.3.2](#geo-incorrect-object-storage-lfs-file-deletion-on-secondary-sites-in-gitlab-1500-to-1532). +- LFS transfers can [redirect to the primary from secondary site mid-session](https://gitlab.com/gitlab-org/gitlab/-/issues/371571) causing failed pull and clone requests when [Geo proxying](../administration/geo/secondary_proxy/index.md) is enabled. Geo proxying is enabled by default in GitLab 15.1 and later. See [Geo: LFS transfer redirect to primary from secondary site mid-session issue in GitLab 15.1.0 to 15.3.2](#geo-lfs-transfers-redirect-to-primary-from-secondary-site-mid-session-in-gitlab-1510-to-1532) for more details. + ### 15.2.0 - GitLab installations that have multiple web nodes should be @@ -493,6 +498,8 @@ In GitLab 15.3.3, [SAML Group Links](../api/groups.md#saml-group-links) API `acc 1. Add `gitaly['runtime_dir'] = '<PATH_WITH_EXEC_PERM>'` to `/etc/gitlab/gitlab.rb` and specify a location without `noexec` set. 1. Run `sudo gitlab-ctl reconfigure`. +- [Incorrect deletion of object storage files on Geo secondary sites]((https://gitlab.com/gitlab-org/gitlab/-/issues/371397)) can occur in certain situations. See [Geo: Incorrect object storage LFS file deletion on secondary site issue in GitLab 15.0.0 to 15.3.2](#geo-incorrect-object-storage-lfs-file-deletion-on-secondary-sites-in-gitlab-1500-to-1532). +- LFS transfers can [redirect to the primary from secondary site mid-session](https://gitlab.com/gitlab-org/gitlab/-/issues/371571) causing failed pull and clone requests when [Geo proxying](../administration/geo/secondary_proxy/index.md) is enabled. Geo proxying is enabled by default in GitLab 15.1 and later. See [Geo: LFS transfer redirect to primary from secondary site mid-session issue in GitLab 15.1.0 to 15.3.2](#geo-lfs-transfers-redirect-to-primary-from-secondary-site-mid-session-in-gitlab-1510-to-1532) for more details. ### 15.1.0 @@ -511,6 +518,8 @@ In GitLab 15.3.3, [SAML Group Links](../api/groups.md#saml-group-links) API `acc - Unauthenticated requests to the [`ciConfig` GraphQL field](../api/graphql/reference/index.md#queryciconfig) are no longer supported. Before you upgrade to GitLab 15.1, add an [access token](../api/index.md#authentication) to your requests. The user creating the token must have [permission](../user/permissions.md) to create pipelines in the project. +- [Incorrect deletion of object storage files on Geo secondary sites]((https://gitlab.com/gitlab-org/gitlab/-/issues/371397)) can occur in certain situations. See [Geo: Incorrect object storage LFS file deletion on secondary site issue in GitLab 15.0.0 to 15.3.2](#geo-incorrect-object-storage-lfs-file-deletion-on-secondary-sites-in-gitlab-1500-to-1532). +- LFS transfers can [redirect to the primary from secondary site mid-session](https://gitlab.com/gitlab-org/gitlab/-/issues/371571) causing failed pull and clone requests when [Geo proxying](../administration/geo/secondary_proxy/index.md) is enabled. Geo proxying is enabled by default in GitLab 15.1 and later. See [Geo: LFS transfer redirect to primary from secondary site mid-session issue in GitLab 15.1.0 to 15.3.2](#geo-lfs-transfers-redirect-to-primary-from-secondary-site-mid-session-in-gitlab-1510-to-1532) for more details. ### 15.0.0 @@ -525,6 +534,7 @@ In GitLab 15.3.3, [SAML Group Links](../api/groups.md#saml-group-links) API `acc Gitaly. The previous implementation in GitLab Shell was removed in GitLab 15.0. With this change, global server hooks are stored only inside a subdirectory named after the hook type. Global server hooks can no longer be a single hook file in the root of the custom hooks directory. For example, you must use `<custom_hooks_dir>/<hook_name>.d/*` rather than `<custom_hooks_dir>/<hook_name>`. +- [Incorrect deletion of object storage files on Geo secondary sites]((https://gitlab.com/gitlab-org/gitlab/-/issues/371397)) can occur in certain situations. See [Geo: Incorrect object storage LFS file deletion on secondary site issue in GitLab 15.0.0 to 15.3.2](#geo-incorrect-object-storage-lfs-file-deletion-on-secondary-sites-in-gitlab-1500-to-1532). ### 14.10.0 @@ -1170,6 +1180,27 @@ by a database engine bug that causes a segmentation fault. Read more [in the issue](https://gitlab.com/gitlab-org/gitlab/-/issues/364763). +### Geo: Incorrect object storage LFS file deletion on secondary sites in GitLab 15.0.0 to 15.3.2 + +[Incorrect deletion of object storage files on Geo secondary sites]((https://gitlab.com/gitlab-org/gitlab/-/issues/371397)) +can occur in GitLab 15.0.0 to 15.3.2 in the following situations: + +- GitLab-managed object storage replication is disabled, and LFS objects are created while importing a project with object storage enabled. +- GitLab-managed replication to sync object storage is enabled and subsequently disabled. + +This issue is resolved in 15.3.3. Customers who have both LFS enabled and LFS objects being replicated across Geo sites +should upgrade directly to 15.3.3 to reduce the risk of data loss on secondary sites. + +### Geo: LFS transfers redirect to primary from secondary site mid-session in GitLab 15.1.0 to 15.3.2 + +LFS transfers can [redirect to the primary from secondary site mid-session](https://gitlab.com/gitlab-org/gitlab/-/issues/371571) causing failed pull and clone requests in GitLab 15.1.0 to 15.3.2 when [Geo proxying](../administration/geo/secondary_proxy/index.md) is enabled. Geo proxying is enabled by default in GitLab 15.1 and later. + +This issue is resolved in GitLab 15.3.3, so customers with the following configuration should upgrade to 15.3.3 or later: + +- LFS is enabled. +- LFS objects are being replicated across Geo sites. +- Repositories are being pulled by using a Geo secondary site. + ## Miscellaneous - [MySQL to PostgreSQL](mysql_to_postgresql.md) guides you through migrating diff --git a/lib/gitlab/ci/templates/npm.gitlab-ci.yml b/lib/gitlab/ci/templates/npm.gitlab-ci.yml index 64c784f43cb..fb0d300338b 100644 --- a/lib/gitlab/ci/templates/npm.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/npm.gitlab-ci.yml @@ -38,7 +38,7 @@ publish: # Compare the version in package.json to all published versions. # If the package.json version has not yet been published, run `npm publish`. - | - if [[ $(npm view "${NPM_PACKAGE_NAME}" versions) != *"'${NPM_PACKAGE_VERSION}'"* ]]; then + if [[ "$(npm view ${NPM_PACKAGE_NAME} versions)" != *"'${NPM_PACKAGE_VERSION}'"* ]]; then npm publish echo "Successfully published version ${NPM_PACKAGE_VERSION} of ${NPM_PACKAGE_NAME} to GitLab's NPM registry: ${CI_PROJECT_URL}/-/packages" else diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb index ddc43b1764b..d26834089b4 100644 --- a/lib/gitlab/git/repository.rb +++ b/lib/gitlab/git/repository.rb @@ -913,8 +913,29 @@ module Gitlab true end + # Creates a commit + # + # @param [User] user The committer of the commit. + # @param [String] branch_name: The name of the branch to be created/updated. + # @param [String] message: The commit message. + # @param [Array<Hash>] actions: An array of files to be added/updated/removed. + # @option actions: [Symbol] :action One of :create, :create_dir, :update, :move, :delete, :chmod + # @option actions: [String] :file_path The path of the file or directory being added/updated/removed. + # @option actions: [String] :previous_path The path of the file being moved. Only used for the :move action. + # @option actions: [String,IO] :content The file content for :create or :update + # @option actions: [String] :encoding One of text, base64 + # @option actions: [Boolean] :execute_filemode True sets the executable filemode on the file. + # @option actions: [Boolean] :infer_content True uses the existing file contents instead of using content on move. + # @param [String] author_email: The authors email, if unspecified the committers email is used. + # @param [String] author_name: The authors name, if unspecified the committers name is used. + # @param [String] start_branch_name: The name of the branch to be used as the parent of the commit. Only used if start_sha: is unspecified. + # @param [String] start_sha: The sha to be used as the parent of the commit. + # @param [Gitlab::Git::Repository] start_repository: The repository that contains the start branch or sha. Defaults to use this repository. + # @param [Boolean] force: Force update the branch. + # @return [Gitlab::Git::OperationService::BranchUpdate] + # # rubocop:disable Metrics/ParameterLists - def multi_action( + def commit_files( user, branch_name:, message:, actions:, author_email: nil, author_name: nil, start_branch_name: nil, start_sha: nil, start_repository: nil, diff --git a/locale/gitlab.pot b/locale/gitlab.pot index d1909e9d210..8446b04684d 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -14478,9 +14478,6 @@ msgstr "" msgid "Emails" msgstr "" -msgid "Emails sent from Service Desk have this name." -msgstr "" - msgid "Emails sent to %{email} are also supported." msgstr "" @@ -25963,6 +25960,9 @@ msgstr "" msgid "Name new label" msgstr "" +msgid "Name to be used as the sender for emails from Service Desk." +msgstr "" + msgid "Name:" msgstr "" @@ -41141,6 +41141,9 @@ msgstr "" msgid "To add a custom suffix, set up a Service Desk email address. %{linkStart}Learn more.%{linkEnd}" msgstr "" +msgid "To add display name, set up a Service Desk email address. %{linkStart}Learn more.%{linkEnd}" +msgstr "" + msgid "To add the entry manually, provide the following details to the application on your phone." msgstr "" diff --git a/qa/.confiner/master.yml b/qa/.confiner/master.yml index bfb44facd7d..e6fc3e68747 100644 --- a/qa/.confiner/master.yml +++ b/qa/.confiner/master.yml @@ -4,7 +4,7 @@ args: threshold: 3 # 3 failures private_token: $QA_GITLAB_CI_TOKEN - project_id: gitlab-org/gitlab-qa-mirror # https://gitlab.com/gitlab-org/gitlab-qa-mirror/ + project_id: gitlab-org/gitlab target_project: gitlab-org/gitlab failure_issue_labels: QA,Quality failure_issue_prefix: "Failure in " diff --git a/qa/qa/support/json_formatter.rb b/qa/qa/support/json_formatter.rb index 0b805cd9eec..252ccfe73d3 100644 --- a/qa/qa/support/json_formatter.rb +++ b/qa/qa/support/json_formatter.rb @@ -51,6 +51,7 @@ module QA testcase: example.metadata[:testcase], quarantine: example.metadata[:quarantine], screenshot: example.metadata[:screenshot], + product_group: example.metadata[:product_group], ci_job_url: QA::Runtime::Env.ci_job_url } end diff --git a/scripts/rubocop-parse b/scripts/rubocop-parse new file mode 100755 index 00000000000..4c82be5934b --- /dev/null +++ b/scripts/rubocop-parse @@ -0,0 +1,73 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# Emit AST from parsed Ruby code by RuboCop. +# +# This is an alternative to `ruby-parser` shipped with `parser` gem. +# +# Usage: +# rubocop-parse -e 'puts "hello"' +# (send nil :puts +# (str "hello")) +# +# rubocop-parse -e 'puts "hello"' -v 3.0 +# (send nil :puts +# (str "hello")) +# +# rubocop-parse app/models/project.rb +# (begin +# (send nil :require +# (str "carrierwave/orm/activerecord")) +# (class +# (const nil :Project) +# (const nil :ApplicationRecord) +# (begin +# (send nil :include +# ... + +require_relative '../config/bundler_setup' + +require 'rubocop' +require 'optparse' + +def print_ast(file, source, version) + version ||= RuboCop::ConfigStore.new.for_file(file).target_ruby_version + puts RuboCop::AST::ProcessedSource.new(source, version).ast.to_s +end + +options = Struct.new(:eval, :ruby_version, :print_help, keyword_init: true).new + +parser = OptionParser.new do |opts| + opts.banner = "Usage: #{$0} [-e code] [FILE...]" + + opts.on('-e FRAGMENT', '--eval FRAGMENT', 'Process a fragment of Ruby code') do |code| + options.eval = code + end + + opts.on('-v RUBY_VERSION', '--ruby-version RUBY_VERSION', + 'Parse as Ruby would. Defaults to RuboCop TargetRubyVersion setting.') do |ruby_version| + options.ruby_version = Float(ruby_version) + end + + opts.on('-h', '--help') do + options.print_help = true + end +end + +args = parser.parse! + +if options.print_help + puts parser + exit +end + +print_ast('', options.eval, options.ruby_version) if options.eval + +args.each do |arg| + if File.file?(arg) + source = File.read(arg) + print_ast(arg, source, options.ruby_version) + else + warn "Skipping non-file #{arg.inspect}" + end +end diff --git a/spec/factories/design_management/designs.rb b/spec/factories/design_management/designs.rb index 56a1b55b969..3d95c754a96 100644 --- a/spec/factories/design_management/designs.rb +++ b/spec/factories/design_management/designs.rb @@ -109,7 +109,7 @@ FactoryBot.define do repository = project.design_repository commit_version = ->(action) do - repository.multi_action( + repository.commit_files( evaluator.author, branch_name: 'master', message: "#{action.action} for #{design.filename}", diff --git a/spec/factories/design_management/versions.rb b/spec/factories/design_management/versions.rb index e505a77d6bd..9d965c6e86c 100644 --- a/spec/factories/design_management/versions.rb +++ b/spec/factories/design_management/versions.rb @@ -102,7 +102,7 @@ FactoryBot.define do end if actions.present? - repository.multi_action( + repository.commit_files( evaluator.author, branch_name: 'master', message: "created #{actions.size} files", @@ -123,7 +123,7 @@ FactoryBot.define do end end - sha = repository.multi_action( + sha = repository.commit_files( evaluator.author, branch_name: 'master', message: "edited #{version_actions.size} files", diff --git a/spec/features/projects/settings/webhooks_settings_spec.rb b/spec/features/projects/settings/webhooks_settings_spec.rb index c84de7fc03f..d525544ac15 100644 --- a/spec/features/projects/settings/webhooks_settings_spec.rb +++ b/spec/features/projects/settings/webhooks_settings_spec.rb @@ -139,6 +139,12 @@ RSpec.describe 'Projects > Settings > Webhook Settings' do expect(page).to have_current_path(edit_project_hook_path(project, hook), ignore_query: true) end + + it 'does not show search settings on the hook log details' do + visit project_hook_hook_log_path(project, hook, hook_log) + + expect(page).not_to have_field(placeholder: 'Search settings', disabled: true) + end end end end diff --git a/spec/frontend/pipelines/components/pipeline_mini_graph/pipeline_stage_spec.js b/spec/frontend/pipelines/components/pipeline_mini_graph/pipeline_stage_spec.js index 27645f1dd82..52b440f18bb 100644 --- a/spec/frontend/pipelines/components/pipeline_mini_graph/pipeline_stage_spec.js +++ b/spec/frontend/pipelines/components/pipeline_mini_graph/pipeline_stage_spec.js @@ -129,10 +129,11 @@ describe('Pipelines stage component', () => { await axios.waitForAll(); }); - it('renders the received data and emit `clickedDropdown` event', async () => { + it('renders the received data and emits the correct events', async () => { expect(findDropdownMenu().text()).toContain(stageReply.latest_statuses[0].name); expect(findDropdownMenuTitle().text()).toContain(stageReply.name); expect(eventHub.$emit).toHaveBeenCalledWith('clickedDropdown'); + expect(wrapper.emitted('miniGraphStageClick')).toEqual([[]]); }); it('refreshes when updateDropdown is set to true', async () => { diff --git a/spec/frontend/pipelines/pipelines_table_spec.js b/spec/frontend/pipelines/pipelines_table_spec.js index 12ee06d81a3..044683ce533 100644 --- a/spec/frontend/pipelines/pipelines_table_spec.js +++ b/spec/frontend/pipelines/pipelines_table_spec.js @@ -200,6 +200,14 @@ describe('Pipelines Table', () => { label: TRACKING_CATEGORIES.table, }); }); + + it('tracks pipeline mini graph stage click', () => { + findPipelineMiniGraph().vm.$emit('miniGraphStageClick'); + + expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_minigraph', { + label: TRACKING_CATEGORIES.table, + }); + }); }); }); }); diff --git a/spec/frontend/projects/settings_service_desk/components/service_desk_setting_spec.js b/spec/frontend/projects/settings_service_desk/components/service_desk_setting_spec.js index f9f520cb2ab..7c3f4e76ae5 100644 --- a/spec/frontend/projects/settings_service_desk/components/service_desk_setting_spec.js +++ b/spec/frontend/projects/settings_service_desk/components/service_desk_setting_spec.js @@ -15,6 +15,7 @@ describe('ServiceDeskSetting', () => { const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon); const findTemplateDropdown = () => wrapper.findComponent(GlDropdown); const findToggle = () => wrapper.findComponent(GlToggle); + const findSuffixFormGroup = () => wrapper.findByTestId('suffix-form-group'); const createComponent = ({ props = {} } = {}) => extendedWrapper( @@ -51,6 +52,32 @@ describe('ServiceDeskSetting', () => { expect(findLoadingIcon().exists()).toBe(true); expect(findIncomingEmail().exists()).toBe(false); }); + + it('should display help text', () => { + expect(findSuffixFormGroup().text()).toContain( + 'To add a custom suffix, set up a Service Desk email address', + ); + expect(findSuffixFormGroup().text()).not.toContain( + 'Add a suffix to Service Desk email address', + ); + }); + }); + }); + + describe('when customEmailEnabled', () => { + beforeEach(() => { + wrapper = createComponent({ + props: { customEmailEnabled: true }, + }); + }); + + it('should not display help text', () => { + expect(findSuffixFormGroup().text()).not.toContain( + 'To add a custom suffix, set up a Service Desk email address', + ); + expect(findSuffixFormGroup().text()).toContain( + 'Add a suffix to Service Desk email address', + ); }); }); diff --git a/spec/lib/gitlab/git/branch_spec.rb b/spec/lib/gitlab/git/branch_spec.rb index feaa1f6595c..95cc833390f 100644 --- a/spec/lib/gitlab/git/branch_spec.rb +++ b/spec/lib/gitlab/git/branch_spec.rb @@ -111,7 +111,7 @@ RSpec.describe Gitlab::Git::Branch do end def create_commit - repository.multi_action( + repository.commit_files( user, branch_name: 'HEAD', message: 'commit message', diff --git a/spec/lib/gitlab/git/cross_repo_comparer_spec.rb b/spec/lib/gitlab/git/cross_repo_comparer_spec.rb index 1c49486b7b1..7888e224d59 100644 --- a/spec/lib/gitlab/git/cross_repo_comparer_spec.rb +++ b/spec/lib/gitlab/git/cross_repo_comparer_spec.rb @@ -110,7 +110,7 @@ RSpec.describe Gitlab::Git::CrossRepoComparer do def create_commit(user, repo, branch) action = { action: :create, file_path: '/FILE', content: 'content' } - result = repo.multi_action(user, branch_name: branch, message: 'Commit', actions: [action]) + result = repo.commit_files(user, branch_name: branch, message: 'Commit', actions: [action]) result.newrev end diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb index 3f990cff0be..e1ea5c2d825 100644 --- a/spec/lib/gitlab/git/repository_spec.rb +++ b/spec/lib/gitlab/git/repository_spec.rb @@ -309,7 +309,7 @@ RSpec.describe Gitlab::Git::Repository do repository.create_branch('right-branch') left.times do |i| - repository.multi_action( + repository.commit_files( user, branch_name: 'left-branch', message: 'some more content for a', @@ -322,7 +322,7 @@ RSpec.describe Gitlab::Git::Repository do end right.times do |i| - repository.multi_action( + repository.commit_files( user, branch_name: 'right-branch', message: 'some more content for b', @@ -367,7 +367,7 @@ RSpec.describe Gitlab::Git::Repository do repository.create_branch('right-branch') left.times do |i| - repository.multi_action( + repository.commit_files( user, branch_name: 'left-branch', message: 'some more content for a', @@ -380,7 +380,7 @@ RSpec.describe Gitlab::Git::Repository do end right.times do |i| - repository.multi_action( + repository.commit_files( user, branch_name: 'right-branch', message: 'some more content for b', @@ -518,7 +518,7 @@ RSpec.describe Gitlab::Git::Repository do before do repository.create_branch(ref) - repository.multi_action( + repository.commit_files( user, branch_name: ref, message: 'committing something', @@ -528,7 +528,7 @@ RSpec.describe Gitlab::Git::Repository do content: content }] ) - repository.multi_action( + repository.commit_files( user, branch_name: ref, message: 'committing something', @@ -605,7 +605,7 @@ RSpec.describe Gitlab::Git::Repository do let(:query) { 'file with space.md' } before do - mutable_repository.multi_action( + mutable_repository.commit_files( user, actions: [{ action: :create, file_path: "file with space.md", content: "Test content" }], branch_name: ref, message: "Test" @@ -622,7 +622,7 @@ RSpec.describe Gitlab::Git::Repository do let(:query) { file_name } before do - mutable_repository.multi_action( + mutable_repository.commit_files( user, actions: [{ action: :create, file_path: file_name, content: "Test content" }], branch_name: ref, message: "Test" @@ -690,7 +690,7 @@ RSpec.describe Gitlab::Git::Repository do before do # Add new commits so that there's a renamed file in the commit history - @commit_with_old_name_id = repository.multi_action( + @commit_with_old_name_id = repository.commit_files( user, branch_name: repository.root_ref, message: 'Update CHANGELOG', @@ -700,7 +700,7 @@ RSpec.describe Gitlab::Git::Repository do content: 'CHANGELOG' }] ).newrev - @rename_commit_id = repository.multi_action( + @rename_commit_id = repository.commit_files( user, branch_name: repository.root_ref, message: 'Move CHANGELOG to encoding/', @@ -711,7 +711,7 @@ RSpec.describe Gitlab::Git::Repository do content: 'CHANGELOG' }] ).newrev - @commit_with_new_name_id = repository.multi_action( + @commit_with_new_name_id = repository.commit_files( user, branch_name: repository.root_ref, message: 'Edit encoding/CHANGELOG', @@ -1005,7 +1005,7 @@ RSpec.describe Gitlab::Git::Repository do let(:commit) { create_commit('nested/new-blob.txt' => 'This is a new blob') } def create_commit(blobs) - commit_result = repository.multi_action( + commit_result = repository.commit_files( user, branch_name: 'a-new-branch', message: 'Add a file', @@ -1154,7 +1154,7 @@ RSpec.describe Gitlab::Git::Repository do describe '#new_commits' do let(:repository) { mutable_repository } let(:new_commit) do - commit_result = repository.multi_action( + commit_result = repository.commit_files( user, branch_name: 'a-new-branch', message: 'Message', @@ -1793,7 +1793,7 @@ RSpec.describe Gitlab::Git::Repository do let(:source_branch) { 'new-branch-for-fetch-source-branch' } let!(:new_oid) do - source_repository.multi_action( + source_repository.commit_files( user, branch_name: source_branch, message: 'Add a file', @@ -2241,7 +2241,7 @@ RSpec.describe Gitlab::Git::Repository do context 'when the diff contains a rename' do let(:end_sha) do - repository.multi_action( + repository.commit_files( user, branch_name: repository.root_ref, message: 'Move CHANGELOG to encoding/', @@ -2324,7 +2324,7 @@ RSpec.describe Gitlab::Git::Repository do it 'can still access objects in the object pool' do object_pool.link(repository) - new_commit_id = object_pool.repository.multi_action( + new_commit_id = object_pool.repository.commit_files( project.owner, branch_name: object_pool.repository.root_ref, message: 'Add a file', diff --git a/spec/lib/gitlab/git/tree_spec.rb b/spec/lib/gitlab/git/tree_spec.rb index 2e4520cd3a0..7c84c737c00 100644 --- a/spec/lib/gitlab/git/tree_spec.rb +++ b/spec/lib/gitlab/git/tree_spec.rb @@ -95,7 +95,7 @@ RSpec.describe Gitlab::Git::Tree do let(:subdir_file) { entries.first } # rubocop: enable Rails/FindBy let!(:sha) do - repository.multi_action( + repository.commit_files( user, branch_name: 'HEAD', message: "Create #{filename}", diff --git a/spec/models/ci/build_dependencies_spec.rb b/spec/models/ci/build_dependencies_spec.rb index 737348765d9..1dd0386060d 100644 --- a/spec/models/ci/build_dependencies_spec.rb +++ b/spec/models/ci/build_dependencies_spec.rb @@ -13,10 +13,15 @@ RSpec.describe Ci::BuildDependencies do status: 'success') end - let!(:build) { create(:ci_build, pipeline: pipeline, name: 'build', stage_idx: 0, stage: 'build') } - let!(:rspec_test) { create(:ci_build, :success, pipeline: pipeline, name: 'rspec', stage_idx: 1, stage: 'test') } - let!(:rubocop_test) { create(:ci_build, pipeline: pipeline, name: 'rubocop', stage_idx: 1, stage: 'test') } - let!(:staging) { create(:ci_build, pipeline: pipeline, name: 'staging', stage_idx: 2, stage: 'deploy') } + let(:build_stage) { create(:ci_stage, name: 'build', pipeline: pipeline) } + let(:test_stage) { create(:ci_stage, name: 'test', pipeline: pipeline) } + let(:deploy_stage) { create(:ci_stage, name: 'deploy', pipeline: pipeline) } + let!(:build) { create(:ci_build, pipeline: pipeline, name: 'build', stage_idx: 0, ci_stage: build_stage) } + let!(:rubocop_test) { create(:ci_build, pipeline: pipeline, name: 'rubocop', stage_idx: 1, ci_stage: test_stage) } + let!(:staging) { create(:ci_build, pipeline: pipeline, name: 'staging', stage_idx: 2, ci_stage: deploy_stage) } + let!(:rspec_test) do + create(:ci_build, :success, pipeline: pipeline, name: 'rspec', stage_idx: 1, ci_stage: test_stage) + end context 'for local dependencies' do subject { described_class.new(job).all } @@ -63,7 +68,7 @@ RSpec.describe Ci::BuildDependencies do name: 'dag_job', scheduling_type: :dag, stage_idx: 2, - stage: 'deploy' + ci_stage: deploy_stage ) end @@ -87,7 +92,7 @@ RSpec.describe Ci::BuildDependencies do name: 'final', scheduling_type: scheduling_type, stage_idx: 3, - stage: 'deploy', + ci_stage: deploy_stage, options: { dependencies: dependencies } ) end @@ -218,12 +223,12 @@ RSpec.describe Ci::BuildDependencies do cross_pipeline_limit.times do |index| create(:ci_build, :success, pipeline: parent_pipeline, name: "dependency-#{index}", - stage_idx: 1, stage: 'build', user: user + stage_idx: 1, ci_stage: build_stage, user: user ) create(:ci_build, :success, pipeline: sibling_pipeline, name: "dependency-#{index}", - stage_idx: 1, stage: 'build', user: user + stage_idx: 1, ci_stage: build_stage, user: user ) end end @@ -355,7 +360,7 @@ RSpec.describe Ci::BuildDependencies do describe '#all' do let!(:job) do - create(:ci_build, pipeline: pipeline, name: 'deploy', stage_idx: 3, stage: 'deploy') + create(:ci_build, pipeline: pipeline, name: 'deploy', stage_idx: 3, ci_stage: deploy_stage) end let(:dependencies) { described_class.new(job) } diff --git a/spec/models/projects/build_artifacts_size_refresh_spec.rb b/spec/models/projects/build_artifacts_size_refresh_spec.rb index 052e654af76..21cd8e0b9d4 100644 --- a/spec/models/projects/build_artifacts_size_refresh_spec.rb +++ b/spec/models/projects/build_artifacts_size_refresh_spec.rb @@ -265,4 +265,16 @@ RSpec.describe Projects::BuildArtifactsSizeRefresh, type: :model do it { is_expected.to eq(result) } end end + + describe 'callbacks' do + context 'when destroyed' do + it 'enqueues a Namespaces::ScheduleAggregationWorker' do + refresh = create(:project_build_artifacts_size_refresh) + + expect(Namespaces::ScheduleAggregationWorker).to receive(:perform_async).with(refresh.project.namespace_id) + + refresh.destroy! + end + end + end end diff --git a/spec/models/snippet_repository_spec.rb b/spec/models/snippet_repository_spec.rb index e8a933d2277..655cfad57c9 100644 --- a/spec/models/snippet_repository_spec.rb +++ b/spec/models/snippet_repository_spec.rb @@ -39,7 +39,7 @@ RSpec.describe SnippetRepository do let(:data) { [new_file, move_file, update_file] } it 'returns nil when files argument is empty' do - expect(snippet.repository).not_to receive(:multi_action) + expect(snippet.repository).not_to receive(:commit_files) operation = snippet_repository.multi_files_action(user, [], **commit_opts) @@ -47,7 +47,7 @@ RSpec.describe SnippetRepository do end it 'returns nil when files argument is nil' do - expect(snippet.repository).not_to receive(:multi_action) + expect(snippet.repository).not_to receive(:commit_files) operation = snippet_repository.multi_files_action(user, nil, **commit_opts) @@ -119,7 +119,7 @@ RSpec.describe SnippetRepository do end it 'infers the commit action based on the parameters if not present' do - expect(repo).to receive(:multi_action).with(user, hash_including(actions: result)) + expect(repo).to receive(:commit_files).with(user, hash_including(actions: result)) snippet_repository.multi_files_action(user, data, **commit_opts) end @@ -131,7 +131,7 @@ RSpec.describe SnippetRepository do specify do expect(repo).to( - receive(:multi_action).with( + receive(:commit_files).with( user, hash_including(actions: array_including(hash_including(action: expected_action))))) diff --git a/spec/services/auto_merge/merge_when_pipeline_succeeds_service_spec.rb b/spec/services/auto_merge/merge_when_pipeline_succeeds_service_spec.rb index 6f28f892f00..73d185283b6 100644 --- a/spec/services/auto_merge/merge_when_pipeline_succeeds_service_spec.rb +++ b/spec/services/auto_merge/merge_when_pipeline_succeeds_service_spec.rb @@ -217,18 +217,20 @@ RSpec.describe AutoMerge::MergeWhenPipelineSucceedsService do let(:ref) { mr_merge_if_green_enabled.source_branch } let(:sha) { project.commit(ref).id } + let(:build_stage) { create(:ci_stage, name: 'build', pipeline: pipeline) } + let(:pipeline) do create(:ci_empty_pipeline, ref: ref, sha: sha, project: project) end let!(:build) do create(:ci_build, :created, pipeline: pipeline, ref: ref, - name: 'build', stage: 'build') + name: 'build', ci_stage: build_stage ) end let!(:test) do create(:ci_build, :created, pipeline: pipeline, ref: ref, - name: 'test', stage: 'test') + name: 'test') end before do diff --git a/spec/services/git/branch_hooks_service_spec.rb b/spec/services/git/branch_hooks_service_spec.rb index 5de1c0e27be..973ead28462 100644 --- a/spec/services/git/branch_hooks_service_spec.rb +++ b/spec/services/git/branch_hooks_service_spec.rb @@ -596,7 +596,7 @@ RSpec.describe Git::BranchHooksService, :clean_gitlab_redis_shared_state do end end - project.repository.multi_action( + project.repository.commit_files( user, message: 'message', branch_name: branch, actions: actions ) end diff --git a/spec/support/shared_examples/requests/api/snippets_shared_examples.rb b/spec/support/shared_examples/requests/api/snippets_shared_examples.rb index 2b72c69cb37..1b92eb56f54 100644 --- a/spec/support/shared_examples/requests/api/snippets_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/snippets_shared_examples.rb @@ -133,7 +133,7 @@ RSpec.shared_examples 'snippet file updates' do context 'when save fails due to a repository commit error' do before do allow_next_instance_of(Repository) do |instance| - allow(instance).to receive(:multi_action).and_raise(Gitlab::Git::CommitError) + allow(instance).to receive(:commit_files).and_raise(Gitlab::Git::CommitError) end update_snippet(params: { files: [create_action] }) |