Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2024-12-06 15:21:47 +00:00
parent 403730bab4
commit d2167e96a0
48 changed files with 812 additions and 452 deletions

View File

@ -18,4 +18,4 @@ variables:
# Retry failed specs in separate process
QA_RETRY_FAILED_SPECS: "true"
# helm chart ref used by test-on-cng pipeline
GITLAB_HELM_CHART_REF: "a2ea74990ebffd32c28b2562e4d5a29effb1d95b"
GITLAB_HELM_CHART_REF: "e42af7c4041033043adbe415ac5abb869daa9d20"

View File

@ -328,7 +328,7 @@ export default {
<gl-form-group :label="$options.i18n.userCapLabel" data-testid="user-cap-group">
<input
type="hidden"
name="application_setting[pending_user_auto_approval]"
name="application_setting[auto_approve_pending_users]"
:value="form.shouldProceedWithAutoApproval"
/>
<gl-form-input

View File

@ -229,8 +229,7 @@ const initTreeHistoryLinkApp = (el) => {
{
attrs: {
href: url.href,
category: 'tertiary',
class: 'gl-ml-4',
class: '!gl-ml-3',
},
},
[__('History')],

View File

@ -1,5 +1,5 @@
<script>
import { GlButton, GlDrawer, GlLink, GlFormTextarea, GlModal, GlFormInput } from '@gitlab/ui';
import { GlButton, GlDrawer, GlLink, GlFormTextarea } from '@gitlab/ui';
import { InternalEvents } from '~/tracking';
import { visitUrl } from '~/lib/utils/url_utility';
import { helpPagePath } from '~/helpers/help_page_helper';
@ -8,6 +8,7 @@ import { getContentWrapperHeight } from '~/lib/utils/dom_utils';
import { s__ } from '~/locale';
import { createAlert, VARIANT_WARNING } from '~/alert';
import removeBlobsMutation from './graphql/mutations/remove_blobs.mutation.graphql';
import WarningModal from './warning_modal.vue';
const trackingMixin = InternalEvents.mixin();
@ -19,20 +20,31 @@ const i18n = {
'ProjectMaintenance|Enter a list of object IDs to be removed to reduce repository size.',
),
helpLink: s__('ProjectMaintenance|How do I get a list of object IDs?'),
warningHelpLink: s__('ProjectMaintenance|How does blobs removal work?'),
label: s__('ProjectMaintenance|Blob IDs to remove'),
helpText: s__('ProjectMaintenance|Enter multiple entries on separate lines.'),
modalPrimaryText: s__('ProjectMaintenance|Yes, remove blobs'),
modalCancelText: s__('ProjectMaintenance|Cancel'),
modalContent: s__(
'ProjectMaintenance|Removing blobs by ID cannot be undone. Are you sure you want to continue?',
),
modalConfirm: s__('ProjectMaintenance|Enter the following to confirm:'),
removeBlobsError: s__('ProjectMaintenance|Something went wrong while removing blobs.'),
scheduledRemovalSuccessAlertTitle: s__('ProjectMaintenance|Blobs removal is scheduled.'),
scheduledSuccessAlertContent: s__(
'ProjectMaintenance|You will receive an email notification when the process is complete. Run housekeeping to remove old versions from repository.',
),
successAlertButtonText: s__('ProjectMaintenance|Go to housekeeping'),
warningModalTitle: s__(
'ProjectMaintenance|You are about to permanently remove blobs from this project.',
),
warningModalPrimaryText: s__('ProjectMaintenance|Yes, remove blobs'),
warningModalListItems: [
s__('ProjectMaintenance|Open merge requests might fail to merge and require manual rebasing.'),
s__(
'ProjectMaintenance|Local copies become incompatible with the updated repository and must be re-cloned.',
),
s__(
'ProjectMaintenance|Pipelines referencing old commit SHAs might break and require reconfiguration.',
),
s__(
'ProjectMaintenance|Historical tags and branches based on the old commit history might no longer work correctly.',
),
],
};
export default {
@ -41,8 +53,10 @@ export default {
removeBlobsHelpLink: helpPagePath('/user/project/repository/repository_size', {
anchor: 'get-a-list-of-object-ids',
}),
modalCancel: { text: i18n.modalCancelText },
components: { GlButton, GlDrawer, GlLink, GlFormTextarea, GlModal, GlFormInput },
removeBlobsWarningHelpLink: helpPagePath('/user/project/repository/repository_size', {
anchor: 'remove-files',
}),
components: { GlButton, GlDrawer, GlLink, GlFormTextarea, WarningModal },
mixins: [trackingMixin],
inject: { projectPath: { default: '' }, housekeepingPath: { default: '' } },
data() {
@ -50,7 +64,6 @@ export default {
isDrawerOpen: false,
blobIDs: null,
showConfirmationModal: false,
confirmInput: null,
isLoading: false,
};
},
@ -64,15 +77,6 @@ export default {
isValid() {
return this.blobOids.length && this.blobOids.every((s) => s.length >= BLOB_OID_LENGTH);
},
modalPrimary() {
return {
text: i18n.modalPrimaryText,
attributes: { variant: 'danger', disabled: !this.isConfirmEnabled },
};
},
isConfirmEnabled() {
return this.confirmInput === this.projectPath;
},
},
methods: {
openDrawer() {
@ -82,14 +86,12 @@ export default {
this.blobIDs = null;
this.isDrawerOpen = false;
},
clearConfirmInput() {
this.confirmInput = null;
},
removeBlobs() {
this.showConfirmationModal = true;
},
removeBlobsConfirm() {
this.isLoading = true;
this.showConfirmationModal = false;
this.trackEvent('click_remove_blob_button_repository_settings');
this.$apollo
.mutate({
@ -186,26 +188,24 @@ export default {
</div>
</gl-drawer>
<gl-modal
v-model="showConfirmationModal"
:title="$options.i18n.removeBlobs"
modal-id="remove-blobs-confirmation-modal"
:action-cancel="$options.modalCancel"
:action-primary="modalPrimary"
@hide="clearConfirmInput"
@primary="removeBlobsConfirm"
<warning-modal
:visible="showConfirmationModal"
:title="$options.i18n.warningModalTitle"
:primary-text="$options.i18n.warningModalPrimaryText"
:confirm-phrase="projectPath"
:confirm-loading="isLoading"
@confirm="removeBlobsConfirm"
@hide="showConfirmationModal = false"
>
<p>{{ $options.i18n.modalContent }}</p>
<ul class="mb-0">
<li v-for="(item, index) in $options.i18n.warningModalListItems" :key="index">
{{ item }}
</li>
</ul>
<p id="confirmationInstruction" class="gl-mb-0">
{{ $options.i18n.modalConfirm }} <code>{{ projectPath }}</code>
</p>
<gl-form-input
v-model="confirmInput"
class="gl-mt-3 gl-max-w-34"
aria-labelledby="confirmationInstruction"
/>
</gl-modal>
<gl-link :href="$options.removeBlobsWarningHelpLink" target="_blank">{{
$options.i18n.warningHelpLink
}}</gl-link>
</warning-modal>
</div>
</template>

View File

@ -0,0 +1,89 @@
<script>
import { GlModal, GlAlert, GlFormInput } from '@gitlab/ui';
import uniqueId from 'lodash/uniqueId';
import { __ } from '~/locale';
export default {
i18n: {
title: __('Are you absolutely sure?'),
confirmText: __('Enter the following to confirm:'),
},
components: { GlModal, GlAlert, GlFormInput },
props: {
visible: {
type: Boolean,
required: true,
},
confirmPhrase: {
type: String,
required: true,
},
title: {
type: String,
required: true,
},
primaryText: {
type: String,
required: true,
},
confirmLoading: {
type: Boolean,
required: false,
default: false,
},
},
data() {
return {
userInput: null,
modalId: uniqueId('rewrite-history-warning-modal'),
};
},
computed: {
confirmDisabled() {
return this.userInput !== this.confirmPhrase;
},
modalActionProps() {
return {
primary: {
text: this.primaryText,
attributes: {
variant: 'danger',
disabled: this.confirmDisabled,
loading: this.confirmLoading,
},
},
cancel: { text: __('Cancel') },
};
},
},
};
</script>
<template>
<gl-modal
:visible="visible"
:no-focus-on-show="true"
:modal-id="modalId"
:action-primary="modalActionProps.primary"
:action-cancel="modalActionProps.cancel"
@primary.prevent="$emit('confirm')"
@show="userInput = ''"
v-on="$listeners"
>
<template #modal-title>{{ $options.i18n.title }}</template>
<div>
<gl-alert class="gl-mb-5" variant="danger" :dismissible="false">
<h4 class="gl-alert-title">
{{ title }}
</h4>
<slot></slot>
</gl-alert>
<p>
{{ $options.i18n.confirmText }} <code>{{ confirmPhrase }}</code>
</p>
<gl-form-input v-model="userInput" autofocus />
</div>
</gl-modal>
</template>

View File

@ -65,25 +65,21 @@ export default {
</script>
<template>
<div class="well-segment">
<div class="gl-flex gl-items-center gl-justify-between">
<div class="gl-flex gl-items-center gl-gap-3">
<div class="well-segment !gl-px-4 !gl-py-3">
<div class="gl-flex gl-flex-wrap gl-items-center gl-justify-between">
<div class="gl-flex gl-items-center gl-gap-3 gl-text-sm">
<user-avatar-link
v-if="commit.author"
:link-href="commit.author.webPath"
:img-src="commit.author.avatarUrl"
:img-alt="avatarLinkAltText"
:img-size="32"
class="gl-my-2 gl-mr-3"
/>
<user-avatar-image
v-else
class="gl-my-2 gl-mr-3"
:img-src="commit.authorGravatar || $options.defaultAvatarUrl"
:size="32"
/>
<timeago-tooltip :time="commit.authoredDate" tooltip-placement="bottom" />
<gl-link
:href="commit.webPath"
:class="{ 'gl-italic': !commit.message }"
@ -92,26 +88,26 @@ export default {
<gl-icon name="commit" />
{{ commitId }}
</gl-link>
<timeago-tooltip
:time="commit.authoredDate"
tooltip-placement="bottom"
class="gl-text-subtle"
/>
</div>
<div class="gl-flex gl-items-center">
<div class="gl-flex gl-items-center gl-gap-3">
<gl-button
v-gl-tooltip
:class="{ open: showDescription }"
:title="$options.i18n.toggleCommitDescription"
:aria-label="$options.i18n.toggleCommitDescription"
:selected="showDescription"
class="text-expander !gl-ml-0"
class="text-expander"
icon="ellipsis_h"
data-testid="text-expander"
@click="toggleShowDescription"
/>
<gl-button
category="tertiary"
data-testid="collapsible-commit-history"
:href="historyUrl"
class="gl-ml-2"
>
<gl-button size="small" data-testid="collapsible-commit-history" :href="historyUrl">
{{ __('History') }}
</gl-button>
</div>

View File

@ -69,7 +69,7 @@ export default {
</script>
<template>
<div class="well-segment commit gl-flex gl-min-h-8 gl-w-full gl-p-2">
<div class="well-segment commit gl-flex gl-w-full !gl-px-5 !gl-py-4">
<user-avatar-link
v-if="commit.author"
:link-href="commit.author.webPath"

View File

@ -114,34 +114,37 @@ export default {
</script>
<template>
<gl-loading-icon v-if="isLoading" size="md" color="dark" class="m-auto gl-min-h-8 gl-py-6" />
<gl-loading-icon v-if="isLoading" size="md" color="dark" class="gl-m-auto gl-py-6" />
<div v-else-if="commit">
<commit-info :commit="commit" class="gl-hidden sm:gl-flex">
<div class="commit-actions gl-flex gl-items-start">
<div class="commit-actions gl-flex gl-items-center gl-gap-3">
<signature-badge v-if="commit.signature" :signature="commit.signature" class="gl-h-7" />
<div v-if="commit.pipeline" class="gl-ml-5 gl-flex gl-h-7 gl-items-center">
<ci-icon
:status="commit.pipeline.detailedStatus"
:aria-label="statusTitle"
class="js-commit-pipeline"
class="js-commit-pipeline gl-mr-2"
/>
</div>
<gl-button-group class="js-commit-sha-group gl-ml-4 gl-flex gl-items-center">
<gl-button label class="gl-font-monospace" data-testid="last-commit-id-label">{{
showCommitId
}}</gl-button>
<gl-button
label
class="gl-font-monospace dark:!gl-bg-strong"
data-testid="last-commit-id-label"
>{{ showCommitId }}</gl-button
>
<clipboard-button
:text="commit.sha"
:title="__('Copy commit SHA')"
class="input-group-text"
class="input-group-text dark:!gl-border-l-section"
/>
</gl-button-group>
<gl-button
category="tertiary"
category="secondary"
data-testid="last-commit-history"
:href="historyUrl"
class="gl-ml-4"
class="!gl-ml-0"
>
{{ __('History') }}
</gl-button>
@ -150,7 +153,7 @@ export default {
<collapsible-commit-info
:commit="commit"
:history-url="historyUrl"
class="gl-block sm:gl-hidden"
class="gl-block !gl-border-t-0 sm:gl-hidden"
/>
</div>
</template>

View File

@ -21,7 +21,7 @@ export default {
};
</script>
<template>
<div class="blame gl-border-r gl-bg-gray-10">
<div class="blame gl-border-r gl-bg-subtle">
<div class="blame-commit !gl-border-none">
<commit-info
v-for="(blame, index) in blameInfo"

View File

@ -1,9 +1,5 @@
@import 'mixins_and_variables_and_functions';
.project-last-commit {
min-height: 4.75rem;
}
.tree-holder {
.nav-block {
margin: $gl-spacing-scale-2 0 $gl-spacing-scale-5;

View File

@ -98,6 +98,15 @@ class Compare
)
end
def changed_paths
project
.repository
.find_changed_paths(
[Gitlab::Git::DiffTree.new(diff_refs.base_sha, diff_refs.head_sha)],
find_renames: true
)
end
def modified_paths
paths = Set.new
diffs.diff_files.each do |diff|

View File

@ -47,7 +47,7 @@ module ApplicationSettings
params[:usage_stats_set_by_user_id] = current_user.id
end
@application_setting.assign_attributes(params.except(:pending_user_auto_approval))
@application_setting.assign_attributes(params.except(:auto_approve_pending_users))
if invalidate_markdown_cache?
@application_setting[:local_markdown_version] = @application_setting.local_markdown_version + 1
@ -130,7 +130,7 @@ module ApplicationSettings
end
def auto_approve_blocked_users
return unless pending_user_auto_approval?
return unless auto_approve_pending_users?
return unless should_auto_approve_blocked_users?
ApproveBlockedPendingApprovalUsersWorker.perform_async(current_user.id)
@ -144,8 +144,8 @@ module ApplicationSettings
enabled_previous && !enabled_current
end
def pending_user_auto_approval?
Gitlab::Utils.to_boolean(params.fetch(:pending_user_auto_approval, false))
def auto_approve_pending_users?
Gitlab::Utils.to_boolean(params.fetch(:auto_approve_pending_users, false))
end
end
end

View File

@ -21,30 +21,28 @@ module Packages
XPATH_PACKAGE_TYPES = "#{ROOT_XPATH}:packageTypes/xmlns:packageType".freeze
def initialize(nuspec_file_content)
@nuspec_file_content = nuspec_file_content
@doc = Nokogiri::XML(nuspec_file_content)
end
def execute
ServiceResponse.success(payload: extract_metadata(nuspec_file_content))
ServiceResponse.success(payload: extract_metadata)
end
private
attr_reader :nuspec_file_content
def extract_metadata(file)
doc = Nokogiri::XML(file)
attr_reader :doc
def extract_metadata
XPATHS.transform_values { |query| doc.xpath(query).text.presence }
.compact
.tap do |metadata|
metadata[:package_dependencies] = extract_dependencies(doc)
metadata[:package_tags] = extract_tags(doc)
metadata[:package_types] = extract_package_types(doc)
metadata[:package_dependencies] = extract_dependencies
metadata[:package_tags] = extract_tags
metadata[:package_types] = extract_package_types
end
end
def extract_dependencies(doc)
def extract_dependencies
dependencies = []
doc.xpath(XPATH_DEPENDENCIES).each do |node|
@ -69,7 +67,7 @@ module Packages
}.compact
end
def extract_tags(doc)
def extract_tags
tags = doc.xpath(XPATH_TAGS).text
return [] if tags.blank?
@ -77,7 +75,7 @@ module Packages
tags.split(::Packages::Tag::NUGET_TAGS_SEPARATOR)
end
def extract_package_types(doc)
def extract_package_types
doc.xpath(XPATH_PACKAGE_TYPES).map { |node| node.attr('name') }.uniq
end
end

View File

@ -1,6 +1,8 @@
= render ::Layouts::PageHeadingComponent.new(_('Groups')) do |c|
- c.with_actions do
= link_to _("Explore groups"), explore_groups_path
= render Pajamas::ButtonComponent.new(href: explore_groups_path, variant: :link, button_options: { class: 'gl-m-2' }) do
= _("Explore groups")
- if current_user.can_create_group?
= render Pajamas::ButtonComponent.new(href: new_group_path, variant: :confirm, button_options: { data: { testid: "new-group-button" } }) do
= _("New group")

View File

@ -4,7 +4,7 @@
= render ::Layouts::PageHeadingComponent.new(_('Projects')) do |c|
- c.with_actions do
= render Pajamas::ButtonComponent.new(href: starred_explore_projects_path, variant: :link, button_options: { class: 'gl-mr-2' }) do
= render Pajamas::ButtonComponent.new(href: starred_explore_projects_path, variant: :link, button_options: { class: 'gl-m-2' }) do
= _("Explore projects")
- if current_user.can_create_project?

View File

@ -1,6 +1,8 @@
= render ::Layouts::PageHeadingComponent.new(_('Snippets')) do |c|
- c.with_actions do
= link_to _("Explore snippets"), explore_snippets_path
= render Pajamas::ButtonComponent.new(href: explore_snippets_path, variant: :link, button_options: { class: 'gl-m-2' }) do
= _("Explore snippets")
- if can?(current_user, :create_snippet)
= render Pajamas::ButtonComponent.new(href: new_snippet_path, variant: :confirm, button_options: { title: _("New snippet") }) do
= _("New snippet")

View File

@ -28,9 +28,9 @@
= s_('Notify|%{changed_files}:') % {changed_files: changed_files}
%ul
- @message.diffs.each do |diff_file|
- @message.changed_files.each do |diff_file|
%li.file-stats
%a{ href: "#{@message.target_url if @message.disable_diffs?}##{hexdigest(diff_file.file_path)}" }
%a{ href: "#{@message.target_url if @message.disable_diffs?}##{hexdigest(diff_file.path)}" }
- if diff_file.deleted_file?
%span.deleted-file
&minus;
@ -38,13 +38,13 @@
- elsif diff_file.renamed_file?
= diff_file.old_path
&rarr;
= diff_file.new_path
= diff_file.path
- elsif diff_file.new_file?
%span.new-file
&#43;
= diff_file.new_path
= diff_file.path
- else
= diff_file.new_path
= diff_file.path
- unless @message.disable_diffs?
- if @message.compare_timeout

View File

@ -15,15 +15,15 @@
\
#{pluralize @message.diffs_count, 'changed file'}:
\
- @message.diffs.each do |diff_file|
- @message.changed_files.each do |diff_file|
- if diff_file.deleted_file?
\- #{diff_file.old_path}
- elsif diff_file.renamed_file?
\- #{diff_file.old_path} → #{diff_file.new_path}
\- #{diff_file.old_path} → #{diff_file.path}
- elsif diff_file.new_file?
\- + #{diff_file.new_path}
\- + #{diff_file.path}
- else
\- #{diff_file.new_path}
\- #{diff_file.path}
- unless @message.disable_diffs?
- if @message.compare_timeout
\

View File

@ -43,7 +43,7 @@
#js-fork-info{ data: vue_fork_divergence_data(project, ref) }
.info-well.project-last-commit.gl-flex-col.gl-mt-5
#js-last-commit.gl-m-auto{ data: {ref_type: @ref_type.to_s, history_link: project_commits_path(@project, @ref)} }
#js-last-commit.gl-flex.gl-items-center.gl-justify-center.gl-m-auto.gl-min-h-9{ class: 'lg:gl-min-h-[4.5rem]', data: {ref_type: @ref_type.to_s, history_link: project_commits_path(@project, @ref)} }
= gl_loading_icon(size: 'md')
- if project.licensed_feature_available?(:code_owners)
#js-code-owners.gl-hidden.sm:gl-flex{ data: { branch: @ref, can_view_branch_rules: can_view_branch_rules?, branch_rules_path: branch_rules_path } }

View File

@ -258,7 +258,6 @@ end
Gitlab.ee do
Settings['elasticsearch'] ||= {}
Settings.elasticsearch['enabled'] = false if Settings.elasticsearch['enabled'].nil?
Settings.elasticsearch['url'] = ENV['ELASTIC_URL'] || "http://localhost:9200"
Settings.elasticsearch['indexer_path'] ||= Gitlab::Utils.which('gitlab-elasticsearch-indexer')
end

View File

@ -7087,6 +7087,30 @@ Input type: `MemberRoleDeleteInput`
| <a id="mutationmemberroledeleteerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
| <a id="mutationmemberroledeletememberrole"></a>`memberRole` | [`MemberRole`](#memberrole) | Deleted member role. |
### `Mutation.memberRoleToUserAssign`
DETAILS:
**Introduced** in GitLab 17.7.
**Status**: Experiment.
Input type: `MemberRoleToUserAssignInput`
#### Arguments
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationmemberroletouserassignclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationmemberroletouserassignmemberroleid"></a>`memberRoleId` | [`MemberRoleID!`](#memberroleid) | Global ID of the custom role to be assigned to a user. |
| <a id="mutationmemberroletouserassignuserid"></a>`userId` | [`UserID!`](#userid) | Global ID of the user to be assigned to a custom role. |
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationmemberroletouserassignclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationmemberroletouserassignerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
| <a id="mutationmemberroletouserassignusermemberrole"></a>`userMemberRole` | [`UserMemberRole`](#usermemberrole) | Created user member role. |
### `Mutation.memberRoleUpdate`
Input type: `MemberRoleUpdateInput`
@ -35970,6 +35994,16 @@ four standard [pagination arguments](#pagination-arguments):
| <a id="usercoreworkspacesincludeactualstates"></a>`includeActualStates` **{warning-solid}** | [`[String!]`](#string) | **Deprecated** in GitLab 16.7. Use actual_states instead. |
| <a id="usercoreworkspacesprojectids"></a>`projectIds` | [`[ProjectID!]`](#projectid) | Filter workspaces by project GlobalIDs. |
### `UserMemberRole`
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="usermemberroleid"></a>`id` | [`GlobalID!`](#globalid) | Global ID of the user member role association. |
| <a id="usermemberrolememberrole"></a>`memberRole` | [`MemberRole!`](#memberrole) | Member Role to which the user belongs. |
| <a id="usermemberroleuser"></a>`user` | [`UserCore!`](#usercore) | User to which the member role belongs. |
### `UserMergeRequestInteraction`
Information about a merge request given a specific user.

View File

@ -1425,7 +1425,7 @@ Supported general project attributes:
| `merge_trains_skip_train_allowed` | boolean | No | Allows merge train merge requests to be merged without waiting for pipelines to finish. |
| `mirror_trigger_builds` | boolean | No | Pull mirroring triggers builds. Premium and Ultimate only. |
| `mirror` | boolean | No | Enables pull mirroring in a project. Premium and Ultimate only. |
| `namespace_id` | integer | No | Namespace for the new project (defaults to the current user's namespace). |
| `namespace_id` | integer | No | Namespace for the new project. Specify a group ID or subgroup ID. If not provided, defaults to the current user's personal namespace. |
| `only_allow_merge_if_all_discussions_are_resolved` | boolean | No | Set whether merge requests can only be merged when all the discussions are resolved. |
| `only_allow_merge_if_all_status_checks_passed` | boolean | No | Indicates that merges of merge requests should be blocked unless all status checks have passed. Defaults to false. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/369859) in GitLab 15.5 with feature flag `only_allow_merge_if_all_status_checks_passed` disabled by default. Ultimate only. |
| `only_allow_merge_if_pipeline_succeeds` | boolean | No | Set whether merge requests can only be merged with successful pipelines. This setting is named [**Pipelines must succeed**](../user/project/merge_requests/auto_merge.md#require-a-successful-pipeline-for-merge) in the project settings. |

View File

@ -70,7 +70,7 @@ class MigrationName < Elastic::Migration
The migration is executed only if the condition is `false`. Skipped migrations will not be shown as part of pending migrations.
Skipped migrations can be marked as obsolete, but the `skip_if` condition must be kept so that these migrations are always skipped.
Once a skipped migration is obsolete, the only way to apply the change is by [recreating the index from scratch](../../integration/advanced_search/elasticsearch_troubleshooting.md#last-resort-to-recreate-an-index).
Once a skipped migration is obsolete, the only way to apply the change is by [recreating the index from scratch](../../integration/elasticsearch/troubleshooting/indexing.md#last-resort-to-recreate-an-index).
Update the skipped migration's documentation file with the following attributes:

View File

@ -800,7 +800,7 @@ When you believe you've fixed the cause of the failure:
If you cannot get the migration to succeed, you may
consider the
[last resort to recreate the index from scratch](elasticsearch_troubleshooting.md#last-resort-to-recreate-an-index).
[last resort to recreate the index from scratch](../elasticsearch/troubleshooting/indexing.md#last-resort-to-recreate-an-index).
This may allow you to skip over
the problem because a newly created index skips all migrations as the index
is recreated with the correct up-to-date schema.
@ -817,7 +817,7 @@ Migrations that have been removed are
If you upgrade GitLab before all pending advanced search migrations are completed,
any pending migrations that have been removed in the new version cannot be executed or retried.
In this case, you must
[re-create your index from scratch](elasticsearch_troubleshooting.md#last-resort-to-recreate-an-index).
[re-create your index from scratch](../elasticsearch/troubleshooting/indexing.md#last-resort-to-recreate-an-index).
### Skippable migrations
@ -825,7 +825,7 @@ Skippable migrations are only executed when a condition is met.
For example, if a migration depends on a specific version of Elasticsearch, it could be skipped until that version is reached.
If a skippable migration is not executed by the time the migration is marked as obsolete, to apply the change you must
[re-create the index](elasticsearch_troubleshooting.md#last-resort-to-recreate-an-index).
[re-create the index](../elasticsearch/troubleshooting/indexing.md#last-resort-to-recreate-an-index).
## GitLab advanced search Rake tasks

View File

@ -12,275 +12,6 @@ DETAILS:
When working with Elasticsearch, you might encounter the following issues.
## Troubleshooting indexing
Troubleshooting indexing issues can be tricky. It can pretty quickly go to either GitLab
support or your Elasticsearch administrator.
The best place to start is to determine if the issue is with creating an empty index.
If it is, check on the Elasticsearch side to determine if the `gitlab-production` (the
name for the GitLab index) exists. If it exists, manually delete it on the Elasticsearch
side and attempt to recreate it from the
[`recreate_index`](../../integration/advanced_search/elasticsearch.md#gitlab-advanced-search-rake-tasks)
Rake task.
If you still encounter issues, try creating an index manually on the Elasticsearch
instance. The details of the index aren't important here, as we want to test if indices
can be made. If the indices:
- Cannot be made, speak with your Elasticsearch administrator.
- Can be made, Escalate this to GitLab support.
If the issue is not with creating an empty index, the next step is to check for errors
during the indexing of projects. If errors do occur, they stem from either the indexing:
- On the GitLab side. You need to rectify those. If they are not
something you are familiar with, contact GitLab support for guidance.
- Within the Elasticsearch instance itself. See if the error is [documented and has a fix](../../integration/advanced_search/elasticsearch_troubleshooting.md). If not, speak with your Elasticsearch administrator.
If the indexing process does not present errors, check the status of the indexed projects. You can do this via the following Rake tasks:
- [`sudo gitlab-rake gitlab:elastic:index_projects_status`](../../integration/advanced_search/elasticsearch.md#gitlab-advanced-search-rake-tasks) (shows the overall status)
- [`sudo gitlab-rake gitlab:elastic:projects_not_indexed`](../../integration/advanced_search/elasticsearch.md#gitlab-advanced-search-rake-tasks) (shows specific projects that are not indexed)
If:
- Everything is showing at 100%, escalate to GitLab support. This could be a potential
bug/issue.
- You do see something not at 100%, attempt to reindex that project. To do this,
run `sudo gitlab-rake gitlab:elastic:index_projects ID_FROM=<project ID> ID_TO=<project ID>`.
If reindexing the project shows:
- Errors on the GitLab side, escalate those to GitLab support.
- Elasticsearch errors or doesn't present any errors at all, reach out to your
Elasticsearch administrator to check the instance.
### You updated GitLab and now you can't find anything
We continuously make updates to our indexing strategies and aim to support
newer versions of Elasticsearch. When indexing changes are made, it may
be necessary for you to [reindex](elasticsearch.md#zero-downtime-reindexing) after updating GitLab.
### No search results in the UI after indexing all repositories
Make sure you [indexed all the database data](elasticsearch.md#enable-advanced-search).
If there aren't any results (hits) in the UI search, check if you are seeing the same results via the rails console (`sudo gitlab-rails console`):
```ruby
u = User.find_by_username('your-username')
s = SearchService.new(u, {:search => 'search_term', :scope => 'blobs'})
pp s.search_objects.to_a
```
Beyond that, check via the [Elasticsearch Search API](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-search.html) to see if the data shows up on the Elasticsearch side:
```shell
curl --request GET <elasticsearch_server_ip>:9200/gitlab-production/_search?q=<search_term>
```
More [complex Elasticsearch API calls](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-filter-context.html) are also possible.
If the results:
- Sync up, check that you are using [supported syntax](../../user/search/advanced_search.md#syntax). Advanced search does not support [exact substring matching](https://gitlab.com/gitlab-org/gitlab/-/issues/325234).
- Do not match up, this indicates a problem with the documents generated from the project. It is best to [re-index that project](../advanced_search/elasticsearch.md#indexing-a-range-of-projects-or-a-specific-project).
NOTE:
The above instructions are not to be used for scenarios that only index a [subset of namespaces](elasticsearch.md#limit-the-amount-of-namespace-and-project-data-to-index).
See [Elasticsearch Index Scopes](elasticsearch.md#advanced-search-index-scopes) for more information on searching for specific types of data.
### All repositories indexed, but no results after switching Elasticsearch servers
You must re-run all the Rake tasks to reindex the database, repositories, and wikis.
### There are some projects that weren't indexed, but you don't know which ones
You can run `sudo gitlab-rake gitlab:elastic:projects_not_indexed` to display projects that aren't indexed.
### No new data is added to the Elasticsearch index when you push code
NOTE:
This was [fixed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/35936) in GitLab 13.2 and the Rake task is not available for versions greater than that.
When performing the initial indexing of blobs, we lock all projects until the project finishes indexing. It could happen that an error during the process causes one or multiple projects to remain locked. To unlock them, run:
```shell
sudo gitlab-rake gitlab:elastic:clear_locked_projects
```
### Indexing fails with `error: elastic: Error 429 (Too Many Requests)`
If `ElasticCommitIndexerWorker` Sidekiq workers are failing with this error during indexing, it usually means that Elasticsearch is unable to keep up with the concurrency of indexing request. To address change the following settings:
- To decrease the indexing throughput you can decrease `Bulk request concurrency` (see [Advanced search settings](elasticsearch.md#advanced-search-configuration)). This is set to `10` by default, but you change it to as low as 1 to reduce the number of concurrent indexing operations.
- If changing `Bulk request concurrency` didn't help, you can use the [routing rules](../../administration/sidekiq/processing_specific_job_classes.md#routing-rules) option to [limit indexing jobs only to specific Sidekiq nodes](elasticsearch.md#index-large-instances-with-dedicated-sidekiq-nodes-or-processes), which should reduce the number of indexing requests.
### Indexing is very slow or fails with `rejected execution of coordinating operation` messages
Bulk requests getting rejected by the Elasticsearch nodes are likely due to load and lack of available memory.
Ensure that your Elasticsearch cluster meets the [system requirements](elasticsearch.md#system-requirements) and has enough resources
to perform bulk operations. See also the error ["429 (Too Many Requests)"](#indexing-fails-with-error-elastic-error-429-too-many-requests).
### Indexing fails with `strict_dynamic_mapping_exception`
Indexing might fail if all [advanced search migrations were not finished before doing a major upgrade](elasticsearch.md#all-migrations-must-be-finished-before-doing-a-major-upgrade).
A large Sidekiq backlog might accompany this error. To fix the indexing failures, you must re-index the database, repositories, and wikis.
1. Pause indexing so Sidekiq can catch up:
```shell
sudo gitlab-rake gitlab:elastic:pause_indexing
```
1. [Recreate the index from scratch](#last-resort-to-recreate-an-index).
1. Resume indexing:
```shell
sudo gitlab-rake gitlab:elastic:resume_indexing
```
### Indexing keeps pausing with `elasticsearch_pause_indexing setting is enabled`
You might notice that new data is not being detected when you run a search.
This error occurs when that new data is not being indexed properly.
To resolve this error, [reindex your data](elasticsearch.md#zero-downtime-reindexing).
However, when reindexing, you might get an error where the indexing process keeps pausing, and the Elasticsearch logs show the following:
```shell
"message":"elasticsearch_pause_indexing setting is enabled. Job was added to the waiting queue"
```
If reindexing does not resolve this issue, and you did not pause the indexing process manually, this error might be happening because two GitLab instances share one Elasticsearch cluster.
To resolve this error, disconnect one of the GitLab instances from using the Elasticsearch cluster.
For more information, see [issue 3421](https://gitlab.com/gitlab-org/gitlab/-/issues/3421).
### Last resort to recreate an index
There may be cases where somehow data never got indexed and it's not in the
queue, or the index is somehow in a state where migrations just cannot
proceed. It is always best to try to troubleshoot the root cause of the problem
by [viewing the logs](../elasticsearch/troubleshooting/access.md#view-logs).
As a last resort, you can recreate the index from scratch. For small GitLab installations,
recreating the index can be a quick way to resolve some issues. For large GitLab
installations, however, this method might take a very long time. Your index
does not show correct search results until the indexing is complete. You might
want to clear the **Search with Elasticsearch enabled** checkbox
while the indexing is running.
If you are sure you've read the above caveats and want to proceed, then you
should run the following Rake task to recreate the entire index from scratch.
::Tabs
:::TabTitle Linux package (Omnibus)
```shell
# WARNING: DO NOT RUN THIS UNTIL YOU READ THE DESCRIPTION ABOVE
sudo gitlab-rake gitlab:elastic:index
```
:::TabTitle Self-compiled (source)
```shell
# WARNING: DO NOT RUN THIS UNTIL YOU READ THE DESCRIPTION ABOVE
cd /home/git/gitlab
sudo -u git -H bundle exec rake gitlab:elastic:index
```
::EndTabs
### Troubleshooting performance
Troubleshooting performance can be difficult on Elasticsearch. There is a ton of tuning
that *can* be done, but the majority of this falls on shoulders of a skilled
Elasticsearch administrator.
Generally speaking, ensure:
- The Elasticsearch server **is not** running on the same node as GitLab.
- The Elasticsearch server have enough RAM and CPU cores.
- That sharding **is** being used.
Going into some more detail here, if Elasticsearch is running on the same server as GitLab, resource contention is **very** likely to occur. Ideally, Elasticsearch, which requires ample resources, should be running on its own server (maybe coupled with Logstash and Kibana).
When it comes to Elasticsearch, RAM is the key resource. Elasticsearch themselves recommend:
- **At least** 8 GB of RAM for a non-production instance.
- **At least** 16 GB of RAM for a production instance.
- Ideally, 64 GB of RAM.
For CPU, Elasticsearch recommends at least 2 CPU cores, but Elasticsearch states common
setups use up to 8 cores. For more details on server specs, check out the
[Elasticsearch hardware guide](https://www.elastic.co/guide/en/elasticsearch/guide/current/hardware.html).
Beyond the obvious, sharding comes into play. Sharding is a core part of Elasticsearch.
It allows for horizontal scaling of indices, which is helpful when you are dealing with
a large amount of data.
With the way GitLab does indexing, there is a **huge** amount of documents being
indexed. By using sharding, you can speed up the ability of Elasticsearch to locate
data because each shard is a Lucene index.
If you are not using sharding, you are likely to hit issues when you start using
Elasticsearch in a production environment.
An index with only one shard has **no scale factor** and is likely
to encounter issues when called upon with some frequency. See the
[Elasticsearch documentation on capacity planning](https://www.elastic.co/guide/en/elasticsearch/guide/2.x/capacity-planning.html).
The easiest way to determine if sharding is in use is to check the output of the
[Elasticsearch Health API](https://www.elastic.co/guide/en/elasticsearch/reference/current/cluster-health.html):
- Red means the cluster is down.
- Yellow means it is up with no sharding/replication.
- Green means it is healthy (up, sharding, replicating).
For production use, it should always be green.
Beyond these steps, you get into some of the more complicated things to check,
such as merges and caching. These can get complicated and it takes some time to
learn them, so it is best to escalate/pair with an Elasticsearch expert if you need to
dig further into these.
Feel free to reach out to GitLab support, but this is likely to be something a skilled
Elasticsearch administrator has more experience with.
### Slow initial indexing
The more data your GitLab instance has, the longer the indexing takes.
You can estimate cluster size with the Rake task `sudo gitlab-rake gitlab:elastic:estimate_cluster_size`.
#### For code documents
Ensure you have enough Sidekiq nodes and processes to efficiently index code, commits, and wikis.
If your initial indexing is slow, consider [dedicated Sidekiq nodes or processes](../../integration/advanced_search/elasticsearch.md#index-large-instances-with-dedicated-sidekiq-nodes-or-processes).
#### For non-code documents
If the initial indexing is slow but Sidekiq has enough nodes and processes,
you can adjust advanced search worker settings in GitLab.
For **Requeue indexing workers**, the default value is `false`.
For **Number of shards for non-code indexing**, the default value is `2`.
These settings limit indexing to 2000 documents per minute.
To adjust worker settings:
1. On the left sidebar, at the bottom, select **Admin**.
1. Select **Settings > Search**.
1. Expand **Advanced Search**.
1. Select the **Requeue indexing workers** checkbox.
1. In the **Number of shards for non-code indexing** text box, enter a value higher than `2`.
1. Select **Save changes**.
## Issues with migrations
Ensure you've read about [Elasticsearch Migrations](../advanced_search/elasticsearch.md#advanced-search-migrations).
@ -423,7 +154,7 @@ unexpectedly high `buff/cache` usage.
## Error: `Couldn't load task status`
When you reindex, you might get a `Couldn't load task status` error. A `sliceId must be greater than 0 but was [-1]` error might also appear on the Elasticsearch host. As a workaround, consider [reindexing from scratch](../../integration/advanced_search/elasticsearch_troubleshooting.md#last-resort-to-recreate-an-index) or upgrading to GitLab 16.3.
When you reindex, you might get a `Couldn't load task status` error. A `sliceId must be greater than 0 but was [-1]` error might also appear on the Elasticsearch host. As a workaround, consider [reindexing from scratch](../elasticsearch/troubleshooting/indexing.md#last-resort-to-recreate-an-index) or upgrading to GitLab 16.3.
For more information, see [issue 422938](https://gitlab.com/gitlab-org/gitlab/-/issues/422938).

View File

@ -127,7 +127,7 @@ To verify that your GitLab instance is using Elasticsearch:
::Gitlab::CurrentSettings.search_using_elasticsearch?(scope: Project.find_by_full_path("/my-namespace/my-project"))
```
## User: anonymous is not authorized to perform: es:ESHttpGet
## Error: `User: anonymous is not authorized to perform: es:ESHttpGet`
When using a domain level access policy with AWS OpenSearch or Elasticsearch, the AWS role is not assigned to the
correct GitLab nodes. The GitLab Rails and Sidekiq nodes require permission to communicate with the search cluster.
@ -139,11 +139,11 @@ action
To fix this, ensure the AWS role is assigned to the correct GitLab nodes.
## `Credential should be scoped to a valid region`
## No valid region specified
When using AWS authorization with Advanced search, the region must be valid.
When using AWS authorization with advanced search, the region you specify must be valid.
## No permissions for `[indices:data/write/bulk]`
## Error: `no permissions for [indices:data/write/bulk]`
When using fine-grained access control with an IAM role or a role created using AWS OpenSearch Dashboards, you might
encounter the following error:

View File

@ -0,0 +1,259 @@
---
stage: Foundations
group: Global Search
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
---
# Troubleshooting Elasticsearch indexing
DETAILS:
**Tier:** Premium, Ultimate
**Offering:** Self-managed, GitLab Dedicated
When working with Elasticsearch indexing, you might encounter the following issues.
## Create an empty index
For indexing issues, try first to create an empty index.
Check the Elasticsearch instance to see if the `gitlab-production` index exists.
If it does, manually delete the index on the Elasticsearch instance and try to recreate it from the
[`recreate_index`](../../advanced_search/elasticsearch.md#gitlab-advanced-search-rake-tasks)
Rake task.
If you still encounter issues, try to create an index manually on the Elasticsearch instance.
If you:
- Cannot create indices, contact your Elasticsearch administrator.
- Can create indices, contact GitLab Support.
## Check the status of indexed projects
You can check for errors during project indexing.
Errors might occur on:
- The GitLab instance: if you cannot fix them yourself, contact GitLab Support for guidance.
- The Elasticsearch instance: [if the error is not listed](../../advanced_search/elasticsearch_troubleshooting.md), contact your Elasticsearch administrator.
If indexing does not return errors, check the status of indexed projects with the following Rake tasks:
- [`sudo gitlab-rake gitlab:elastic:index_projects_status`](../../advanced_search/elasticsearch.md#gitlab-advanced-search-rake-tasks)
for the overall status
- [`sudo gitlab-rake gitlab:elastic:projects_not_indexed`](../../advanced_search/elasticsearch.md#gitlab-advanced-search-rake-tasks)
for specific projects that are not indexed
If indexing is:
- Complete, contact GitLab Support.
- Not complete, try to reindex that project by running
`sudo gitlab-rake gitlab:elastic:index_projects ID_FROM=<project ID> ID_TO=<project ID>`.
If reindexing the project shows errors on:
- The GitLab instance: contact GitLab Support.
- The Elasticsearch instance or no errors at all: contact your Elasticsearch administrator to check the instance.
## No search results after updating GitLab
We continuously make updates to our indexing strategies and aim to support
newer versions of Elasticsearch. When indexing changes are made, you might
have to [reindex](../../advanced_search/elasticsearch.md#zero-downtime-reindexing) after updating GitLab.
## No search results after indexing all repositories
Make sure you [indexed all the database data](../../advanced_search/elasticsearch.md#enable-advanced-search).
If there aren't any results (hits) in the UI search, check if you are seeing the same results via the rails console (`sudo gitlab-rails console`):
```ruby
u = User.find_by_username('your-username')
s = SearchService.new(u, {:search => 'search_term', :scope => 'blobs'})
pp s.search_objects.to_a
```
Beyond that, check via the [Elasticsearch Search API](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-search.html) to see if the data shows up on the Elasticsearch side:
```shell
curl --request GET <elasticsearch_server_ip>:9200/gitlab-production/_search?q=<search_term>
```
More [complex Elasticsearch API calls](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-filter-context.html) are also possible.
If the results:
- Sync up, check that you are using [supported syntax](../../../user/search/advanced_search.md#syntax). Advanced search does not support [exact substring matching](https://gitlab.com/gitlab-org/gitlab/-/issues/325234).
- Do not match up, this indicates a problem with the documents generated from the project. It is best to [re-index that project](../../advanced_search/elasticsearch.md#indexing-a-range-of-projects-or-a-specific-project).
NOTE:
The above instructions are not to be used for scenarios that only index a [subset of namespaces](../../advanced_search/elasticsearch.md#limit-the-amount-of-namespace-and-project-data-to-index).
See [Elasticsearch Index Scopes](../../advanced_search/elasticsearch.md#advanced-search-index-scopes) for more information on searching for specific types of data.
## No search results after switching Elasticsearch servers
To reindex the database, repositories, and wikis, run all Rake tasks again.
## Indexing fails with `error: elastic: Error 429 (Too Many Requests)`
If `ElasticCommitIndexerWorker` Sidekiq workers are failing with this error during indexing, it usually means that Elasticsearch is unable to keep up with the concurrency of indexing request. To address change the following settings:
- To decrease the indexing throughput you can decrease `Bulk request concurrency` (see [Advanced search settings](../../advanced_search/elasticsearch.md#advanced-search-configuration)). This is set to `10` by default, but you change it to as low as 1 to reduce the number of concurrent indexing operations.
- If changing `Bulk request concurrency` didn't help, you can use the [routing rules](../../../administration/sidekiq/processing_specific_job_classes.md#routing-rules) option to [limit indexing jobs only to specific Sidekiq nodes](../../advanced_search/elasticsearch.md#index-large-instances-with-dedicated-sidekiq-nodes-or-processes), which should reduce the number of indexing requests.
## Indexing is very slow or fails with `rejected execution of coordinating operation`
Bulk requests getting rejected by the Elasticsearch nodes are likely due to load and lack of available memory.
Ensure that your Elasticsearch cluster meets the [system requirements](../../advanced_search/elasticsearch.md#system-requirements) and has enough resources
to perform bulk operations. See also the error ["429 (Too Many Requests)"](#indexing-fails-with-error-elastic-error-429-too-many-requests).
## Indexing fails with `strict_dynamic_mapping_exception`
Indexing might fail if all [advanced search migrations were not finished before doing a major upgrade](../../advanced_search/elasticsearch.md#all-migrations-must-be-finished-before-doing-a-major-upgrade).
A large Sidekiq backlog might accompany this error. To fix the indexing failures, you must re-index the database, repositories, and wikis.
1. Pause indexing so Sidekiq can catch up:
```shell
sudo gitlab-rake gitlab:elastic:pause_indexing
```
1. [Recreate the index from scratch](#last-resort-to-recreate-an-index).
1. Resume indexing:
```shell
sudo gitlab-rake gitlab:elastic:resume_indexing
```
## Indexing keeps pausing with `elasticsearch_pause_indexing setting is enabled`
You might notice that new data is not being detected when you run a search.
This error occurs when that new data is not being indexed properly.
To resolve this error, [reindex your data](../../advanced_search/elasticsearch.md#zero-downtime-reindexing).
However, when reindexing, you might get an error where the indexing process keeps pausing, and the Elasticsearch logs show the following:
```shell
"message":"elasticsearch_pause_indexing setting is enabled. Job was added to the waiting queue"
```
If reindexing does not resolve this issue, and you did not pause the indexing process manually, this error might be happening because two GitLab instances share one Elasticsearch cluster.
To resolve this error, disconnect one of the GitLab instances from using the Elasticsearch cluster.
For more information, see [issue 3421](https://gitlab.com/gitlab-org/gitlab/-/issues/3421).
## Last resort to recreate an index
There may be cases where somehow data never got indexed and it's not in the
queue, or the index is somehow in a state where migrations just cannot
proceed. It is always best to try to troubleshoot the root cause of the problem
by [viewing the logs](access.md#view-logs).
As a last resort, you can recreate the index from scratch. For small GitLab installations,
recreating the index can be a quick way to resolve some issues. For large GitLab
installations, however, this method might take a very long time. Your index
does not show correct search results until the indexing is complete. You might
want to clear the **Search with Elasticsearch enabled** checkbox
while the indexing is running.
If you are sure you've read the above caveats and want to proceed, then you
should run the following Rake task to recreate the entire index from scratch.
::Tabs
:::TabTitle Linux package (Omnibus)
```shell
# WARNING: DO NOT RUN THIS UNTIL YOU READ THE DESCRIPTION ABOVE
sudo gitlab-rake gitlab:elastic:index
```
:::TabTitle Self-compiled (source)
```shell
# WARNING: DO NOT RUN THIS UNTIL YOU READ THE DESCRIPTION ABOVE
cd /home/git/gitlab
sudo -u git -H bundle exec rake gitlab:elastic:index
```
::EndTabs
## Improve Elasticsearch performance
To improve performance, ensure:
- The Elasticsearch server **is not** running on the same node as GitLab.
- The Elasticsearch server have enough RAM and CPU cores.
- That sharding **is** being used.
Going into some more detail here, if Elasticsearch is running on the same server as GitLab, resource contention is **very** likely to occur. Ideally, Elasticsearch, which requires ample resources, should be running on its own server (maybe coupled with Logstash and Kibana).
When it comes to Elasticsearch, RAM is the key resource. Elasticsearch themselves recommend:
- **At least** 8 GB of RAM for a non-production instance.
- **At least** 16 GB of RAM for a production instance.
- Ideally, 64 GB of RAM.
For CPU, Elasticsearch recommends at least 2 CPU cores, but Elasticsearch states common
setups use up to 8 cores. For more details on server specs, check out the
[Elasticsearch hardware guide](https://www.elastic.co/guide/en/elasticsearch/guide/current/hardware.html).
Beyond the obvious, sharding comes into play. Sharding is a core part of Elasticsearch.
It allows for horizontal scaling of indices, which is helpful when you are dealing with
a large amount of data.
With the way GitLab does indexing, there is a **huge** amount of documents being
indexed. By using sharding, you can speed up the ability of Elasticsearch to locate
data because each shard is a Lucene index.
If you are not using sharding, you are likely to hit issues when you start using
Elasticsearch in a production environment.
An index with only one shard has **no scale factor** and is likely
to encounter issues when called upon with some frequency. See the
[Elasticsearch documentation on capacity planning](https://www.elastic.co/guide/en/elasticsearch/guide/2.x/capacity-planning.html).
The easiest way to determine if sharding is in use is to check the output of the
[Elasticsearch Health API](https://www.elastic.co/guide/en/elasticsearch/reference/current/cluster-health.html):
- Red means the cluster is down.
- Yellow means it is up with no sharding/replication.
- Green means it is healthy (up, sharding, replicating).
For production use, it should always be green.
Beyond these steps, you get into some of the more complicated things to check,
such as merges and caching. These can get complicated and it takes some time to
learn them, so it is best to escalate/pair with an Elasticsearch expert if you need to
dig further into these.
Reach out to GitLab Support, but this is likely to be something a skilled
Elasticsearch administrator has more experience with.
## Slow initial indexing
The more data your GitLab instance has, the longer the indexing takes.
You can estimate cluster size with the Rake task `sudo gitlab-rake gitlab:elastic:estimate_cluster_size`.
### For code documents
Ensure you have enough Sidekiq nodes and processes to efficiently index code, commits, and wikis.
If your initial indexing is slow, consider [dedicated Sidekiq nodes or processes](../../advanced_search/elasticsearch.md#index-large-instances-with-dedicated-sidekiq-nodes-or-processes).
### For non-code documents
If the initial indexing is slow but Sidekiq has enough nodes and processes,
you can adjust advanced search worker settings in GitLab.
For **Requeue indexing workers**, the default value is `false`.
For **Number of shards for non-code indexing**, the default value is `2`.
These settings limit indexing to 2000 documents per minute.
To adjust worker settings:
1. On the left sidebar, at the bottom, select **Admin**.
1. Select **Settings > Search**.
1. Expand **Advanced Search**.
1. Select the **Requeue indexing workers** checkbox.
1. In the **Number of shards for non-code indexing** text box, enter a value higher than `2`.
1. Select **Save changes**.

View File

@ -189,7 +189,7 @@ For other advanced search migrations stuck in pending, see [how to retry a halte
If you upgrade GitLab before all pending advanced search migrations are completed, any pending migrations
that have been removed in the new version cannot be executed or retried.
In this case, you must
[re-create your index from scratch](../integration/advanced_search/elasticsearch_troubleshooting.md#last-resort-to-recreate-an-index).
[re-create your index from scratch](../integration/elasticsearch/troubleshooting/indexing.md#last-resort-to-recreate-an-index).
## What do you do for the error `Elasticsearch version not compatible`

View File

@ -108,7 +108,7 @@ if you can't upgrade to 15.11.12 and later.
- This issue may not manifest immediately as it can take up to a week before the Sidekiq is saturated enough.
- Elasticsearch does not need to be enabled for this to occur.
- To resolve this issue, upgrade to 15.11 or use the workaround in the issue.
- A [bug with zero-downtime reindexing](https://gitlab.com/gitlab-org/gitlab/-/issues/422938) can cause a `Couldn't load task status` error when you reindex. You might also get a `sliceId must be greater than 0 but was [-1]` error on the Elasticsearch host. As a workaround, consider [reindexing from scratch](../../integration/advanced_search/elasticsearch_troubleshooting.md#last-resort-to-recreate-an-index) or upgrading to GitLab 16.3.
- A [bug with zero-downtime reindexing](https://gitlab.com/gitlab-org/gitlab/-/issues/422938) can cause a `Couldn't load task status` error when you reindex. You might also get a `sliceId must be greater than 0 but was [-1]` error on the Elasticsearch host. As a workaround, consider [reindexing from scratch](../../integration/elasticsearch/troubleshooting/indexing.md#last-resort-to-recreate-an-index) or upgrading to GitLab 16.3.
- Gitaly configuration changes significantly in Omnibus GitLab 16.0. You can begin migrating to the new structure in Omnibus GitLab 15.10 while backwards compatibility is
maintained in the lead up to Omnibus GitLab 16.0. [Read more about this change](gitlab_16_changes.md#gitaly-configuration-structure-change).
- You might encounter the following error while upgrading to GitLab 15.10 or later:

View File

@ -103,12 +103,25 @@ GitLab sends an email notification with the recalculated repository size after t
Use this method to permanently delete files containing sensitive or confidential information from your repository.
WARNING:
**This action cannot be undone.**
After the history is rewritten and housekeeping is run, the changes are permanent.
When removing files from your repository using this method, be aware of the following impacts.
When you remove blobs:
- Open merge requests might fail to merge and require manual rebasing.
- Existing local clones become incompatible with the updated repository and must be re-cloned.
- Pipelines referencing old commit SHAs might break and require reconfiguration.
- Historical tags and branches based on the old commit history might no longer work correctly.
- Commit signatures are dropped during the rewrite process.
NOTE:
To replace strings with `***REMOVED***`, see [Redact information](../../../topics/git/undo.md#redact-information).
Prerequisites:
- Owner role for the project
- You must have the Owner role for the project
- [A list of object IDs](#get-a-list-of-object-ids) to remove.
To remove blobs from your repository:

View File

@ -47,8 +47,14 @@ module Gitlab
@diffs ||= compare.diffs(max_files: 30, max_lines: 5000, expanded: true).diff_files
end
def changed_files
return unless compare
@changed_files ||= compare.changed_paths
end
def diffs_count
diffs&.size
changed_files&.size
end
def compare

View File

@ -19,6 +19,14 @@ module Gitlab
status == :ADDED
end
def deleted_file?
status == :DELETED
end
def renamed_file?
status == :RENAMED
end
def submodule_change?
# The file mode 160000 represents a "Gitlink" or a git submodule.
# The first two digits can be used to distinguish it from regular files.

View File

@ -16,6 +16,8 @@ module Gitlab
rescue Timeout::Error => e
Gitlab::ErrorTracking.log_exception(e)
raise ParseError, 'timeout while parsing TOML'
rescue TomlRB::Error => e
raise ParseError, "error parsing TOML: #{e.message}"
end
end
end

View File

@ -42992,18 +42992,30 @@ msgstr ""
msgid "ProjectMaintenance|Enter multiple entries on separate lines."
msgstr ""
msgid "ProjectMaintenance|Enter the following to confirm:"
msgid "ProjectMaintenance|Go to housekeeping"
msgstr ""
msgid "ProjectMaintenance|Go to housekeeping"
msgid "ProjectMaintenance|Historical tags and branches based on the old commit history might no longer work correctly."
msgstr ""
msgid "ProjectMaintenance|How do I get a list of object IDs?"
msgstr ""
msgid "ProjectMaintenance|How does blobs removal work?"
msgstr ""
msgid "ProjectMaintenance|Local copies become incompatible with the updated repository and must be re-cloned."
msgstr ""
msgid "ProjectMaintenance|Manage repository storage and cleanup."
msgstr ""
msgid "ProjectMaintenance|Open merge requests might fail to merge and require manual rebasing."
msgstr ""
msgid "ProjectMaintenance|Pipelines referencing old commit SHAs might break and require reconfiguration."
msgstr ""
msgid "ProjectMaintenance|Provide a list of blob object IDs to be removed."
msgstr ""
@ -43031,9 +43043,6 @@ msgstr ""
msgid "ProjectMaintenance|Remove blobs"
msgstr ""
msgid "ProjectMaintenance|Removing blobs by ID cannot be undone. Are you sure you want to continue?"
msgstr ""
msgid "ProjectMaintenance|Something went wrong while redacting text."
msgstr ""
@ -43058,6 +43067,9 @@ msgstr ""
msgid "ProjectMaintenance|Yes, remove blobs"
msgstr ""
msgid "ProjectMaintenance|You are about to permanently remove blobs from this project."
msgstr ""
msgid "ProjectMaintenance|You will receive an email notification when the process is complete. Run housekeeping to remove old versions from repository."
msgstr ""

View File

@ -216,21 +216,22 @@ RSpec.describe 'Pipeline Schedules', :js, feature_category: :continuous_integrat
visit_pipelines_schedules
click_link 'New schedule'
fill_in_schedule_form
all('[name="schedule[variables_attributes][][key]"]')[0].set('AAA')
all('[name="schedule[variables_attributes][][secret_value]"]')[0].set('AAA123')
all('[name="schedule[variables_attributes][][key]"]')[1].set('BBB')
all('[name="schedule[variables_attributes][][secret_value]"]')[1].set('BBB123')
all('[data-testid="pipeline-form-ci-variable-key"]')[0].set('AAA')
all('[data-testid="pipeline-form-ci-variable-value"]')[0].set('AAA123')
all('[data-testid="pipeline-form-ci-variable-key"]')[1].set('BBB')
all('[data-testid="pipeline-form-ci-variable-value"]')[1].set('BBB123')
create_pipeline_schedule
end
it 'user sees the new variable in edit window', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/397040' do
find(".content-list .pipeline-schedule-table-row:nth-child(1) .btn-group a[title='Edit']").click
page.within('.ci-variable-list') do
expect(find(".ci-variable-row:nth-child(1) .js-ci-variable-input-key").value).to eq('AAA')
expect(find(".ci-variable-row:nth-child(1) .js-ci-variable-input-value", visible: false).value).to eq('AAA123')
expect(find(".ci-variable-row:nth-child(2) .js-ci-variable-input-key").value).to eq('BBB')
expect(find(".ci-variable-row:nth-child(2) .js-ci-variable-input-value", visible: false).value).to eq('BBB123')
end
it 'user sees the new variable in edit window' do
find("body [data-testid='pipeline-schedule-table-row']:nth-child(1) .btn-group a[title='Edit scheduled pipeline']")
.click
expected_keys = [
all("[data-testid='pipeline-form-ci-variable-key']")[0].value,
all("[data-testid='pipeline-form-ci-variable-key']")[1].value
]
expect(expected_keys).to include('AAA', 'BBB')
end
end
@ -315,7 +316,7 @@ RSpec.describe 'Pipeline Schedules', :js, feature_category: :continuous_integrat
it 'shows Pipelines Schedules page' do
visit_pipelines_schedules
expect(page).to have_link('New schedule')
expect(page).to have_selector(:css, '[data-testid="new-schedule-button"]')
end
context 'when public pipelines are disabled' do

View File

@ -30,7 +30,7 @@ describe('Signup Form', () => {
const findForm = () => wrapper.findByTestId('form');
const findInputCsrf = () => findForm().find('[name="authenticity_token"]');
const findUserCapAutoApprovalInput = () =>
findForm().find('[name="application_setting[pending_user_auto_approval]"]');
findForm().find('[name="application_setting[auto_approve_pending_users]"]');
const findFormSubmitButton = () => findForm().findComponent(GlButton);
const findDenyListRawRadio = () => queryByLabelText('Enter denylist manually');

View File

@ -1,6 +1,6 @@
import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
import { GlDrawer, GlFormTextarea, GlModal, GlFormInput } from '@gitlab/ui';
import { GlDrawer, GlFormTextarea } from '@gitlab/ui';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
@ -9,6 +9,7 @@ import { useMockInternalEventsTracking } from 'helpers/tracking_internal_events_
import { DRAWER_Z_INDEX } from '~/lib/utils/constants';
import { createAlert, VARIANT_WARNING } from '~/alert';
import RemoveBlobs from '~/projects/settings/repository/maintenance/remove_blobs.vue';
import WarningModal from '~/projects/settings/repository/maintenance/warning_modal.vue';
import removeBlobsMutation from '~/projects/settings/repository/maintenance/graphql/mutations/remove_blobs.mutation.graphql';
import {
TEST_HEADER_HEIGHT,
@ -45,8 +46,7 @@ describe('Remove blobs', () => {
const findDrawerTrigger = () => wrapper.findByTestId('drawer-trigger');
const findDrawer = () => wrapper.findComponent(GlDrawer);
const findModal = () => wrapper.findComponent(GlModal);
const findModalInput = () => findModal().findComponent(GlFormInput);
const findWarningModal = () => wrapper.findComponent(WarningModal);
const removeBlobsButton = () => wrapper.findByTestId('remove-blobs');
const findTextarea = () => wrapper.findComponent(GlFormTextarea);
@ -70,19 +70,13 @@ describe('Remove blobs', () => {
});
it('renders a modal, closed by default', () => {
expect(findModal().props()).toMatchObject({
expect(findWarningModal().props()).toMatchObject({
visible: false,
title: 'Remove blobs',
modalId: 'remove-blobs-confirmation-modal',
actionCancel: { text: 'Cancel' },
actionPrimary: { text: 'Yes, remove blobs' },
title: 'You are about to permanently remove blobs from this project.',
primaryText: 'Yes, remove blobs',
confirmPhrase: 'project/path',
confirmLoading: false,
});
expect(findModal().text()).toContain(
'Removing blobs by ID cannot be undone. Are you sure you want to continue?',
);
expect(findModal().text()).toContain('Enter the following to confirm: project/path');
});
});
@ -119,13 +113,12 @@ describe('Remove blobs', () => {
beforeEach(() => removeBlobsButton().vm.$emit('click'));
it('renders the confirmation modal when remove blobs button is clicked', () => {
expect(findModal().props('visible')).toBe(true);
expect(findWarningModal().props('visible')).toBe(true);
});
describe('removal confirmed (success)', () => {
beforeEach(() => {
findModalInput().vm.$emit('input', TEST_PROJECT_PATH);
findModal().vm.$emit('primary');
findWarningModal().vm.$emit('confirm');
});
it('disables user input while loading', () => {
@ -156,10 +149,10 @@ describe('Remove blobs', () => {
});
it('clears the input on the modal when the hide event is emitted', async () => {
findModal().vm.$emit('hide');
findWarningModal().vm.$emit('hide');
await nextTick();
expect(findModalInput().attributes('value')).toBe(undefined);
expect(findWarningModal().props('visible')).toBe(false);
});
it('generates a housekeeping alert', async () => {
@ -183,7 +176,7 @@ describe('Remove blobs', () => {
findDrawerTrigger().vm.$emit('click');
findTextarea().vm.$emit('input', TEST_BLOB_ID);
removeBlobsButton().vm.$emit('click');
findModal().vm.$emit('primary');
findWarningModal().vm.$emit('confirm');
await waitForPromises();
});

View File

@ -0,0 +1,99 @@
import { GlFormInput, GlModal, GlAlert } from '@gitlab/ui';
import { nextTick } from 'vue';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import WarningModal from '~/projects/settings/repository/maintenance/warning_modal.vue';
import { stubComponent } from 'helpers/stub_component';
jest.mock('lodash/uniqueId', () => () => 'fake-id');
describe('WarningModal', () => {
let wrapper;
const defaultPropsData = {
visible: false,
confirmPhrase: 'confirm/phrase',
title: 'some title',
primaryText: 'Yes, remove blobs',
confirmLoading: false,
};
const createComponent = (propsData) => {
wrapper = mountExtended(WarningModal, {
propsData: { ...defaultPropsData, ...propsData },
stubs: { GlModal: stubComponent(GlModal) },
scopedSlots: { default: '<p>Custom warning message</p>' },
});
};
const findModal = () => wrapper.findComponent(GlModal);
const findAlert = () => wrapper.findComponent(GlAlert);
const findFormInput = () => wrapper.findComponent(GlFormInput);
beforeEach(() => createComponent());
it('renders modal with correct props', () => {
expect(findModal().props()).toMatchObject({
visible: false,
noFocusOnShow: true,
modalId: 'fake-id',
actionPrimary: {
text: 'Yes, remove blobs',
attributes: { variant: 'danger', disabled: true },
},
actionCancel: { text: 'Cancel' },
});
});
describe('modal content', () => {
it('displays correct title', () => {
expect(findModal().text()).toContain('some title');
});
it('displays a confirm phrase', () => {
expect(findModal().text()).toContain('Enter the following to confirm:');
expect(findModal().text()).toContain('confirm/phrase');
});
});
describe('when correct confirm phrase is used', () => {
beforeEach(() => findFormInput().vm.$emit('input', defaultPropsData.confirmPhrase));
it('enables the primary action', () => {
expect(findModal().props('actionPrimary').attributes.disabled).toBe(false);
});
});
describe('when incorrect confirm phrase is used', () => {
beforeEach(() => findFormInput().vm.$emit('input', 'bar'));
it('keeps the primary action disabled', () => {
expect(findModal().props('actionPrimary').attributes.disabled).toBe(true);
});
});
it('emits `confirm` event when primary button is emitted', () => {
findModal().vm.$emit('primary', { preventDefault: jest.fn() });
expect(wrapper.emitted('confirm')).toEqual([[]]);
});
describe('modal visibility handling', () => {
it('resets userInput when modal is shown', async () => {
findFormInput().vm.$emit('input', defaultPropsData.confirmPhrase);
await nextTick();
expect(findModal().props('actionPrimary').attributes.disabled).toBe(false);
findModal().vm.$emit('show');
await nextTick();
expect(findModal().props('actionPrimary').attributes.disabled).toBe(true);
});
});
describe('slot content', () => {
it('renders slot content in alert', () => {
expect(findAlert().text()).toContain('Custom warning message');
});
});
});

View File

@ -7,14 +7,14 @@ exports[`Repository last commit component renders commit widget 1`] = `
commit="[object Object]"
>
<div
class="commit-actions gl-flex gl-items-start"
class="commit-actions gl-flex gl-gap-3 gl-items-center"
>
<div
class="gl-flex gl-h-7 gl-items-center gl-ml-5"
>
<ci-icon-stub
aria-label="Pipeline: failed"
class="js-commit-pipeline"
class="gl-mr-2 js-commit-pipeline"
showtooltip="true"
status="[object Object]"
uselink="true"
@ -26,7 +26,7 @@ exports[`Repository last commit component renders commit widget 1`] = `
<gl-button-stub
buttontextclasses=""
category="primary"
class="gl-font-monospace"
class="dark:!gl-bg-strong gl-font-monospace"
data-testid="last-commit-id-label"
icon=""
label="true"
@ -37,7 +37,7 @@ exports[`Repository last commit component renders commit widget 1`] = `
</gl-button-stub>
<clipboard-button-stub
category="secondary"
class="input-group-text"
class="dark:!gl-border-l-section input-group-text"
size="medium"
text="123456789"
title="Copy commit SHA"
@ -47,8 +47,8 @@ exports[`Repository last commit component renders commit widget 1`] = `
</gl-button-group-stub>
<gl-button-stub
buttontextclasses=""
category="tertiary"
class="gl-ml-4"
category="secondary"
class="!gl-ml-0"
data-testid="last-commit-history"
href="/history"
icon=""
@ -60,7 +60,7 @@ exports[`Repository last commit component renders commit widget 1`] = `
</div>
</commit-info-stub>
<collapsible-commit-info-stub
class="gl-block sm:gl-hidden"
class="!gl-border-t-0 gl-block sm:gl-hidden"
commit="[object Object]"
historyurl="/history"
/>

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Gitlab::Email::Message::RepositoryPush do
RSpec.describe Gitlab::Email::Message::RepositoryPush, feature_category: :source_code_management do
include RepoHelpers
let!(:group) { create(:group, name: 'my_group') }
@ -74,6 +74,12 @@ RSpec.describe Gitlab::Email::Message::RepositoryPush do
it { is_expected.to all(be_an_instance_of(Gitlab::Diff::File)) }
end
describe '#changed_files' do
subject { message.changed_files }
it { is_expected.to all(be_an_instance_of(Gitlab::Git::ChangedPath)) }
end
describe '#diffs_count' do
subject { message.diffs_count }

View File

@ -2,7 +2,7 @@
require 'fast_spec_helper'
RSpec.describe Gitlab::Git::ChangedPath do
RSpec.describe Gitlab::Git::ChangedPath, feature_category: :source_code_management do
subject(:changed_path) do
described_class.new(
path: path,
@ -39,6 +39,30 @@ RSpec.describe Gitlab::Git::ChangedPath do
end
end
describe '#deleted_file?' do
subject(:deleted_file?) { changed_path.deleted_file? }
it { is_expected.to be_falsey }
context 'when it is a deleted file' do
let(:status) { :DELETED }
it { is_expected.to be_truthy }
end
end
describe '#renamed_file?' do
subject(:renamed_file?) { changed_path.renamed_file? }
it { is_expected.to be_falsey }
context 'when it is a renamed file' do
let(:status) { :RENAMED }
it { is_expected.to be_truthy }
end
end
describe '#submodule_change?' do
subject(:submodule_change?) { changed_path.submodule_change? }

View File

@ -37,5 +37,34 @@ RSpec.describe Gitlab::Utils::TomlParser, feature_category: :source_code_managem
expect { result }.to raise_error(Gitlab::Utils::TomlParser::ParseError, 'timeout while parsing TOML')
end
end
context 'with error raised by TomlRB' do
context 'with TomlRB::ValueOverwriteError' do
let(:content) do
<<~TOML
rust.unused_must_use = "deny"
rust.rust_2018_idioms = { level = "deny", priority = -1 }
TOML
end
it 'raises a ParserError with the error message' do
error_message = 'error parsing TOML: Key "rust" is defined more than once'
expect { result }.to raise_error(Gitlab::Utils::TomlParser::ParseError, error_message)
end
end
context 'with unexpected TomlRB errors' do
let(:future_error) { Class.new(TomlRB::Error) }
before do
allow(TomlRB).to receive(:parse).and_raise(future_error.new("Unexpected error"))
end
it 'raises a ParserError with the error message' do
error_message = 'error parsing TOML: Unexpected error'
expect { result }.to raise_error(Gitlab::Utils::TomlParser::ParseError, error_message)
end
end
end
end
end

View File

@ -119,6 +119,38 @@ RSpec.describe Compare, feature_category: :source_code_management do
end
end
describe '#changed_paths' do
subject(:changed_paths) { compare.changed_paths }
context 'changes are present' do
let(:raw_compare) do
Gitlab::Git::Compare.new(
project.repository.raw_repository, 'before-create-delete-modify-move', 'after-create-delete-modify-move'
)
end
it 'returns affected file paths' do
is_expected.to all(be_a(Gitlab::Git::ChangedPath))
expect(changed_paths.map { |a| [a.old_path, a.path, a.status] }).to match_array(
[
['foo/for_move.txt', 'foo/bar/for_move.txt', :RENAMED],
['foo/for_create.txt', 'foo/for_create.txt', :ADDED],
['foo/for_delete.txt', 'foo/for_delete.txt', :DELETED],
['foo/for_edit.txt', 'foo/for_edit.txt', :MODIFIED]
]
)
end
end
context 'changes are absent' do
let(:start_commit) { sample_commit }
let(:head_commit) { sample_commit }
it { is_expected.to eq([]) }
end
end
describe '#modified_paths' do
context 'changes are present' do
let(:raw_compare) do

View File

@ -524,7 +524,7 @@ RSpec.describe ApplicationSettings::UpdateService, feature_category: :shared do
let(:params) { { require_admin_approval_after_user_signup: false } }
describe 'when auto approval is enabled' do
let(:params) { { require_admin_approval_after_user_signup: false, pending_user_auto_approval: 'true' } }
let(:params) { { require_admin_approval_after_user_signup: false, auto_approve_pending_users: 'true' } }
it 'calls ApproveBlockedPendingApprovalUsersWorker' do
expect(ApproveBlockedPendingApprovalUsersWorker).to receive(:perform_async)

View File

@ -84,8 +84,10 @@ RSpec.describe Packages::Nuget::CheckDuplicatesService, feature_category: :packa
allow(instance).to receive(:execute)
.and_return(ServiceResponse.success(payload: Nokogiri::XML::Document.new))
end
allow_next_instance_of(::Packages::Nuget::ExtractMetadataContentService) do |instance|
allow(instance).to receive(:execute).and_return(ServiceResponse.success(payload: metadata))
allow(::Packages::Nuget::ExtractMetadataContentService).to receive(:new) do
instance_double(
::Packages::Nuget::ExtractMetadataContentService, execute: ServiceResponse.success(payload: metadata)
)
end
end

View File

@ -20,11 +20,9 @@ RSpec.describe Packages::Nuget::ExtractMetadataContentService, feature_category:
dependencies = subject[:package_dependencies]
expect(dependencies).to include(name: 'Moqi', version: '2.5.6')
expect(dependencies).to include(name: 'Castle.Core')
expect(dependencies).to include(name: 'Test.Dependency', version: '2.3.7',
target_framework: '.NETStandard2.0')
expect(dependencies).to include(name: 'Newtonsoft.Json', version: '12.0.3',
target_framework: '.NETStandard2.0')
.and include(name: 'Castle.Core')
.and include(name: 'Test.Dependency', version: '2.3.7', target_framework: '.NETStandard2.0')
.and include(name: 'Newtonsoft.Json', version: '12.0.3', target_framework: '.NETStandard2.0')
end
end

View File

@ -34,7 +34,7 @@ RSpec.shared_context 'ProjectPolicy context' do
]
end
let(:base_planner_permissions) do
let(:planner_permissions) do
base_guest_permissions +
%i[
admin_issue admin_issue_board admin_issue_board_list admin_label admin_milestone
@ -118,13 +118,11 @@ RSpec.shared_context 'ProjectPolicy context' do
# Used in EE specs
let(:additional_guest_permissions) { [] }
let(:additional_planner_permissions) { [] }
let(:additional_reporter_permissions) { [] }
let(:additional_maintainer_permissions) { [] }
let(:additional_owner_permissions) { [] }
let(:guest_permissions) { base_guest_permissions + additional_guest_permissions }
let(:planner_permissions) { base_planner_permissions + additional_planner_permissions }
let(:reporter_permissions) { base_reporter_permissions + additional_reporter_permissions }
let(:maintainer_permissions) { base_maintainer_permissions + additional_maintainer_permissions }
let(:owner_permissions) { base_owner_permissions + additional_owner_permissions }

View File

@ -5,7 +5,11 @@ require 'spec_helper'
RSpec.describe 'notify/repository_push_email.text.haml', feature_category: :source_code_management do
let(:message) do
instance_double(
Gitlab::Email::Message::RepositoryPush, compare: double, commits: [commit], diffs: []
Gitlab::Email::Message::RepositoryPush,
compare: double,
commits: [commit],
changed_files: changed_files,
diffs: []
).as_null_object
end
@ -15,15 +19,31 @@ RSpec.describe 'notify/repository_push_email.text.haml', feature_category: :sour
).as_null_object
end
let(:commit_message) { "special char'acters" }
let(:commit_message) { 'message' }
let(:changed_files) do
[
Gitlab::Git::ChangedPath.new(status: :DELETED, path: 'a.txt', old_mode: '100644', new_mode: '100644'),
Gitlab::Git::ChangedPath.new(status: :DELETED, path: 'b.txt', old_mode: '100644', new_mode: '100644')
]
end
before do
assign(:message, message)
end
it 'does not escape special characters for plain text emails' do
it 'renders changed files' do
render
expect(rendered).to have_content(commit_message)
expect(rendered).to have_content('a.txt').and have_content('b.txt')
end
context 'when commit message includes special characters' do
let(:commit_message) { "special char'acters" }
it 'does not escape special characters for plain text emails' do
render
expect(rendered).to have_content(commit_message)
end
end
end