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: rules:
- <<: *if-merge-request - <<: *if-merge-request
changes: ["**/*click_house*"] changes: ["**/*click_house*"]
- <<: *if-merge-request-labels-run-all-rspec
######################### #########################
# Static analysis rules # # Static analysis rules #

View File

@ -2,54 +2,6 @@
# Cop supports --autocorrect. # Cop supports --autocorrect.
Style/RedundantFreeze: Style/RedundantFreeze:
Exclude: 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/api.rb'
- 'lib/api/debian_group_packages.rb' - 'lib/api/debian_group_packages.rb'
- 'lib/api/go_proxy.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 gem 'gitlab_quality-test_tooling', '~> 0.9.3', require: false
end end
gem 'octokit', '~> 4.15' gem 'octokit', '~> 6.0'
gem 'gitlab-mail_room', '~> 0.0.23', require: 'mail_room' 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":"crystalball","version":"0.7.0","platform":"ruby","checksum":"6e729f372a5071daec877adb40c5df4cb25fe21f350635e2a9624373fc151ef2"},
{"name":"css_parser","version":"1.14.0","platform":"ruby","checksum":"f2ce6148cd505297b07bdbe7a5db4cce5cf530071f9b732b9a23538d6cdc0113"}, {"name":"css_parser","version":"1.14.0","platform":"ruby","checksum":"f2ce6148cd505297b07bdbe7a5db4cce5cf530071f9b732b9a23538d6cdc0113"},
{"name":"cvss-suite","version":"3.0.1","platform":"ruby","checksum":"b5ca9e9e94032a42fd0dc28c1e305378b62c949e35ed7111fc4a1d76f68ad3f9"}, {"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":"danger-gitlab","version":"8.0.0","platform":"ruby","checksum":"497dd7d0f6513913de651019223d8058cf494df10acbd17de92b175dfa04a3a8"},
{"name":"database_cleaner","version":"1.7.0","platform":"ruby","checksum":"bdf833c197afac7054015bcde2567c3834c366bbfe6a377c30151ca984b32016"}, {"name":"database_cleaner","version":"1.7.0","platform":"ruby","checksum":"bdf833c197afac7054015bcde2567c3834c366bbfe6a377c30151ca984b32016"},
{"name":"date","version":"3.3.3","platform":"java","checksum":"584e0a582d1eb2207b4eaac089d8a43f2ca10bea02682f286099642f15c56cce"}, {"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","version":"3.3.6","platform":"ruby","checksum":"ee6bbd1b2f833ee52d7797fa68acbfecc4726aec6b6280fd7eab92aa0190b413"},
{"name":"gettext_i18n_rails","version":"1.11.0","platform":"ruby","checksum":"e19c7e4a256c500f7f38396dca44a282b9838ae278f57c362993a54964b22bbe"}, {"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":"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":"gitaly","version":"16.3.0.pre.rc1","platform":"ruby","checksum":"55d9cc414a4f3859588f3770bd88d7c67c0f5454a1178b018b7a6f6913674c43"},
{"name":"gitlab","version":"4.19.0","platform":"ruby","checksum":"3f645e3e195dbc24f0834fbf83e8ccfb2056d8e9712b01a640aad418a6949679"}, {"name":"gitlab","version":"4.19.0","platform":"ruby","checksum":"3f645e3e195dbc24f0834fbf83e8ccfb2056d8e9712b01a640aad418a6949679"},
{"name":"gitlab-chronic","version":"0.10.5","platform":"ruby","checksum":"f80f18dc699b708870a80685243331290bc10cfeedb6b99c92219722f729c875"}, {"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":"numerizer","version":"0.2.0","platform":"ruby","checksum":"e58076d5ee5370417b7e52d9cb25836d62acd1b8d9a194c308707986c1705d7b"},
{"name":"oauth","version":"0.5.6","platform":"ruby","checksum":"4085fe28e0c5e2434135e00a6555294fd2a4ff96a98d1bdecdcd619fc6368dff"}, {"name":"oauth","version":"0.5.6","platform":"ruby","checksum":"4085fe28e0c5e2434135e00a6555294fd2a4ff96a98d1bdecdcd619fc6368dff"},
{"name":"oauth2","version":"2.0.9","platform":"ruby","checksum":"b21f9defcf52dc1610e0dfab4c868342173dcd707fd15c777d9f4f04e153f7fb"}, {"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":"ohai","version":"17.9.0","platform":"ruby","checksum":"c59cf16124c0a6481fb85013ec7ec5b398651b6abed782d3e06ab058ce9a5406"},
{"name":"oj","version":"3.13.23","platform":"ruby","checksum":"206dfdc4020ad9974705037f269cfba211d61b7662a58c717cce771829ccef51"}, {"name":"oj","version":"3.13.23","platform":"ruby","checksum":"206dfdc4020ad9974705037f269cfba211d61b7662a58c717cce771829ccef51"},
{"name":"oj-introspect","version":"0.7.2","platform":"ruby","checksum":"c415a44567ed2870d8e963a69421d9322128e194fab7867e37e54d5a25d5333d"}, {"name":"oj-introspect","version":"0.7.2","platform":"ruby","checksum":"c415a44567ed2870d8e963a69421d9322128e194fab7867e37e54d5a25d5333d"},

View File

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

View File

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

View File

@ -52,7 +52,7 @@ export default {
}; };
</script> </script>
<template> <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 <gl-button
v-if="showFileTreeToggle" v-if="showFileTreeToggle"
id="file-tree-toggle" id="file-tree-toggle"

View File

@ -33,7 +33,7 @@ export default {
</script> </script>
<template> <template>
<gl-card <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" header-class="gl-new-card-header"
body-class="gl-new-card-body gl-py-4 gl-px-5" body-class="gl-new-card-body gl-py-4 gl-px-5"
> >

View File

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

View File

@ -70,6 +70,12 @@ export default {
); );
}, },
}, },
watch: {
default(newVal) {
this.current = newVal;
this.selected = newVal.value;
},
},
methods: { methods: {
async fetchData() { async fetchData() {
if (!this.endpoint) return; 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 { 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 initPipelines from '~/commit/pipelines/pipelines_bundle';
import MergeRequest from '~/merge_request'; import MergeRequest from '~/merge_request';
import CompareApp from '~/merge_requests/components/compare_app.vue'; import CompareApp from '~/merge_requests/components/compare_app.vue';
@ -13,14 +15,15 @@ if (mrNewCompareNode) {
const targetCompareEl = document.getElementById('js-target-project-dropdown'); const targetCompareEl = document.getElementById('js-target-project-dropdown');
const sourceCompareEl = document.getElementById('js-source-project-dropdown'); const sourceCompareEl = document.getElementById('js-source-project-dropdown');
const compareEl = document.querySelector('.js-merge-request-new-compare'); 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 // eslint-disable-next-line no-new
new Vue({ new Vue({
el: sourceCompareEl, el: sourceCompareEl,
name: 'SourceCompareApp', name: 'SourceCompareApp',
provide: { provide: {
currentProject: JSON.parse(sourceCompareEl.dataset.currentProject), currentProject: JSON.parse(sourceCompareEl.dataset.currentProject),
currentBranch: JSON.parse(sourceCompareEl.dataset.currentBranch),
branchCommitPath: compareEl.dataset.sourceBranchUrl, branchCommitPath: compareEl.dataset.sourceBranchUrl,
inputs: { inputs: {
project: { project: {
@ -42,18 +45,34 @@ if (mrNewCompareNode) {
}, },
branchQaSelector: 'source_branch_dropdown', branchQaSelector: 'source_branch_dropdown',
}, },
methods: {
async selectedBranch(branchName) {
const targetBranchName = await findTargetBranch(branchName);
if (targetBranchName) {
targetBranch.name = targetBranchName;
}
},
},
render(h) { 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 // eslint-disable-next-line no-new
new Vue({ new Vue({
el: targetCompareEl, el: targetCompareEl,
name: 'TargetCompareApp', name: 'TargetCompareApp',
provide: { provide: {
currentProject: JSON.parse(targetCompareEl.dataset.currentProject), currentProject: JSON.parse(targetCompareEl.dataset.currentProject),
currentBranch: JSON.parse(targetCompareEl.dataset.currentBranch),
projectsPath: targetCompareEl.dataset.targetProjectsPath, projectsPath: targetCompareEl.dataset.targetProjectsPath,
branchCommitPath: compareEl.dataset.targetBranchUrl, branchCommitPath: compareEl.dataset.targetBranchUrl,
inputs: { inputs: {
@ -75,8 +94,17 @@ if (mrNewCompareNode) {
branch: 'js-target-branch gl-font-monospace', branch: 'js-target-branch gl-font-monospace',
}, },
}, },
computed: {
currentBranch() {
if (targetBranch.name) {
return { text: targetBranch.name, value: targetBranch.name };
}
return currentTargetBranch;
},
},
render(h) { render(h) {
return h(CompareApp); return h(CompareApp, { props: { currentBranch: this.currentBranch } });
}, },
}); });
} else { } else {

View File

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

View File

@ -233,6 +233,7 @@ export default {
<gl-link <gl-link
:href="$options.FEEDBACK_ISSUE_URL" :href="$options.FEEDBACK_ISSUE_URL"
target="_blank" target="_blank"
data-testid="feedback-link"
class="gl-text-blue-600 font-size-inherit" class="gl-text-blue-600 font-size-inherit"
>{{ content }} >{{ content }}
</gl-link> </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_TOAST_DISABLED = s__('ServiceDesk|Custom email disabled.');
export const I18N_FORM_INTRODUCTION_PARAGRAPH = s__( 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__( export const I18N_FORM_FORWARDING_LABEL = s__(
'ServiceDesk|Service Desk email address to forward emails to', 'ServiceDesk|Service Desk email address to forward emails to',

View File

@ -1,6 +1,6 @@
<script> <script>
import { GlAvatar, GlButton, GlIcon, GlBadge, GlTooltipDirective } from '@gitlab/ui'; import { GlAvatar, GlButton, GlIcon, GlBadge, GlTooltipDirective } from '@gitlab/ui';
import { s__ } from '~/locale'; import { s__, sprintf } from '~/locale';
import { import {
CLICK_MENU_ITEM_ACTION, CLICK_MENU_ITEM_ACTION,
CLICK_PINNED_MENU_ITEM_ACTION, CLICK_PINNED_MENU_ITEM_ACTION,
@ -12,7 +12,9 @@ import NavItemRouterLink from './nav_item_router_link.vue';
export default { export default {
i18n: { i18n: {
pin: s__('Navigation|Pin %{title}'),
pinItem: s__('Navigation|Pin item'), pinItem: s__('Navigation|Pin item'),
unpin: s__('Navigation|Unpin %{title}'),
unpinItem: s__('Navigation|Unpin item'), unpinItem: s__('Navigation|Unpin item'),
}, },
name: 'NavItem', name: 'NavItem',
@ -143,6 +145,16 @@ export default {
avatarShape() { avatarShape() {
return this.item.avatar_shape || 'rect'; 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() { mounted() {
if (this.item.is_active) { if (this.item.is_active) {
@ -151,26 +163,27 @@ export default {
}, },
methods: { methods: {
togglePointerEvents() { togglePointerEvents() {
if (this.isMouseIn) { this.canClickPinButton = this.isMouseIn;
this.canClickPinButton = true;
} else {
this.canClickPinButton = false;
}
}, },
}, },
}; };
</script> </script>
<template> <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 <component
:is="navItemLinkComponent" :is="navItemLinkComponent"
#default="{ isActive }" #default="{ isActive }"
v-bind="linkProps" 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" :class="computedLinkClasses"
data-qa-selector="nav_item_link"
data-testid="nav-item-link" data-testid="nav-item-link"
data-qa-selector="nav_item_link"
> >
<div <div
:class="[isActive ? 'gl-opacity-10' : 'gl-opacity-0']" :class="[isActive ? 'gl-opacity-10' : 'gl-opacity-0']"
@ -204,41 +217,47 @@ export default {
</div> </div>
</div> </div>
<slot name="actions"></slot> <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 <gl-badge
v-if="hasPill" v-if="hasPill"
size="sm" size="sm"
variant="neutral" variant="neutral"
class="gl-bg-t-gray-a-08!" 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 }} {{ pillData }}
</gl-badge> </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> </span>
</component> </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> </li>
</template> </template>

View File

@ -150,7 +150,7 @@ export default {
<user-bar :has-collapse-button="!showOverlay" :sidebar-data="sidebarData" /> <user-bar :has-collapse-button="!showOverlay" :sidebar-data="sidebarData" />
<div v-if="showTrialStatusWidget" class="gl-px-2 gl-py-2"> <div v-if="showTrialStatusWidget" class="gl-px-2 gl-py-2">
<trial-status-widget <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 /> <trial-status-popover />
</div> </div>

View File

@ -1,6 +1,6 @@
*:not( *:not(
/* Keep transition enabled where it would otherwise break specs */ /* 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 */ /* stylelint-disable property-no-vendor-prefix */
-o-transition: none !important; -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 { #trial-status-sidebar-widget:hover {
text-decoration: none; text-decoration: none;
@include gl-text-contrast-light; @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--context {
.show-on-focus-or-hover--target { .show-on-focus-or-hover--target {
opacity: 0; opacity: 0;
&:hover,
&:focus {
opacity: 1;
}
} }
&:hover, &:hover,
&:focus { &:focus-within {
.show-on-focus-or-hover--control {
@include gl-bg-t-gray-a-08;
}
.show-on-focus-or-hover--target { .show-on-focus-or-hover--target {
opacity: 1; opacity: 1;
} }
} }
.show-on-focus-or-hover--target:focus { .show-on-focus-or-hover--control {
opacity: 1; &: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 # Taken from Jekyll
# https://github.com/jekyll/jekyll/blob/3.5-stable/lib/jekyll/document.rb#L13 # 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 def index
@help_index = get_markdown_without_frontmatter(path_to_doc('index.md')) @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) # (https://community.atlassian.com/t5/Answers-Developer-Questions/stash-repository-names/qaq-p/499054)
# #
# Bitbucket Server starts personal project names with a tilde. # Bitbucket Server starts personal project names with a tilde.
VALID_BITBUCKET_PROJECT_CHARS = /\A~?[\w\-\.\s]+\z/.freeze VALID_BITBUCKET_PROJECT_CHARS = /\A~?[\w\-\.\s]+\z/
VALID_BITBUCKET_CHARS = /\A[\w\-\.\s]+\z/.freeze VALID_BITBUCKET_CHARS = /\A[\w\-\.\s]+\z/
def new def new
end end

View File

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

View File

@ -21,7 +21,7 @@ module Repositories
COMMITS_PER_PAGE = 1024 COMMITS_PER_PAGE = 1024
# The regex to use for extracting the SHA of a reverted commit. # 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 # The `project` argument specifies the project for which to obtain the
# commits. # commits.

View File

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

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
module ColorsHelper 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) def hex_color_to_rgb_array(hex_color)
unless hex_color.is_a?(String) && HEX_COLOR_PATTERN.match?(hex_color) unless hex_color.is_a?(String) && HEX_COLOR_PATTERN.match?(hex_color)

View File

@ -226,6 +226,13 @@ module MergeRequestsHelper
} }
end 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 private
def review_requested_merge_requests_count def review_requested_merge_requests_count

View File

@ -8,7 +8,7 @@ module SidekiqHelper
(?<state>[DIEKNRSTVWXZLpsl\+<>/\d]+)\s+ (?<state>[DIEKNRSTVWXZLpsl\+<>/\d]+)\s+
(?<start>.+?)\s+ (?<start>.+?)\s+
(?<command>(?:ruby\d+:\s+)?sidekiq.*\].*) (?<command>(?:ruby\d+:\s+)?sidekiq.*\].*)
\z}x.freeze \z}x
def parse_sidekiq_ps(line) def parse_sidekiq_ps(line)
match = line.strip.match(SIDEKIQ_PS_REGEXP) 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 \s # any whitespace character
| # or | # or
[\r\n] # any number of newline characters [\r\n] # any number of newline characters
}x.freeze }x
# Setting a key restriction to `-1` means that all keys of this type are # Setting a key restriction to `-1` means that all keys of this type are
# forbidden. # forbidden.

View File

@ -18,7 +18,7 @@ class Badge < ApplicationRecord
# This regex is built dynamically using the keys from the PLACEHOLDER struct. # 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. # 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 # 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 default_scope { order_created_at_asc } # rubocop:disable Cop/DefaultScope

View File

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

View File

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

View File

@ -52,7 +52,7 @@ module Ci
RUNNER_QUEUE_EXPIRY_TIME = 1.hour RUNNER_QUEUE_EXPIRY_TIME = 1.hour
# The `UPDATE_CONTACT_COLUMN_EVERY` defines how often the Runner DB entry can be updated # 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 # 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 STALE_TIMEOUT = 3.months

View File

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

View File

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

View File

@ -11,12 +11,12 @@ module Ci
# * Minimal length of 8 characters # * Minimal length of 8 characters
# * Characters must be from the Base64 alphabet (RFC4648) with the addition of '@', ':', '.', and '~' # * Characters must be from the Base64 alphabet (RFC4648) with the addition of '@', ':', '.', and '~'
# * Absolutely no fun is allowed # * 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 # * Single line
# * No spaces # * No spaces
# * Minimal length of 8 characters # * Minimal length of 8 characters
# * Some fun is allowed # * 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 included do
validates :masked, inclusion: { in: [true, false] } validates :masked, inclusion: { in: [true, false] }

View File

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

View File

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

View File

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

View File

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

View File

@ -79,7 +79,7 @@ class EnvironmentStatus
private 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 def deployment_metrics
@deployment_metrics ||= DeploymentMetrics.new(project, deployment) @deployment_metrics ||= DeploymentMetrics.new(project, deployment)

View File

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

View File

@ -10,6 +10,7 @@ class Event < ApplicationRecord
include UsageStatistics include UsageStatistics
include ShaAttribute include ShaAttribute
include IgnorableColumns include IgnorableColumns
include EachBatch
ignore_column :target_id_convert_to_bigint, remove_with: '16.4', remove_after: '2023-09-22' 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 end
# See app/validators/json_schemas/web_hooks_url_variables.json # 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) def interpolated_url(url = self.url, url_variables = self.url_variables)
return url unless url.include?('{') return url unless url.include?('{')

View File

@ -4,8 +4,8 @@ require 'app_store_connect'
module Integrations module Integrations
class AppleAppStore < Integration 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 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/.freeze KEY_ID_REGEX = /\A(?=.*[A-Z])(?=.*[0-9])[A-Z0-9]+\z/
IS_KEY_CONTENT_BASE64 = "true" IS_KEY_CONTENT_BASE64 = "true"
SECTION_TYPE_APPLE_APP_STORE = 'apple_app_store' SECTION_TYPE_APPLE_APP_STORE = 'apple_app_store'

View File

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

View File

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

View File

@ -12,7 +12,7 @@ module Integrations
pipeline build archive_trace pipeline build archive_trace
].freeze ].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, field :datadog_site,
exposes_secrets: true, exposes_secrets: true,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -643,7 +643,7 @@ class MergeRequest < ApplicationRecord
end end
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) def self.draft?(title)
!!(title =~ DRAFT_REGEX) !!(title =~ DRAFT_REGEX)

View File

@ -3,7 +3,7 @@
module Namespaces module Namespaces
class RandomizedSuffixPath class RandomizedSuffixPath
MAX_TRIES = 4 MAX_TRIES = 4
LEADING_ZEROS = /^0+/.freeze LEADING_ZEROS = /^0+/
def initialize(path) def initialize(path)
@path = 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' 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 cache_markdown_field :note, pipeline: :note, issuable_reference_expansion_enabled: true

View File

@ -4,13 +4,13 @@ module Packages
module Debian module Debian
TEMPORARY_PACKAGE_NAME = 'debian-temporary-package' 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 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' INCOMING_PACKAGE_NAME = 'incoming'

View File

@ -6,7 +6,7 @@ module Packages
include ActiveModel::Model include ActiveModel::Model
DIGESTS = %i[md5 sha1 sha256].freeze 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, attr_accessor :filename,
:size, :size,

View File

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

View File

@ -8,7 +8,7 @@ module Releases
# See https://gitlab.com/gitlab-org/gitlab/-/issues/218753 # See https://gitlab.com/gitlab-org/gitlab/-/issues/218753
# Regex modified to prevent catastrophic backtracking # 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 FILEPATH_MAX_LENGTH = 128
validates :url, presence: true, addressable_url: { schemes: %w[http https ftp] }, uniqueness: { scope: :release } 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 def user_mentions
merge_request.user_mentions.where.not(note_id: nil) merge_request.user_mentions.where.not(note_id: nil)
end end
def from_merge_request_author?
merge_request.author_id == author_id
end
end end
Review.prepend_mod Review.prepend_mod

View File

@ -5,7 +5,7 @@ class SnippetRepository < ApplicationRecord
include Shardable include Shardable
DEFAULT_EMPTY_FILE_NAME = 'snippetfile' 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) CommitError = Class.new(StandardError)
InvalidPathError = Class.new(CommitError) InvalidPathError = Class.new(CommitError)

View File

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

View File

@ -4,7 +4,7 @@ module Clusters
module AgentTokens module AgentTokens
class TrackUsageService class TrackUsageService
# The `UPDATE_USED_COLUMN_EVERY` defines how often the token DB entry can be updated # 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 delegate :agent, to: :token

View File

@ -8,7 +8,7 @@ module Import
GIT_SERVICE_NAME = "git-upload-pack" GIT_SERVICE_NAME = "git-upload-pack"
GIT_EXPECTED_FIRST_PACKET_LINE = "# service=#{GIT_SERVICE_NAME}" 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 # https://github.com/git/git/blob/master/Documentation/technical/protocol-common.txt#L56-L59
GIT_PROTOCOL_PKT_LEN = 4 GIT_PROTOCOL_PKT_LEN = 4
GIT_MINIMUM_RESPONSE_LENGTH = GIT_PROTOCOL_PKT_LEN + GIT_EXPECTED_FIRST_PACKET_LINE.length GIT_MINIMUM_RESPONSE_LENGTH = GIT_PROTOCOL_PKT_LEN + GIT_EXPECTED_FIRST_PACKET_LINE.length

View File

@ -21,7 +21,7 @@ module Issues
Issues::CloseService Issues::CloseService
end 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) def rebalance_if_needed(issue)
return unless issue return unless issue

View File

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

View File

@ -9,7 +9,7 @@ module Projects
include Gitlab::Utils::StrongMemoize include Gitlab::Utils::StrongMemoize
HEAD_REV = 'HEAD' 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' LFS_BATCH_API_ENDPOINT = '/info/lfs/objects/batch'
LfsObjectDownloadListError = Class.new(StandardError) LfsObjectDownloadListError = Class.new(StandardError)

View File

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

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
class CertificateFingerprintValidator < ActiveModel::EachValidator 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) def validate_each(record, attribute, value)
unless value.try(:match, FINGERPRINT_PATTERN) unless value.try(:match, FINGERPRINT_PATTERN)

View File

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

View File

@ -4,7 +4,7 @@
# #
# Custom validator for GitLab line codes. # Custom validator for GitLab line codes.
class LineCodeValidator < ActiveModel::EachValidator 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) def validate_each(record, attribute, value)
unless PATTERN.match?(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| = 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? - if params[:nav_source].present?
= hidden_field_tag(:nav_source, params[:nav_source]) = 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 .col-lg-6
.card-new-merge-request .card-new-merge-request
%h2.gl-font-size-h2 %h2.gl-font-size-h2

View File

@ -13,15 +13,5 @@ class BuildSuccessWorker # rubocop:disable Scalability/IdempotentWorker
queue_namespace :pipeline_processing queue_namespace :pipeline_processing
urgency :high urgency :high
def perform(build_id) def perform(build_id); end
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
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 # the job is scheduled every 3 minutes and we will allow maximum 2.5 minutes runtime
MAX_TTL = 2.5.minutes.to_i 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 def perform
unless enabled? unless enabled?
@ -22,12 +63,15 @@ module ClickHouse
metadata = { status: :processed } metadata = { status: :processed }
# Prevent parallel jobs
begin begin
# Prevent parallel jobs
in_lock(self.class.to_s, ttl: MAX_TTL, retries: 0) do in_lock(self.class.to_s, ttl: MAX_TTL, retries: 0) do
true loop { break unless next_batch }
end
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 rescue Gitlab::ExclusiveLeaseHelpers::FailedToObtainLockError
# Skip retrying, just let the next worker to start after a few minutes # Skip retrying, just let the next worker to start after a few minutes
metadata = { status: :skipped } metadata = { status: :skipped }
@ -38,8 +82,51 @@ module ClickHouse
private 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? def enabled?
ClickHouse::Client.configuration.databases[:main].present? && Feature.enabled?(:event_sync_worker_for_click_house) ClickHouse::Client.configuration.databases[:main].present? && Feature.enabled?(:event_sync_worker_for_click_house)
end 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
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' milestone: '15.9'
type: development type: development
group: group::incubation group: group::incubation
default_enabled: false default_enabled: true
log_state_changes: 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' milestone: '16.4'
type: development type: development
group: group::incubation 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 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 description: Weekly unique user count doing commits which contains the CI config file
product_section: ops product_section: ops

View File

@ -1,5 +1,5 @@
--- ---
data_category: optional data_category: operational
key_path: counts.ci_pipeline_config_repository key_path: counts.ci_pipeline_config_repository
description: Total Pipelines from CI files in repository description: Total Pipelines from CI files in repository
product_section: ops 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 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 description: Total count of unique users creating pipelines from CI files in the repository
product_section: ops 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 key_path: usage_activity_by_stage.verify.ci_pipeline_schedules
description: Distinct users creating pipeline schedules description: Distinct users creating pipeline schedules
product_section: ops 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: WARNING:
The ability to pass a runner registration token, and support for certain configuration arguments was 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). 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. You must have the Owner role for the group.

View File

@ -178,6 +178,40 @@ Example response:
- GitLab Shell - 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 ## Get user for user ID or key
This endpoint is used when a user performs `ssh git@gitlab.com`. It 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 ## Built-in dashboards
To help you get started with analytics, GitLab provides built-in dashboards with predefined visualizations. 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 ### 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. - **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. - **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 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). - **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. On GitLab.com, this feature is not available.
This feature is not ready for production use. 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: You can use the dashboard designer to:
- Create custom dashboards. - 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` 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. > - `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. > - `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: 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 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. On GitLab.com, this feature is not available.
This feature is not ready for production use. 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. 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/). 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`. 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. > - `product_analytics_internal_preview` replaced with `product_analytics_dashboards` in GitLab 15.11.
FLAG: 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. On GitLab.com, this feature is not available.
This feature is not ready for production use. 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)** ## 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: FLAG:
On self-managed GitLab, by default this feature is not available. To make it available per project or for your On self-managed GitLab, by default this feature is available. To hide the feature per project or for
entire instance, an administrator can [enable the feature flag](../../../administration/feature_flags.md) 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 not available. The feature is not ready for production use. 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. 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. 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. - Assign the workspace to a specific project.
- Select a project with a [`.devfile.yaml`](#devfile) file. - 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 ### Open and manage workspaces from a project

View File

@ -2,12 +2,14 @@
module CsvBuilder module CsvBuilder
class Gzip < CsvBuilder::Builder 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: # 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 tempfile.path
# > puts `zcat #{tempfile.path}` # > puts `zcat #{tempfile.path}`
# > puts rows
# > end # > end
def render def render
Tempfile.open(['csv_builder_gzip', '.csv.gz']) do |tempfile| 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 write_csv csv, until_condition: -> {} # truncation must be handled outside of the CsvBuilder
csv.close csv.close
yield tempfile yield tempfile, @rows_written
end end
end end
end end

View File

@ -26,6 +26,13 @@ RSpec.describe CsvBuilder::Gzip do
]) ])
end 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 it 'requires a block' do
expect { builder.render }.to raise_error(LocalJumpError) expect { builder.render }.to raise_error(LocalJumpError)
end end

View File

@ -80,85 +80,14 @@ module API
post ':id/statuses/:sha' do post ':id/statuses/:sha' do
authorize! :create_commit_status, user_project authorize! :create_commit_status, user_project
if Feature.enabled?(:ci_commit_statuses_api_exclusive_lock, user_project) response =
response = ::Ci::CreateCommitStatusService
::Ci::CreateCommitStatusService .new(user_project, current_user, params)
.new(user_project, current_user, params) .execute(optional_commit_status_params: optional_commit_status_params)
.execute(optional_commit_status_params: optional_commit_status_params)
if response.error? if response.error?
render_api_error!(response.message, response.http_status) render_api_error!(response.message, response.http_status)
else
present response.payload[:job], with: Entities::CommitStatus
end
else 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 present response.payload[:job], with: Entities::CommitStatus
end end
end end

View File

@ -54,6 +54,10 @@ module Bitbucket
target_branch.dig('commit', 'hash') target_branch.dig('commit', 'hash')
end end
def merge_commit_sha
raw['merge_commit']&.dig('hash')
end
private private
def source_branch 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 source_branch_sha = pull_request.source_branch_sha
target_branch_sha = pull_request.target_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 target_branch_sha = project.repository.commit(target_branch_sha)&.sha || target_branch_sha
merge_request = project.merge_requests.create!( 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 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: stages:
- build - build
- test - test

View File

@ -216,6 +216,28 @@ module Gitlab
end end
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 def self.random
"RANDOM()" "RANDOM()"
end end

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