Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2023-09-14 12:09:48 +00:00
parent 8f55aaede8
commit 7f73b108d4
132 changed files with 1558 additions and 842 deletions

View File

@ -2290,6 +2290,7 @@
rules:
- <<: *if-merge-request
changes: ["**/*click_house*"]
- <<: *if-merge-request-labels-run-all-rspec
#########################
# Static analysis rules #

View File

@ -2,54 +2,6 @@
# Cop supports --autocorrect.
Style/RedundantFreeze:
Exclude:
- 'app/controllers/help_controller.rb'
- 'app/controllers/import/bitbucket_server_controller.rb'
- 'app/finders/issuable_finder.rb'
- 'app/finders/repositories/changelog_commits_finder.rb'
- 'app/helpers/auth_helper.rb'
- 'app/helpers/colors_helper.rb'
- 'app/helpers/sidekiq_helper.rb'
- 'app/models/application_setting_implementation.rb'
- 'app/models/badge.rb'
- 'app/models/blob_viewer/go_mod.rb'
- 'app/models/ci/runner.rb'
- 'app/models/commit.rb'
- 'app/models/commit_range.rb'
- 'app/models/concerns/ci/maskable.rb'
- 'app/models/concerns/pg_full_text_searchable.rb'
- 'app/models/concerns/redactable.rb'
- 'app/models/concerns/taskable.rb'
- 'app/models/custom_emoji.rb'
- 'app/models/environment_status.rb'
- 'app/models/error_tracking/project_error_tracking_setting.rb'
- 'app/models/hooks/web_hook.rb'
- 'app/models/integrations/apple_app_store.rb'
- 'app/models/integrations/chat_message/base_message.rb'
- 'app/models/integrations/confluence.rb'
- 'app/models/integrations/datadog.rb'
- 'app/models/integrations/discord.rb'
- 'app/models/integrations/teamcity.rb'
- 'app/models/license_template.rb'
- 'app/models/members/group_member.rb'
- 'app/models/members/project_member.rb'
- 'app/models/merge_request.rb'
- 'app/models/namespaces/randomized_suffix_path.rb'
- 'app/models/note.rb'
- 'app/models/packages/debian.rb'
- 'app/models/packages/debian/file_entry.rb'
- 'app/models/personal_access_token.rb'
- 'app/models/releases/link.rb'
- 'app/models/snippet_repository.rb'
- 'app/models/terraform/state.rb'
- 'app/services/clusters/agent_tokens/track_usage_service.rb'
- 'app/services/import/validate_remote_git_endpoint_service.rb'
- 'app/services/issues/base_service.rb'
- 'app/services/projects/import_error_filter.rb'
- 'app/services/projects/lfs_pointers/lfs_object_download_list_service.rb'
- 'app/uploaders/file_uploader.rb'
- 'app/validators/certificate_fingerprint_validator.rb'
- 'app/validators/json_schema_validator.rb'
- 'app/validators/line_code_validator.rb'
- 'lib/api/api.rb'
- 'lib/api/debian_group_packages.rb'
- 'lib/api/go_proxy.rb'

View File

@ -1 +1 @@
f1d2afbee25a73855d931ad2d406e04e2062dc96
5688f0d78dfb631f600868b80e8bfa9f13834f65

View File

@ -496,7 +496,7 @@ group :test do
gem 'gitlab_quality-test_tooling', '~> 0.9.3', require: false
end
gem 'octokit', '~> 4.15'
gem 'octokit', '~> 6.0'
gem 'gitlab-mail_room', '~> 0.0.23', require: 'mail_room'

View File

@ -96,7 +96,7 @@
{"name":"crystalball","version":"0.7.0","platform":"ruby","checksum":"6e729f372a5071daec877adb40c5df4cb25fe21f350635e2a9624373fc151ef2"},
{"name":"css_parser","version":"1.14.0","platform":"ruby","checksum":"f2ce6148cd505297b07bdbe7a5db4cce5cf530071f9b732b9a23538d6cdc0113"},
{"name":"cvss-suite","version":"3.0.1","platform":"ruby","checksum":"b5ca9e9e94032a42fd0dc28c1e305378b62c949e35ed7111fc4a1d76f68ad3f9"},
{"name":"danger","version":"8.6.1","platform":"ruby","checksum":"d95eb58b41f68d3aaa9bbef697916b6b4d161a38819517c98562531be75cdfd8"},
{"name":"danger","version":"9.3.1","platform":"ruby","checksum":"9070fbac181eb45fb9b69ea25e6ea4faa86796ef33bf8d00346cab4385e51df5"},
{"name":"danger-gitlab","version":"8.0.0","platform":"ruby","checksum":"497dd7d0f6513913de651019223d8058cf494df10acbd17de92b175dfa04a3a8"},
{"name":"database_cleaner","version":"1.7.0","platform":"ruby","checksum":"bdf833c197afac7054015bcde2567c3834c366bbfe6a377c30151ca984b32016"},
{"name":"date","version":"3.3.3","platform":"java","checksum":"584e0a582d1eb2207b4eaac089d8a43f2ca10bea02682f286099642f15c56cce"},
@ -204,7 +204,7 @@
{"name":"gettext","version":"3.3.6","platform":"ruby","checksum":"ee6bbd1b2f833ee52d7797fa68acbfecc4726aec6b6280fd7eab92aa0190b413"},
{"name":"gettext_i18n_rails","version":"1.11.0","platform":"ruby","checksum":"e19c7e4a256c500f7f38396dca44a282b9838ae278f57c362993a54964b22bbe"},
{"name":"gettext_i18n_rails_js","version":"1.3.0","platform":"ruby","checksum":"5d10afe4be3639bff78c50a56768c20f39aecdabc580c08aa45573911c2bd687"},
{"name":"git","version":"1.11.0","platform":"ruby","checksum":"7e95ba4da8298a0373ef1a6862aa22007d761f3c8274b675aa787966fecea0f1"},
{"name":"git","version":"1.18.0","platform":"ruby","checksum":"c9b80462e4565cd3d7a9ba8440c41d2c52244b17b0dad0bfddb46de70630c465"},
{"name":"gitaly","version":"16.3.0.pre.rc1","platform":"ruby","checksum":"55d9cc414a4f3859588f3770bd88d7c67c0f5454a1178b018b7a6f6913674c43"},
{"name":"gitlab","version":"4.19.0","platform":"ruby","checksum":"3f645e3e195dbc24f0834fbf83e8ccfb2056d8e9712b01a640aad418a6949679"},
{"name":"gitlab-chronic","version":"0.10.5","platform":"ruby","checksum":"f80f18dc699b708870a80685243331290bc10cfeedb6b99c92219722f729c875"},
@ -406,7 +406,7 @@
{"name":"numerizer","version":"0.2.0","platform":"ruby","checksum":"e58076d5ee5370417b7e52d9cb25836d62acd1b8d9a194c308707986c1705d7b"},
{"name":"oauth","version":"0.5.6","platform":"ruby","checksum":"4085fe28e0c5e2434135e00a6555294fd2a4ff96a98d1bdecdcd619fc6368dff"},
{"name":"oauth2","version":"2.0.9","platform":"ruby","checksum":"b21f9defcf52dc1610e0dfab4c868342173dcd707fd15c777d9f4f04e153f7fb"},
{"name":"octokit","version":"4.25.1","platform":"ruby","checksum":"c02092ee82dcdfe84db0e0ea630a70d32becc54245a4f0bacfd21c010df09b96"},
{"name":"octokit","version":"6.1.1","platform":"ruby","checksum":"920e4a9d820205f70738f58de6a7e6ef0e2f25b27db954b5806a63105207b0bf"},
{"name":"ohai","version":"17.9.0","platform":"ruby","checksum":"c59cf16124c0a6481fb85013ec7ec5b398651b6abed782d3e06ab058ce9a5406"},
{"name":"oj","version":"3.13.23","platform":"ruby","checksum":"206dfdc4020ad9974705037f269cfba211d61b7662a58c717cce771829ccef51"},
{"name":"oj-introspect","version":"0.7.2","platform":"ruby","checksum":"c415a44567ed2870d8e963a69421d9322128e194fab7867e37e54d5a25d5333d"},

View File

@ -389,18 +389,18 @@ GEM
css_parser (1.14.0)
addressable
cvss-suite (3.0.1)
danger (8.6.1)
danger (9.3.1)
claide (~> 1.0)
claide-plugins (>= 0.9.2)
colored2 (~> 3.1)
cork (~> 0.1)
faraday (>= 0.9.0, < 2.0)
faraday (>= 0.9.0, < 3.0)
faraday-http-cache (~> 2.0)
git (~> 1.7)
git (~> 1.13)
kramdown (~> 2.3)
kramdown-parser-gfm (~> 1.0)
no_proxy_fix
octokit (~> 4.7)
octokit (~> 6.0)
terminal-table (>= 1, < 4)
danger-gitlab (8.0.0)
danger
@ -634,7 +634,8 @@ GEM
gettext_i18n_rails (>= 0.7.1)
po_to_json (>= 1.0.0)
rails (>= 3.2.0)
git (1.11.0)
git (1.18.0)
addressable (~> 2.8)
rchardet (~> 1.8)
gitaly (16.3.0.pre.rc1)
grpc (~> 1.0)
@ -1074,7 +1075,7 @@ GEM
rack (>= 1.2, < 4)
snaky_hash (~> 2.0)
version_gem (~> 1.1)
octokit (4.25.1)
octokit (6.1.1)
faraday (>= 1, < 3)
sawyer (~> 0.9)
ohai (17.9.0)
@ -1911,7 +1912,7 @@ DEPENDENCIES
net-protocol (~> 0.1.3)
nokogiri (~> 1.15, >= 1.15.4)
oauth2 (~> 2.0)
octokit (~> 4.15)
octokit (~> 6.0)
ohai (~> 17.9)
oj (~> 3.13.21)
oj-introspect (~> 0.7)

