Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
403730bab4
commit
d2167e96a0
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -229,8 +229,7 @@ const initTreeHistoryLinkApp = (el) => {
|
|||
{
|
||||
attrs: {
|
||||
href: url.href,
|
||||
category: 'tertiary',
|
||||
class: 'gl-ml-4',
|
||||
class: '!gl-ml-3',
|
||||
},
|
||||
},
|
||||
[__('History')],
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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|
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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?
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
−
|
||||
|
|
@ -38,13 +38,13 @@
|
|||
- elsif diff_file.renamed_file?
|
||||
= diff_file.old_path
|
||||
→
|
||||
= diff_file.new_path
|
||||
= diff_file.path
|
||||
- elsif diff_file.new_file?
|
||||
%span.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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
\
|
||||
|
|
|
|||
|
|
@ -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 } }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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. |
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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).
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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**.
|
||||
|
|
@ -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`
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 ""
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -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"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
||||
|
|
|
|||
|
|
@ -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? }
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue