Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-05-09 21:07:53 +00:00
parent b031a57ae7
commit b558e1ad8f
76 changed files with 875 additions and 445 deletions

View File

@ -156,7 +156,7 @@ export default class ShortcutsIssuable extends Shortcuts {
static copyBranchName() {
// There are two buttons - one that is shown when the sidebar
// is expanded, and one that is shown when it's collapsed.
const allCopyBtns = Array.from(document.querySelectorAll('.js-sidebar-source-branch button'));
const allCopyBtns = Array.from(document.querySelectorAll('.js-source-branch-copy'));
// Select whichever button is currently visible so that
// the "Copied" tooltip is shown when a click is simulated.

View File

@ -3,6 +3,7 @@ import { GlIcon, GlTooltipDirective } from '@gitlab/ui';
import { mapGetters } from 'vuex';
import { __ } from '~/locale';
import { IssuableType, WorkspaceType } from '~/issues/constants';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import ConfidentialityBadge from '~/vue_shared/components/confidentiality_badge.vue';
export default {
@ -15,6 +16,7 @@ export default {
directives: {
GlTooltip: GlTooltipDirective,
},
mixins: [glFeatureFlagMixin()],
inject: ['hidden'],
computed: {
...mapGetters(['getNoteableData']),
@ -24,6 +26,9 @@ export default {
isConfidential() {
return this.getNoteableData.confidential;
},
isMergeRequest() {
return this.getNoteableData.targetType === 'merge_request' && this.glFeatures.updatedMrHeader;
},
warningIconsMeta() {
return [
{
@ -58,7 +63,8 @@ export default {
v-gl-tooltip
:data-testid="meta.dataTestId"
:title="meta.tooltip || null"
class="issuable-warning-icon inline"
:class="{ 'gl-mr-3 gl-mt-2': isMergeRequest }"
class="issuable-warning-icon gl-display-flex gl-justify-content-center gl-align-items-center"
>
<gl-icon :name="meta.iconName" class="icon" />
</div>

View File

@ -1,6 +1,7 @@
<script>
import { GlIcon } from '@gitlab/ui';
import Vue from 'vue';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { fetchPolicies } from '~/lib/graphql';
import { __ } from '~/locale';
@ -11,9 +12,12 @@ export const statusBoxState = Vue.observable({
const CLASSES = {
opened: 'status-box-open',
merge_request_opened: 'badge-success',
locked: 'status-box-open',
merge_request_locked: 'badge-success',
closed: 'status-box-mr-closed',
merged: 'status-box-mr-merged',
merge_request_closed: 'badge-danger',
merged: 'badge-info',
};
const STATUS = {
@ -27,6 +31,7 @@ export default {
components: {
GlIcon,
},
mixins: [glFeatureFlagMixin()],
inject: {
query: { default: null },
projectPath: { default: null },
@ -52,8 +57,17 @@ export default {
return statusBoxState;
},
computed: {
isMergeRequest() {
return this.issuableType === 'merge_request' && this.glFeatures.updatedMrHeader;
},
statusBoxClass() {
return CLASSES[`${this.issuableType}_${this.state}`] || CLASSES[this.state];
return [
CLASSES[`${this.issuableType}_${this.state}`] || CLASSES[this.state],
{
'badge badge-pill gl-badge gl-mr-3': this.isMergeRequest,
'issuable-status-box status-box': !this.isMergeRequest,
},
];
},
statusHumanName() {
return (STATUS[`${this.issuableType}_${this.state}`] || STATUS[this.state])[0];
@ -90,9 +104,13 @@ export default {
</script>
<template>
<div :class="statusBoxClass" class="issuable-status-box status-box">
<gl-icon :name="statusIconName" class="gl-display-block gl-sm-display-none!" />
<span class="gl-display-none gl-sm-display-block">
<div :class="statusBoxClass">
<gl-icon
v-if="!isMergeRequest"
:name="statusIconName"
class="gl-display-block gl-sm-display-none!"
/>
<span :class="{ 'gl-display-none gl-sm-display-block': !isMergeRequest }">
{{ statusHumanName }}
</span>
</div>

View File

@ -32,8 +32,16 @@ function MergeRequest(opts) {
selector: '.detail-page-description',
lockVersion: this.$el.data('lockVersion'),
onSuccess: (result) => {
document.querySelector('#task_status').innerText = result.task_status;
document.querySelector('#task_status_short').innerText = result.task_status_short;
const taskStatus = document.querySelector('#task_status');
const taskStatusShort = document.querySelector('#task_status_short');
if (taskStatus) {
taskStatus.innerText = result.task_status;
}
if (taskStatusShort) {
document.querySelector('#task_status_short').innerText = result.task_status_short;
}
},
onError: () => {
createFlash({
@ -148,7 +156,11 @@ MergeRequest.toggleDraftStatus = function (title, isReady) {
} else {
toast(__('Marked as draft. Can only be merged when marked as ready.'));
}
const titleEl = document.querySelector('.merge-request .detail-page-description .title');
const titleEl = document.querySelector(
`.merge-request .detail-page-${
window.gon?.features?.updatedMrHeader ? 'header' : 'description'
} .title`,
);
if (titleEl) {
titleEl.textContent = title;

View File

@ -36,6 +36,7 @@ export default function initMergeRequestShow() {
return h(StatusBox, {
props: {
initialState: el.dataset.state,
issuableType: 'merge_request',
},
});
},

View File

@ -49,7 +49,7 @@ export default {
]"
class="gl-rounded-full gl-mr-3 gl-relative gl-p-2"
>
<gl-loading-icon v-if="isLoading" size="md" inline class="gl-display-block" />
<gl-loading-icon v-if="isLoading" size="lg" inline class="gl-display-block" />
<gl-icon
v-else
:name="$options.EXTENSION_ICON_NAMES[iconName]"

View File

@ -1,5 +1,5 @@
<script>
import { GlSafeHtmlDirective as SafeHtml, GlLink } from '@gitlab/ui';
import { GlSafeHtmlDirective as SafeHtml, GlLink, GlSprintf } from '@gitlab/ui';
import { s__, n__ } from '~/locale';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
@ -10,6 +10,7 @@ export default {
},
components: {
GlLink,
GlSprintf,
},
mixins: [glFeatureFlagMixin()],
props: {
@ -28,6 +29,16 @@ export default {
required: false,
default: true,
},
divergedCommitsCount: {
type: Number,
required: false,
default: 0,
},
targetBranchPath: {
type: String,
required: false,
default: '',
},
},
computed: {
closesText() {
@ -81,5 +92,19 @@ export default {
}}</gl-link>
</span>
</p>
<div
v-if="
divergedCommitsCount > 0 && glFeatures.updatedMrHeader && !glFeatures.restructuredMrWidget
"
class="diverged-commits-count"
>
<gl-sprintf :message="s__('mrWidget|The source branch is %{link} the target branch')">
<template #link>
<gl-link :href="targetBranchPath">{{
n__('%d commit behind', '%d commits behind', divergedCommitsCount)
}}</gl-link>
</template>
</gl-sprintf>
</div>
</section>
</template>

View File

@ -716,6 +716,20 @@ export default {
{{ __('Merge details') }}
</strong>
<ul class="gl-pl-4 gl-m-0">
<li
v-if="mr.divergedCommitsCount > 0 && glFeatures.updatedMrHeader"
class="gl-line-height-normal"
>
<gl-sprintf
:message="s__('mrWidget|The source branch is %{link} the target branch')"
>
<template #link>
<gl-link :href="mr.targetBranchPath">{{
n__('%d commit behind', '%d commits behind', mr.divergedCommitsCount)
}}</gl-link>
</template>
</gl-sprintf>
</li>
<li class="gl-line-height-normal">
<added-commit-message
:state="mr.state"
@ -756,6 +770,8 @@ export default {
:state="mr.state"
:related-links="mr.relatedLinks"
:show-assign-to-me="false"
:diverged-commits-count="mr.divergedCommitsCount"
:target-branch-path="mr.targetBranchPath"
class="mr-ready-merge-related-links gl-display-inline"
/>
</template>
@ -769,6 +785,22 @@ export default {
>
{{ __('The latest pipeline for this merge request did not complete successfully.') }}
</div>
<div
v-if="
mr.divergedCommitsCount > 0 &&
glFeatures.updatedMrHeader &&
!glFeatures.restructuredMrWidget
"
class="diverged-commits-count gl-mt-4"
>
<gl-sprintf :message="s__('mrWidget|The source branch is %{link} the target branch')">
<template #link>
<gl-link :href="mr.targetBranchPath">{{
n__('%d commit behind', '%d commits behind', mr.divergedCommitsCount)
}}</gl-link>
</template>
</gl-sprintf>
</div>
</div>
</div>
<template v-if="shouldShowMergeControls && !glFeatures.restructuredMrWidget">

View File

@ -165,7 +165,12 @@ export default {
return this.mr?.codequalityReportsPath;
},
shouldRenderRelatedLinks() {
return Boolean(this.mr.relatedLinks) && !this.mr.isNothingToMergeState;
const showDivergedCounts =
this.mr.divergedCommitsCount > 0 && this.mr.state !== 'readyToMerge';
return (
(Boolean(this.mr.relatedLinks) || showDivergedCounts) && !this.mr.isNothingToMergeState
);
},
shouldRenderSourceBranchRemovalStatus() {
return (
@ -231,6 +236,9 @@ export default {
isRestructuredMrWidgetEnabled() {
return window.gon?.features?.restructuredMrWidget;
},
isUpdatedHeaderEnabled() {
return window.gon?.features?.updatedMrHeader;
},
},
watch: {
'mr.machineValue': {
@ -524,11 +532,15 @@ export default {
</script>
<template>
<div v-if="isLoaded" class="mr-state-widget gl-mt-3">
<header class="gl-rounded-base gl-border-solid gl-border-1 gl-border-gray-100">
<header
v-if="shouldRenderCollaborationStatus || !isUpdatedHeaderEnabled"
:class="{ 'mr-widget-workflow gl-mt-0!': isUpdatedHeaderEnabled }"
class="gl-rounded-base gl-border-solid gl-border-1 gl-border-gray-100"
>
<mr-widget-alert-message v-if="shouldRenderCollaborationStatus" type="info">
{{ s__('mrWidget|Members who can merge are allowed to add commits.') }}
</mr-widget-alert-message>
<mr-widget-header :mr="mr" />
<mr-widget-header v-if="!isUpdatedHeaderEnabled" :mr="mr" />
</header>
<mr-widget-suggest-pipeline
v-if="shouldSuggestPipelines"
@ -620,6 +632,8 @@ export default {
v-if="shouldRenderRelatedLinks"
:state="mr.state"
:related-links="mr.relatedLinks"
:diverged-commits-count="mr.divergedCommitsCount"
:target-branch-path="mr.targetBranchPath"
class="mr-info-list gl-ml-7 gl-pb-5"
/>

View File

@ -140,7 +140,7 @@ export default {
<gl-dropdown-form class="gl-relative gl-min-h-7" data-qa-selector="labels_dropdown_content">
<gl-loading-icon
v-if="isLoading"
size="md"
size="lg"
class="gl-absolute gl-left-0 gl-top-0 gl-right-0"
/>
<template v-else>

View File

@ -175,7 +175,7 @@ export default {
:debounce="300"
/>
<div data-testid="content" class="dropdown-content">
<gl-loading-icon v-if="projectsListLoading" size="md" class="gl-p-5" />
<gl-loading-icon v-if="projectsListLoading" size="lg" class="gl-p-5" />
<ul v-else>
<gl-dropdown-item
v-for="project in projects"

View File

@ -185,7 +185,7 @@ export default {
<gl-loading-icon
v-if="labelsFetchInProgress"
class="labels-fetch-loading gl-align-items-center w-100 h-100"
size="md"
size="lg"
/>
<ul v-else class="list-unstyled gl-mb-0 gl-word-break-word">
<label-item

View File

@ -147,7 +147,7 @@ export default {
<gl-loading-icon
v-if="labelsFetchInProgress"
class="labels-fetch-loading gl-align-items-center gl-w-full gl-h-full gl-mb-3"
size="md"
size="lg"
/>
<template v-else>
<gl-dropdown-item

View File

@ -298,7 +298,7 @@ export default {
<gl-loading-icon
v-if="isLoading"
data-testid="loading-participants"
size="md"
size="lg"
class="gl-absolute gl-left-0 gl-top-0 gl-right-0"
/>
<template v-else>

View File

@ -192,7 +192,7 @@ export default {
<div>
<gl-loading-icon
v-if="$apollo.queries.workItemTypes.loading"
size="md"
size="lg"
data-testid="loading-types"
/>
<gl-form-select

View File

@ -666,12 +666,12 @@ $tabs-holder-z-index: 250;
margin-top: $gl-padding;
position: relative;
&::before {
&:not(:last-child)::before {
content: '';
border-left: 1px solid var(--gray-100, $gray-100);
position: absolute;
left: 28px;
top: -17px;
bottom: -17px;
height: 16px;
}
}

View File

@ -8,7 +8,8 @@
a {
color: $gl-text-color;
&.link {
&.link,
&.gl-link {
color: $blue-600;
}
}
@ -38,9 +39,18 @@
align-self: center;
flex: 0 0 auto;
@include media-breakpoint-down(xs) {
width: 100%;
margin-top: 10px;
&:not(.is-merge-request) {
@include media-breakpoint-down(xs) {
width: 100%;
margin-top: 10px;
}
}
&.is-merge-request {
@include media-breakpoint-down(sm) {
width: 100%;
margin-top: 10px;
}
}
}
@ -56,4 +66,8 @@
.description {
margin-top: 6px;
}
.author-link {
color: $gl-text-color;
}
}

View File

@ -45,6 +45,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
push_frontend_feature_flag(:realtime_labels, project)
push_frontend_feature_flag(:updated_diff_expansion_buttons, project)
push_frontend_feature_flag(:mr_attention_requests, current_user)
push_frontend_feature_flag(:updated_mr_header, project)
end
before_action do

View File

@ -28,16 +28,18 @@ module IssuesHelper
end
def status_box_class(item)
updated_mr_header_enabled = Feature.enabled?(:updated_mr_header, @project)
if item.try(:expired?)
'status-box-expired'
elsif item.try(:merged?)
'status-box-mr-merged'
updated_mr_header_enabled ? 'badge-info' : 'status-box-mr-merged'
elsif item.closed?
'status-box-mr-closed'
item.is_a?(MergeRequest) && updated_mr_header_enabled ? 'badge-danger' : 'status-box-mr-closed'
elsif item.try(:upcoming?)
'status-box-upcoming'
else
'status-box-open'
item.is_a?(MergeRequest) && updated_mr_header_enabled ? 'badge-success' : 'status-box-open'
end
end

View File

@ -232,6 +232,30 @@ module MergeRequestsHelper
def default_suggestion_commit_message
@project.suggestion_commit_message.presence || Gitlab::Suggestions::CommitMessage::DEFAULT_SUGGESTION_COMMIT_MESSAGE
end
def merge_request_source_branch(merge_request)
branch = if merge_request.for_fork?
"#{merge_request.source_project_path}:#{merge_request.source_branch}"
else
merge_request.source_branch
end
branch_path = if merge_request.source_project
project_tree_path(merge_request.source_project, merge_request.source_branch)
else
''
end
link_to branch, branch_path, class: 'gl-link gl-font-monospace gl-bg-blue-50 gl-rounded-base gl-font-sm gl-p-2'
end
def merge_request_header(project, merge_request)
link_to_author = link_to_member(project, merge_request.author, size: 24, extra_class: 'gl-font-weight-bold', avatar: false)
copy_button = clipboard_button(text: merge_request.source_branch, title: _('Copy branch name'), class: 'btn btn-default btn-sm gl-button btn-default-tertiary btn-icon gl-display-none! gl-md-display-inline-block! js-source-branch-copy')
target_branch = link_to merge_request.target_branch, project_tree_path(merge_request.target_project, merge_request.target_branch), class: 'gl-link gl-font-monospace gl-bg-blue-50 gl-rounded-base gl-font-sm gl-p-2'
_('%{author} requested to merge %{span_start}%{source_branch} %{copy_button}%{span_end} into %{target_branch} %{created_at}').html_safe % { author: link_to_author.html_safe, source_branch: merge_request_source_branch(merge_request).html_safe, copy_button: copy_button.html_safe, target_branch: target_branch.html_safe, created_at: time_ago_with_tooltip(merge_request.created_at, html_class: 'gl-display-inline-block').html_safe, span_start: '<span class="gl-display-inline-block">'.html_safe, span_end: '</span>'.html_safe }
end
end
MergeRequestsHelper.prepend_mod_with('MergeRequestsHelper')

View File

@ -4,4 +4,4 @@
= password_field_tag 'user[password]', nil, class: 'form-control', autocomplete: 'current-password', required: true, title: _('This field is required.'), data: { qa_selector: 'password_field' }
.submit-container.move-submit-down
= submit_tag _('Enter Admin Mode'), class: 'gl-button btn btn-success', data: { qa_selector: 'enter_admin_mode_button' }
= submit_tag _('Enter Admin Mode'), class: 'gl-button btn btn-confirm', data: { qa_selector: 'enter_admin_mode_button' }

View File

@ -14,6 +14,6 @@
= render_if_exists 'devise/sessions/new_smartcard'
- if allow_admin_mode_password_authentication_for_web?
.login-box.tab-pane{ id: 'login-pane', role: 'tabpanel', class: active_when(!any_form_based_providers_enabled?) }
.login-box.tab-pane.gl-p-5{ id: 'login-pane', role: 'tabpanel', class: active_when(!any_form_based_providers_enabled?) }
.login-body
= render 'admin/sessions/new_base'

View File

@ -6,4 +6,4 @@
= _("Enter the code from the two-factor app on your mobile device. If you've lost your device, you may enter one of your recovery codes.")
.submit-container.move-submit-down
= submit_tag 'Verify code', class: 'gl-button btn btn-success'
= submit_tag 'Verify code', class: 'gl-button btn btn-confirm'

View File

@ -8,7 +8,7 @@
- if any_form_based_providers_enabled?
= render 'devise/shared/tabs_ldap', show_password_form: allow_admin_mode_password_authentication_for_web?, render_signup_link: false
- else
= render 'devise/shared/tabs_normal', tab_title: _('Enter Admin Mode'), render_signup_link: false
= render 'devise/shared/tab_single', tab_title: page_title
.tab-content
- if allow_admin_mode_password_authentication_for_web? || ldap_sign_in_enabled? || crowd_enabled?
= render 'admin/sessions/signin_box'

View File

@ -5,9 +5,9 @@
.col-md-5.new-session-forms-container
.login-page
#signin-container
= render 'devise/shared/tabs_normal', tab_title: _('Enter Admin Mode'), render_signup_link: false
= render 'devise/shared/tab_single', tab_title: _('Enter Admin Mode')
.tab-content
.login-box.tab-pane.active{ id: 'login-pane', role: 'tabpanel' }
.login-box.tab-pane.gl-p-5.active{ id: 'login-pane', role: 'tabpanel' }
.login-body
- if current_user.two_factor_otp_enabled?
= render 'admin/sessions/two_factor_otp'

View File

@ -1,2 +1,2 @@
= gl_tabs_nav({ class: 'new-session-tabs gl-border-0' }) do
= gl_tab_link_to tab_title, '#', { item_active: true, class: 'gl-cursor-default!', tab_class: 'gl-bg-transparent!', tabindex: '-1' }
= gl_tab_link_to tab_title, '#', { item_active: true, class: 'gl-cursor-default!', tab_class: 'gl-bg-transparent!', tabindex: '-1', data: { qa_selector: 'sign_in_tab' } }

View File

@ -1,9 +0,0 @@
- tab_title = local_assigns.fetch(:tab_title, _('Sign in'))
- render_signup_link = local_assigns.fetch(:render_signup_link, true)
%ul.nav-links.new-session-tabs.nav-tabs.nav{ role: 'tablist' }
%li.nav-item{ role: 'presentation' }
%a.nav-link.active{ href: '#login-pane', data: { toggle: 'tab', qa_selector: 'sign_in_tab' }, role: 'tab' }= tab_title
- if render_signup_link && allow_signup?
%li.nav-item{ role: 'presentation' }
%a.nav-link{ href: '#register-pane', data: { track_label: 'sign_in_register', track_property: '', track_action: 'click_button', track_value: '', toggle: 'tab', qa_selector: 'register_tab' }, role: 'tab' } Register

View File

@ -2,6 +2,6 @@
- add_to_breadcrumbs s_('FeatureFlags|Feature Flags'), project_feature_flags_path(@project)
- breadcrumb_title @feature_flag.name
- page_title s_('FeatureFlags|Edit Feature Flag')
- page_title s_('FeatureFlags|Edit Feature Flag'), @feature_flag.name
#js-edit-feature-flag{ data: edit_feature_flag_data }

View File

@ -1,3 +1,9 @@
.detail-page-description.py-2
%h2.title.mb-0{ data: { qa_selector: 'title_content' } }
= markdown_field(@merge_request, :title)
- if Feature.enabled?(:updated_mr_header, @project)
- state_human_name, _ = state_name_with_icon(@merge_request)
.badge.badge-pill.gl-badge.gl-mr-3.js-mr-status-box{ class: status_box_class(@merge_request), data: { project_path: @merge_request.project.path_with_namespace, iid: @merge_request.iid, state: @merge_request.state } }>
= state_human_name
= merge_request_header(@project, @merge_request)
- else
%h2.title.mb-0{ data: { qa_selector: 'title_content' } }
= markdown_field(@merge_request, :title)

View File

@ -3,6 +3,7 @@
- can_reopen_merge_request = can?(current_user, :reopen_merge_request, @merge_request)
- are_close_and_open_buttons_hidden = merge_request_button_hidden?(@merge_request, true) && merge_request_button_hidden?(@merge_request, false)
- cache_key = [@project, @merge_request, can_update_merge_request, can_reopen_merge_request, are_close_and_open_buttons_hidden, current_user&.preferred_language]
- moved_sidebar_enabled = Feature.enabled?(:updated_mr_header, @project)
= cache(cache_key, expires_in: 1.day) do
- if @merge_request.closed_or_merged_without_fork?
@ -12,18 +13,24 @@
= c.body do
= _('The source project of this merge request has been removed.')
.detail-page-header.border-bottom-0.pt-0.pb-0
.detail-page-header.border-bottom-0.pt-0.pb-0{ class: "#{'gl-display-block gl-md-display-flex!' if moved_sidebar_enabled}" }
.detail-page-header-body
= render "shared/issuable/status_box", issuable: @merge_request
- unless moved_sidebar_enabled
= render "shared/issuable/status_box", issuable: @merge_request
.issuable-meta{ class: "#{'gl-display-flex' if moved_sidebar_enabled}" }
- if moved_sidebar_enabled
#js-issuable-header-warnings
%h2.title.gl-my-0.gl-display-inline-block{ data: { qa_selector: 'title_content' } }
= markdown_field(@merge_request, :title)
- else
#js-issuable-header-warnings
= issuable_meta(@merge_request, @project)
.issuable-meta
#js-issuable-header-warnings
= issuable_meta(@merge_request, @project)
%div
%button.gl-button.btn.btn-default.btn-icon.float-right.d-block.d-sm-none.gutter-toggle.issuable-gutter-toggle.js-sidebar-toggle{ type: 'button' }
= sprite_icon('chevron-double-lg-left')
%a.gl-button.btn.btn-default.btn-icon.float-right.d-block.d-sm-none.gutter-toggle.issuable-gutter-toggle.js-sidebar-toggle{ href: "#" }
= sprite_icon('chevron-double-lg-left')
.detail-page-header-actions.js-issuable-actions
.detail-page-header-actions.js-issuable-actions{ class: "#{'gl-align-self-start is-merge-request' if moved_sidebar_enabled}" }
- if @merge_request.source_project
= render 'projects/merge_requests/code_dropdown'

View File

@ -3,7 +3,7 @@
- group_deploy_tokens_help_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: group_deploy_tokens_help_link_url }
= s_('DeployTokens|Create a new deploy token for all projects in this group. %{link_start}What are deploy tokens?%{link_end}').html_safe % { link_start: group_deploy_tokens_help_link_start, link_end: '</a>'.html_safe }
= form_for token, url: create_deploy_token_path(group_or_project, anchor: 'js-deploy-tokens'), method: :post, remote: Feature.enabled?(:ajax_new_deploy_token, group_or_project) do |f|
= gitlab_ui_form_for token, url: create_deploy_token_path(group_or_project, anchor: 'js-deploy-tokens'), method: :post, remote: Feature.enabled?(:ajax_new_deploy_token, group_or_project) do |f|
.form-group
= f.label :name, class: 'label-bold'
@ -23,33 +23,15 @@
.form-group
= f.label :scopes, _('Scopes (select at least one)'), class: 'label-bold'
%fieldset.form-group.form-check
= f.check_box :read_repository, class: 'form-check-input', data: { qa_selector: 'deploy_token_read_repository_checkbox' }
= f.label :read_repository, 'read_repository', class: 'label-bold form-check-label'
.text-secondary
= s_('DeployTokens|Allows read-only access to the repository.')
= f.gitlab_ui_checkbox_component :read_repository, 'read_repository', help_text: s_('DeployTokens|Allows read-only access to the repository.'), checkbox_options: { data: { qa_selector: 'deploy_token_read_repository_checkbox' } }
- if container_registry_enabled?(group_or_project)
%fieldset.form-group.form-check
= f.check_box :read_registry, class: 'form-check-input', data: { qa_selector: 'deploy_token_read_registry_checkbox' }
= f.label :read_registry, 'read_registry', class: 'label-bold form-check-label'
.text-secondary= s_('DeployTokens|Allows read-only access to registry images.')
%fieldset.form-group.form-check
= f.check_box :write_registry, class: 'form-check-input', data: { qa_selector: 'deploy_token_write_registry_checkbox' }
= f.label :write_registry, 'write_registry', class: 'label-bold form-check-label'
.text-secondary= s_('DeployTokens|Allows write access to registry images.')
= f.gitlab_ui_checkbox_component :read_registry, 'read_registry', help_text: s_('DeployTokens|Allows read-only access to registry images.'), checkbox_options: { data: { qa_selector: 'deploy_token_read_registry_checkbox' } }
= f.gitlab_ui_checkbox_component :write_registry, 'write_registry', help_text: s_('DeployTokens|Allows write access to registry images.'), checkbox_options: { data: { qa_selector: 'deploy_token_write_registry_checkbox' } }
- if packages_registry_enabled?(group_or_project)
%fieldset.form-group.form-check
= f.check_box :read_package_registry, class: 'form-check-input', data: { qa_selector: 'deploy_token_read_package_registry_checkbox' }
= f.label :read_package_registry, 'read_package_registry', class: 'label-bold form-check-label'
.text-secondary= s_('DeployTokens|Allows read-only access to the package registry.')
%fieldset.form-group.form-check
= f.check_box :write_package_registry, class: 'form-check-input', data: { qa_selector: 'deploy_token_write_package_registry_checkbox' }
= f.label :write_package_registry, 'write_package_registry', class: 'label-bold form-check-label'
.text-secondary= s_('DeployTokens|Allows read and write access to the package registry.')
= f.gitlab_ui_checkbox_component :read_package_registry, 'read_package_registry', help_text: s_('DeployTokens|Allows read-only access to the package registry.'), checkbox_options: { data: { qa_selector: 'deploy_token_read_package_registry_checkbox' } }
= f.gitlab_ui_checkbox_component :write_package_registry, 'write_package_registry', help_text: s_('DeployTokens|Allows read and write access to the package registry.'), checkbox_options: { data: { qa_selector: 'deploy_token_write_package_registry_checkbox' } }
.gl-mt-3
= f.submit s_('DeployTokens|Create deploy token'), class: 'btn gl-button btn-confirm', data: { qa_selector: 'create_deploy_token_button' }

View File

@ -86,7 +86,7 @@
- if issuable_type == 'merge_request'
.sub-block.js-sidebar-source-branch
.sidebar-collapsed-icon.js-dont-change-state
= clipboard_button(text: source_branch, title: _('Copy branch name'), placement: "left", boundary: 'viewport')
= clipboard_button(text: source_branch, title: _('Copy branch name'), placement: "left", boundary: 'viewport', class: 'btn-clipboard gl-button btn-default-tertiary btn-icon btn-sm js-source-branch-copy')
.gl-display-flex.gl-align-items-center.gl-justify-content-space-between.gl-mb-2.hide-collapsed
%span.gl-overflow-hidden.gl-text-overflow-ellipsis.gl-white-space-nowrap
= _('Source branch: %{source_branch_open}%{source_branch}%{source_branch_close}').html_safe % { source_branch_open: "<span class='gl-font-monospace' data-testid='ref-name' title='#{html_escape(source_branch)}'>".html_safe, source_branch_close: "</span>".html_safe, source_branch: html_escape(source_branch) }

View File

@ -0,0 +1,8 @@
---
name: updated_mr_header
introduced_by_url:
rollout_issue_url:
milestone: '14.10'
type: development
group: group::code review
default_enabled: false

View File

@ -288,7 +288,6 @@ production: &base
# object_store:
# enabled: false
# remote_directory: artifacts # The bucket name
# background_upload: false # Temporary option to limit automatic upload (Default: true)
# proxy_download: false # Passthrough all downloads via GitLab instead of using Redirects to Object Storage
# connection:
# provider: AWS # Only AWS supported at the moment
@ -308,7 +307,6 @@ production: &base
# object_store:
# enabled: false
# remote_directory: external-diffs
# background_upload: false
# proxy_download: false
# connection:
# provider: AWS
@ -324,8 +322,6 @@ production: &base
object_store:
enabled: false
remote_directory: lfs-objects # Bucket name
# direct_upload: false # Use Object Storage directly for uploads instead of background uploads if enabled (Default: false)
# background_upload: false # Temporary option to limit automatic upload (Default: true)
# proxy_download: false # Passthrough all downloads via GitLab instead of using Redirects to Object Storage
connection:
provider: AWS
@ -346,8 +342,6 @@ production: &base
object_store:
enabled: false
remote_directory: uploads # Bucket name
# direct_upload: false # Use Object Storage directly for uploads instead of background uploads if enabled (Default: false)
# background_upload: false # Temporary option to limit automatic upload (Default: true)
# proxy_download: false # Passthrough all downloads via GitLab instead of using Redirects to Object Storage
connection:
provider: AWS
@ -368,8 +362,6 @@ production: &base
object_store:
enabled: false
remote_directory: packages # The bucket name
# direct_upload: false # Use Object Storage directly for uploads instead of background uploads if enabled (Default: false)
# background_upload: false # Temporary option to limit automatic upload (Default: true)
# proxy_download: false # Passthrough all downloads via GitLab instead of using Redirects to Object Storage
connection:
provider: AWS
@ -389,8 +381,6 @@ production: &base
object_store:
enabled: false
remote_directory: dependency_proxy # The bucket name
# direct_upload: false # Use Object Storage directly for uploads instead of background uploads if enabled (Default: false)
# background_upload: false # Temporary option to limit automatic upload (Default: true)
# proxy_download: false # Passthrough all downloads via GitLab instead of using Redirects to Object Storage
connection:
provider: AWS
@ -1419,7 +1409,6 @@ test:
object_store:
enabled: false
remote_directory: artifacts # The bucket name
background_upload: false
connection:
provider: AWS # Only AWS supported at the moment
aws_access_key_id: AWS_ACCESS_KEY_ID

View File

@ -252,7 +252,7 @@ Settings.gitlab_ci['url'] ||= Settings.__send__(:build_gitlab_ci
Settings['ci_secure_files'] ||= Settingslogic.new({})
Settings.ci_secure_files['enabled'] = true if Settings.ci_secure_files['enabled'].nil?
Settings.ci_secure_files['storage_path'] = Settings.absolute(Settings.ci_secure_files['storage_path'] || File.join(Settings.shared['path'], "ci_secure_files"))
Settings.ci_secure_files['object_store'] = ObjectStoreSettings.legacy_parse(Settings.ci_secure_files['object_store'])
Settings.ci_secure_files['object_store'] = ObjectStoreSettings.legacy_parse(Settings.ci_secure_files['object_store'], 'secure_files')
#
# Reply by email
@ -276,7 +276,7 @@ Settings.artifacts['storage_path'] = Settings.absolute(Settings.artifacts.values
# Settings.artifact['path'] is deprecated, use `storage_path` instead
Settings.artifacts['path'] = Settings.artifacts['storage_path']
Settings.artifacts['max_size'] ||= 100 # in megabytes
Settings.artifacts['object_store'] = ObjectStoreSettings.legacy_parse(Settings.artifacts['object_store'])
Settings.artifacts['object_store'] = ObjectStoreSettings.legacy_parse(Settings.artifacts['object_store'], 'artifacts')
#
# Registry
@ -321,7 +321,7 @@ Settings.pages['secret_file'] ||= Rails.root.join('.gitlab_pages_secret')
# We want pages zip archives to be stored on the same directory as old pages hierarchical structure
# this will allow us to easier migrate existing instances with NFS
Settings.pages['storage_path'] = Settings.pages['path']
Settings.pages['object_store'] = ObjectStoreSettings.legacy_parse(Settings.pages['object_store'])
Settings.pages['object_store'] = ObjectStoreSettings.legacy_parse(Settings.pages['object_store'], 'pages')
Settings.pages['local_store'] ||= Settingslogic.new({})
Settings.pages['local_store']['path'] = Settings.absolute(Settings.pages['local_store']['path'] || File.join(Settings.shared['path'], "pages"))
Settings.pages['local_store']['enabled'] = true if Settings.pages['local_store']['enabled'].nil?
@ -362,7 +362,7 @@ Settings['external_diffs'] ||= Settingslogic.new({})
Settings.external_diffs['enabled'] = false if Settings.external_diffs['enabled'].nil?
Settings.external_diffs['when'] = 'always' if Settings.external_diffs['when'].nil?
Settings.external_diffs['storage_path'] = Settings.absolute(Settings.external_diffs['storage_path'] || File.join(Settings.shared['path'], 'external-diffs'))
Settings.external_diffs['object_store'] = ObjectStoreSettings.legacy_parse(Settings.external_diffs['object_store'])
Settings.external_diffs['object_store'] = ObjectStoreSettings.legacy_parse(Settings.external_diffs['object_store'], 'external_diffs')
#
# Git LFS
@ -370,7 +370,7 @@ Settings.external_diffs['object_store'] = ObjectStoreSettings.legacy_parse(Setti
Settings['lfs'] ||= Settingslogic.new({})
Settings.lfs['enabled'] = true if Settings.lfs['enabled'].nil?
Settings.lfs['storage_path'] = Settings.absolute(Settings.lfs['storage_path'] || File.join(Settings.shared['path'], "lfs-objects"))
Settings.lfs['object_store'] = ObjectStoreSettings.legacy_parse(Settings.lfs['object_store'])
Settings.lfs['object_store'] = ObjectStoreSettings.legacy_parse(Settings.lfs['object_store'], 'lfs')
#
# Uploads
@ -378,7 +378,7 @@ Settings.lfs['object_store'] = ObjectStoreSettings.legacy_parse(Settings.lfs['ob
Settings['uploads'] ||= Settingslogic.new({})
Settings.uploads['storage_path'] = Settings.absolute(Settings.uploads['storage_path'] || 'public')
Settings.uploads['base_dir'] = Settings.uploads['base_dir'] || 'uploads/-/system'
Settings.uploads['object_store'] = ObjectStoreSettings.legacy_parse(Settings.uploads['object_store'])
Settings.uploads['object_store'] = ObjectStoreSettings.legacy_parse(Settings.uploads['object_store'], 'uploads')
Settings.uploads['object_store']['remote_directory'] ||= 'uploads'
#
@ -388,7 +388,7 @@ Settings['packages'] ||= Settingslogic.new({})
Settings.packages['enabled'] = true if Settings.packages['enabled'].nil?
Settings.packages['dpkg_deb_path'] = '/usr/bin/dpkg-deb' if Settings.packages['dpkg_deb_path'].nil?
Settings.packages['storage_path'] = Settings.absolute(Settings.packages['storage_path'] || File.join(Settings.shared['path'], "packages"))
Settings.packages['object_store'] = ObjectStoreSettings.legacy_parse(Settings.packages['object_store'])
Settings.packages['object_store'] = ObjectStoreSettings.legacy_parse(Settings.packages['object_store'], 'packages')
#
# Dependency Proxy
@ -396,7 +396,7 @@ Settings.packages['object_store'] = ObjectStoreSettings.legacy_parse(Settings.p
Settings['dependency_proxy'] ||= Settingslogic.new({})
Settings.dependency_proxy['enabled'] = true if Settings.dependency_proxy['enabled'].nil?
Settings.dependency_proxy['storage_path'] = Settings.absolute(Settings.dependency_proxy['storage_path'] || File.join(Settings.shared['path'], "dependency_proxy"))
Settings.dependency_proxy['object_store'] = ObjectStoreSettings.legacy_parse(Settings.dependency_proxy['object_store'])
Settings.dependency_proxy['object_store'] = ObjectStoreSettings.legacy_parse(Settings.dependency_proxy['object_store'], 'dependency_proxy')
# For first iteration dependency proxy uses Rails server to download blobs.
# To ensure acceptable performance we only allow feature to be used with
@ -410,7 +410,7 @@ Settings.dependency_proxy['enabled'] = false unless Gitlab::Runtime.puma?
Settings['terraform_state'] ||= Settingslogic.new({})
Settings.terraform_state['enabled'] = true if Settings.terraform_state['enabled'].nil?
Settings.terraform_state['storage_path'] = Settings.absolute(Settings.terraform_state['storage_path'] || File.join(Settings.shared['path'], "terraform_state"))
Settings.terraform_state['object_store'] = ObjectStoreSettings.legacy_parse(Settings.terraform_state['object_store'])
Settings.terraform_state['object_store'] = ObjectStoreSettings.legacy_parse(Settings.terraform_state['object_store'], 'terraform_state')
#
# Mattermost

View File

@ -16,15 +16,26 @@ class ObjectStoreSettings
# we don't need to raise an error in that case
ALLOWED_INCOMPLETE_TYPES = %w(pages).freeze
# A fallback switch in case anyone gets a trouble with background upload removal
# Epic: https://gitlab.com/groups/gitlab-com/gl-infra/-/epics/734
LEGACY_BACKGROUND_UPLOADS_ENV = "GITLAB_LEGACY_BACKGROUND_UPLOADS"
attr_accessor :settings
# Legacy parser
def self.legacy_parse(object_store)
def self.legacy_parse(object_store, object_store_type)
object_store ||= Settingslogic.new({})
object_store['enabled'] = false if object_store['enabled'].nil?
object_store['remote_directory'] ||= nil
object_store['direct_upload'] = false if object_store['direct_upload'].nil?
object_store['background_upload'] = true if object_store['background_upload'].nil?
if support_legacy_background_upload?(object_store_type)
object_store['direct_upload'] = false
object_store['background_upload'] = true
else
object_store['direct_upload'] = true
object_store['background_upload'] = false
end
object_store['proxy_download'] = false if object_store['proxy_download'].nil?
object_store['storage_options'] ||= {}
@ -33,6 +44,10 @@ class ObjectStoreSettings
object_store
end
def self.support_legacy_background_upload?(object_store_type)
ENV[LEGACY_BACKGROUND_UPLOADS_ENV].to_s.split(',').map(&:strip).include?(object_store_type)
end
def initialize(settings)
@settings = settings
end

View File

@ -0,0 +1,24 @@
- name: "Sidekiq configuration for metrics and health checks"
announcement_milestone: "14.7"
announcement_date: "2021-01-22"
removal_milestone: "15.0"
removal_date: "2022-05-22"
breaking_change: true
body: | # Do not modify this line, instead modify the lines below.
In GitLab 15.0, you can no longer serve Sidekiq metrics and health checks over a single address and port.
To improve stability, availability, and prevent data loss in edge cases, GitLab now serves
[Sidekiq metrics and health checks from two separate servers](https://gitlab.com/groups/gitlab-org/-/epics/6409).
When you use Omnibus or Helm charts, if GitLab is configured for both servers to bind to the same address,
a configuration error occurs.
To prevent this error, choose different ports for the metrics and health check servers:
- [Configure Sidekiq health checks](https://docs.gitlab.com/ee/administration/sidekiq.html#configure-health-checks)
- [Configure the Sidekiq metrics server](https://docs.gitlab.com/ee/administration/sidekiq.html#configure-the-sidekiq-metrics-server)
If you installed GitLab from source, verify manually that both servers are configured to bind to separate addresses and ports.
stage: Enablement
tiers: [Free, Premium, Ultimate]
issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/347509
documentation_url: https://docs.gitlab.com/ee/administration/sidekiq.html

View File

@ -113,8 +113,6 @@ and then `object_store:`. On Omnibus GitLab installs they are prefixed by
|---------------------|---------|-------------|
| `enabled` | `false` | Enable or disable object storage. |
| `remote_directory` | | The bucket name where Artifacts are stored. Use the name only, do not include the path. |
| `direct_upload` | `false` | Set to `true` to enable direct upload of Artifacts without the need of local shared storage. Option may be removed once we decide to support only single storage for all files. |
| `background_upload` | `true` | Set to `false` to disable automatic upload. Option may be removed once upload is direct to S3. |
| `proxy_download` | `false` | Set to `true` to enable proxying all files served. Option allows to reduce egress traffic as this allows clients to download directly from remote storage instead of proxying all data. |
| `connection` | | Various connection options described below. |
@ -549,7 +547,7 @@ Bucket names that include folder paths are not supported with [consolidated obje
For example, `bucket/path`. If a bucket name has a path in it, you might receive an error similar to:
```plaintext
WARNING: Uploading artifacts as "archive" to coordinator... POST https://gitlab.example.com/api/v4/jobs/job_id/artifacts?artifact_format=zip&artifact_type=archive&expire_in=1+day: 500 Internal Server Error (Missing file)
WARNING: Uploading artifacts as "archive" to coordinator... POST https://gitlab.example.com/api/v4/jobs/job_id/artifacts?artifact_format=zip&artifact_type=archive&expire_in=1+day: 500 Internal Server Error (Missing file)
FATAL: invalid argument
```

View File

@ -89,8 +89,6 @@ The following general settings are supported.
|---------------------|-------------|---------|
| `enabled` | Enable/disable object storage. | `false` |
| `remote_directory` | The bucket name where LFS objects are stored. | |
| `direct_upload` | Set to true to enable direct upload of LFS without the need of local shared storage. Option may be removed after we decide to support only single storage for all files. | `false` |
| `background_upload` | Set to false to disable automatic upload. Option may be removed once upload is direct to S3. | `true` |
| `proxy_download` | Set to true to enable proxying all files served. Option allows to reduce egress traffic as this allows clients to download directly from remote storage instead of proxying all data. | `false` |
| `connection` | Various connection options described below. | |

View File

@ -117,8 +117,6 @@ then `object_store:`. On Omnibus installations, they are prefixed by
|---------|-------------|---------|
| `enabled` | Enable/disable object storage | `false` |
| `remote_directory` | The bucket name where external diffs are stored| |
| `direct_upload` | Set to `true` to enable direct upload of external diffs without the need of local shared storage. Option may be removed once we decide to support only single storage for all files. | `false` |
| `background_upload` | Set to `false` to disable automatic upload. Option may be removed once upload is direct to S3 | `true` |
| `proxy_download` | Set to `true` to enable proxying all files served. Option allows to reduce egress traffic as this allows clients to download directly from remote storage instead of proxying all data | `false` |
| `connection` | Various connection options described below | |

View File

@ -140,8 +140,6 @@ This section describes the earlier configuration format.
gitlab_rails['dependency_proxy_storage_path'] = "/var/opt/gitlab/gitlab-rails/shared/dependency_proxy"
gitlab_rails['dependency_proxy_object_store_enabled'] = true
gitlab_rails['dependency_proxy_object_store_remote_directory'] = "dependency_proxy" # The bucket name.
gitlab_rails['dependency_proxy_object_store_direct_upload'] = false # Use Object Storage directly for uploads instead of background uploads if enabled (Default: false).
gitlab_rails['dependency_proxy_object_store_background_upload'] = true # Temporary option to limit automatic upload (Default: true).
gitlab_rails['dependency_proxy_object_store_proxy_download'] = false # Passthrough all downloads via GitLab instead of using Redirects to Object Storage.
gitlab_rails['dependency_proxy_object_store_connection'] = {
##
@ -177,8 +175,6 @@ This section describes the earlier configuration format.
object_store:
enabled: false
remote_directory: dependency_proxy # The bucket name.
# direct_upload: false # Use Object Storage directly for uploads instead of background uploads if enabled (Default: false).
# background_upload: true # Temporary option to limit automatic upload (Default: true).
# proxy_download: false # Passthrough all downloads via GitLab instead of using Redirects to Object Storage.
connection:
##

View File

@ -152,8 +152,6 @@ We recommend using the [consolidated object storage settings](../object_storage.
gitlab_rails['packages_enabled'] = true
gitlab_rails['packages_object_store_enabled'] = true
gitlab_rails['packages_object_store_remote_directory'] = "packages" # The bucket name.
gitlab_rails['packages_object_store_direct_upload'] = false # Use Object Storage directly for uploads instead of background uploads if enabled (Default: false).
gitlab_rails['packages_object_store_background_upload'] = true # Temporary option to limit automatic upload (Default: true).
gitlab_rails['packages_object_store_proxy_download'] = false # Passthrough all downloads via GitLab instead of using Redirects to Object Storage.
gitlab_rails['packages_object_store_connection'] = {
##
@ -192,8 +190,6 @@ We recommend using the [consolidated object storage settings](../object_storage.
object_store:
enabled: false
remote_directory: packages # The bucket name.
# direct_upload: false # Use Object Storage directly for uploads instead of background uploads if enabled (Default: false).
# background_upload: true # Temporary option to limit automatic upload (Default: true).
# proxy_download: false # Passthrough all downloads via GitLab instead of using Redirects to Object Storage.
connection:
##

View File

@ -67,8 +67,6 @@ For source installations the following settings are nested under `uploads:` and
|---------|-------------|---------|
| `enabled` | Enable/disable object storage | `false` |
| `remote_directory` | The bucket name where Uploads will be stored| |
| `direct_upload` | Set to `true` to remove Puma from the Upload path. Workhorse handles the actual Artifact Upload to Object Storage while Puma does minimal processing to keep track of the upload. There is no need for local shared storage. The option may be removed if support for a single storage type for all files is introduced. Read more on [direct upload](../development/uploads/index.md#direct-upload). | `false` |
| `background_upload` | Set to `false` to disable automatic upload. Option may be removed once upload is direct to S3 (if `direct_upload` is set to `true` it will override `background_upload`) | `true` |
| `proxy_download` | Set to `true` to enable proxying all files served. Option allows to reduce egress traffic as this allows clients to download directly from remote storage instead of proxying all data | `false` |
| `connection` | Various connection options described below | |

View File

@ -14,8 +14,6 @@ Some variables are only available with more recent versions of [GitLab Runner](h
You can [output the values of all variables available for a job](index.md#list-all-environment-variables)
with a `script` command.
There are also [Kubernetes-specific deployment variables (deprecated)](../../user/project/clusters/deploy_to_cluster.md#deployment-variables).
There are also a number of [variables you can use to configure runner behavior](../runners/configure_runners.md#configure-runner-behavior-with-variables) globally or for individual jobs.
| Variable | GitLab | Runner | Description |

View File

@ -342,32 +342,6 @@ include:
- remote: https://gitlab.com/gitlab-org/gitlab/-/raw/v13.0.1-ee/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml
```
### Use a feature flag to roll out a `latest` template
With a major version release like 13.0 or 14.0, [stable templates](#stable-version) must be
updated with their corresponding [latest template versions](#latest-version).
It may be hard to gauge the impact of this change, so use the `redirect_to_latest_template_<name>`
feature flag to test the impact on a subset of users. Using a feature flag can help
reduce the risk of reverts or rollbacks on production.
For example, to redirect the stable `Jobs/Deploy` template to its latest template in 25% of
projects on `gitlab.com`:
```shell
/chatops run feature set redirect_to_latest_template_jobs_deploy 25 --actors
```
After you're confident the latest template can be moved to stable:
1. Update the stable template with the content of the latest version.
1. Remove the migration template from `Gitlab::Template::GitlabCiYmlTemplate::TEMPLATES_WITH_LATEST_VERSION` const.
1. Remove the corresponding feature flag.
NOTE:
Feature flags are enabled by default in RSpec, so all tests are performed
against the latest templates. You should also test the stable templates
with `stub_feature_flags(redirect_to_latest_template_<name>: false)`.
### Further reading
There is an [open issue](https://gitlab.com/gitlab-org/gitlab/-/issues/17716) about

View File

@ -196,10 +196,11 @@ is not shared to forks.
Contributors can configure Danger for their forks with the following steps:
1. Add an [environment variable](../ci/variables/index.md) called `DANGER_GITLAB_API_TOKEN` with a
[personal API token](https://gitlab.com/-/profile/personal_access_tokens?name=GitLab+Dangerbot&scopes=api)
to your fork that has the `api` scope set.
1. Making the variable [masked](../ci/variables/index.md#mask-a-cicd-variable) makes sure it
1. Create a [personal API token](https://gitlab.com/-/profile/personal_access_tokens?name=GitLab+Dangerbot&scopes=api).
that has the `api` scope set (don't forget to copy it to the clipboard).
1. Add a [project CI/CD variable](../ci/variables/index.md#add-a-cicd-variable-to-a-project)
called `DANGER_GITLAB_API_TOKEN` with the token copied in the previous step.
1. Make the variable [masked](../ci/variables/index.md#mask-a-cicd-variable) so it
doesn't show up in the job logs. The variable cannot be
[protected](../ci/variables/index.md#protected-cicd-variables), as it needs
to be present for all feature branches.
[protected](../ci/variables/index.md#protected-cicd-variables), because it needs
to be present for all branches.

View File

@ -160,6 +160,28 @@ It also depends on a few third-party gems that are not actively maintained anymo
For more information, check the [summary section of the deprecation issue](https://gitlab.com/gitlab-org/gitlab/-/issues/352488#deprecation-summary).
### Sidekiq configuration for metrics and health checks
WARNING:
This feature was changed or removed in 15.0
as a [breaking change](https://docs.gitlab.com/ee/development/contributing/#breaking-changes).
Before updating GitLab, review the details carefully to determine if you need to make any
changes to your code, settings, or workflow.
In GitLab 15.0, you can no longer serve Sidekiq metrics and health checks over a single address and port.
To improve stability, availability, and prevent data loss in edge cases, GitLab now serves
[Sidekiq metrics and health checks from two separate servers](https://gitlab.com/groups/gitlab-org/-/epics/6409).
When you use Omnibus or Helm charts, if GitLab is configured for both servers to bind to the same address,
a configuration error occurs.
To prevent this error, choose different ports for the metrics and health check servers:
- [Configure Sidekiq health checks](https://docs.gitlab.com/ee/administration/sidekiq.html#configure-health-checks)
- [Configure the Sidekiq metrics server](https://docs.gitlab.com/ee/administration/sidekiq.html#configure-the-sidekiq-metrics-server)
If you installed GitLab from source, verify manually that both servers are configured to bind to separate addresses and ports.
### Static Site Editor
The Static Site Editor was deprecated in GitLab 14.7 and the feature is being removed in GitLab 15.0. Incoming requests to the Static Site Editor will be redirected and open the target file to edit in the Web IDE. Current users of the Static Site Editor can view the [documentation](https://docs.gitlab.com/ee/user/project/static_site_editor/) for more information, including how to remove the configuration files from existing projects. We will continue investing in improvements to the Markdown editing experience by [maturing the Content Editor](https://gitlab.com/groups/gitlab-org/-/epics/5401) and making it available as a way to edit content across GitLab.

View File

@ -136,82 +136,82 @@ These shortcuts are available when browsing the files in a project (go to
These shortcuts are available when editing a file with the [Web IDE](project/web_ide/index.md):
| macOS shortcut | Windows shortcut | Description |
| macOS shortcut | Windows/Linux shortcut | Description |
|---------------------------------|---------------------|-------------|
| <kbd>Option</kbd> + <kbd>Command</kbd> + <kbd></kbd> | | Add cursor above |
| <kbd>Option</kbd> + <kbd>Command</kbd> + <kbd></kbd> | | Add cursor below |
| <kbd>Shift</kbd> + <kbd>Option</kbd> + <kbd>I</kbd> | | Add cursors to line ends |
| <kbd>Option</kbd> + <kbd>Command</kbd> + <kbd></kbd> | <kbd>Shift</kbd> + <kbd>Alt</kbd> + <kbd></kbd> | Add cursor above |
| <kbd>Option</kbd> + <kbd>Command</kbd> + <kbd></kbd> | <kbd>Shift</kbd> + <kbd>Alt</kbd> + <kbd></kbd> | Add cursor below |
| <kbd>Shift</kbd> + <kbd>Option</kbd> + <kbd>I</kbd> | <kbd>Shift</kbd> + <kbd>Alt</kbd> + <kbd>I</kbd> | Add cursors to line ends |
| <kbd>Command</kbd> + <kbd>K</kbd>, <kbd>Command</kbd> + <kbd>C</kbd> | <kbd>Control</kbd> + <kbd>K</kbd>, <kbd>Control</kbd> + <kbd>C</kbd> _or_ <kbd>Control</kbd> + <kbd>/</kbd> | Add line comment |
| <kbd>Command</kbd> + <kbd>D</kbd> | | Add selection to next find match |
| <kbd>Command</kbd> + <kbd>F2</kbd> | | Change all occurrences |
| <kbd>F1</kbd> | | Command palette |
| <kbd>Shift</kbd> + <kbd>Option</kbd> + <kbd></kbd> | | Copy line down |
| <kbd>Shift</kbd> + <kbd>Option</kbd> + <kbd></kbd> | | Copy line up |
| <kbd>Command</kbd> + <kbd>U</kbd> | | Cursor undo |
| <kbd>Command</kbd> + <kbd>Backspace<kbd> | | Delete all left |
| <kbd>Command</kbd> + <kbd>D</kbd> | <kbd>Control</kbd> + <kbd>D</kbd> | Add selection to next find match |
| <kbd>Command</kbd> + <kbd>F2</kbd> | <kbd>Control</kbd> + <kbd>F2</kbd> | Change all occurrences |
| <kbd>F1</kbd> | <kbd>F1</kbd> | Command palette |
| <kbd>Shift</kbd> + <kbd>Option</kbd> + <kbd></kbd> | <kbd>Control</kbd> + <kbd>Shift</kbd> + <kbd>Alt</kbd> + <kbd></kbd> | Copy line down |
| <kbd>Shift</kbd> + <kbd>Option</kbd> + <kbd></kbd> | <kbd>Control</kbd> + <kbd>Shift</kbd> + <kbd>Alt</kbd> + <kbd></kbd> | Copy line up [(Linux note)](#linux-shortcuts) |
| <kbd>Command</kbd> + <kbd>U</kbd> | <kbd>Control</kbd> + <kbd>U</kbd> | Cursor undo |
| <kbd>Command</kbd> + <kbd>Backspace<kbd> | <kbd>Control</kbd> + <kbd>Shift</kbd> + <kbd>Backspace</kbd> | Delete all left |
| <kbd>Control</kbd> + <kbd>K</kbd> | | Delete all right |
| <kbd>Shift</kbd> + <kbd>Command</kbd> + <kbd>K</kbd> | | Delete line |
| <kbd>Control</kbd> + <kbd>Shift</kbd> + <kbd>Command</kbd> + <kbd></kbd> | | Expand selection |
| <kbd>Command</kbd> + <kbd>P</kbd> | | File finder |
| <kbd>Command</kbd> + <kbd>F</kbd> | | Find |
| <kbd>Enter</kbd> | | Find next |
| <kbd>Command</kbd> + <kbd>F3</kbd> | | Find next selection |
| <kbd>Shift</kbd> + <kbd>Enter</kbd> + <kbd>F3</kbd> | | Find previous |
| <kbd>Shift</kbd> + <kbd>Command</kbd> + <kbd>F3</kbd> | | Find previous selection |
| <kbd>Shift</kbd> + <kbd>Command</kbd> + <kbd>K</kbd> | <kbd>Control</kbd> + <kbd>Shift</kbd> + <kbd>K</kbd> | Delete line |
| | <kbd>Control</kbd> + <kbd>Backspace</kbd> | Delete word |
| <kbd>Control</kbd> + <kbd>Shift</kbd> + <kbd>Command</kbd> + <kbd></kbd> | <kbd>Shift</kbd> + <kbd>Alt</kbd> + <kbd></kbd> | Expand selection |
| <kbd>Command</kbd> + <kbd>P</kbd> | <kbd>Control</kbd> + <kbd>P</kbd> | File finder |
| <kbd>Command</kbd> + <kbd>F</kbd> | <kbd>Control</kbd> + <kbd>F</kbd> | Find |
| <kbd>Enter</kbd> | <kbd>Enter</kbd> or <kbd>F3</kbd> | Find next |
| <kbd>Command</kbd> + <kbd>F3</kbd> | <kbd>F3</kbd> | Find next selection [(Linux note)](#linux-shortcuts) |
| <kbd>Shift</kbd> + <kbd>Enter</kbd> + <kbd>F3</kbd> | <kbd>Shift</kbd> + <kbd>F3</kbd> | Find previous |
| <kbd>Shift</kbd> + <kbd>Command</kbd> + <kbd>F3</kbd> | <kbd>Shift</kbd> + <kbd>F3</kbd> | Find previous selection |
| <kbd>Command</kbd> + <kbd>E</kbd> | | Find with selection |
| <kbd>Option</kbd> + <kbd>Command</kbd> + <kbd>&#91;</kbd> | | Fold |
| <kbd>Command</kbd> + <kbd>K</kbd>, then <kbd>Command</kbd> + <kbd>O</kbd> | | Fold all |
| <kbd>Command</kbd> + <kbd>K</kbd>, then <kbd>Command</kbd> + <kbd>/</kbd> | | Fold all block comments |
| <kbd>Command</kbd> + <kbd>K</kbd> , then <kbd>Command</kbd> + <kbd>8</kbd> | | Fold all regions |
| <kbd>Command</kbd> + <kbd>K</kbd> , then <kbd>Command</kbd> + <kbd>-</kbd> | | Fold all regions except selected |
| <kbd>Command</kbd> + <kbd>K</kbd> , then <kbd>Command</kbd> + <kbd>1</kbd> | | Fold level 1 |
| <kbd>Command</kbd> + <kbd>K</kbd> , then <kbd>Command</kbd> + <kbd>2</kbd> | | Fold level 2 |
| <kbd>Command</kbd> + <kbd>K</kbd> , then <kbd>Command</kbd> + <kbd>3</kbd> | | Fold level 3 |
| <kbd>Command</kbd> + <kbd>K</kbd> , then <kbd>Command</kbd> + <kbd>4</kbd> | | Fold level 4 |
| <kbd>Command</kbd> + <kbd>K</kbd> , then <kbd>Command</kbd> + <kbd>5</kbd> | | Fold level 5 |
| <kbd>Command</kbd> + <kbd>K</kbd> , then <kbd>Command</kbd> + <kbd>6</kbd> | | Fold level 6 |
| <kbd>Command</kbd> + <kbd>K</kbd> , then <kbd>Command</kbd> + <kbd>7</kbd> | | Fold level 7 |
| <kbd>Command</kbd> + <kbd>K</kbd> , then <kbd>Command</kbd> + <kbd>&#91;</kbd> | | Fold recursively |
| <kbd>Shift</kbd> + <kbd>Command</kbd> + <kbd>\ </kbd> | | Go to bracket |
| <kbd>Control</kbd> + <kbd>G</kbd> | | Go to line or column |
| <kbd>Option</kbd> + <kbd>F8</kbd> | | Go to next problem (error, warning, information) |
| <kbd>F8</kbd> | | Go to next problem in files (error, warning, information) |
| <kbd>Shift</kbd> + <kbd>Option</kbd> + <kbd>F8</kbd> | | Go to previous problem (error, warning, information) |
| <kbd>Shift</kbd> + <kbd>F8</kbd> | | Go to previous problem in files (error, warning, information) |
| <kbd>Command</kbd> + <kbd>]</kbd> | | Indent line |
| <kbd>Shift</kbd> + <kbd>Command</kbd> | | Enter Insert line above |
| <kbd>Command</kbd> + <kbd>Enter</kbd> | | Insert line below |
| <kbd>Control</kbd> + <kbd>J</kbd> | | Join lines |
| <kbd>Command</kbd> + <kbd>K</kbd>, then <kbd>Command</kbd> + <kbd>D</kbd> | | Move last selection to next find match |
| <kbd>Option</kbd> + <kbd></kbd> | | Move line down |
| <kbd>Option</kbd> + <kbd></kbd> | | Move line up |
| <kbd>Command</kbd> + <kbd>&#91;</kbd> | | Outdent line |
| <kbd>Shift</kbd> + <kbd>Command</kbd> + <kbd>P</kbd> | | Preview Markdown |
| <kbd>Command</kbd> + <kbd>K</kbd>, then <kbd>Command</kbd> + <kbd>U</kbd> | | Remove line comment |
| <kbd>Option</kbd> + <kbd>Command</kbd> + <kbd>F</kbd> | | Replace |
| <kbd>Shift</kbd> + <kbd>Command</kbd> + <kbd>.</kbd> | | Replace with next value |
| <kbd>Shift</kbd> + <kbd>Command</kbd> + <kbd>,</kbd> | | Replace with previous value |
| <kbd>Command</kbd> + <kbd>S</kbd> | | Save files |
| <kbd>Command</kbd> + <kbd>P</kbd> | <kbd>Control</kbd> + <kbd>P</kbd> | Search for, and then open another file for editing. |
| <kbd>Shift</kbd> + <kbd>Command</kbd> + <kbd>L</kbd> | | Select all occurrence of Find Match |
| <kbd>Command</kbd> + <kbd>K</kbd>, then <kbd>Command</kbd> + <kbd>B</kbd> | | Set selection anchor |
| <kbd>Option</kbd> + <kbd>F1</kbd> | | Show accessibility help |
| <kbd>Shift</kbd> + <kbd>F10</kbd> | | Show editor context menu |
| <kbd>Command</kbd> + <kbd>K</kbd>, then <kbd>Command</kbd> + <kbd>I</kbd> | | Show hover |
| <kbd>Control</kbd> + <kbd>Shift</kbd> + <kbd>Command</kbd> + <kbd></kbd> | | Shrink selection |
| <kbd>Shift</kbd> + <kbd>Option</kbd> + <kbd>A</kbd> | | Toggle block comment |
| <kbd>Command</kbd> + <kbd>K</kbd>, then <kbd>Command</kbd> + <kbd>L</kbd> | | Toggle fold |
| <kbd>Command</kbd> + <kbd>/</kbd> | | Toggle line comment |
| <kbd>Control</kbd> + <kbd>Shift</kbd> + <kbd>M</kbd> | | Toggle Tab key moves focus |
| <kbd>Option</kbd> + <kbd>Command</kbd> + <kbd>&#91;</kbd> | <kbd>Control</kbd> + <kbd>Shift</kbd> + <kbd>&#91;</kbd> | Fold |
| <kbd>Command</kbd> + <kbd>K</kbd>, then <kbd>Command</kbd> + <kbd>O</kbd> | <kbd>Control</kbd> + <kbd>K</kbd>, then <kbd>Control</kbd> + <kbd>O</kbd> | Fold all |
| <kbd>Command</kbd> + <kbd>K</kbd>, then <kbd>Command</kbd> + <kbd>/</kbd> | <kbd>Control</kbd> + <kbd>K</kbd>, then <kbd>Control</kbd> + <kbd>/</kbd> | Fold all block comments |
| <kbd>Command</kbd> + <kbd>K</kbd> , then <kbd>Command</kbd> + <kbd>8</kbd> | <kbd>Control</kbd> + <kbd>K</kbd> , then <kbd>Control</kbd> + <kbd>8</kbd> | Fold all regions |
| <kbd>Command</kbd> + <kbd>K</kbd> , then <kbd>Command</kbd> + <kbd>-</kbd> | <kbd>Control</kbd> + <kbd>K</kbd> , then <kbd>Control</kbd> + <kbd>-</kbd> | Fold all regions except selected |
| <kbd>Command</kbd> + <kbd>K</kbd> , then <kbd>Command</kbd> + <kbd>1</kbd> | <kbd>Control</kbd> + <kbd>K</kbd> , then <kbd>Control</kbd> + <kbd>1</kbd> | Fold level 1 |
| <kbd>Command</kbd> + <kbd>K</kbd> , then <kbd>Command</kbd> + <kbd>2</kbd> | <kbd>Control</kbd> + <kbd>K</kbd> , then <kbd>Control</kbd> + <kbd>2</kbd> | Fold level 2 |
| <kbd>Command</kbd> + <kbd>K</kbd> , then <kbd>Command</kbd> + <kbd>3</kbd> | <kbd>Control</kbd> + <kbd>K</kbd> , then <kbd>Control</kbd> + <kbd>3</kbd> | Fold level 3 |
| <kbd>Command</kbd> + <kbd>K</kbd> , then <kbd>Command</kbd> + <kbd>4</kbd> | <kbd>Control</kbd> + <kbd>K</kbd> , then <kbd>Control</kbd> + <kbd>4</kbd> | Fold level 4 |
| <kbd>Command</kbd> + <kbd>K</kbd> , then <kbd>Command</kbd> + <kbd>5</kbd> | <kbd>Control</kbd> + <kbd>K</kbd> , then <kbd>Control</kbd> + <kbd>5</kbd> | Fold level 5 |
| <kbd>Command</kbd> + <kbd>K</kbd> , then <kbd>Command</kbd> + <kbd>6</kbd> | <kbd>Control</kbd> + <kbd>K</kbd> , then <kbd>Control</kbd> + <kbd>6</kbd> | Fold level 6 |
| <kbd>Command</kbd> + <kbd>K</kbd> , then <kbd>Command</kbd> + <kbd>7</kbd> | <kbd>Control</kbd> + <kbd>K</kbd> , then <kbd>Control</kbd> + <kbd>7</kbd> | Fold level 7 |
| <kbd>Command</kbd> + <kbd>K</kbd> , then <kbd>Command</kbd> + <kbd>&#91;</kbd> | <kbd>Control</kbd> + <kbd>K</kbd> , then <kbd>Control</kbd> + <kbd>&#91;</kbd> | Fold recursively |
| <kbd>Shift</kbd> + <kbd>Command</kbd> + <kbd>&#92;</kbd> | <kbd>Control</kbd> + <kbd>Shift</kbd> + <kbd>&#92;</kbd> | Go to bracket |
| <kbd>Control</kbd> + <kbd>G</kbd> | <kbd>Control</kbd> + <kbd>G</kbd> | Go to line or column |
| <kbd>Option</kbd> + <kbd>F8</kbd> | <kbd>Alt</kbd> + <kbd>F8</kbd> | Go to next problem (error, warning, information) |
| <kbd>F8</kbd> | <kbd>F8</kbd> | Go to next problem in files (error, warning, information) |
| <kbd>Shift</kbd> + <kbd>Option</kbd> + <kbd>F8</kbd> | <kbd>Shift</kbd> + <kbd>Alt</kbd> + <kbd>F8</kbd> | Go to previous problem (error, warning, information) |
| <kbd>Shift</kbd> + <kbd>F8</kbd> | <kbd>Shift</kbd> + <kbd>F8</kbd> | Go to previous problem in files (error, warning, information) |
| <kbd>Command</kbd> + <kbd>&#93;</kbd> | <kbd>Control</kbd> + <kbd>&#93;</kbd> | Indent line |
| <kbd>Shift</kbd> + <kbd>Command</kbd> | <kbd>Control</kbd> + <kbd>Shift</kbd> + <kbd>Enter</kbd> | Insert line above |
| <kbd>Command</kbd> + <kbd>Enter</kbd> | <kbd>Control</kbd> + <kbd>Enter</kbd> | Insert line below |
| <kbd>Control</kbd> + <kbd>J</kbd> | <kbd>Control</kbd> + <kbd>J</kbd> | Join lines [(Linux note)](#linux-shortcuts) |
| <kbd>Command</kbd> + <kbd>K</kbd>, then <kbd>Command</kbd> + <kbd>D</kbd> | <kbd>Control</kbd> + <kbd>K</kbd>, then <kbd>Control</kbd> + <kbd>D</kbd> | Move last selection to next find match |
| <kbd>Option</kbd> + <kbd></kbd> | <kbd>Alt</kbd> + <kbd></kbd> | Move line down |
| <kbd>Option</kbd> + <kbd></kbd> | <kbd>Alt</kbd> + <kbd></kbd> | Move line up |
| <kbd>Command</kbd> + <kbd>&#91;</kbd> | <kbd>Control</kbd> + <kbd>&#91;</kbd> | Outdent line |
| <kbd>Shift</kbd> + <kbd>Command</kbd> + <kbd>P</kbd> | <kbd>Control</kbd> + <kbd>Shift</kbd> + <kbd>P</kbd> | Preview Markdown [(Linux note)](#linux-shortcuts) |
| <kbd>Command</kbd> + <kbd>K</kbd>, then <kbd>Command</kbd> + <kbd>U</kbd> | <kbd>Control</kbd> + <kbd>K</kbd>, then <kbd>Control</kbd> + <kbd>U</kbd> or <kbd>Control</kbd> + <kbd>/</kbd> | Remove line comment |
| <kbd>Option</kbd> + <kbd>Command</kbd> + <kbd>F</kbd> | <kbd>Control</kbd> + <kbd>F</kbd> | Replace |
| <kbd>Shift</kbd> + <kbd>Command</kbd> + <kbd>.</kbd> | <kbd>Control</kbd> + <kbd>Shift</kbd> + <kbd>.</kbd> | Replace with next value |
| <kbd>Shift</kbd> + <kbd>Command</kbd> + <kbd>,</kbd> | <kbd>Control</kbd> + <kbd>Shift</kbd> + <kbd>,</kbd> | Replace with previous value |
| <kbd>Command</kbd> + <kbd>S</kbd> | <kbd>Control</kbd> + <kbd>S</kbd> | Save files |
| <kbd>Shift</kbd> + <kbd>Command</kbd> + <kbd>L</kbd> | <kbd>Control</kbd> + <kbd>Shift</kbd> + <kbd>L</kbd> | Select all occurrences of find match |
| <kbd>Command</kbd> + <kbd>K</kbd>, then <kbd>Command</kbd> + <kbd>B</kbd> | <kbd>Control</kbd> + <kbd>K</kbd>, then <kbd>Control</kbd> + <kbd>B</kbd> | Set selection anchor |
| <kbd>Option</kbd> + <kbd>F1</kbd> | <kbd>Shift</kbd> + <kbd>Alt</kbd> + <kbd>F1</kbd> | Show accessibility help |
| <kbd>Shift</kbd> + <kbd>F10</kbd> | <kbd>Shift</kbd> + <kbd>F10</kbd> | Show editor context menu |
| <kbd>Command</kbd> + <kbd>K</kbd>, then <kbd>Command</kbd> + <kbd>I</kbd> | <kbd>Control</kbd> + <kbd>K</kbd>, then <kbd>Control</kbd> + <kbd>I</kbd> | Show hover |
| <kbd>Control</kbd> + <kbd>Shift</kbd> + <kbd>Command</kbd> + <kbd></kbd> | <kbd>Shift</kbd> + <kbd>Alt</kbd> + <kbd></kbd> | Shrink selection |
| <kbd>Shift</kbd> + <kbd>Option</kbd> + <kbd>A</kbd> | <kbd>Control</kbd> + <kbd>Shift</kbd> + <kbd>A</kbd> | Toggle block comment |
| <kbd>Command</kbd> + <kbd>K</kbd>, then <kbd>Command</kbd> + <kbd>L</kbd> | <kbd>Control</kbd> + <kbd>K</kbd>, then <kbd>Control</kbd> + <kbd>L</kbd> | Toggle fold |
| <kbd>Control</kbd> + <kbd>Shift</kbd> + <kbd>M</kbd> | <kbd>Control</kbd> + <kbd>M</kbd> | Toggle Tab key moves focus |
| <kbd>Command</kbd> + <kbd>/</kbd> | <kbd>Control</kbd> + <kbd>/</kbd> | Toggle line comment |
| <kbd>Control</kbd> + <kbd>T</kbd> | | Transpose letters |
| <kbd>Control</kbd> + <kbd>Space</kbd> | | Trigger Suggest |
| <kbd>Command</kbd> + <kbd>K</kbd>, then <kbd>Command</kbd> + <kbd>X</kbd> | | Trim trailing whitespace |
| <kbd>Option</kbd> + <kbd>Command</kbd> + <kbd>]</kbd> | | Unfold |
| <kbd>Command</kbd> + <kbd>K</kbd>, then <kbd>Command</kbd> + <kbd>J</kbd> | | Unfold all |
| <kbd>Command</kbd> + <kbd>K</kbd>, then <kbd>Command</kbd> + <kbd>9</kbd> | | Unfold all regions |
| <kbd>Command</kbd> + <kbd>K</kbd>, then <kbd>Command</kbd> + <kbd>=</kbd> | | Unfold all regions except selected |
| <kbd>Command</kbd> + <kbd>K</kbd>, then <kbd>Command</kbd> + <kbd>]</kbd> | | Unfold recursively |
| <kbd>Command</kbd> + <kbd>K</kbd>, then <kbd>Command</kbd> + <kbd>X</kbd> | | Trim trailing whitespace |
| <kbd>Control</kbd> + <kbd>Space</kbd> | <kbd>Control</kbd> + <kbd>Space</kbd> | Trigger Suggest |
| <kbd>Command</kbd> + <kbd>K</kbd>, then <kbd>Command</kbd> + <kbd>X</kbd> | <kbd>Control</kbd> + <kbd>K</kbd>, then <kbd>Control</kbd> + <kbd>X</kbd> | Trim trailing whitespace |
| <kbd>Option</kbd> + <kbd>Command</kbd> + <kbd>&#93;</kbd> | <kbd>Control</kbd> + <kbd>Shift</kbd> + <kbd>&#93;</kbd> | Unfold |
| <kbd>Command</kbd> + <kbd>K</kbd>, then <kbd>Command</kbd> + <kbd>J</kbd> | <kbd>Control</kbd> + <kbd>K</kbd>, then <kbd>Control</kbd> + <kbd>J</kbd> | Unfold all |
| <kbd>Command</kbd> + <kbd>K</kbd>, then <kbd>Command</kbd> + <kbd>9</kbd> | <kbd>Control</kbd> + <kbd>K</kbd>, then <kbd>Control</kbd> + <kbd>9</kbd> | Unfold all regions |
| <kbd>Command</kbd> + <kbd>K</kbd>, then <kbd>Command</kbd> + <kbd>=</kbd> | <kbd>Control</kbd> + <kbd>K</kbd>, then <kbd>Control</kbd> + <kbd>=</kbd> | Unfold all regions except selected |
| <kbd>Command</kbd> + <kbd>K</kbd>, then <kbd>Command</kbd> + <kbd>&#93;</kbd> | <kbd>Control</kbd> + <kbd>K</kbd>, then <kbd>Control</kbd> + <kbd>&#93;</kbd> | Unfold recursively |
| <kbd>Command</kbd> + <kbd>K</kbd>, then <kbd>Command</kbd> + <kbd>X</kbd> | <kbd>Control</kbd> + <kbd>K</kbd>, then <kbd>Control</kbd> + <kbd>X</kbd> | Trim trailing whitespace |
| <kbd>Command</kbd> + <kbd>Enter</kbd> | <kbd>Control</kbd> + <kbd>Enter</kbd> | Commit (when editing the commit message). |
### Repository graph
@ -267,7 +267,7 @@ These shortcuts are available when editing a file with the
| <kbd>Command</kbd> + <kbd>Alt</kbd> + <kbd>5</kbd> | <kbd>Control</kbd> + <kbd>Alt</kbd> + <kbd>5</kbd> | Apply heading style 5 |
| <kbd>Command</kbd> + <kbd>Alt</kbd> + <kbd>6</kbd> | <kbd>Control</kbd> + <kbd>Alt</kbd> + <kbd>6</kbd> | Apply heading style 6 |
| <kbd>Command</kbd> + <kbd>Shift</kbd> + <kbd>7</kbd> | <kbd>Control</kbd> + <kbd>Shift</kbd> + <kbd>7</kbd> | Ordered list |
| <kbd>Command</kbd> + <kbd>Shift</kbd> + <kbd>8</kbd> | <kbd>Control</kbd> + <kbd>Shift</kbd> + <kbd>8</kbd> | Bullet list |
| <kbd>Command</kbd> + <kbd>Shift</kbd> + <kbd>8</kbd> | <kbd>Control</kbd> + <kbd>Shift</kbd> + <kbd>8</kbd> | Unordered list |
| <kbd>Command</kbd> + <kbd>Shift</kbd> + <kbd>9</kbd> | <kbd>Control</kbd> + <kbd>Shift</kbd> + <kbd>9</kbd> | Task list |
| <kbd>Command</kbd> + <kbd>Shift</kbd> + <kbd>b</kbd> | <kbd>Control</kbd> + <kbd>Shift</kbd> + <kbd>b</kbd> | Blockquote |
| <kbd>Command</kbd> + <kbd>Alt</kbd> + <kbd>c</kbd> | <kbd>Control</kbd> + <kbd>Shift</kbd> + <kbd>c</kbd> | Code block |
@ -328,3 +328,10 @@ To disable keyboard shortcuts:
1. While viewing a page that supports keyboard shortcuts, and outside a text box,
press <kbd>?</kbd> to display the list of shortcuts.
1. Select **Toggle shortcuts**.
## Troubleshooting
### Linux shortcuts
Linux users may encounter GitLab keyboard shortcuts that are overridden by
their operating system, or their browser.

View File

@ -28,6 +28,8 @@ module Gitlab
# on_hold_until is a temporary runtime status which puts execution "on hold"
scope :executable, -> { with_status(:active).where('on_hold_until IS NULL OR on_hold_until < NOW()') }
scope :created_after, ->(time) { where('created_at > ?', time) }
scope :for_configuration, ->(job_class_name, table_name, column_name, job_arguments) do
where(job_class_name: job_class_name, table_name: table_name, column_name: column_name)
.where("job_arguments = ?", job_arguments.to_json) # rubocop:disable Rails/WhereEquals

View File

@ -0,0 +1,56 @@
# frozen_string_literal: true
module Gitlab
module Database
module Migrations
class BaseBackgroundRunner
attr_reader :result_dir
def initialize(result_dir:)
@result_dir = result_dir
end
def jobs_by_migration_name
raise NotImplementedError, 'subclass must implement'
end
def run_job(job)
raise NotImplementedError, 'subclass must implement'
end
def run_jobs(for_duration:)
jobs_to_run = jobs_by_migration_name
return if jobs_to_run.empty?
# without .to_f, we do integer division
# For example, 3.minutes / 2 == 1.minute whereas 3.minutes / 2.to_f == (1.minute + 30.seconds)
duration_per_migration_type = for_duration / jobs_to_run.count.to_f
jobs_to_run.each do |migration_name, jobs|
run_until = duration_per_migration_type.from_now
run_jobs_for_migration(migration_name: migration_name, jobs: jobs, run_until: run_until)
end
end
private
def run_jobs_for_migration(migration_name:, jobs:, run_until:)
per_background_migration_result_dir = File.join(@result_dir, migration_name)
instrumentation = Instrumentation.new(result_dir: per_background_migration_result_dir)
batch_names = (1..).each.lazy.map { |i| "batch_#{i}"}
jobs.shuffle.each do |j|
break if run_until <= Time.current
instrumentation.observe(version: nil,
name: batch_names.next,
connection: ActiveRecord::Migration.connection) do
run_job(j)
end
end
end
end
end
end
end

View File

@ -21,6 +21,18 @@ module Gitlab
TestBackgroundRunner.new(result_dir: BASE_RESULT_DIR.join('background_migrations'))
end
def batched_background_migrations(for_database:)
runner = nil
# Only one loop iteration since we pass `only:` here
Gitlab::Database::EachDatabase.each_database_connection(only: for_database) do |connection|
runner = Gitlab::Database::Migrations::TestBatchedBackgroundRunner
.new(result_dir: BASE_RESULT_DIR.join('background_migrations'), connection: connection)
end
runner
end
def migration_context
@migration_context ||= ApplicationRecord.connection.migration_context
end

View File

@ -3,11 +3,9 @@
module Gitlab
module Database
module Migrations
class TestBackgroundRunner
attr_reader :result_dir
class TestBackgroundRunner < BaseBackgroundRunner
def initialize(result_dir:)
@result_dir = result_dir
super(result_dir: result_dir)
@job_coordinator = Gitlab::BackgroundMigration.coordinator_for_database(Gitlab::Database::MAIN_DATABASE_NAME)
end
@ -15,37 +13,12 @@ module Gitlab
@job_coordinator.pending_jobs
end
def run_jobs(for_duration:)
jobs_to_run = traditional_background_migrations.group_by { |j| class_name_for_job(j) }
return if jobs_to_run.empty?
# without .to_f, we do integer division
# For example, 3.minutes / 2 == 1.minute whereas 3.minutes / 2.to_f == (1.minute + 30.seconds)
duration_per_migration_type = for_duration / jobs_to_run.count.to_f
jobs_to_run.each do |migration_name, jobs|
run_until = duration_per_migration_type.from_now
run_jobs_for_migration(migration_name: migration_name, jobs: jobs, run_until: run_until)
end
def jobs_by_migration_name
traditional_background_migrations.group_by { |j| class_name_for_job(j) }
end
private
def run_jobs_for_migration(migration_name:, jobs:, run_until:)
per_background_migration_result_dir = File.join(@result_dir, migration_name)
instrumentation = Instrumentation.new(result_dir: per_background_migration_result_dir)
batch_names = (1..).each.lazy.map { |i| "batch_#{i}"}
jobs.shuffle.each do |j|
break if run_until <= Time.current
instrumentation.observe(version: nil, name: batch_names.next, connection: ActiveRecord::Migration.connection) do
run_job(j)
end
end
end
def run_job(job)
Gitlab::BackgroundMigration.perform(job.args[0], job.args[1])
end

View File

@ -0,0 +1,49 @@
# frozen_string_literal: true
module Gitlab
module Database
module Migrations
class TestBatchedBackgroundRunner < BaseBackgroundRunner
attr_reader :connection
def initialize(result_dir:, connection:)
super(result_dir: result_dir)
@connection = connection
end
def jobs_by_migration_name
Gitlab::Database::BackgroundMigration::BatchedMigration
.executable
.created_after(2.days.ago) # Simple way to exclude migrations already running before migration testing
.to_h do |migration|
batching_strategy = migration.batch_class.new(connection: connection)
all_migration_jobs = []
min_value = migration.next_min_value
while (next_bounds = batching_strategy.next_batch(
migration.table_name,
migration.column_name,
batch_min_value: min_value,
batch_size: migration.batch_size,
job_arguments: migration.job_arguments
))
batch_min, batch_max = next_bounds
all_migration_jobs << migration.create_batched_job!(batch_min, batch_max)
min_value = batch_max + 1
end
[migration.job_class_name, all_migration_jobs]
end
end
def run_job(job)
Gitlab::Database::BackgroundMigration::BatchedMigrationWrapper.new(connection: connection).perform(job)
end
end
end
end
end

View File

@ -6,8 +6,6 @@ module Gitlab
BASE_EXCLUDED_PATTERNS = [%r{\.latest\.}].freeze
BASE_DIR = 'lib/gitlab/ci/templates'
TEMPLATES_WITH_LATEST_VERSION = {}.freeze
def description
"# This file is a template, and might need editing before it works on your project."
end
@ -58,31 +56,6 @@ module Gitlab
excluded_patterns: self.excluded_patterns
)
end
override :find
def find(key, project = nil)
if try_redirect_to_latest?(key, project)
key += '.latest'
end
super(key, project)
end
private
# To gauge the impact of the latest template,
# you can redirect the stable template to the latest template by enabling the feature flag.
# See https://docs.gitlab.com/ee/development/cicd/templates.html#versioning for more information.
def try_redirect_to_latest?(key, project)
return false unless templates_with_latest_version[key]
flag_name = "redirect_to_latest_template_#{key.underscore.tr('/', '_')}"
::Feature.enabled?(flag_name, project)
end
def templates_with_latest_version
TEMPLATES_WITH_LATEST_VERSION
end
end
end
end

View File

@ -328,6 +328,15 @@ namespace :gitlab do
Gitlab::Database::Migrations::Runner.background_migrations.run_jobs(for_duration: duration)
end
desc 'Sample batched background migrations with instrumentation'
task :sample_batched_background_migrations, [:database, :duration_s] => [:environment] do |_t, args|
database_name = args[:database] || 'main'
duration = args[:duration_s]&.to_i&.seconds || 30.minutes # Default of 30 minutes
Gitlab::Database::Migrations::Runner.batched_background_migrations(for_database: database_name)
.run_jobs(for_duration: duration)
end
end
desc 'Run all pending batched migrations'

View File

@ -454,6 +454,9 @@ msgstr ""
msgid "%{authorsName}'s thread"
msgstr ""
msgid "%{author} requested to merge %{span_start}%{source_branch} %{copy_button}%{span_end} into %{target_branch} %{created_at}"
msgstr ""
msgid "%{board_target} not found"
msgstr ""

View File

@ -30,10 +30,10 @@ module QA
end
def fill_scopes(read_repository: false, read_registry: false, read_package_registry: false, write_package_registry: false )
check_element(:deploy_token_read_repository_checkbox) if read_repository
check_element(:deploy_token_read_package_registry_checkbox) if read_package_registry
check_element(:deploy_token_read_registry_checkbox) if read_registry
check_element(:deploy_token_write_package_registry_checkbox) if write_package_registry
check_element(:deploy_token_read_repository_checkbox, true) if read_repository
check_element(:deploy_token_read_package_registry_checkbox, true) if read_package_registry
check_element(:deploy_token_read_registry_checkbox, true) if read_registry
check_element(:deploy_token_write_package_registry_checkbox, true) if write_package_registry
end
def add_token

View File

@ -30,9 +30,8 @@ module QA
element :register_tab
end
view 'app/views/devise/shared/_tabs_normal.html.haml' do
view 'app/views/devise/shared/_tab_single.html.haml' do
element :sign_in_tab
element :register_tab
end
view 'app/helpers/auth_helper.rb' do

View File

@ -31,11 +31,25 @@ module QA
end
def fill_scopes(scopes)
check_element(:deploy_token_read_repository_checkbox) if scopes.include? :read_repository
check_element(:deploy_token_read_package_registry_checkbox) if scopes.include? :read_package_registry
check_element(:deploy_token_write_package_registry_checkbox) if scopes.include? :write_package_registry
check_element(:deploy_token_read_registry_checkbox) if scopes.include? :read_registry
check_element(:deploy_token_write_registry_checkbox) if scopes.include? :write_registry
if scopes.include? :read_repository
check_element(:deploy_token_read_repository_checkbox, true)
end
if scopes.include? :read_package_registry
check_element(:deploy_token_read_package_registry_checkbox, true)
end
if scopes.include? :write_package_registry
check_element(:deploy_token_write_package_registry_checkbox, true)
end
if scopes.include? :read_registry
check_element(:deploy_token_read_registry_checkbox, true)
end
if scopes.include? :write_registry
check_element(:deploy_token_write_registry_checkbox, true)
end
end
def add_token

View File

@ -70,13 +70,6 @@ flags_paths.each do |flags_path|
next
end
# Dynamic feature flag names for redirect to latest CI templates
# See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/63144/diffs#fa2193ace3f6a02f7ef9995ef9bc519eca92c4ee_57_84
if feature_flag_name.start_with?('redirect_to_latest_template_')
puts "Skipping the #{feature_flag_name} feature flag since it starts with 'redirect_to_latest_template_'."
next
end
all_flags[feature_flag_name] = File.exist?(File.join('tmp', 'feature_flags', feature_flag_name + '.used'))
end
end

View File

@ -162,14 +162,14 @@ RSpec.describe ObjectStoreSettings do
{
'enabled' => true,
'remote_directory' => 'some-bucket',
'direct_upload' => true,
'background_upload' => false,
'direct_upload' => false,
'background_upload' => true,
'proxy_download' => false
}
end
before do
settings.lfs['object_store'] = described_class.legacy_parse(legacy_settings)
settings.lfs['object_store'] = described_class.legacy_parse(legacy_settings, 'lfs')
end
it 'does not alter config if legacy settings are specified' do
@ -177,6 +177,35 @@ RSpec.describe ObjectStoreSettings do
expect(settings.artifacts['object_store']).to be_nil
expect(settings.lfs['object_store']['remote_directory']).to eq('some-bucket')
# Disable background_upload, regardless of the input config
expect(settings.lfs['object_store']['direct_upload']).to eq(true)
expect(settings.lfs['object_store']['background_upload']).to eq(false)
expect(settings.external_diffs['object_store']).to be_nil
end
end
context 'with legacy config and legacy background upload is enabled' do
let(:legacy_settings) do
{
'enabled' => true,
'remote_directory' => 'some-bucket',
'proxy_download' => false
}
end
before do
stub_env(ObjectStoreSettings::LEGACY_BACKGROUND_UPLOADS_ENV, 'lfs')
settings.lfs['object_store'] = described_class.legacy_parse(legacy_settings, 'lfs')
end
it 'enables background_upload and disables direct_upload' do
subject
expect(settings.artifacts['object_store']).to be_nil
expect(settings.lfs['object_store']['remote_directory']).to eq('some-bucket')
# Enable background_upload if the environment variable is available
expect(settings.lfs['object_store']['direct_upload']).to eq(false)
expect(settings.lfs['object_store']['background_upload']).to eq(true)
expect(settings.external_diffs['object_store']).to be_nil
end
end
@ -185,11 +214,11 @@ RSpec.describe ObjectStoreSettings do
describe '.legacy_parse' do
it 'sets correct default values' do
settings = described_class.legacy_parse(nil)
settings = described_class.legacy_parse(nil, 'artifacts')
expect(settings['enabled']).to be false
expect(settings['direct_upload']).to be false
expect(settings['background_upload']).to be true
expect(settings['direct_upload']).to be true
expect(settings['background_upload']).to be false
expect(settings['remote_directory']).to be nil
end
@ -199,12 +228,52 @@ RSpec.describe ObjectStoreSettings do
'remote_directory' => 'artifacts'
})
settings = described_class.legacy_parse(original_settings)
settings = described_class.legacy_parse(original_settings, 'artifacts')
expect(settings['enabled']).to be true
expect(settings['direct_upload']).to be false
expect(settings['background_upload']).to be true
expect(settings['direct_upload']).to be true
expect(settings['background_upload']).to be false
expect(settings['remote_directory']).to eq 'artifacts'
end
context 'legacy background upload environment variable is enabled' do
before do
stub_env(ObjectStoreSettings::LEGACY_BACKGROUND_UPLOADS_ENV, 'artifacts,lfs')
end
it 'enables background_upload and disables direct_upload' do
original_settings = Settingslogic.new({
'enabled' => true,
'remote_directory' => 'artifacts'
})
settings = described_class.legacy_parse(original_settings, 'artifacts')
expect(settings['enabled']).to be true
expect(settings['direct_upload']).to be false
expect(settings['background_upload']).to be true
expect(settings['remote_directory']).to eq 'artifacts'
end
end
context 'legacy background upload environment variable is enabled for other types' do
before do
stub_env(ObjectStoreSettings::LEGACY_BACKGROUND_UPLOADS_ENV, 'uploads,lfs')
end
it 'enables direct_upload and disables background_upload' do
original_settings = Settingslogic.new({
'enabled' => true,
'remote_directory' => 'artifacts'
})
settings = described_class.legacy_parse(original_settings, 'artifacts')
expect(settings['enabled']).to be true
expect(settings['direct_upload']).to be true
expect(settings['background_upload']).to be false
expect(settings['remote_directory']).to eq 'artifacts'
end
end
end
end

View File

@ -112,9 +112,7 @@ RSpec.describe 'User accepts a merge request', :js, :sidekiq_might_not_need_inli
click_button('Merge')
page.within('.status-box') do
expect(page).to have_content('Merged')
end
expect(page).to have_selector('.gl-badge', text: 'Merged')
end
end
end

View File

@ -118,7 +118,7 @@ RSpec.describe "User creates a merge request", :js do
click_button("Create merge request")
expect(page).to have_content(title).and have_content("Request to merge #{user.namespace.path}:#{source_branch} into master")
expect(page).to have_content(title).and have_content("requested to merge #{forked_project.full_path}:#{source_branch} into master")
end
end
end

View File

@ -17,9 +17,7 @@ RSpec.describe "User merges a merge request", :js do
click_button("Merge")
end
page.within(".status-box") do
expect(page).to have_content("Merged")
end
expect(page).to have_selector('.gl-badge', text: 'Merged')
end
end

View File

@ -71,13 +71,14 @@ RSpec.describe 'User views an open merge request' do
let(:merge_request) { create(:merge_request, :rebased, source_project: project, target_project: project) }
before do
project.add_maintainer(project.creator)
sign_in(project.creator)
visit(merge_request_path(merge_request))
end
it 'does not show diverged commits count' do
page.within('.mr-source-target') do
expect(page).not_to have_content(/([0-9]+ commits? behind)/)
end
expect(page).not_to have_content(/([0-9]+ commits? behind)/)
end
end
@ -85,13 +86,14 @@ RSpec.describe 'User views an open merge request' do
let(:merge_request) { create(:merge_request, :diverged, source_project: project, target_project: project) }
before do
project.add_maintainer(project.creator)
sign_in(project.creator)
visit(merge_request_path(merge_request))
end
it 'shows diverged commits count' do
page.within('.mr-source-target') do
expect(page).to have_content(/([0-9]+ commits behind)/)
end
expect(page).not_to have_content(/([0-9]+ commits? behind)/)
end
end

View File

@ -11,6 +11,10 @@ RSpec.describe 'Project > Merge request > View user status' do
subject { visit merge_request_path(merge_request) }
describe 'the status of the merge request author' do
before do
stub_feature_flags(updated_mr_header: false)
end
it_behaves_like 'showing user status' do
let(:user_with_status) { merge_request.author }
end

View File

@ -291,7 +291,7 @@ describe('ShortcutsIssuable', () => {
window.shortcut = new ShortcutsIssuable();
[sidebarCollapsedBtn, sidebarExpandedBtn] = document.querySelectorAll(
'.js-sidebar-source-branch button',
'.js-source-branch-copy',
);
[sidebarCollapsedBtn, sidebarExpandedBtn].forEach((btn) => jest.spyOn(btn, 'click'));
@ -312,22 +312,6 @@ describe('ShortcutsIssuable', () => {
it('clicks the "expanded" version of the copy source branch button', () => {
expect(sidebarExpandedBtn.click).toHaveBeenCalled();
expect(sidebarCollapsedBtn.click).not.toHaveBeenCalled();
});
});
describe('when the sidebar is collapsed', () => {
beforeEach(() => {
// simulate the applied CSS styles when the
// sidebar is collapsed
sidebarExpandedBtn.style.display = 'none';
Mousetrap.trigger('b');
});
it('clicks the "collapsed" version of the copy source branch button', () => {
expect(sidebarCollapsedBtn.click).toHaveBeenCalled();
expect(sidebarExpandedBtn.click).not.toHaveBeenCalled();
});
});
});

View File

@ -1,37 +1,37 @@
import { GlIcon, GlSprintf } from '@gitlab/ui';
import { GlSprintf } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import StatusBox from '~/issuable/components/status_box.vue';
let wrapper;
function factory(propsData) {
wrapper = shallowMount(StatusBox, { propsData, stubs: { GlSprintf } });
wrapper = shallowMount(StatusBox, {
propsData,
stubs: { GlSprintf },
provide: { glFeatures: { updatedMrHeader: true } },
});
}
const testCases = [
{
name: 'Open',
state: 'opened',
class: 'status-box-open',
icon: 'issue-open-m',
class: 'badge-success',
},
{
name: 'Open',
state: 'locked',
class: 'status-box-open',
icon: 'issue-open-m',
class: 'badge-success',
},
{
name: 'Closed',
state: 'closed',
class: 'status-box-mr-closed',
icon: 'issue-close',
class: 'badge-danger',
},
{
name: 'Merged',
state: 'merged',
class: 'status-box-mr-merged',
icon: 'git-merge',
class: 'badge-info',
},
];
@ -46,6 +46,7 @@ describe('Merge request status box component', () => {
it('renders human readable test', () => {
factory({
initialState: testCase.state,
issuableType: 'merge_request',
});
expect(wrapper.text()).toContain(testCase.name);
@ -54,18 +55,11 @@ describe('Merge request status box component', () => {
it('sets css class', () => {
factory({
initialState: testCase.state,
issuableType: 'merge_request',
});
expect(wrapper.classes()).toContain(testCase.class);
});
it('renders icon', () => {
factory({
initialState: testCase.state,
});
expect(wrapper.findComponent(GlIcon).props('name')).toBe(testCase.icon);
});
});
});
});

View File

@ -99,6 +99,15 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigration, type: :m
end
end
describe '.created_after' do
let!(:migration_old) { create :batched_background_migration, created_at: 2.days.ago }
let!(:migration_new) { create :batched_background_migration, created_at: 0.days.ago }
it 'only returns migrations created after the specified time' do
expect(described_class.created_after(1.day.ago)).to contain_exactly(migration_new)
end
end
describe '.queued' do
let!(:migration1) { create(:batched_background_migration, :finished) }
let!(:migration2) { create(:batched_background_migration, :paused) }

View File

@ -0,0 +1,23 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Database::Migrations::BaseBackgroundRunner, :freeze_time do
let(:result_dir) { Dir.mktmpdir }
after do
FileUtils.rm_rf(result_dir)
end
context 'subclassing' do
subject { described_class.new(result_dir: result_dir) }
it 'requires that jobs_by_migration_name be implemented' do
expect { subject.jobs_by_migration_name }.to raise_error(NotImplementedError)
end
it 'requires that run_job be implemented' do
expect { subject.run_job(nil) }.to raise_error(NotImplementedError)
end
end
end

View File

@ -2,6 +2,8 @@
require 'spec_helper'
RSpec.describe Gitlab::Database::Migrations::Runner do
include Database::MultipleDatabases
let(:result_dir) { Pathname.new(Dir.mktmpdir) }
let(:migration_runs) { [] } # This list gets populated as the runner tries to run migrations
@ -136,4 +138,35 @@ RSpec.describe Gitlab::Database::Migrations::Runner do
expect(runner.result_dir).to eq(described_class::BASE_RESULT_DIR.join( 'background_migrations'))
end
end
describe '.batched_background_migrations' do
it 'is a TestBatchedBackgroundRunner' do
expect(described_class.batched_background_migrations(for_database: 'main')).to be_a(Gitlab::Database::Migrations::TestBatchedBackgroundRunner)
end
context 'choosing the database to test against' do
it 'chooses the main database' do
runner = described_class.batched_background_migrations(for_database: 'main')
chosen_connection_name = Gitlab::Database.db_config_name(runner.connection)
expect(chosen_connection_name).to eq('main')
end
it 'chooses the ci database' do
skip_if_multiple_databases_not_setup
runner = described_class.batched_background_migrations(for_database: 'ci')
chosen_connection_name = Gitlab::Database.db_config_name(runner.connection)
expect(chosen_connection_name).to eq('ci')
end
it 'throws an error with an invalid name' do
expect { described_class.batched_background_migrations(for_database: 'not_a_database') }
.to raise_error(/not a valid database name/)
end
end
end
end

View File

@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe Gitlab::Database::Migrations::TestBackgroundRunner, :redis do
include Gitlab::Database::Migrations::BackgroundMigrationHelpers
include Database::MigrationTestingHelpers
# In order to test the interaction between queueing sidekiq jobs and seeing those jobs in queues,
# we need to disable sidekiq's testing mode and actually send our jobs to redis
@ -41,40 +42,6 @@ RSpec.describe Gitlab::Database::Migrations::TestBackgroundRunner, :redis do
end
context 'running migrations', :freeze_time do
def define_background_migration(name)
klass = Class.new do
# Can't simply def perform here as we won't have access to the block,
# similarly can't define_method(:perform, &block) here as it would change the block receiver
define_method(:perform) { |*args| yield(*args) }
end
stub_const("Gitlab::BackgroundMigration::#{name}", klass)
klass
end
def expect_migration_call_counts(migrations_to_calls)
migrations_to_calls.each do |migration, calls|
expect_next_instances_of(migration, calls) do |m|
expect(m).to receive(:perform).and_call_original
end
end
end
def expect_recorded_migration_runs(migrations_to_runs)
migrations_to_runs.each do |migration, runs|
path = File.join(result_dir, migration.name.demodulize)
num_subdirs = Pathname(path).children.count(&:directory?)
expect(num_subdirs).to eq(runs)
end
end
def expect_migration_runs(migrations_to_run_counts)
expect_migration_call_counts(migrations_to_run_counts)
yield
expect_recorded_migration_runs(migrations_to_run_counts)
end
it 'runs the migration class correctly' do
calls = []
define_background_migration(migration_name) do |i|

View File

@ -0,0 +1,87 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Database::Migrations::TestBatchedBackgroundRunner, :freeze_time do
include Gitlab::Database::MigrationHelpers
include Gitlab::Database::Migrations::BatchedBackgroundMigrationHelpers
include Database::MigrationTestingHelpers
let(:result_dir) { Dir.mktmpdir }
after do
FileUtils.rm_rf(result_dir)
end
let(:connection) { ApplicationRecord.connection }
let(:table_name) { "_test_column_copying"}
before do
connection.execute(<<~SQL)
CREATE TABLE #{table_name} (
id bigint primary key not null,
data bigint
);
insert into #{table_name} (id) select i from generate_series(1, 1000) g(i);
SQL
end
context 'running a real background migration' do
it 'runs sampled jobs from the batched background migration' do
queue_batched_background_migration('CopyColumnUsingBackgroundMigrationJob',
table_name, :id,
:id, :data,
batch_size: 100,
job_interval: 5.minutes) # job_interval is skipped when testing
described_class.new(result_dir: result_dir, connection: connection).run_jobs(for_duration: 1.minute)
unmigrated_row_count = define_batchable_model(table_name).where('id != data').count
expect(unmigrated_row_count).to eq(0)
end
end
context 'with jobs to run' do
let(:migration_name) { 'TestBackgroundMigration' }
before do
queue_batched_background_migration(migration_name, table_name, :id, job_interval: 5.minutes, batch_size: 100)
end
it 'samples jobs' do
calls = []
define_background_migration(migration_name) do |*args|
calls << args
end
described_class.new(result_dir: result_dir, connection: connection).run_jobs(for_duration: 3.minutes)
expect(calls.count).to eq(10) # 1000 rows / batch size 100 = 10
end
context 'with multiple jobs to run' do
it 'runs all jobs created within the last 48 hours' do
old_migration = define_background_migration(migration_name)
travel 3.days
new_migration = define_background_migration('NewMigration') { travel 1.second }
queue_batched_background_migration('NewMigration', table_name, :id,
job_interval: 5.minutes,
batch_size: 10,
sub_batch_size: 5)
other_new_migration = define_background_migration('NewMigration2') { travel 2.seconds }
queue_batched_background_migration('NewMigration2', table_name, :id,
job_interval: 5.minutes,
batch_size: 10,
sub_batch_size: 5)
expect_migration_runs(new_migration => 3, other_new_migration => 2, old_migration => 0) do
described_class.new(result_dir: result_dir, connection: connection).run_jobs(for_duration: 5.seconds)
end
end
end
end
end

View File

@ -21,55 +21,6 @@ RSpec.describe Gitlab::Template::GitlabCiYmlTemplate do
end
end
describe '.find' do
let_it_be(:project) { create(:project) }
let_it_be(:other_project) { create(:project) }
described_class::TEMPLATES_WITH_LATEST_VERSION.keys.each do |key|
it "finds the latest template for #{key}" do
result = described_class.find(key, project)
expect(result.full_name).to eq("#{key}.latest.gitlab-ci.yml")
expect(result.content).to be_present
end
context 'when `redirect_to_latest_template` feature flag is disabled' do
before do
stub_feature_flags("redirect_to_latest_template_#{key.underscore.tr('/', '_')}".to_sym => false)
end
it "finds the stable template for #{key}" do
result = described_class.find(key, project)
expect(result.full_name).to eq("#{key}.gitlab-ci.yml")
expect(result.content).to be_present
end
end
context 'when `redirect_to_latest_template` feature flag is enabled on the project' do
before do
stub_feature_flags("redirect_to_latest_template_#{key.underscore.tr('/', '_')}".to_sym => project)
end
it "finds the latest template for #{key}" do
result = described_class.find(key, project)
expect(result.full_name).to eq("#{key}.latest.gitlab-ci.yml")
expect(result.content).to be_present
end
end
context 'when `redirect_to_latest_template` feature flag is enabled on the other project' do
before do
stub_feature_flags("redirect_to_latest_template_#{key.underscore.tr('/', '_')}".to_sym => other_project)
end
it "finds the stable template for #{key}" do
result = described_class.find(key, project)
expect(result.full_name).to eq("#{key}.gitlab-ci.yml")
expect(result.content).to be_present
end
end
end
end
describe '#content' do
it 'loads the full file' do
gitignore = subject.new(Rails.root.join('lib/gitlab/ci/templates/Ruby.gitlab-ci.yml'))

View File

@ -0,0 +1,43 @@
# frozen_string_literal: true
module Database
module MigrationTestingHelpers
def define_background_migration(name)
klass = Class.new do
# Can't simply def perform here as we won't have access to the block,
# similarly can't define_method(:perform, &block) here as it would change the block receiver
define_method(:perform) { |*args| yield(*args) }
end
stub_const("Gitlab::BackgroundMigration::#{name}", klass)
klass
end
def expect_migration_call_counts(migrations_to_calls)
migrations_to_calls.each do |migration, calls|
expect_next_instances_of(migration, calls) do |m|
expect(m).to receive(:perform).and_call_original
end
end
end
def expect_recorded_migration_runs(migrations_to_runs)
migrations_to_runs.each do |migration, runs|
path = File.join(result_dir, migration.name.demodulize)
if runs.zero?
expect(Pathname(path)).not_to be_exist
else
num_subdirs = Pathname(path).children.count(&:directory?)
expect(num_subdirs).to eq(runs)
end
end
end
def expect_migration_runs(migrations_to_run_counts)
expect_migration_call_counts(migrations_to_run_counts)
yield
expect_recorded_migration_runs(migrations_to_run_counts)
end
end
end

View File

@ -697,6 +697,34 @@ RSpec.describe 'gitlab:db namespace rake task', :silence_stdout do
run_rake_task('gitlab:db:migration_testing:sample_background_migrations', '[100]')
end
end
describe '#sample_batched_background_migrations' do
let(:batched_runner) { instance_double(::Gitlab::Database::Migrations::TestBatchedBackgroundRunner) }
it 'delegates to the migration runner for the main database with a default sample duration' do
expect(::Gitlab::Database::Migrations::Runner).to receive(:batched_background_migrations)
.with(for_database: 'main').and_return(batched_runner)
expect(batched_runner).to receive(:run_jobs).with(for_duration: 30.minutes)
run_rake_task('gitlab:db:migration_testing:sample_batched_background_migrations')
end
it 'delegates to the migration runner for a specified database with a default sample duration' do
expect(::Gitlab::Database::Migrations::Runner).to receive(:batched_background_migrations)
.with(for_database: 'ci').and_return(batched_runner)
expect(batched_runner).to receive(:run_jobs).with(for_duration: 30.minutes)
run_rake_task('gitlab:db:migration_testing:sample_batched_background_migrations', '[ci]')
end
it 'delegates to the migration runner for a specified database and sample duration' do
expect(::Gitlab::Database::Migrations::Runner).to receive(:batched_background_migrations)
.with(for_database: 'ci').and_return(batched_runner)
expect(batched_runner).to receive(:run_jobs).with(for_duration: 100.seconds)
run_rake_task('gitlab:db:migration_testing:sample_batched_background_migrations', '[ci, 100]')
end
end
end
describe '#execute_batched_migrations' do