View File

@ -33,10 +33,16 @@ export default async function initPipelineDetailsBundle() {
if (tabsEl) {
const { dataset } = tabsEl;
const dismissalDescriptions = JSON.parse(dataset.dismissalDescriptions || '{}');
const { createAppOptions } = await import('ee_else_ce/ci/pipeline_details/pipeline_tabs');
const { createPipelineTabs } = await import('./pipeline_tabs');
const { routes } = await import('ee_else_ce/ci/pipeline_details/routes');
const securityRoute = routes.find((route) => route.path === '/security');
if (securityRoute) {
securityRoute.props = { dismissalDescriptions };
}
const router = new VueRouter({
mode: 'history',
base: dataset.pipelinePath,

View File

@ -52,7 +52,7 @@ export default {
};
</script>
<template>
<div class="gl-display-flex gl-flex-wrap gl-gap-3">
<div class="gl-display-flex gl-flex-wrap gl-gap-3 gl-mb-4">
<gl-button
v-if="showFileTreeToggle"
id="file-tree-toggle"

View File

@ -33,7 +33,7 @@ export default {
</script>
<template>
<gl-card
class="gl-new-card gl-mb-3"
class="gl-new-card gl-mb-3 gl-mt-0"
header-class="gl-new-card-header"
body-class="gl-new-card-body gl-py-4 gl-px-5"
>

View File

@ -23,9 +23,6 @@ export default {
currentProject: {
default: () => ({}),
},
currentBranch: {
default: () => ({}),
},
inputs: {
default: () => ({}),
},
@ -39,6 +36,13 @@ export default {
default: '',
},
},
props: {
currentBranch: {
type: Object,
required: false,
default: () => ({}),
},
},
data() {
return {
selectedProject: this.currentProject,
@ -57,6 +61,12 @@ export default {
return this.commitHtml || this.loading || !this.selectedBranch.value;
},
},
watch: {
currentBranch(newVal) {
this.selectedBranch = newVal;
this.fetchCommit();
},
},
mounted() {
this.fetchCommit();
},
@ -67,6 +77,7 @@ export default {
selectBranch(branch) {
this.selectedBranch = branch;
this.fetchCommit();
this.$emit('select-branch', branch.value);
},
async fetchCommit() {
if (!this.selectedBranch.value) return;
@ -109,6 +120,7 @@ export default {
:default="currentBranch"
:toggle-class="toggleClass.branch"
:qa-selector="branchQaSelector"
data-testid="compare-dropdown"
@selected="selectBranch"
/>
</div>

View File

@ -70,6 +70,12 @@ export default {
);
},
},
watch: {
default(newVal) {
this.current = newVal;
this.selected = newVal.value;
},
},
methods: {
async fetchData() {
if (!this.endpoint) return;

View File

@ -0,0 +1 @@
export const findTargetBranch = async () => {};

View File

@ -2,6 +2,8 @@ import Vue from 'vue';
import { mountMarkdownEditor } from 'ee_else_ce/vue_shared/components/markdown/mount_markdown_editor';
import { findTargetBranch } from 'ee_else_ce/pages/projects/merge_requests/creations/new/branch_finder';
import initPipelines from '~/commit/pipelines/pipelines_bundle';
import MergeRequest from '~/merge_request';
import CompareApp from '~/merge_requests/components/compare_app.vue';
@ -13,14 +15,15 @@ if (mrNewCompareNode) {
const targetCompareEl = document.getElementById('js-target-project-dropdown');
const sourceCompareEl = document.getElementById('js-source-project-dropdown');
const compareEl = document.querySelector('.js-merge-request-new-compare');
const targetBranch = Vue.observable({ name: '' });
const currentSourceBranch = JSON.parse(sourceCompareEl.dataset.currentBranch);
// eslint-disable-next-line no-new
new Vue({
el: sourceCompareEl,
name: 'SourceCompareApp',
provide: {
currentProject: JSON.parse(sourceCompareEl.dataset.currentProject),
currentBranch: JSON.parse(sourceCompareEl.dataset.currentBranch),
branchCommitPath: compareEl.dataset.sourceBranchUrl,
inputs: {
project: {
@ -42,18 +45,34 @@ if (mrNewCompareNode) {
},
branchQaSelector: 'source_branch_dropdown',
},
methods: {
async selectedBranch(branchName) {
const targetBranchName = await findTargetBranch(branchName);
if (targetBranchName) {
targetBranch.name = targetBranchName;
}
},
},
render(h) {
return h(CompareApp);
return h(CompareApp, {
props: {
currentBranch: currentSourceBranch,
},
on: {
'select-branch': this.selectedBranch,
},
});
},
});
const currentTargetBranch = JSON.parse(targetCompareEl.dataset.currentBranch);
// eslint-disable-next-line no-new
new Vue({
el: targetCompareEl,
name: 'TargetCompareApp',
provide: {
currentProject: JSON.parse(targetCompareEl.dataset.currentProject),
currentBranch: JSON.parse(targetCompareEl.dataset.currentBranch),
projectsPath: targetCompareEl.dataset.targetProjectsPath,
branchCommitPath: compareEl.dataset.targetBranchUrl,
inputs: {
@ -75,8 +94,17 @@ if (mrNewCompareNode) {
branch: 'js-target-branch gl-font-monospace',
},
},
computed: {
currentBranch() {
if (targetBranch.name) {
return { text: targetBranch.name, value: targetBranch.name };
}
return currentTargetBranch;
},
},
render(h) {
return h(CompareApp);
return h(CompareApp, { props: { currentBranch: this.currentBranch } });
},
});
} else {

View File

@ -1,5 +1,14 @@
<script>
import { GlButton, GlForm, GlFormGroup, GlFormInputGroup, GlFormInput } from '@gitlab/ui';
import {
GlButton,
GlForm,
GlFormGroup,
GlFormInputGroup,
GlFormInput,
GlLink,
GlSprintf,
} from '@gitlab/ui';
import { helpPagePath } from '~/helpers/help_page_helper';
import { isEmptyValue, hasMinimumLength, isIntegerGreaterThan, isEmail } from '~/lib/utils/forms';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import {
@ -23,6 +32,9 @@ import {
} from '../custom_email_constants';
export default {
customEmailHelpUrl: helpPagePath('user/project/service_desk/configure.html', {
anchor: 'custom-email-address',
}),
components: {
ClipboardButton,
GlButton,
@ -30,6 +42,8 @@ export default {
GlFormGroup,
GlFormInputGroup,
GlFormInput,
GlLink,
GlSprintf,
},
I18N_FORM_INTRODUCTION_PARAGRAPH,
I18N_FORM_CUSTOM_EMAIL_LABEL,
@ -137,7 +151,19 @@ export default {
<template>
<div>
<p>{{ $options.I18N_FORM_INTRODUCTION_PARAGRAPH }}</p>
<p>
<gl-sprintf :message="$options.I18N_FORM_INTRODUCTION_PARAGRAPH">
<template #link="{ content }">
<gl-link
:href="$options.customEmailHelpUrl"
class="gl-display-inline-block"
target="_blank"
>
{{ content }}
</gl-link>
</template>
</gl-sprintf>
</p>
<gl-form class="js-quick-submit" @submit.prevent="onSubmit">
<gl-form-group
:label="$options.I18N_FORM_FORWARDING_LABEL"

View File

@ -233,6 +233,7 @@ export default {
<gl-link
:href="$options.FEEDBACK_ISSUE_URL"
target="_blank"
data-testid="feedback-link"
class="gl-text-blue-600 font-size-inherit"
>{{ content }}
</gl-link>

View File

@ -17,7 +17,7 @@ export const I18N_TOAST_ENABLED = s__('ServiceDesk|Custom email enabled.');
export const I18N_TOAST_DISABLED = s__('ServiceDesk|Custom email disabled.');
export const I18N_FORM_INTRODUCTION_PARAGRAPH = s__(
'ServiceDesk|Connect a custom email address your customers can use to create Service Desk issues. Forward all emails from your custom email address to the Service Desk email address of this project. GitLab will send Service Desk emails from the custom address on your behalf using your SMTP credentials.',
'ServiceDesk|Connect a custom email address your customers can use to create Service Desk issues. Forward all emails from your custom email address to the Service Desk email address of this project. GitLab will send Service Desk emails from the custom address on your behalf using your SMTP credentials. %{linkStart}Learn more about prerequisites and the verification process%{linkEnd}.',
);
export const I18N_FORM_FORWARDING_LABEL = s__(
'ServiceDesk|Service Desk email address to forward emails to',

View File

@ -1,6 +1,6 @@
<script>
import { GlAvatar, GlButton, GlIcon, GlBadge, GlTooltipDirective } from '@gitlab/ui';
import { s__ } from '~/locale';
import { s__, sprintf } from '~/locale';
import {
CLICK_MENU_ITEM_ACTION,
CLICK_PINNED_MENU_ITEM_ACTION,
@ -12,7 +12,9 @@ import NavItemRouterLink from './nav_item_router_link.vue';
export default {
i18n: {
pin: s__('Navigation|Pin %{title}'),
pinItem: s__('Navigation|Pin item'),
unpin: s__('Navigation|Unpin %{title}'),
unpinItem: s__('Navigation|Unpin item'),
},
name: 'NavItem',
@ -143,6 +145,16 @@ export default {
avatarShape() {
return this.item.avatar_shape || 'rect';
},
pinAriaLabel() {
return sprintf(this.$options.i18n.pin, {
title: this.item.title,
});
},
unpinAriaLabel() {
return sprintf(this.$options.i18n.unpin, {
title: this.item.title,
});
},
},
mounted() {
if (this.item.is_active) {
@ -151,26 +163,27 @@ export default {
},
methods: {
togglePointerEvents() {
if (this.isMouseIn) {
this.canClickPinButton = true;
} else {
this.canClickPinButton = false;
}
this.canClickPinButton = this.isMouseIn;
},
},
};
</script>
<template>
<li @mouseenter="isMouseIn = true" @mouseleave="isMouseIn = false">
<li
class="gl-relative show-on-focus-or-hover--context hide-on-focus-or-hover--context transition-opacity-on-hover--context"
data-testid="nav-item"
@mouseenter="isMouseIn = true"
@mouseleave="isMouseIn = false"
>
<component
:is="navItemLinkComponent"
#default="{ isActive }"
v-bind="linkProps"
class="nav-item-link gl-relative gl-display-flex gl-align-items-center gl-min-h-7 gl-gap-3 gl-mb-1 gl-py-2 gl-text-black-normal! gl-hover-bg-t-gray-a-08 gl-focus-bg-t-gray-a-08 gl-text-decoration-none! gl-focus--focus show-on-focus-or-hover--context"
class="gl-relative gl-display-flex gl-align-items-center gl-min-h-7 gl-gap-3 gl-mb-1 gl-py-2 gl-text-black-normal! gl-hover-bg-t-gray-a-08 gl-focus-bg-t-gray-a-08 gl-text-decoration-none! gl-focus--focus show-on-focus-or-hover--control hide-on-focus-or-hover--control"
:class="computedLinkClasses"
data-qa-selector="nav_item_link"
data-testid="nav-item-link"
data-qa-selector="nav_item_link"
>
<div
:class="[isActive ? 'gl-opacity-10' : 'gl-opacity-0']"
@ -204,41 +217,47 @@ export default {
</div>
</div>
<slot name="actions"></slot>
<span v-if="hasPill || isPinnable" class="gl-text-right gl-relative gl-min-w-8">
<span v-if="hasPill" class="gl-text-right gl-relative gl-min-w-8">
<gl-badge
v-if="hasPill"
size="sm"
variant="neutral"
class="gl-bg-t-gray-a-08!"
:class="{ 'nav-item-badge gl-absolute gl-right-0 gl-top-2': isPinnable }"
:class="{
'hide-on-focus-or-hover--target transition-opacity-on-hover--target': isPinnable,
}"
>
{{ pillData }}
</gl-badge>
<gl-button
v-if="isPinnable && !isPinned"
v-gl-tooltip.ds500.right.viewport="$options.i18n.pinItem"
size="small"
category="tertiary"
icon="thumbtack"
class="show-on-focus-or-hover--target"
:class="{ 'gl-pointer-events-none': !canClickPinButton }"
:aria-label="$options.i18n.pinItem"
@click.prevent="$emit('pin-add', item.id)"
@transitionend="togglePointerEvents"
/>
<gl-button
v-else-if="isPinnable && isPinned"
v-gl-tooltip.ds500.right.viewport="$options.i18n.unpinItem"
size="small"
category="tertiary"
:aria-label="$options.i18n.unpinItem"
icon="thumbtack-solid"
class="show-on-focus-or-hover--target"
:class="{ 'gl-pointer-events-none': !canClickPinButton }"
@click.prevent="$emit('pin-remove', item.id)"
@transitionend="togglePointerEvents"
/>
</span>
</component>
<template v-if="isPinnable">
<gl-button
v-if="isPinned"
v-gl-tooltip.noninteractive.ds500.right.viewport="$options.i18n.unpinItem"
:aria-label="unpinAriaLabel"
category="tertiary"
class="show-on-focus-or-hover--target transition-opacity-on-hover--target always-animate gl-absolute gl-right-3 gl-top-2"
:class="{ 'gl-pointer-events-none': !canClickPinButton }"
data-testid="nav-item-unpin"
icon="thumbtack-solid"
size="small"
@click="$emit('pin-remove', item.id)"
@transitionend="togglePointerEvents"
/>
<gl-button
v-else
v-gl-tooltip.noninteractive.ds500.right.viewport="$options.i18n.pinItem"
:aria-label="pinAriaLabel"
category="tertiary"
class="show-on-focus-or-hover--target transition-opacity-on-hover--target always-animate gl-absolute gl-right-3 gl-top-2"
:class="{ 'gl-pointer-events-none': !canClickPinButton }"
data-testid="nav-item-pin"
icon="thumbtack"
size="small"
@click="$emit('pin-add', item.id)"
@transitionend="togglePointerEvents"
/>
</template>
</li>
</template>

View File

@ -150,7 +150,7 @@ export default {
<user-bar :has-collapse-button="!showOverlay" :sidebar-data="sidebarData" />
<div v-if="showTrialStatusWidget" class="gl-px-2 gl-py-2">
<trial-status-widget
class="gl-rounded-base gl-relative gl-display-flex gl-align-items-center gl-mb-1 gl-px-3 gl-line-height-normal gl-text-black-normal! gl-hover-bg-t-gray-a-08 gl-focus-bg-t-gray-a-08 gl-text-decoration-none! nav-item-link gl-py-3"
class="gl-rounded-base gl-relative gl-display-flex gl-align-items-center gl-mb-1 gl-px-3 gl-line-height-normal gl-text-black-normal! gl-hover-bg-t-gray-a-08 gl-focus-bg-t-gray-a-08 gl-text-decoration-none! gl-py-3"
/>
<trial-status-popover />
</div>

View File

@ -1,6 +1,6 @@
*:not(
/* Keep transition enabled where it would otherwise break specs */
.nav-item-link .show-on-focus-or-hover--target /* for spec/features/nav/pinned_nav_items_spec.rb */
.always-animate
) {
/* stylelint-disable property-no-vendor-prefix */
-o-transition: none !important;

View File

@ -158,23 +158,6 @@ $super-sidebar-transition-hint-duration: $super-sidebar-transition-duration / 4;
}
}
.nav-item-link {
button.show-on-focus-or-hover--target {
transition: opacity $gl-transition-duration-fast;
}
&:hover,
&:focus-within {
.nav-item-badge {
opacity: 0;
}
button.show-on-focus-or-hover--target {
transition-delay: $gl-transition-duration-slow;
}
}
}
#trial-status-sidebar-widget:hover {
text-decoration: none;
@include gl-text-contrast-light;
@ -320,19 +303,71 @@ $super-sidebar-transition-hint-duration: $super-sidebar-transition-duration / 4;
}
}
.transition-opacity-on-hover--context {
.transition-opacity-on-hover--target {
transition: opacity $gl-transition-duration-fast linear;
&:hover {
transition-delay: $gl-transition-duration-slow;
}
}
&:hover {
.transition-opacity-on-hover--target {
transition-delay: $gl-transition-duration-slow;
}
}
}
.show-on-focus-or-hover--context {
.show-on-focus-or-hover--target {
opacity: 0;
}
&:hover,
&:focus {
opacity: 1;
}
}
&:hover,
&:focus-within {
.show-on-focus-or-hover--control {
@include gl-bg-t-gray-a-08;
}
.show-on-focus-or-hover--target {
opacity: 1;
}
}
.show-on-focus-or-hover--target:focus {
.show-on-focus-or-hover--control {
&:hover,
&:focus {
+ .show-on-focus-or-hover--target {
opacity: 1;
}
}
}
}
.hide-on-focus-or-hover--context {
.hide-on-focus-or-hover--target {
opacity: 1;
}
&:hover,
&:focus-within {
.hide-on-focus-or-hover--target {
opacity: 0;
}
}
.hide-on-focus-or-hover--control {
&:hover,
&:focus {
.hide-on-focus-or-hover--target {
opacity: 0;
}
}
}
}

View File

@ -9,7 +9,7 @@ class HelpController < ApplicationController
# Taken from Jekyll
# https://github.com/jekyll/jekyll/blob/3.5-stable/lib/jekyll/document.rb#L13
YAML_FRONT_MATTER_REGEXP = /\A(---\s*\n.*?\n?)^((---|\.\.\.)\s*$\n?)/m.freeze
YAML_FRONT_MATTER_REGEXP = /\A(---\s*\n.*?\n?)^((---|\.\.\.)\s*$\n?)/m
def index
@help_index = get_markdown_without_frontmatter(path_to_doc('index.md'))

View File

@ -22,8 +22,8 @@ class Import::BitbucketServerController < Import::BaseController
# (https://community.atlassian.com/t5/Answers-Developer-Questions/stash-repository-names/qaq-p/499054)
#
# Bitbucket Server starts personal project names with a tilde.
VALID_BITBUCKET_PROJECT_CHARS = /\A~?[\w\-\.\s]+\z/.freeze
VALID_BITBUCKET_CHARS = /\A[\w\-\.\s]+\z/.freeze
VALID_BITBUCKET_PROJECT_CHARS = /\A~?[\w\-\.\s]+\z/
VALID_BITBUCKET_CHARS = /\A[\w\-\.\s]+\z/
def new
end

View File

@ -48,7 +48,7 @@ class IssuableFinder
requires_cross_project_access unless: -> { params.project? }
FULL_TEXT_SEARCH_TERM_PATTERN = '[\u0000-\u02FF\u1E00-\u1EFF\u2070-\u218F]*'
FULL_TEXT_SEARCH_TERM_REGEX = /\A#{FULL_TEXT_SEARCH_TERM_PATTERN}\z/.freeze
FULL_TEXT_SEARCH_TERM_REGEX = /\A#{FULL_TEXT_SEARCH_TERM_PATTERN}\z/
NEGATABLE_PARAMS_HELPER_KEYS = %i[project_id scope status include_subgroups].freeze
attr_accessor :current_user, :params

View File

@ -21,7 +21,7 @@ module Repositories
COMMITS_PER_PAGE = 1024
# The regex to use for extracting the SHA of a reverted commit.
REVERT_REGEX = /^This reverts commit (?<sha>[0-9a-f]{40})/i.freeze
REVERT_REGEX = /^This reverts commit (?<sha>[0-9a-f]{40})/i
# The `project` argument specifies the project for which to obtain the
# commits.

View File

@ -19,7 +19,7 @@ module AuthHelper
shibboleth
twitter
].freeze
LDAP_PROVIDER = /\Aldap/.freeze
LDAP_PROVIDER = /\Aldap/
POPULAR_PROVIDERS = %w[google_oauth2 github].freeze
delegate :slack_app_id, to: :'Gitlab::CurrentSettings.current_application_settings'

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true
module ColorsHelper
HEX_COLOR_PATTERN = /\A\#(?:[0-9A-Fa-f]{3}){1,2}\Z/.freeze
HEX_COLOR_PATTERN = /\A\#(?:[0-9A-Fa-f]{3}){1,2}\Z/
def hex_color_to_rgb_array(hex_color)
unless hex_color.is_a?(String) && HEX_COLOR_PATTERN.match?(hex_color)

View File

@ -226,6 +226,13 @@ module MergeRequestsHelper
}
end
def mr_compare_form_data(_, merge_request)
{
source_branch_url: project_new_merge_request_branch_from_path(merge_request.source_project),
target_branch_url: project_new_merge_request_branch_to_path(merge_request.source_project)
}
end
private
def review_requested_merge_requests_count

View File

@ -8,7 +8,7 @@ module SidekiqHelper
(?<state>[DIEKNRSTVWXZLpsl\+<>/\d]+)\s+
(?<start>.+?)\s+
(?<command>(?:ruby\d+:\s+)?sidekiq.*\].*)
\z}x.freeze
\z}x
def parse_sidekiq_ps(line)
match = line.strip.match(SIDEKIQ_PS_REGEXP)

View File

@ -0,0 +1,30 @@
# frozen_string_literal: true
module Analytics
module CycleAnalytics
class RuntimeLimiter
delegate :monotonic_time, to: :'Gitlab::Metrics::System'
DEFAULT_MAX_RUNTIME = 200.seconds
attr_reader :max_runtime, :start_time
def initialize(max_runtime = DEFAULT_MAX_RUNTIME)
@start_time = monotonic_time
@max_runtime = max_runtime
end
def elapsed_time
monotonic_time - start_time
end
def over_time?
@last_check = elapsed_time >= max_runtime
end
def was_over_time?
!!@last_check
end
end
end
end

View File

@ -9,7 +9,7 @@ module ApplicationSettingImplementation
\s # any whitespace character
| # or
[\r\n] # any number of newline characters
}x.freeze
}x
# Setting a key restriction to `-1` means that all keys of this type are
# forbidden.

View File

@ -18,7 +18,7 @@ class Badge < ApplicationRecord
# This regex is built dynamically using the keys from the PLACEHOLDER struct.
# So, we can easily add new placeholder just by modifying the PLACEHOLDER hash.
# This regex will build the new PLACEHOLDER_REGEX with the new information
PLACEHOLDERS_REGEX = /(#{PLACEHOLDERS.keys.join('|')})/.freeze
PLACEHOLDERS_REGEX = /(#{PLACEHOLDERS.keys.join('|')})/
default_scope { order_created_at_asc } # rubocop:disable Cop/DefaultScope

View File

@ -11,7 +11,7 @@ module BlobViewer
(?<name>.*?) (?# module name)
\s*(?://.*)? (?# comment)
(?:\n|\z) (?# newline or end of file)
}x.freeze
}x
self.file_types = %i[go_mod go_sum]

View File

@ -18,6 +18,8 @@ module BulkImports
event :start do
transition created: :started
# To avoid errors when re-starting a pipeline in case of network errors
transition started: :started
end
event :retry do

View File

@ -52,7 +52,7 @@ module Ci
RUNNER_QUEUE_EXPIRY_TIME = 1.hour
# The `UPDATE_CONTACT_COLUMN_EVERY` defines how often the Runner DB entry can be updated
UPDATE_CONTACT_COLUMN_EVERY = (40.minutes..55.minutes).freeze
UPDATE_CONTACT_COLUMN_EVERY = (40.minutes..55.minutes)
# The `STALE_TIMEOUT` constant defines the how far past the last contact or creation date a runner will be considered stale
STALE_TIMEOUT = 3.months

View File

@ -30,10 +30,10 @@ class Commit
MIN_SHA_LENGTH = Gitlab::Git::Commit::MIN_SHA_LENGTH
MAX_SHA_LENGTH = Gitlab::Git::Commit::MAX_SHA_LENGTH
COMMIT_SHA_PATTERN = Gitlab::Git::Commit::SHA_PATTERN.freeze
EXACT_COMMIT_SHA_PATTERN = /\A#{COMMIT_SHA_PATTERN}\z/.freeze
COMMIT_SHA_PATTERN = Gitlab::Git::Commit::SHA_PATTERN
EXACT_COMMIT_SHA_PATTERN = /\A#{COMMIT_SHA_PATTERN}\z/
# Used by GFM to match and present link extensions on node texts and hrefs.
LINK_EXTENSION_PATTERN = /(patch)/.freeze
LINK_EXTENSION_PATTERN = /(patch)/
DEFAULT_MAX_DIFF_LINES_SETTING = 50_000
DEFAULT_MAX_DIFF_FILES_SETTING = 1_000
@ -539,7 +539,7 @@ class Commit
# added by `git commit --fixup` which is used by some community members.
# https://gitlab.com/gitlab-org/gitlab/-/issues/342937#note_892065311
#
DRAFT_REGEX = /\A\s*#{Gitlab::Regex.merge_request_draft}|(fixup!|squash!)\s/.freeze
DRAFT_REGEX = /\A\s*#{Gitlab::Regex.merge_request_draft}|(fixup!|squash!)\s/
def draft?
!!(title =~ DRAFT_REGEX)

View File

@ -28,11 +28,11 @@ class CommitRange
# The beginning and ending refs can be named or SHAs, and
# the range notation can be double- or triple-dot.
REF_PATTERN = /[0-9a-zA-Z][0-9a-zA-Z_.-]*[0-9a-zA-Z\^]/.freeze
PATTERN = /#{REF_PATTERN}\.{2,3}#{REF_PATTERN}/.freeze
REF_PATTERN = /[0-9a-zA-Z][0-9a-zA-Z_.-]*[0-9a-zA-Z\^]/
PATTERN = /#{REF_PATTERN}\.{2,3}#{REF_PATTERN}/
# In text references, the beginning and ending refs can only be valid SHAs.
STRICT_PATTERN = /#{Gitlab::Git::Commit::RAW_SHA_PATTERN}\.{2,3}#{Gitlab::Git::Commit::RAW_SHA_PATTERN}/.freeze
STRICT_PATTERN = /#{Gitlab::Git::Commit::RAW_SHA_PATTERN}\.{2,3}#{Gitlab::Git::Commit::RAW_SHA_PATTERN}/
def self.reference_prefix
'@'

View File

@ -11,12 +11,12 @@ module Ci
# * Minimal length of 8 characters
# * Characters must be from the Base64 alphabet (RFC4648) with the addition of '@', ':', '.', and '~'
# * Absolutely no fun is allowed
REGEX = %r{\A[a-zA-Z0-9_+=/@:.~-]{8,}\z}.freeze
REGEX = %r{\A[a-zA-Z0-9_+=/@:.~-]{8,}\z}
# * Single line
# * No spaces
# * Minimal length of 8 characters
# * Some fun is allowed
MASK_AND_RAW_REGEX = %r{\A\S{8,}\z}.freeze
MASK_AND_RAW_REGEX = %r{\A\S{8,}\z}
included do
validates :masked, inclusion: { in: [true, false] }

View File

@ -21,11 +21,11 @@
module PgFullTextSearchable
extend ActiveSupport::Concern
LONG_WORDS_REGEX = %r([A-Za-z0-9+/@]{50,}).freeze
LONG_WORDS_REGEX = %r([A-Za-z0-9+/@]{50,})
TSVECTOR_MAX_LENGTH = 1.megabyte.freeze
TEXT_SEARCH_DICTIONARY = 'english'
URL_SCHEME_REGEX = %r{(?<=\A|\W)\w+://(?=\w+)}.freeze
TSQUERY_DISALLOWED_CHARACTERS_REGEX = %r{[^a-zA-Z0-9 .@/\-_"]}.freeze
URL_SCHEME_REGEX = %r{(?<=\A|\W)\w+://(?=\w+)}
TSQUERY_DISALLOWED_CHARACTERS_REGEX = %r{[^a-zA-Z0-9 .@/\-_"]}
def update_search_data!
tsvector_sql_nodes = self.class.pg_full_text_searchable_columns.map do |column, weight|

View File

@ -10,7 +10,7 @@
module Redactable
extend ActiveSupport::Concern
UNSUBSCRIBE_PATTERN = %r{/sent_notifications/\h{32}/unsubscribe}.freeze
UNSUBSCRIBE_PATTERN = %r{/sent_notifications/\h{32}/unsubscribe}
class_methods do
def redact_field(field)

View File

@ -11,8 +11,8 @@ require 'task_list/filter'
module Taskable
COMPLETED = 'completed'
INCOMPLETE = 'incomplete'
COMPLETE_PATTERN = /\[[xX]\]/.freeze
INCOMPLETE_PATTERN = /\[[[:space:]]\]/.freeze
COMPLETE_PATTERN = /\[[xX]\]/
INCOMPLETE_PATTERN = /\[[[:space:]]\]/
ITEM_PATTERN = %r{
^
(?:(?:>\s{0,4})*) # optional blockquote characters
@ -22,7 +22,7 @@ module Taskable
#{COMPLETE_PATTERN}|#{INCOMPLETE_PATTERN}
)
(\s.+) # followed by whitespace and some text.
}x.freeze
}x
ITEM_PATTERN_UNTRUSTED =
'^' \

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true
class CustomEmoji < ApplicationRecord
NAME_REGEXP = /[a-z0-9_-]+/.freeze
NAME_REGEXP = /[a-z0-9_-]+/
belongs_to :namespace, inverse_of: :custom_emoji

View File

@ -79,7 +79,7 @@ class EnvironmentStatus
private
PAGE_EXTENSIONS = /\A\.(s?html?|php|asp|cgi|pl)\z/i.freeze
PAGE_EXTENSIONS = /\A\.(s?html?|php|asp|cgi|pl)\z/i
def deployment_metrics
@deployment_metrics ||= DeploymentMetrics.new(project, deployment)

View File

@ -19,7 +19,7 @@ module ErrorTracking
(?<project>[^/]+)/*
)?
\z
}x.freeze
}x
self.reactive_cache_key = ->(setting) { [setting.class.model_name.singular, setting.project_id] }
self.reactive_cache_work_type = :external_dependency

View File

@ -10,6 +10,7 @@ class Event < ApplicationRecord
include UsageStatistics
include ShaAttribute
include IgnorableColumns
include EachBatch
ignore_column :target_id_convert_to_bigint, remove_with: '16.4', remove_after: '2023-09-22'

View File

@ -103,7 +103,7 @@ class WebHook < ApplicationRecord
end
# See app/validators/json_schemas/web_hooks_url_variables.json
VARIABLE_REFERENCE_RE = /\{([A-Za-z]+[0-9]*(?:[._-][A-Za-z0-9]+)*)\}/.freeze
VARIABLE_REFERENCE_RE = /\{([A-Za-z]+[0-9]*(?:[._-][A-Za-z0-9]+)*)\}/
def interpolated_url(url = self.url, url_variables = self.url_variables)
return url unless url.include?('{')

View File

@ -4,8 +4,8 @@ require 'app_store_connect'
module Integrations
class AppleAppStore < Integration
ISSUER_ID_REGEX = /\A[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\z/.freeze
KEY_ID_REGEX = /\A(?=.*[A-Z])(?=.*[0-9])[A-Z0-9]+\z/.freeze
ISSUER_ID_REGEX = /\A[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\z/
KEY_ID_REGEX = /\A(?=.*[A-Z])(?=.*[0-9])[A-Z0-9]+\z/
IS_KEY_CONTENT_BASE64 = "true"
SECTION_TYPE_APPLE_APP_STORE = 'apple_app_store'

View File

@ -3,7 +3,7 @@
module Integrations
module ChatMessage
class BaseMessage
RELATIVE_LINK_REGEX = %r{!\[[^\]]*\]\((/uploads/[^\)]*)\)}.freeze
RELATIVE_LINK_REGEX = %r{!\[[^\]]*\]\((/uploads/[^\)]*)\)}
attr_reader :markdown
attr_reader :user_full_name

View File

@ -2,9 +2,9 @@
module Integrations
class Confluence < BaseThirdPartyWiki
VALID_SCHEME_MATCH = %r{\Ahttps?\Z}.freeze
VALID_HOST_MATCH = %r{\A.+\.atlassian\.net\Z}.freeze
VALID_PATH_MATCH = %r{\A/wiki(/|\Z)}.freeze
VALID_SCHEME_MATCH = %r{\Ahttps?\Z}
VALID_HOST_MATCH = %r{\A.+\.atlassian\.net\Z}
VALID_PATH_MATCH = %r{\A/wiki(/|\Z)}
validates :confluence_url, presence: true, if: :activated?
validate :validate_confluence_url_is_cloud, if: :activated?

View File

@ -12,7 +12,7 @@ module Integrations
pipeline build archive_trace
].freeze
TAG_KEY_VALUE_RE = %r{\A [\w-]+ : .*\S.* \z}x.freeze
TAG_KEY_VALUE_RE = %r{\A [\w-]+ : .*\S.* \z}x
field :datadog_site,
exposes_secrets: true,

View File

@ -4,7 +4,7 @@ require "discordrb/webhooks"
module Integrations
class Discord < BaseChatNotification
ATTACHMENT_REGEX = /: (?<entry>.*?)\n - (?<name>.*)\n*/.freeze
ATTACHMENT_REGEX = /: (?<entry>.*?)\n - (?<name>.*)\n*/
field :webhook,
section: SECTION_TYPE_CONNECTION,

View File

@ -6,7 +6,7 @@ module Integrations
include ReactivelyCached
prepend EnableSslVerification
TEAMCITY_SAAS_HOSTNAME = /\A[^\.]+\.teamcity\.com\z/i.freeze
TEAMCITY_SAAS_HOSTNAME = /\A[^\.]+\.teamcity\.com\z/i
field :teamcity_url,
title: -> { s_('ProjectService|TeamCity server URL') },

View File

@ -5,12 +5,12 @@ class LicenseTemplate
%r{[\<\{\[]
(project|description|
one\sline\s.+\swhat\sit\sdoes\.) # matching the start and end is enough here
[\>\}\]]}xi.freeze
YEAR_TEMPLATE_REGEX = /[<{\[](year|yyyy)[>}\]]/i.freeze
[\>\}\]]}xi
YEAR_TEMPLATE_REGEX = /[<{\[](year|yyyy)[>}\]]/i
FULLNAME_TEMPLATE_REGEX =
%r{[\<\{\[]
(fullname|name\sof\s(author|copyright\sowner))
[\>\}\]]}xi.freeze
[\>\}\]]}xi
attr_reader :key, :name, :project, :category, :nickname, :url, :meta

View File

@ -5,7 +5,7 @@ class GroupMember < Member
include CreatedAtFilterable
SOURCE_TYPE = 'Namespace'
SOURCE_TYPE_FORMAT = /\ANamespace\z/.freeze
SOURCE_TYPE_FORMAT = /\ANamespace\z/
belongs_to :group, foreign_key: 'source_id'
alias_attribute :namespace_id, :source_id

View File

@ -2,7 +2,7 @@
class ProjectMember < Member
SOURCE_TYPE = 'Project'
SOURCE_TYPE_FORMAT = /\AProject\z/.freeze
SOURCE_TYPE_FORMAT = /\AProject\z/
belongs_to :project, foreign_key: 'source_id'

View File

@ -643,7 +643,7 @@ class MergeRequest < ApplicationRecord
end
end
DRAFT_REGEX = /\A*#{Gitlab::Regex.merge_request_draft}+\s*/i.freeze
DRAFT_REGEX = /\A*#{Gitlab::Regex.merge_request_draft}+\s*/i
def self.draft?(title)
!!(title =~ DRAFT_REGEX)

View File

@ -3,7 +3,7 @@
module Namespaces
class RandomizedSuffixPath
MAX_TRIES = 4
LEADING_ZEROS = /^0+/.freeze
LEADING_ZEROS = /^0+/
def initialize(path)
@path = path

View File

@ -28,7 +28,7 @@ class Note < ApplicationRecord
ignore_column :id_convert_to_bigint, remove_with: '16.3', remove_after: '2023-08-22'
ISSUE_TASK_SYSTEM_NOTE_PATTERN = /\A.*marked\sthe\stask.+as\s(completed|incomplete).*\z/.freeze
ISSUE_TASK_SYSTEM_NOTE_PATTERN = /\A.*marked\sthe\stask.+as\s(completed|incomplete).*\z/
cache_markdown_field :note, pipeline: :note, issuable_reference_expansion_enabled: true

View File

@ -4,13 +4,13 @@ module Packages
module Debian
TEMPORARY_PACKAGE_NAME = 'debian-temporary-package'
DISTRIBUTION_REGEX = %r{[a-z0-9][a-z0-9.-]*}i.freeze
DISTRIBUTION_REGEX = %r{[a-z0-9][a-z0-9.-]*}i
COMPONENT_REGEX = DISTRIBUTION_REGEX.freeze
ARCHITECTURE_REGEX = %r{[a-z0-9][-a-z0-9]*}.freeze
ARCHITECTURE_REGEX = %r{[a-z0-9][-a-z0-9]*}
LETTER_REGEX = %r{(lib)?[a-z0-9]}.freeze
LETTER_REGEX = %r{(lib)?[a-z0-9]}
EMPTY_FILE_SHA256 = 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'.freeze
EMPTY_FILE_SHA256 = 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'
INCOMING_PACKAGE_NAME = 'incoming'

View File

@ -6,7 +6,7 @@ module Packages
include ActiveModel::Model
DIGESTS = %i[md5 sha1 sha256].freeze
FILENAME_REGEX = %r{\A[a-zA-Z0-9][a-zA-Z0-9_.~+-]*\z}.freeze
FILENAME_REGEX = %r{\A[a-zA-Z0-9][a-zA-Z0-9_.~+-]*\z}
attr_accessor :filename,
:size,

View File

@ -14,7 +14,7 @@ class PersonalAccessToken < ApplicationRecord
format_with_prefix: :prefix_from_application_current_settings
# PATs are 20 characters + optional configurable settings prefix (0..20)
TOKEN_LENGTH_RANGE = (20..40).freeze
TOKEN_LENGTH_RANGE = (20..40)
MAX_PERSONAL_ACCESS_TOKEN_LIFETIME_IN_DAYS = 365
serialize :scopes, Array # rubocop:disable Cop/ActiveRecordSerialize

View File

@ -8,7 +8,7 @@ module Releases
# See https://gitlab.com/gitlab-org/gitlab/-/issues/218753
# Regex modified to prevent catastrophic backtracking
FILEPATH_REGEX = %r{\A\/[^\/](?!.*\/\/.*)[\-\.\w\/]+[\da-zA-Z]+\z}.freeze
FILEPATH_REGEX = %r{\A\/[^\/](?!.*\/\/.*)[\-\.\w\/]+[\da-zA-Z]+\z}
FILEPATH_MAX_LENGTH = 128
validates :url, presence: true, addressable_url: { schemes: %w[http https ftp] }, uniqueness: { scope: :release }

View File

@ -31,6 +31,10 @@ class Review < ApplicationRecord
def user_mentions
merge_request.user_mentions.where.not(note_id: nil)
end
def from_merge_request_author?
merge_request.author_id == author_id
end
end
Review.prepend_mod

View File

@ -5,7 +5,7 @@ class SnippetRepository < ApplicationRecord
include Shardable
DEFAULT_EMPTY_FILE_NAME = 'snippetfile'
EMPTY_FILE_PATTERN = /^#{DEFAULT_EMPTY_FILE_NAME}(\d+)\.txt$/.freeze
EMPTY_FILE_PATTERN = /^#{DEFAULT_EMPTY_FILE_NAME}(\d+)\.txt$/
CommitError = Class.new(StandardError)
InvalidPathError = Class.new(CommitError)

View File

@ -5,7 +5,7 @@ module Terraform
include UsageStatistics
include AfterCommitQueue
HEX_REGEXP = %r{\A\h+\z}.freeze
HEX_REGEXP = %r{\A\h+\z}
UUID_LENGTH = 32
self.locking_column = :activerecord_lock_version

View File

@ -4,7 +4,7 @@ module Clusters
module AgentTokens
class TrackUsageService
# The `UPDATE_USED_COLUMN_EVERY` defines how often the token DB entry can be updated
UPDATE_USED_COLUMN_EVERY = (40.minutes..55.minutes).freeze
UPDATE_USED_COLUMN_EVERY = (40.minutes..55.minutes)
delegate :agent, to: :token

View File

@ -8,7 +8,7 @@ module Import
GIT_SERVICE_NAME = "git-upload-pack"
GIT_EXPECTED_FIRST_PACKET_LINE = "# service=#{GIT_SERVICE_NAME}"
GIT_BODY_MESSAGE_REGEXP = /^[0-9a-f]{4}#{GIT_EXPECTED_FIRST_PACKET_LINE}/.freeze
GIT_BODY_MESSAGE_REGEXP = /^[0-9a-f]{4}#{GIT_EXPECTED_FIRST_PACKET_LINE}/
# https://github.com/git/git/blob/master/Documentation/technical/protocol-common.txt#L56-L59
GIT_PROTOCOL_PKT_LEN = 4
GIT_MINIMUM_RESPONSE_LENGTH = GIT_PROTOCOL_PKT_LEN + GIT_EXPECTED_FIRST_PACKET_LINE.length

View File

@ -21,7 +21,7 @@ module Issues
Issues::CloseService
end
NO_REBALANCING_NEEDED = ((RelativePositioning::MIN_POSITION * 0.9999)..(RelativePositioning::MAX_POSITION * 0.9999)).freeze
NO_REBALANCING_NEEDED = ((RelativePositioning::MIN_POSITION * 0.9999)..(RelativePositioning::MAX_POSITION * 0.9999))
def rebalance_if_needed(issue)
return unless issue

View File

@ -4,7 +4,7 @@ module Projects
# Used by project imports, it removes any potential paths
# included in an error message that could be stored in the DB
class ImportErrorFilter
ERROR_MESSAGE_FILTER = /[^\s]*#{File::SEPARATOR}[^\s]*(?=(\s|\z))/.freeze
ERROR_MESSAGE_FILTER = /[^\s]*#{File::SEPARATOR}[^\s]*(?=(\s|\z))/
FILTER_MESSAGE = '[FILTERED]'
def self.filter_message(message)

View File

@ -9,7 +9,7 @@ module Projects
include Gitlab::Utils::StrongMemoize
HEAD_REV = 'HEAD'
LFS_ENDPOINT_PATTERN = /^\t?url\s*=\s*(.+)$/.freeze
LFS_ENDPOINT_PATTERN = /^\t?url\s*=\s*(.+)$/
LFS_BATCH_API_ENDPOINT = '/info/lfs/objects/batch'
LfsObjectDownloadListError = Class.new(StandardError)

View File

@ -20,8 +20,8 @@ class FileUploader < GitlabUploader
'!?\[.*?\]\(/uploads/(?P<secret>[0-9a-f]{32})/(?P<file>.*?)\)'
)
DYNAMIC_PATH_PATTERN = %r{.*(?<secret>\b(\h{10}|\h{32}))\/(?<identifier>.*)}.freeze
VALID_SECRET_PATTERN = %r{\A\h{10,32}\z}.freeze
DYNAMIC_PATH_PATTERN = %r{.*(?<secret>\b(\h{10}|\h{32}))\/(?<identifier>.*)}
VALID_SECRET_PATTERN = %r{\A\h{10,32}\z}
InvalidSecret = Class.new(StandardError)

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true
class CertificateFingerprintValidator < ActiveModel::EachValidator
FINGERPRINT_PATTERN = /\A([a-zA-Z0-9]{2}[\s\-:]?){16,}\z/.freeze
FINGERPRINT_PATTERN = /\A([a-zA-Z0-9]{2}[\s\-:]?){16,}\z/
def validate_each(record, attribute, value)
unless value.try(:match, FINGERPRINT_PATTERN)

View File

@ -10,7 +10,7 @@
# end
#
class JsonSchemaValidator < ActiveModel::EachValidator
FILENAME_ALLOWED = /\A[a-z0-9_-]*\Z/.freeze
FILENAME_ALLOWED = /\A[a-z0-9_-]*\Z/
FilenameError = Class.new(StandardError)
BASE_DIRECTORY = %w[app validators json_schemas].freeze

View File

@ -4,7 +4,7 @@
#
# Custom validator for GitLab line codes.
class LineCodeValidator < ActiveModel::EachValidator
PATTERN = /\A[a-z0-9]+_\d+_\d+\z/.freeze
PATTERN = /\A[a-z0-9]+_\d+_\d+\z/
def validate_each(record, attribute, value)
unless PATTERN.match?(value)

View File

@ -14,7 +14,7 @@
= gitlab_ui_form_for [@project, @merge_request], url: project_new_merge_request_path(@project), method: :get, html: { class: "merge-request-form js-requires-input" } do |f|
- if params[:nav_source].present?
= hidden_field_tag(:nav_source, params[:nav_source])
.js-merge-request-new-compare.row{ 'data-source-branch-url': project_new_merge_request_branch_from_path(@source_project), 'data-target-branch-url': project_new_merge_request_branch_to_path(@source_project) }
.js-merge-request-new-compare.row{ data: mr_compare_form_data(current_user, @merge_request) }
.col-lg-6
.card-new-merge-request
%h2.gl-font-size-h2

View File

@ -13,15 +13,5 @@ class BuildSuccessWorker # rubocop:disable Scalability/IdempotentWorker
queue_namespace :pipeline_processing
urgency :high
def perform(build_id)
Ci::Build.find_by_id(build_id).try do |build|
stop_environment(build) if build.stops_environment? && build.stop_action_successful?
end
end
private
def stop_environment(build)
build.persisted_environment.fire_state_event(:stop_complete)
end
def perform(build_id); end
end

View File

@ -12,6 +12,47 @@ module ClickHouse
# the job is scheduled every 3 minutes and we will allow maximum 2.5 minutes runtime
MAX_TTL = 2.5.minutes.to_i
MAX_RUNTIME = 120.seconds
BATCH_SIZE = 500
INSERT_BATCH_SIZE = 5000
CSV_MAPPING = {
id: :id,
path: :path,
author_id: :author_id,
target_id: :target_id,
target_type: :target_type,
action: :raw_action,
created_at: :casted_created_at,
updated_at: :casted_updated_at
}.freeze
# transforms the traversal_ids to a String:
# Example: group_id/subgroup_id/group_or_projectnamespace_id/
PATH_COLUMN = <<~SQL
(
CASE
WHEN project_id IS NOT NULL THEN (SELECT array_to_string(traversal_ids, '/') || '/' FROM namespaces WHERE id = (SELECT project_namespace_id FROM projects WHERE id = events.project_id LIMIT 1) LIMIT 1)
WHEN group_id IS NOT NULL THEN (SELECT array_to_string(traversal_ids, '/') || '/' FROM namespaces WHERE id = events.group_id LIMIT 1)
ELSE ''
END
) AS path
SQL
EVENT_PROJECTIONS = [
:id,
PATH_COLUMN,
:author_id,
:target_id,
:target_type,
'action AS raw_action',
'EXTRACT(epoch FROM created_at) AS casted_created_at',
'EXTRACT(epoch FROM updated_at) AS casted_updated_at'
].freeze
INSERT_EVENTS_QUERY = <<~SQL.squish
INSERT INTO events (#{CSV_MAPPING.keys.join(', ')})
SETTINGS async_insert=1, wait_for_async_insert=1 FORMAT CSV
SQL
def perform
unless enabled?
@ -22,12 +63,15 @@ module ClickHouse
metadata = { status: :processed }
# Prevent parallel jobs
begin
# Prevent parallel jobs
in_lock(self.class.to_s, ttl: MAX_TTL, retries: 0) do
true
end
loop { break unless next_batch }
metadata.merge!(records_inserted: context.total_record_count, reached_end_of_table: context.no_more_records?)
ClickHouse::SyncCursor.update_cursor_for(:events, context.last_processed_id) if context.last_processed_id
end
rescue Gitlab::ExclusiveLeaseHelpers::FailedToObtainLockError
# Skip retrying, just let the next worker to start after a few minutes
metadata = { status: :skipped }
@ -38,8 +82,51 @@ module ClickHouse
private
def context
@context ||= ClickHouse::RecordSyncContext.new(
last_record_id: ClickHouse::SyncCursor.cursor_for(:events),
max_records_per_batch: INSERT_BATCH_SIZE,
runtime_limiter: Analytics::CycleAnalytics::RuntimeLimiter.new(MAX_RUNTIME)
)
end
def enabled?
ClickHouse::Client.configuration.databases[:main].present? && Feature.enabled?(:event_sync_worker_for_click_house)
end
def next_batch
context.new_batch!
CsvBuilder::Gzip.new(process_batch(context), CSV_MAPPING).render do |tempfile, rows_written|
unless rows_written == 0
ClickHouse::Client.insert_csv(INSERT_EVENTS_QUERY, File.open(tempfile.path),
:main)
end
end
!(context.over_time? || context.no_more_records?)
end
def process_batch(context)
Enumerator.new do |yielder|
has_data = false
# rubocop: disable CodeReuse/ActiveRecord
Event.where(Event.arel_table[:id].gt(context.last_record_id)).each_batch(of: BATCH_SIZE) do |relation|
has_data = true
relation.select(*EVENT_PROJECTIONS).each do |row|
yielder << row
context.last_processed_id = row.id
break if context.record_limit_reached?
end
break if context.over_time? || context.record_limit_reached?
end
context.no_more_records! if has_data == false
# rubocop: enable CodeReuse/ActiveRecord
end
end
end
end

View File

@ -1,8 +0,0 @@
---
name: ci_commit_statuses_api_exclusive_lock
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/129164
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/421657
milestone: '16.4'
type: development
group: group::pipeline execution
default_enabled: false

View File

@ -5,5 +5,5 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/329990
milestone: '15.9'
type: development
group: group::incubation
default_enabled: false
default_enabled: true
log_state_changes: true

View File

@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/423880
milestone: '16.4'
type: development
group: group::incubation
default_enabled: false
default_enabled: true

View File

@ -0,0 +1,3 @@
# frozen_string_literal: true
Gitlab::Database.check_single_connection_and_print_warning

View File

@ -1,5 +1,5 @@
---
data_category: optional
data_category: operational
key_path: redis_hll_counters.pipeline_authoring.o_pipeline_authoring_unique_users_committing_ciconfigfile_weekly
description: Weekly unique user count doing commits which contains the CI config file
product_section: ops

View File

@ -1,5 +1,5 @@
---
data_category: optional
data_category: operational
key_path: counts.ci_pipeline_config_repository
description: Total Pipelines from CI files in repository
product_section: ops

View File

@ -1,5 +1,5 @@
---
data_category: optional
data_category: operational
key_path: usage_activity_by_stage.verify.ci_pipeline_config_repository
description: Total count of unique users creating pipelines from CI files in the repository
product_section: ops

View File

@ -1,5 +1,5 @@
---
data_category: optional
data_category: operational
key_path: usage_activity_by_stage.verify.ci_pipeline_schedules
description: Distinct users creating pipeline schedules
product_section: ops

View File

@ -0,0 +1,9 @@
CREATE TABLE sync_cursors
(
table_name LowCardinality(String) DEFAULT '',
primary_key_value UInt64 DEFAULT 0,
recorded_at DateTime64(6, 'UTC') DEFAULT now()
)
ENGINE = ReplacingMergeTree(recorded_at)
ORDER BY (table_name)
PRIMARY KEY (table_name)

View File

@ -274,7 +274,7 @@ The runner authentication token displays in the UI for only a short period of ti
WARNING:
The ability to pass a runner registration token, and support for certain configuration arguments was
[deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/380872) in GitLab 15.6 and will be removed in GitLab 17.0. Authentication tokens
[deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/380872) in GitLab 15.6 and will be removed in GitLab 18.0. Authentication tokens
should be used instead. For more information, see [Migrating to the new runner registration workflow](new_creation_workflow.md).
You must have the Owner role for the group.

View File

@ -178,6 +178,40 @@ Example response:
- GitLab Shell
## Authorized Certs
This endpoint is called by the GitLab Shell to get the namespace that has a particular CA SSH certificate
configured. It also accepts `user_identifier` to return a GitLab user for specified identifier.
| Attribute | Type | Required | Description |
|:----------------------|:-------|:---------|:------------|
| `key` | string | yes | The fingerprint of the SSH certificate. |
| `user_identifier` | string | yes | The identifier of the user to whom the SSH certificate has been issued (username or primary email). |
```plaintext
GET /internal/authorized_certs
```
Example request:
```shell
curl --request GET --header "Gitlab-Shell-Api-Request: <JWT token>" "http://localhost:3001/api/v4/internal/authorized_certs?key=<key>&user_identifier=<user_identifier>"
```
Example response:
```json
{
"success": true,
"namespace": "gitlab-org",
"username": "root"
}
```
### Known consumers
- GitLab Shell
## Get user for user ID or key
This endpoint is used when a user performs `ssh git@gitlab.com`. It

View File

@ -29,15 +29,16 @@ The following data sources are configured for analytics dashboards:
## Built-in dashboards
To help you get started with analytics, GitLab provides built-in dashboards with predefined visualizations.
These dashboards are labeled **By GitLab**, and you cannot edit them.
Instead, you can create a custom dashboard with a similar style.
### Product analytics
When [product analytics](../product_analytics/index.md) is enabled and onboarded, two built-in dashboard are added:
- **Audience** displays metrics related to traffic, such as the number of users and sessions.
- **Behavior** displays metrics related to user activity, such as the number of page views and events.
These dashboards are labeled **By GitLab**, and you cannot edit them.
Instead, you can create a custom dashboard with a similar style.
### Value Stream Management
- **Value Streams Dashboard** displays metrics related to [DevOps performance, security exposure, and workstream optimization](../analytics/value_streams_dashboard.md#devsecops-metrics-comparison-panel).
@ -64,9 +65,6 @@ On self-managed GitLab, by default this feature is not available. To make it ava
On GitLab.com, this feature is not available.
This feature is not ready for production use.
NOTE:
This feature does not work in conjunction with the `product_analytics_snowplow_support` feature flag.
You can use the dashboard designer to:
- Create custom dashboards.

View File

@ -10,18 +10,14 @@ info: To determine the technical writer assigned to the Stage/Group associated w
> - `cube_api_proxy` revised to only reference the [Product Analytics API](../../api/product_analytics.md) in GitLab 15.6.
> - `cube_api_proxy` removed and replaced with `product_analytics_internal_preview` in GitLab 15.10.
> - `product_analytics_internal_preview` replaced with `product_analytics_dashboards` in GitLab 15.11.
> - Snowplow integration introduced in GitLab 15.11 [with a flag](../../administration/feature_flags.md) named `product_analytics_snowplow_support`. Disabled by default.
> - Snowplow integration [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/398253) in GitLab 15.11 [with a flag](../../administration/feature_flags.md) named `product_analytics_snowplow_support`. Disabled by default.
> - Snowplow integration feature flag `product_analytics_snowplow_support` [removed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/130228) in GitLab 16.4.
FLAG:
On self-managed GitLab, by default this feature is not available. To make it available per project or for your entire instance, an administrator can [enable the feature flag](../../administration/feature_flags.md) named `product_analytics_dashboards`.
On GitLab.com, this feature is not available.
This feature is not ready for production use.
FLAG:
On self-managed GitLab, by default the Snowplow integration is not available. To make it available per project or for your entire instance, an administrator can [enable the feature flag](../../administration/feature_flags.md) named `product_analytics_snowplow_support`.
On GitLab.com, this feature is not available.
This feature is not ready for production use.
This page is a work in progress, and we're updating the information as we add more features.
For more information, see the [group direction page](https://about.gitlab.com/direction/analytics/product-analytics/).
To leave feedback about Product Analytics bugs or functionality, please comment in [issue 391970](https://gitlab.com/gitlab-org/gitlab/-/issues/391970) or open a new issue with the label `group::product analytics`.
@ -67,7 +63,7 @@ flowchart TB
> - `product_analytics_internal_preview` replaced with `product_analytics_dashboards` in GitLab 15.11.
FLAG:
On self-managed GitLab, by default this feature is not available. To make it available per project or for your entire instance, an administrator can [enable the feature flags](../../administration/feature_flags.md) named `product_analytics_dashboards`, `product_analytics_admin_settings`, `product_analytics_snowplow_support`, and `combined_analytics_dashboards`.
On self-managed GitLab, by default this feature is not available. To make it available per project or for your entire instance, an administrator can [enable the feature flags](../../administration/feature_flags.md) named `product_analytics_dashboards`, `product_analytics_admin_settings`, and `combined_analytics_dashboards`.
On GitLab.com, this feature is not available.
This feature is not ready for production use.

View File

@ -157,12 +157,13 @@ To edit the custom email display name:
## Custom email address **(BETA)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/329990) in GitLab 16.3 [with a flag](../../../administration/feature_flags.md) named `service_desk_custom_email`. Disabled by default.
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/329990) in GitLab 16.3 [with a flag](../../../administration/feature_flags.md) named `service_desk_custom_email`. Disabled by default.
> - [Enabled on GitLab.com and self-managed](https://gitlab.com/gitlab-org/gitlab/-/issues/387003) in GitLab 16.4.
FLAG:
On self-managed GitLab, by default this feature is not available. To make it available per project or for your
entire instance, an administrator can [enable the feature flag](../../../administration/feature_flags.md)
named `service_desk_custom_email`. On GitLab.com, this feature is not available. The feature is not ready for production use.
On self-managed GitLab, by default this feature is available. To hide the feature per project or for
your entire instance, an administrator can [disable the feature flag](../../../administration/feature_flags.md)
named `service_desk_custom_email`. On GitLab.com, this feature is available.
Configure a custom email address to show as the sender of your support communication.
Maintain brand identity and instill confidence among support requesters with a domain they recognize.

View File

@ -35,7 +35,8 @@ When you [create a workspace](configuration.md#set-up-a-workspace), you must:
- Assign the workspace to a specific project.
- Select a project with a [`.devfile.yaml`](#devfile) file.
The workspace can then interact with the GitLab API based on the permissions granted to the current user.
The workspace can interact with the GitLab API, with the access level defined by current user permissions.
A running workspace remains accessible even if user permissions are later revoked.
### Open and manage workspaces from a project

View File

@ -2,12 +2,14 @@
module CsvBuilder
class Gzip < CsvBuilder::Builder
# Writes the CSV file compressed and yields the written tempfile.
# Writes the CSV file compressed and yields the written tempfile and rows written.
#
#
# Example:
# > CsvBuilder::Gzip.new(Issue, { title: -> (row) { row.title.upcase }, id: :id }).render do |tempfile|
# > CsvBuilder::Gzip.new(Issue, { title: -> (row) { row.title.upcase }, id: :id }).render do |tempfile, rows|
# > puts tempfile.path
# > puts `zcat #{tempfile.path}`
# > puts rows
# > end
def render
Tempfile.open(['csv_builder_gzip', '.csv.gz']) do |tempfile|
@ -16,7 +18,7 @@ module CsvBuilder
write_csv csv, until_condition: -> {} # truncation must be handled outside of the CsvBuilder
csv.close
yield tempfile
yield tempfile, @rows_written
end
end
end

View File

@ -26,6 +26,13 @@ RSpec.describe CsvBuilder::Gzip do
])
end
it 'yields the number of written rows as the second argument' do
row_count = 0
builder.render { |_, rows| row_count = rows }
expect(row_count).to eq(2)
end
it 'requires a block' do
expect { builder.render }.to raise_error(LocalJumpError)
end

View File

@ -80,7 +80,6 @@ module API
post ':id/statuses/:sha' do
authorize! :create_commit_status, user_project
if Feature.enabled?(:ci_commit_statuses_api_exclusive_lock, user_project)
response =
::Ci::CreateCommitStatusService
.new(user_project, current_user, params)
@ -89,76 +88,6 @@ module API
if response.error?
render_api_error!(response.message, response.http_status)
else
present response.payload[:job], with: Entities::CommitStatus
end
else
not_found! 'Commit' unless commit
# Since the CommitStatus is attached to ::Ci::Pipeline (in the future Pipeline)
# We need to always have the pipeline object
# To have a valid pipeline object that can be attached to specific MR
# Other CI service needs to send `ref`
# If we don't receive it, we will attach the CommitStatus to
# the first found branch on that commit
pipeline = all_matching_pipelines.first
ref = params[:ref]
ref ||= pipeline&.ref
ref ||= user_project.repository.branch_names_contains(commit.sha).first
not_found! 'References for commit' unless ref
name = params[:name] || params[:context] || 'default'
pipeline ||= user_project.ci_pipelines.build(
source: :external,
sha: commit.sha,
ref: ref,
user: current_user,
protected: user_project.protected_for?(ref))
pipeline.ensure_project_iid!
pipeline.save!
authorize! :update_pipeline, pipeline
# rubocop: disable Performance/ActiveRecordSubtransactionMethods
stage = pipeline.stages.safe_find_or_create_by!(name: 'external') do |stage|
stage.position = GenericCommitStatus::EXTERNAL_STAGE_IDX
stage.project = pipeline.project
end
# rubocop: enable Performance/ActiveRecordSubtransactionMethods
status = GenericCommitStatus.running_or_pending.find_or_initialize_by(
project: user_project,
pipeline: pipeline,
name: name,
ref: ref,
user: current_user,
protected: user_project.protected_for?(ref),
ci_stage: stage,
stage_idx: stage.position,
stage: 'external'
)
status.assign_attributes(optional_commit_status_params)
render_validation_error!(status) unless status.valid?
response = ::Ci::Pipelines::AddJobService.new(pipeline).execute!(status) do |job|
apply_job_state!(job)
rescue ::StateMachines::InvalidTransition => e
render_api_error!(e.message, 400)
end
render_validation_error!(response.payload[:job]) unless response.success?
if pipeline.latest?
MergeRequest
.where(source_project: user_project, source_branch: ref)
.update_all(head_pipeline_id: pipeline.id)
end
present response.payload[:job], with: Entities::CommitStatus
end
end

View File

@ -54,6 +54,10 @@ module Bitbucket
target_branch.dig('commit', 'hash')
end
def merge_commit_sha
raw['merge_commit']&.dig('hash')
end
private
def source_branch

View File

@ -0,0 +1,43 @@
# frozen_string_literal: true
module ClickHouse
class RecordSyncContext
attr_reader :last_record_id, :last_processed_id, :total_record_count, :record_count_in_current_batch
def initialize(
last_record_id:, max_records_per_batch:,
runtime_limiter: Analytics::CycleAnalytics::RuntimeLimiter.new)
@last_record_id = last_record_id
@runtime_limiter = runtime_limiter
@max_records_per_batch = max_records_per_batch
@last_processed_id = nil
@record_count_in_current_batch = 0
@total_record_count = 0
end
delegate :over_time?, to: :@runtime_limiter
def new_batch!
@record_count_in_current_batch = 0
end
def no_more_records!
@no_more_records = true
end
def no_more_records?
!!@no_more_records
end
def last_processed_id=(value)
@record_count_in_current_batch += 1
@total_record_count += 1
@last_processed_id = value
@last_record_id = value
end
def record_limit_reached?
@record_count_in_current_batch == @max_records_per_batch
end
end
end

View File

@ -0,0 +1,41 @@
# frozen_string_literal: true
module ClickHouse
class SyncCursor
QUERY = <<~SQL
SELECT argMax(primary_key_value, recorded_at) AS primary_key_value
FROM sync_cursors
WHERE table_name = {table_name:String}
LIMIT 1
SQL
INSERT_CURSOR_QUERY = <<~SQL
INSERT INTO sync_cursors
(primary_key_value, table_name, recorded_at)
VALUES ({primary_key_value:UInt64}, {table_name:String}, {recorded_at:DateTime64})
SQL
def self.cursor_for(identifier)
query = ClickHouse::Client::Query.new(
raw_query: QUERY,
placeholders: { table_name: identifier.to_s }
)
# The query returns the default value (0) when no records are present.
ClickHouse::Client.select(query, :main).first['primary_key_value']
end
def self.update_cursor_for(identifier, value)
query = ClickHouse::Client::Query.new(
raw_query: INSERT_CURSOR_QUERY,
placeholders: {
primary_key_value: value,
table_name: identifier.to_s,
recorded_at: Time.current.to_f
}
)
ClickHouse::Client.execute(query, :main)
end
end
end

View File

@ -210,7 +210,11 @@ module Gitlab
source_branch_sha = pull_request.source_branch_sha
target_branch_sha = pull_request.target_branch_sha
source_branch_sha = project.repository.commit(source_branch_sha)&.sha || source_branch_sha
source_sha_from_commit_sha = project.repository.commit(source_branch_sha)&.sha
source_sha_from_merge_sha = project.repository.commit(pull_request.merge_commit_sha)&.sha
source_branch_sha = source_sha_from_commit_sha || source_sha_from_merge_sha || source_branch_sha
target_branch_sha = project.repository.commit(target_branch_sha)&.sha || target_branch_sha
merge_request = project.merge_requests.create!(

View File

@ -65,6 +65,10 @@ variables:
DOCKER_TLS_CERTDIR: "" # https://gitlab.com/gitlab-org/gitlab-runner/issues/4501
# License-Scanning job is removed from GitLab 16.3
# This is the fix for https://gitlab.com/gitlab-org/gitlab/-/issues/422791
LICENSE_MANAGEMENT_DISABLED: "true"
stages:
- build
- test

View File

@ -216,6 +216,28 @@ module Gitlab
end
end
def self.check_single_connection_and_print_warning
return if Gitlab::Runtime.rails_runner?
return unless database_mode == MODE_SINGLE_DATABASE
Kernel.warn ERB.new(Rainbow.new.wrap(<<~EOS).red).result
******************************************************************************
Your database has a single connection, and single connections were
deprecated in GitLab 15.9 https://docs.gitlab.com/ee/update/deprecations.html#single-database-connection-is-deprecated.
Please add a :ci section to your database, following these instructions:
https://docs.gitlab.com/ee/install/installation.html#configure-gitlab-db-settings.
******************************************************************************
EOS
end
def self.random
"RANDOM()"
end

Some files were not shown because too many files have changed in this diff Show More