Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-01-10 21:07:39 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-01-10 21:07:39 +0300
commit8cc4a6f23d41a1c57dc309130d2ce9ebc04d8334 (patch)
tree8391f5ee4f3391534131ae834b4b0a413845239e
parent87f8fdb93cb1e63f8e9cedf7d3d00c8ade70b18c (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--.gitlab/ci/rails.gitlab-ci.yml18
-rw-r--r--.gitlab/ci/rails/shared.gitlab-ci.yml6
-rw-r--r--app/assets/javascripts/abuse_reports/components/abuse_category_selector.vue7
-rw-r--r--app/assets/javascripts/behaviors/shortcuts/shortcut.vue3
-rw-r--r--app/assets/javascripts/constants.js7
-rw-r--r--app/assets/javascripts/diffs/components/tree_list.vue4
-rw-r--r--app/assets/javascripts/editor/constants.js12
-rw-r--r--app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/tags_list_row.vue2
-rw-r--r--app/assets/javascripts/packages_and_registries/infrastructure_registry/details/components/package_history.vue8
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/components/details/package_history.vue12
-rw-r--r--app/assets/javascripts/users/profile/components/report_abuse_button.vue5
-rw-r--r--app/assets/javascripts/work_items/components/work_item_notes.vue1
-rw-r--r--app/controllers/abuse_reports_controller.rb27
-rw-r--r--app/models/abuse_report.rb19
-rw-r--r--app/models/concerns/project_features_compatibility.rb9
-rw-r--r--app/models/personal_access_token.rb20
-rw-r--r--app/services/projects/create_service.rb6
-rw-r--r--app/views/abuse_reports/new.html.haml46
-rw-r--r--app/views/users/show.html.haml2
-rw-r--r--config/feature_flags/development/repository_archive_hotlinking_interception.yml8
-rw-r--r--config/feature_flags/development/sec_mark_dropped_findings_as_resolved_scheduler.yml (renamed from config/feature_flags/development/rate_limit_gitlab_shell.yml)12
-rw-r--r--db/migrate/20221212054640_add_reported_from_to_abuse_reports.rb21
-rw-r--r--db/post_migrate/20221228103133_queue_backfill_admin_mode_scope_for_personal_access_tokens.rb21
-rw-r--r--db/schema_migrations/202212120546401
-rw-r--r--db/schema_migrations/202212281031331
-rw-r--r--db/structure.sql4
-rw-r--r--doc/administration/instance_limits.md2
-rw-r--r--doc/administration/merge_request_diffs.md2
-rw-r--r--doc/administration/pages/index.md6
-rw-r--r--doc/administration/pages/source.md2
-rw-r--r--doc/api/merge_requests.md2
-rw-r--r--doc/api/oauth2.md8
-rw-r--r--doc/api/repositories.md2
-rw-r--r--doc/api/secure_files.md7
-rw-r--r--doc/ci/secure_files/index.md16
-rw-r--r--doc/development/api_graphql_styleguide.md2
-rw-r--r--doc/development/cached_queries.md6
-rw-r--r--doc/development/chatops_on_gitlabcom.md4
-rw-r--r--doc/development/code_review.md2
-rw-r--r--doc/development/contributing/merge_request_workflow.md4
-rw-r--r--doc/development/database/pagination_guidelines.md2
-rw-r--r--doc/development/database/query_recorder.md8
-rw-r--r--doc/development/database_review.md2
-rw-r--r--doc/development/diffs.md202
-rw-r--r--doc/development/documentation/workflow.md2
-rw-r--r--doc/development/feature_development.md2
-rw-r--r--doc/development/feature_flags/controls.md15
-rw-r--r--doc/development/go_guide/go_upgrade.md2
-rw-r--r--doc/development/img/feature-flag-metrics.pngbin0 -> 88110 bytes
-rw-r--r--doc/development/merge_request_concepts/diffs/development.md188
-rw-r--r--doc/development/merge_request_concepts/diffs/index.md199
-rw-r--r--doc/development/merge_request_concepts/img/merge_ref_head_options_v13_6.png (renamed from doc/development/img/merge_ref_head_options_v13_6.png)bin21605 -> 21605 bytes
-rw-r--r--doc/development/merge_request_concepts/performance.md565
-rw-r--r--doc/development/merge_request_diffs.md191
-rw-r--r--doc/development/merge_request_performance_guidelines.md568
-rw-r--r--doc/development/pages/index.md2
-rw-r--r--doc/development/performance.md4
-rw-r--r--doc/development/value_stream_analytics/value_stream_analytics_aggregated_backend.md2
-rw-r--r--doc/integration/jira/dvcs.md8
-rw-r--r--doc/integration/oauth_provider.md68
-rw-r--r--doc/policy/alpha-beta-support.md47
-rw-r--r--doc/security/rate_limits.md8
-rw-r--r--doc/topics/authentication/index.md2
-rw-r--r--doc/user/application_security/secret_detection/post_processing.md2
-rw-r--r--doc/user/profile/index.md2
-rw-r--r--doc/user/project/merge_requests/versions.md2
-rw-r--r--lib/api/internal/base.rb4
-rw-r--r--lib/gitlab/auth.rb2
-rw-r--r--lib/gitlab/background_migration/backfill_admin_mode_scope_for_personal_access_tokens.rb28
-rw-r--r--lib/gitlab/database/migration_helpers.rb24
-rw-r--r--lib/gitlab/database/postgres_foreign_key.rb18
-rw-r--r--lib/gitlab/hotlinking_detector.rb2
-rw-r--r--locale/gitlab.pot20
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/user/parent_group_access_termination_spec.rb1
-rw-r--r--spec/factories/abuse_reports.rb1
-rw-r--r--spec/features/abuse_report_spec.rb141
-rw-r--r--spec/features/merge_request/user_sees_discussions_navigation_spec.rb23
-rw-r--r--spec/frontend/abuse_reports/components/abuse_category_selector_spec.js2
-rw-r--r--spec/frontend/constants_spec.js30
-rw-r--r--spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/tags_list_row_spec.js8
-rw-r--r--spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/package_history_spec.js16
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/details/package_history_spec.js16
-rw-r--r--spec/frontend/users/profile/components/report_abuse_button_spec.js11
-rw-r--r--spec/frontend/work_items/components/work_item_notes_spec.js13
-rw-r--r--spec/lib/gitlab/background_migration/backfill_admin_mode_scope_for_personal_access_tokens_spec.rb53
-rw-r--r--spec/lib/gitlab/database/migration_helpers_spec.rb93
-rw-r--r--spec/lib/gitlab/database/postgres_foreign_key_spec.rb77
-rw-r--r--spec/migrations/queue_backfill_admin_mode_scope_for_personal_access_tokens_spec.rb18
-rw-r--r--spec/models/abuse_report_spec.rb15
-rw-r--r--spec/models/integrations/apple_app_store_spec.rb2
-rw-r--r--spec/models/personal_access_token_spec.rb31
-rw-r--r--spec/requests/abuse_reports_controller_spec.rb22
-rw-r--r--spec/requests/api/internal/base_spec.rb12
-rw-r--r--spec/requests/api/projects_spec.rb12
-rw-r--r--spec/requests/api/repositories_spec.rb12
-rw-r--r--spec/support/shared_examples/features/reportable_note_shared_examples.rb2
-rw-r--r--workhorse/go.mod2
-rw-r--r--workhorse/go.sum4
98 files changed, 1784 insertions, 1371 deletions
diff --git a/.gitlab/ci/rails.gitlab-ci.yml b/.gitlab/ci/rails.gitlab-ci.yml
index 21af2ef651e..6707e76272e 100644
--- a/.gitlab/ci/rails.gitlab-ci.yml
+++ b/.gitlab/ci/rails.gitlab-ci.yml
@@ -727,12 +727,6 @@ rspec-ee system pg11:
- .rspec-ee-system-parallel
# PG12
-rspec-ee unit pg12 es7:
- extends:
- - .rspec-ee-base-pg12-es7
- - .rspec-ee-unit-parallel
- - .rails:rules:default-branch-schedule-nightly--code-backstage-ee-only
-
rspec-ee unit pg12 opensearch1:
extends:
- .rspec-ee-base-pg12-opensearch1
@@ -745,12 +739,6 @@ rspec-ee unit pg12 opensearch2:
- .rspec-ee-unit-parallel
- .rails:rules:default-branch-schedule-nightly--code-backstage-ee-only
-rspec-ee integration pg12 es7:
- extends:
- - .rspec-ee-base-pg12-es7
- - .rspec-ee-integration-parallel
- - .rails:rules:default-branch-schedule-nightly--code-backstage-ee-only
-
rspec-ee integration pg12 opensearch1:
extends:
- .rspec-ee-base-pg12-opensearch1
@@ -763,12 +751,6 @@ rspec-ee integration pg12 opensearch2:
- .rspec-ee-integration-parallel
- .rails:rules:default-branch-schedule-nightly--code-backstage-ee-only
-rspec-ee system pg12 es7:
- extends:
- - .rspec-ee-base-pg12-es7
- - .rspec-ee-system-parallel
- - .rails:rules:default-branch-schedule-nightly--code-backstage-ee-only
-
rspec-ee system pg12 opensearch1:
extends:
- .rspec-ee-base-pg12-opensearch1
diff --git a/.gitlab/ci/rails/shared.gitlab-ci.yml b/.gitlab/ci/rails/shared.gitlab-ci.yml
index 00bdd8fc34e..0974ad782a8 100644
--- a/.gitlab/ci/rails/shared.gitlab-ci.yml
+++ b/.gitlab/ci/rails/shared.gitlab-ci.yml
@@ -120,12 +120,6 @@ include:
- .rspec-base
- .use-pg12-es7-ee
-.rspec-ee-base-pg12-es7:
- extends:
- - .rspec-base
- - .use-pg12-es7-ee
- - .rails:rules:run-search-tests
-
.rspec-ee-base-pg12-es8:
extends:
- .rspec-base
diff --git a/app/assets/javascripts/abuse_reports/components/abuse_category_selector.vue b/app/assets/javascripts/abuse_reports/components/abuse_category_selector.vue
index 93477a01073..b903bfee587 100644
--- a/app/assets/javascripts/abuse_reports/components/abuse_category_selector.vue
+++ b/app/assets/javascripts/abuse_reports/components/abuse_category_selector.vue
@@ -72,7 +72,12 @@ export default {
<input :value="$options.csrf.token" type="hidden" name="authenticity_token" />
<input type="hidden" name="user_id" :value="userId" data-testid="input-user-id" />
- <input type="hidden" name="ref_url" :value="reportedFromUrl" data-testid="input-referer" />
+ <input
+ type="hidden"
+ name="abuse_report[reported_from_url]"
+ :value="reportedFromUrl"
+ data-testid="input-referer"
+ />
<gl-form-group :label="$options.i18n.label">
<gl-form-radio-group
diff --git a/app/assets/javascripts/behaviors/shortcuts/shortcut.vue b/app/assets/javascripts/behaviors/shortcuts/shortcut.vue
index e5992779a99..38384157007 100644
--- a/app/assets/javascripts/behaviors/shortcuts/shortcut.vue
+++ b/app/assets/javascripts/behaviors/shortcuts/shortcut.vue
@@ -1,4 +1,5 @@
<script>
+import { getModifierKey } from '~/constants';
import { __, s__ } from '~/locale';
// Map some keys to their proper representation depending on the system
@@ -22,7 +23,7 @@ const getKeyMap = () => {
keyMap.alt = keyMap.option;
// Mod is Command on Mac, and Ctrl on Windows/Linux
- keyMap.mod = window.gl?.client?.isMac ? keyMap.command : keyMap.ctrl;
+ keyMap.mod = getModifierKey(true);
return keyMap;
};
diff --git a/app/assets/javascripts/constants.js b/app/assets/javascripts/constants.js
index c56d45166a0..defc2cbe276 100644
--- a/app/assets/javascripts/constants.js
+++ b/app/assets/javascripts/constants.js
@@ -1,3 +1,6 @@
-import { s__ } from '~/locale';
+/* eslint-disable @gitlab/require-i18n-strings */
-export const MODIFIER_KEY = window.gl?.client?.isMac ? '⌘' : s__('KeyboardKey|Ctrl+');
+export const getModifierKey = (removeSuffix = false) => {
+ const winKey = `Ctrl${removeSuffix ? '' : '+'}`;
+ return window.gl?.client?.isMac ? '⌘' : winKey;
+};
diff --git a/app/assets/javascripts/diffs/components/tree_list.vue b/app/assets/javascripts/diffs/components/tree_list.vue
index 709deaf6bb5..abf77fa2ede 100644
--- a/app/assets/javascripts/diffs/components/tree_list.vue
+++ b/app/assets/javascripts/diffs/components/tree_list.vue
@@ -2,11 +2,13 @@
import { GlTooltipDirective, GlIcon } from '@gitlab/ui';
import { mapActions, mapGetters, mapState } from 'vuex';
import micromatch from 'micromatch';
-import { MODIFIER_KEY } from '~/constants';
+import { getModifierKey } from '~/constants';
import { s__, sprintf } from '~/locale';
import FileTree from '~/vue_shared/components/file_tree.vue';
import DiffFileRow from './diff_file_row.vue';
+const MODIFIER_KEY = getModifierKey();
+
export default {
directives: {
GlTooltip: GlTooltipDirective,
diff --git a/app/assets/javascripts/editor/constants.js b/app/assets/javascripts/editor/constants.js
index d0649ecccba..d235319dfd7 100644
--- a/app/assets/javascripts/editor/constants.js
+++ b/app/assets/javascripts/editor/constants.js
@@ -1,7 +1,9 @@
-import { MODIFIER_KEY } from '~/constants';
+import { getModifierKey } from '~/constants';
import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants';
import { s__, __, sprintf } from '~/locale';
+const modifierKey = getModifierKey();
+
export const URI_PREFIX = 'gitlab';
export const CONTENT_UPDATE_DEBOUNCE = DEFAULT_DEBOUNCE_AND_THROTTLE_MS;
@@ -67,7 +69,7 @@ export const EXTENSION_MARKDOWN_BUTTONS = [
{
id: 'bold',
label: sprintf(s__('MarkdownEditor|Add bold text (%{modifierKey}B)'), {
- modifierKey: MODIFIER_KEY,
+ modifierKey,
}),
data: {
mdTag: '**',
@@ -77,7 +79,7 @@ export const EXTENSION_MARKDOWN_BUTTONS = [
{
id: 'italic',
label: sprintf(s__('MarkdownEditor|Add italic text (%{modifierKey}I)'), {
- modifierKey: MODIFIER_KEY,
+ modifierKey,
}),
data: {
mdTag: '_',
@@ -87,7 +89,7 @@ export const EXTENSION_MARKDOWN_BUTTONS = [
{
id: 'strikethrough',
label: sprintf(s__('MarkdownEditor|Add strikethrough text (%{modifierKey}⇧X)'), {
- modifierKey: MODIFIER_KEY,
+ modifierKey,
}),
data: {
mdTag: '~~',
@@ -113,7 +115,7 @@ export const EXTENSION_MARKDOWN_BUTTONS = [
{
id: 'link',
label: sprintf(s__('MarkdownEditor|Add a link (%{modifier_key}K)'), {
- modifierKey: MODIFIER_KEY,
+ modifierKey,
}),
data: {
mdTag: '[{text}](url)',
diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/tags_list_row.vue b/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/tags_list_row.vue
index acf810257e6..38b601ac3ec 100644
--- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/tags_list_row.vue
+++ b/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/tags_list_row.vue
@@ -95,7 +95,7 @@ export default {
return formatDate(this.tag.createdAt, 'isoDate');
},
publishedTime() {
- return formatDate(this.tag.createdAt, 'hh:MM Z');
+ return formatDate(this.tag.createdAt, 'HH:MM:ss Z');
},
formattedRevision() {
// to be removed when API response is adjusted
diff --git a/app/assets/javascripts/packages_and_registries/infrastructure_registry/details/components/package_history.vue b/app/assets/javascripts/packages_and_registries/infrastructure_registry/details/components/package_history.vue
index e5be98b87f7..06e4c38a179 100644
--- a/app/assets/javascripts/packages_and_registries/infrastructure_registry/details/components/package_history.vue
+++ b/app/assets/javascripts/packages_and_registries/infrastructure_registry/details/components/package_history.vue
@@ -96,9 +96,9 @@ export default {
<history-item icon="commit" data-testid="first-pipeline-commit">
<gl-sprintf :message="$options.i18n.createdByCommitText">
<template #link>
- <gl-link :href="firstPipeline.project.commit_url"
- >#{{ truncate(firstPipeline.sha) }}</gl-link
- >
+ <gl-link :href="firstPipeline.project.commit_url">{{
+ truncate(firstPipeline.sha)
+ }}</gl-link>
</template>
<template #branch>
<strong>{{ firstPipeline.ref }}</strong>
@@ -147,7 +147,7 @@ export default {
>
<gl-sprintf :message="$options.i18n.combinedUpdateText">
<template #link>
- <gl-link :href="pipeline.project.commit_url">#{{ truncate(pipeline.sha) }}</gl-link>
+ <gl-link :href="pipeline.project.commit_url">{{ truncate(pipeline.sha) }}</gl-link>
</template>
<template #branch>
<strong>{{ pipeline.ref }}</strong>
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/details/package_history.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/details/package_history.vue
index a1fc7563de1..663c361819e 100644
--- a/app/assets/javascripts/packages_and_registries/package_registry/components/details/package_history.vue
+++ b/app/assets/javascripts/packages_and_registries/package_registry/components/details/package_history.vue
@@ -159,9 +159,9 @@ export default {
<history-item icon="commit" data-testid="first-pipeline-commit">
<gl-sprintf :message="$options.i18n.createdByCommitText">
<template #link>
- <gl-link :href="firstPipeline.commitPath" @click="trackCommitClick"
- >#{{ truncate(firstPipeline.sha) }}</gl-link
- >
+ <gl-link :href="firstPipeline.commitPath" @click="trackCommitClick">{{
+ truncate(firstPipeline.sha)
+ }}</gl-link>
</template>
<template #branch>
<strong>{{ firstPipeline.ref }}</strong>
@@ -212,9 +212,9 @@ export default {
>
<gl-sprintf :message="$options.i18n.combinedUpdateText">
<template #link>
- <gl-link :href="pipeline.commitPath" @click="trackCommitClick"
- >#{{ truncate(pipeline.sha) }}</gl-link
- >
+ <gl-link :href="pipeline.commitPath" @click="trackCommitClick">{{
+ truncate(pipeline.sha)
+ }}</gl-link>
</template>
<template #branch>
<strong>{{ pipeline.ref }}</strong>
diff --git a/app/assets/javascripts/users/profile/components/report_abuse_button.vue b/app/assets/javascripts/users/profile/components/report_abuse_button.vue
index 3008cdb6726..24d4678064a 100644
--- a/app/assets/javascripts/users/profile/components/report_abuse_button.vue
+++ b/app/assets/javascripts/users/profile/components/report_abuse_button.vue
@@ -1,6 +1,7 @@
<script>
import { GlButton, GlTooltipDirective } from '@gitlab/ui';
import { __ } from '~/locale';
+import { BV_HIDE_TOOLTIP } from '~/lib/utils/constants';
import AbuseCategorySelector from '~/abuse_reports/components/abuse_category_selector.vue';
@@ -34,6 +35,9 @@ export default {
closeDrawer() {
this.open = false;
},
+ hideTooltips() {
+ this.$root.$emit(BV_HIDE_TOOLTIP);
+ },
},
};
</script>
@@ -45,6 +49,7 @@ export default {
:aria-label="buttonTooltipText"
icon="error"
@click="openDrawer"
+ @mouseout="hideTooltips"
/>
<abuse-category-selector :show-drawer="open" @close-drawer="closeDrawer" />
</span>
diff --git a/app/assets/javascripts/work_items/components/work_item_notes.vue b/app/assets/javascripts/work_items/components/work_item_notes.vue
index 8bd4e7cdb73..bbcaf066bb8 100644
--- a/app/assets/javascripts/work_items/components/work_item_notes.vue
+++ b/app/assets/javascripts/work_items/components/work_item_notes.vue
@@ -209,6 +209,7 @@ export default {
:query-variables="queryVariables"
:full-path="fullPath"
:work-item-id="workItemId"
+ :fetch-by-iid="fetchByIid"
@error="$emit('error', $event)"
/>
</ul>
diff --git a/app/controllers/abuse_reports_controller.rb b/app/controllers/abuse_reports_controller.rb
index 1efcd55d72a..eec56682300 100644
--- a/app/controllers/abuse_reports_controller.rb
+++ b/app/controllers/abuse_reports_controller.rb
@@ -1,20 +1,22 @@
# frozen_string_literal: true
class AbuseReportsController < ApplicationController
- before_action :set_user, :set_ref_url, only: [:new, :add_category]
- before_action :save_origin_url, only: [:new, :add_category]
- before_action :set_origin_url, only: [:create]
+ before_action :set_user, only: [:new, :add_category]
feature_category :insider_threat
def new
- @abuse_report = AbuseReport.new(user_id: @user.id)
+ @abuse_report = AbuseReport.new(
+ user_id: @user.id,
+ reported_from_url: params.fetch(:ref_url, '')
+ )
end
def add_category
@abuse_report = AbuseReport.new(
user_id: @user.id,
- category: report_params[:category]
+ category: report_params[:category],
+ reported_from_url: report_params[:reported_from_url]
)
render :new
@@ -39,7 +41,7 @@ class AbuseReportsController < ApplicationController
private
def report_params
- params.require(:abuse_report).permit(:message, :user_id, :category)
+ params.require(:abuse_report).permit(:message, :user_id, :category, :reported_from_url)
end
# rubocop: disable CodeReuse/ActiveRecord
@@ -53,17 +55,4 @@ class AbuseReportsController < ApplicationController
end
end
# rubocop: enable CodeReuse/ActiveRecord
-
- def set_ref_url
- @ref_url = params.fetch(:ref_url, '')
- end
-
- def save_origin_url
- @origin_url = params.fetch(:ref_url, request.referer)
- session[:abuse_report_origin_url] = @origin_url
- end
-
- def set_origin_url
- @origin_url = session[:abuse_report_origin_url]
- end
end
diff --git a/app/models/abuse_report.rb b/app/models/abuse_report.rb
index 424778af4e6..cd27fa157f7 100644
--- a/app/models/abuse_report.rb
+++ b/app/models/abuse_report.rb
@@ -17,10 +17,19 @@ class AbuseReport < ApplicationRecord
uniqueness: {
scope: :reporter_id,
message: ->(object, data) do
- _('You have already reported this user')
+ _('has already been reported for abuse')
end
}
+ validates :reported_from_url,
+ allow_blank: true,
+ length: { maximum: 512 },
+ addressable_url: {
+ dns_rebind_protection: true,
+ blocked_message: 'is an invalid URL. You can try reporting the abuse again, ' \
+ 'or contact a GitLab administrator for help.'
+ }
+
scope :by_user, ->(user) { where(user_id: user) }
scope :with_users, -> { includes(:reporter, :user) }
@@ -38,6 +47,14 @@ class AbuseReport < ApplicationRecord
# For CacheMarkdownField
alias_method :author, :reporter
+ HUMANIZED_ATTRIBUTES = {
+ reported_from_url: "Reported from"
+ }.freeze
+
+ def self.human_attribute_name(attr, options = {})
+ HUMANIZED_ATTRIBUTES[attr.to_sym] || super
+ end
+
def remove_user(deleted_by:)
user.delete_async(deleted_by: deleted_by, params: { hard_delete: true })
end
diff --git a/app/models/concerns/project_features_compatibility.rb b/app/models/concerns/project_features_compatibility.rb
index d37f20e2e7c..b910c0ab5c2 100644
--- a/app/models/concerns/project_features_compatibility.rb
+++ b/app/models/concerns/project_features_compatibility.rb
@@ -124,8 +124,13 @@ module ProjectFeaturesCompatibility
private
def write_feature_attribute_boolean(field, value)
- access_level = Gitlab::Utils.to_boolean(value) ? ProjectFeature::ENABLED : ProjectFeature::DISABLED
- write_feature_attribute_raw(field, access_level)
+ value_type = Gitlab::Utils.to_boolean(value)
+ if value_type.in?([true, false])
+ access_level = value_type ? ProjectFeature::ENABLED : ProjectFeature::DISABLED
+ write_feature_attribute_raw(field, access_level)
+ else
+ write_feature_attribute_string(field, value)
+ end
end
def write_feature_attribute_string(field, value)
diff --git a/app/models/personal_access_token.rb b/app/models/personal_access_token.rb
index 887ef36cc17..0da205f86a5 100644
--- a/app/models/personal_access_token.rb
+++ b/app/models/personal_access_token.rb
@@ -21,6 +21,11 @@ class PersonalAccessToken < ApplicationRecord
after_initialize :set_default_scopes, if: :persisted?
before_save :ensure_token
+ # During the implementation of Admin Mode for API, tokens of
+ # administrators should automatically get the `admin_mode` scope as well
+ # See https://gitlab.com/gitlab-org/gitlab/-/issues/42692
+ before_create :add_admin_mode_scope, if: :user_admin?
+
scope :active, -> { not_revoked.not_expired }
scope :expiring_and_not_notified, ->(date) { where(["revoked = false AND expire_notification_delivered = false AND expires_at >= CURRENT_DATE AND expires_at <= ?", date]) }
scope :expired_today_and_not_notified, -> { where(["revoked = false AND expires_at = CURRENT_DATE AND after_expiry_notification_delivered = false"]) }
@@ -79,7 +84,12 @@ class PersonalAccessToken < ApplicationRecord
protected
def validate_scopes
- unless revoked || scopes.all? { |scope| Gitlab::Auth.all_available_scopes.include?(scope.to_sym) }
+ # During the implementation of Admin Mode for API,
+ # the `admin_mode` scope is not yet part of `all_available_scopes` but still valid.
+ # See https://gitlab.com/gitlab-org/gitlab/-/issues/42692
+ valid_scopes = Gitlab::Auth.all_available_scopes + [Gitlab::Auth::ADMIN_MODE_SCOPE]
+
+ unless revoked || scopes.all? { |scope| valid_scopes.include?(scope.to_sym) }
errors.add :scopes, "can only contain available scopes"
end
end
@@ -91,6 +101,14 @@ class PersonalAccessToken < ApplicationRecord
self.scopes = Gitlab::Auth::DEFAULT_SCOPES if self.scopes.empty?
end
+
+ def user_admin?
+ user.admin? # rubocop: disable Cop/UserAdmin
+ end
+
+ def add_admin_mode_scope
+ self.scopes += [Gitlab::Auth::ADMIN_MODE_SCOPE.to_s]
+ end
end
PersonalAccessToken.prepend_mod_with('PersonalAccessToken')
diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb
index a4b473f35c6..d3313526eaf 100644
--- a/app/services/projects/create_service.rb
+++ b/app/services/projects/create_service.rb
@@ -22,6 +22,12 @@ module Projects
end
def execute
+ params[:wiki_enabled] = params[:wiki_access_level] if params[:wiki_access_level]
+ params[:builds_enabled] = params[:builds_access_level] if params[:builds_access_level]
+ params[:snippets_enabled] = params[:builds_access_level] if params[:snippets_access_level]
+ params[:merge_requests_enabled] = params[:merge_requests_access_level] if params[:merge_requests_access_level]
+ params[:issues_enabled] = params[:issues_access_level] if params[:issues_access_level]
+
if create_from_template?
return ::Projects::CreateFromTemplateService.new(current_user, params).execute
end
diff --git a/app/views/abuse_reports/new.html.haml b/app/views/abuse_reports/new.html.haml
index 3cb6207f7ef..393021ed93c 100644
--- a/app/views/abuse_reports/new.html.haml
+++ b/app/views/abuse_reports/new.html.haml
@@ -1,30 +1,36 @@
- page_title _("Report abuse to administrator")
-%h1.page-title.gl-font-size-h-display
- = _("Report abuse to administrator")
-%p
- = _("Use this form to report to the administrator users who create spam issues, comments or behave inappropriately.")
-%p
- = _("A member of the abuse team will review your report as soon as possible.")
-%hr
-= gitlab_ui_form_for @abuse_report, html: { class: 'js-quick-submit js-requires-input'} do |f|
- = form_errors(@abuse_report, custom_message: [:user_id])
+.row
+ .col-lg-8
+ %h1.page-title.gl-font-size-h-display
+ = _("Report abuse to administrator")
+ %p
+ = _("Please use this form to report to the administrator users who create spam issues, comments or behave inappropriately.")
+ = _("A member of the abuse team will review your report as soon as possible.")
+
+= gitlab_ui_form_for @abuse_report, html: { class: 'js-quick-submit'} do |f|
+ = form_errors(@abuse_report)
= f.hidden_field :user_id
= f.hidden_field :category
+
.form-group.row
- .col-sm-2.col-form-label
- = f.label :user_id
- .col-sm-10
+ .col-lg-8
+ = f.label :reported_user
+
- name = "#{@abuse_report.user.name} (@#{@abuse_report.user.username})"
= text_field_tag :user_name, name, class: "form-control", readonly: true
.form-group.row
- .col-sm-2.col-form-label
- = f.label :message
- .col-sm-10
- = f.text_area :message, class: "form-control", rows: 2, required: true, value: sanitize(@ref_url)
+ .col-lg-8
+ = f.label :reported_from
+ = f.text_field :reported_from_url, class: "form-control", readonly: true
+ .form-group.row
+ .col-lg-8
+ = f.label :reason
+ = f.text_area :message, class: "form-control", rows: 4, required: true
.form-text.text-muted
- = _("Explain the problem. If appropriate, provide a link to the relevant issue or comment.")
+ = _("Explain why you're reporting the user.")
- .form-actions
- = f.submit _("Send report"), pajamas_button: true
- = link_to _('Cancel'), @origin_url, class: "gl-button btn btn-default btn-cancel"
+ = render Pajamas::ButtonComponent.new(type: :submit, variant: :confirm) do
+ = _('Send report')
+ = render Pajamas::ButtonComponent.new(href: @abuse_report.reported_from_url, button_options: { class: 'gl-ml-3' }) do
+ = _('Cancel')
diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml
index a65f5bed086..ed1368c7924 100644
--- a/app/views/users/show.html.haml
+++ b/app/views/users/show.html.haml
@@ -18,7 +18,7 @@
icon: 'pencil',
button_options: { class: 'gl-flex-grow-1 gl-mx-1 has-tooltip', title: s_('UserProfile|Edit profile'), 'aria-label': 'Edit profile', data: { toggle: 'tooltip', placement: 'bottom', container: 'body' }})
- elsif current_user
- #js-report-abuse{ data: { form_submit_path: add_category_abuse_reports_path, user_id: @user.id, reported_from_url: request.referer || user_url(@user) } }
+ #js-report-abuse{ data: { form_submit_path: add_category_abuse_reports_path, user_id: @user.id, reported_from_url: user_url(@user) } }
- verified_gpg_keys = @user.gpg_keys.select(&:verified?)
- if verified_gpg_keys.any?
= render Pajamas::ButtonComponent.new(href: user_gpg_keys_path,
diff --git a/config/feature_flags/development/repository_archive_hotlinking_interception.yml b/config/feature_flags/development/repository_archive_hotlinking_interception.yml
deleted file mode 100644
index dbdde4993f8..00000000000
--- a/config/feature_flags/development/repository_archive_hotlinking_interception.yml
+++ /dev/null
@@ -1,8 +0,0 @@
----
-name: repository_archive_hotlinking_interception
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/commit/50c11f278d18fe1f3fb12eb595067216bb58ade2
-rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/369433
-milestone: '12.10'
-type: development
-group: group::source code
-default_enabled: true
diff --git a/config/feature_flags/development/rate_limit_gitlab_shell.yml b/config/feature_flags/development/sec_mark_dropped_findings_as_resolved_scheduler.yml
index 3c29a71af6e..1f26ffb28ae 100644
--- a/config/feature_flags/development/rate_limit_gitlab_shell.yml
+++ b/config/feature_flags/development/sec_mark_dropped_findings_as_resolved_scheduler.yml
@@ -1,8 +1,8 @@
---
-name: rate_limit_gitlab_shell
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/78373
-rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/350465
-milestone: '14.7'
+name: sec_mark_dropped_findings_as_resolved_scheduler
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/108486
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/387577
+milestone: '15.8'
type: development
-group: group::source code
-default_enabled: true
+group: group::static analysis
+default_enabled: false
diff --git a/db/migrate/20221212054640_add_reported_from_to_abuse_reports.rb b/db/migrate/20221212054640_add_reported_from_to_abuse_reports.rb
new file mode 100644
index 00000000000..701c0fa2e2c
--- /dev/null
+++ b/db/migrate/20221212054640_add_reported_from_to_abuse_reports.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+class AddReportedFromToAbuseReports < Gitlab::Database::Migration[2.1]
+ disable_ddl_transaction!
+
+ def up
+ with_lock_retries do
+ unless column_exists?(:abuse_reports, :reported_from_url)
+ add_column :abuse_reports, :reported_from_url, :text, null: false, default: ''
+ end
+ end
+
+ add_text_limit :abuse_reports, :reported_from_url, 512
+ end
+
+ def down
+ with_lock_retries do
+ remove_column :abuse_reports, :reported_from_url if column_exists?(:abuse_reports, :reported_from_url)
+ end
+ end
+end
diff --git a/db/post_migrate/20221228103133_queue_backfill_admin_mode_scope_for_personal_access_tokens.rb b/db/post_migrate/20221228103133_queue_backfill_admin_mode_scope_for_personal_access_tokens.rb
new file mode 100644
index 00000000000..c111d5090e1
--- /dev/null
+++ b/db/post_migrate/20221228103133_queue_backfill_admin_mode_scope_for_personal_access_tokens.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+class QueueBackfillAdminModeScopeForPersonalAccessTokens < Gitlab::Database::Migration[2.1]
+ MIGRATION = 'BackfillAdminModeScopeForPersonalAccessTokens'
+ DELAY_INTERVAL = 2.minutes
+
+ restrict_gitlab_migration gitlab_schema: :gitlab_main
+
+ def up
+ queue_batched_background_migration(
+ MIGRATION,
+ :personal_access_tokens,
+ :id,
+ job_interval: DELAY_INTERVAL
+ )
+ end
+
+ def down
+ delete_batched_background_migration(MIGRATION, :personal_access_tokens, :id, [])
+ end
+end
diff --git a/db/schema_migrations/20221212054640 b/db/schema_migrations/20221212054640
new file mode 100644
index 00000000000..2df095bd898
--- /dev/null
+++ b/db/schema_migrations/20221212054640
@@ -0,0 +1 @@
+18c98815e882f808ec2d5d29d605b89bd725690f0c399627eaa98f4ff7d3ef76 \ No newline at end of file
diff --git a/db/schema_migrations/20221228103133 b/db/schema_migrations/20221228103133
new file mode 100644
index 00000000000..757c1d9169d
--- /dev/null
+++ b/db/schema_migrations/20221228103133
@@ -0,0 +1 @@
+59e19291b3f8bb08dd63c1b1993af8f75e06d56ca776c3e8711adcc8c5c26e86 \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index f04e0cf7d85..21857bfa4e0 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -10603,7 +10603,9 @@ CREATE TABLE abuse_reports (
updated_at timestamp without time zone,
message_html text,
cached_markdown_version integer,
- category smallint DEFAULT 1 NOT NULL
+ category smallint DEFAULT 1 NOT NULL,
+ reported_from_url text DEFAULT ''::text NOT NULL,
+ CONSTRAINT check_ab1260fa6c CHECK ((char_length(reported_from_url) <= 512))
);
CREATE SEQUENCE abuse_reports_id_seq
diff --git a/doc/administration/instance_limits.md b/doc/administration/instance_limits.md
index 59746fc0f07..5215f0eaed2 100644
--- a/doc/administration/instance_limits.md
+++ b/doc/administration/instance_limits.md
@@ -912,7 +912,7 @@ An upper and lower limit applies to each of these:
The lower limits result in additional diffs being collapsed. The higher limits
prevent any more changes from rendering. For more information about these limits,
-[read the development documentation](../development/diffs.md#diff-limits).
+[read the development documentation](../development/merge_request_concepts/diffs/index.md#diff-limits).
### Merge request reports size limit
diff --git a/doc/administration/merge_request_diffs.md b/doc/administration/merge_request_diffs.md
index 25dba00beb9..85677512860 100644
--- a/doc/administration/merge_request_diffs.md
+++ b/doc/administration/merge_request_diffs.md
@@ -1,6 +1,6 @@
---
stage: Create
-group: Editor
+group: Source Code
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
---
diff --git a/doc/administration/pages/index.md b/doc/administration/pages/index.md
index 2a28df96ef4..f475e5ac167 100644
--- a/doc/administration/pages/index.md
+++ b/doc/administration/pages/index.md
@@ -185,7 +185,7 @@ you must also add the full paths as shown below:
1. [Reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure).
1. If you're using [Pages Access Control](#access-control), update the redirect URI in the GitLab Pages
-[System OAuth application](../../integration/oauth_provider.md#instance-wide-applications)
+[System OAuth application](../../integration/oauth_provider.md#create-an-instance-wide-application)
to use the HTTPS protocol.
WARNING:
@@ -384,7 +384,7 @@ then you need to also add the full paths as shown below:
1. [Reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure).
1. If you're using [Pages Access Control](#access-control), update the redirect URI in the GitLab Pages
-[System OAuth application](../../integration/oauth_provider.md#instance-wide-applications)
+[System OAuth application](../../integration/oauth_provider.md#create-an-instance-wide-application)
to use the HTTPS protocol.
### Custom domain verification
@@ -1428,7 +1428,7 @@ Once added, reconfigure with `sudo gitlab-ctl reconfigure` and restart GitLab wi
You may see this error if `pages_external_url` was updated at some point of time. Verify the following:
-1. The **Callback URL**/Redirect URI in the GitLab Pages [System OAuth application](../../integration/oauth_provider.md#instance-wide-applications)
+1. The **Callback URL**/Redirect URI in the GitLab Pages [System OAuth application](../../integration/oauth_provider.md#create-an-instance-wide-application)
is using the protocol (HTTP or HTTPS) that `pages_external_url` is configured to use.
1. The domain and path components of `Redirect URI` are valid: they should look like `projects.<pages_external_url>/auth`.
diff --git a/doc/administration/pages/source.md b/doc/administration/pages/source.md
index e122d49a963..db76d15ec58 100644
--- a/doc/administration/pages/source.md
+++ b/doc/administration/pages/source.md
@@ -445,7 +445,7 @@ Pages access control is disabled by default. To enable it:
```
1. [Restart GitLab](../restart_gitlab.md#installations-from-source).
-1. Create a new [system OAuth application](../../integration/oauth_provider.md#user-owned-applications).
+1. Create a new [system OAuth application](../../integration/oauth_provider.md#create-a-user-owned-application).
This should be called `GitLab Pages` and have a `Redirect URL` of
`https://projects.example.io/auth`. It does not need to be a "trusted"
application, but it does need the `api` scope.
diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md
index f5643ba43be..6e501c9ab4d 100644
--- a/doc/api/merge_requests.md
+++ b/doc/api/merge_requests.md
@@ -986,7 +986,7 @@ returned by the API or viewed via the UI. When these limits impact the results,
field contains a value of `true`. Diff data without these limits applied can be retrieved by
adding the `access_raw_diffs` parameter, accessing diffs not from the database but from Gitaly directly.
This approach is generally slower and more resource-intensive, but isn't subject to size limits
-placed on database-backed diffs. [Limits inherent to Gitaly](../development/diffs.md#diff-limits)
+placed on database-backed diffs. [Limits inherent to Gitaly](../development/merge_request_concepts/diffs/index.md#diff-limits)
still apply.
```plaintext
diff --git a/doc/api/oauth2.md b/doc/api/oauth2.md
index 14f8ae3b15b..3e470c5cb91 100644
--- a/doc/api/oauth2.md
+++ b/doc/api/oauth2.md
@@ -59,7 +59,7 @@ resources which the `application` can access. Upon creation, you obtain the
**must be kept secure**. It is also advantageous to keep the _Application ID_
secret when your application architecture allows.
-For a list of scopes in GitLab, see [the provider documentation](../integration/oauth_provider.md#authorized-applications).
+For a list of scopes in GitLab, see [the provider documentation](../integration/oauth_provider.md#view-all-authorized-applications).
### Prevent CSRF attacks
@@ -116,7 +116,7 @@ Before starting the flow, generate the `STATE`, the `CODE_VERIFIER` and the `COD
This page asks the user to approve the request from the app to access their
account based on the scopes specified in `REQUESTED_SCOPES`. The user is then
- redirected back to the specified `REDIRECT_URI`. The [scope parameter](../integration/oauth_provider.md#authorized-applications)
+ redirected back to the specified `REDIRECT_URI`. The [scope parameter](../integration/oauth_provider.md#view-all-authorized-applications)
is a space-separated list of scopes associated with the user.
For example,`scope=read_user+profile` requests the `read_user` and `profile` scopes.
The redirect includes the authorization `code`, for example:
@@ -196,7 +196,7 @@ be used as a CSRF token.
This page asks the user to approve the request from the app to access their
account based on the scopes specified in `REQUESTED_SCOPES`. The user is then
- redirected back to the specified `REDIRECT_URI`. The [scope parameter](../integration/oauth_provider.md#authorized-applications)
+ redirected back to the specified `REDIRECT_URI`. The [scope parameter](../integration/oauth_provider.md#view-all-authorized-applications)
is a space-separated list of scopes associated with the user.
For example,`scope=read_user+profile` requests the `read_user` and `profile` scopes.
The redirect includes the authorization `code`, for example:
@@ -352,7 +352,7 @@ curl --header "Authorization: Bearer OAUTH-TOKEN" "https://gitlab.example.com/ap
## Access Git over HTTPS with `access token`
-A token with [scope](../integration/oauth_provider.md#authorized-applications)
+A token with [scope](../integration/oauth_provider.md#view-all-authorized-applications)
`read_repository` or `write_repository` can access Git over HTTPS. Use the token as the password.
The username must be `oauth2`, not your username:
diff --git a/doc/api/repositories.md b/doc/api/repositories.md
index 428a09f1bbe..dcbb5d14741 100644
--- a/doc/api/repositories.md
+++ b/doc/api/repositories.md
@@ -171,7 +171,7 @@ curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.com/api/v4/pr
This endpoint can be accessed without authentication if the repository is
publicly accessible. Diffs can have an empty diff string if
-[diff limits](../development/diffs.md#diff-limits) are reached.
+[diff limits](../development/merge_request_concepts/diffs/index.md#diff-limits) are reached.
```plaintext
GET /projects/:id/repository/compare
diff --git a/doc/api/secure_files.md b/doc/api/secure_files.md
index c2c21134d6f..a47c9654557 100644
--- a/doc/api/secure_files.md
+++ b/doc/api/secure_files.md
@@ -7,11 +7,10 @@ type: reference, api
# Project-level Secure Files API **(FREE)**
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/78227) in GitLab 14.8. [Deployed behind the `ci_secure_files` flag](../administration/feature_flags.md), disabled by default.
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/78227) in GitLab 14.8. [Deployed behind the `ci_secure_files` flag](../administration/feature_flags.md), disabled by default.
+> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/350748) in GitLab 15.7.
-FLAG:
-On self-managed GitLab, by default this feature is not available. To make it available,
-ask an administrator to [enable the feature flag](../administration/feature_flags.md) named `ci_secure_files`. Limited to 100 secure files per project. Files must be smaller than 5 MB. The feature is not ready for production use.
+Limited to 100 secure files per project. Files must be smaller than 5 MB. Project-level Secure Files is an experimental feature developed by [GitLab Incubation Engineering](https://about.gitlab.com/handbook/engineering/incubation/).
## List project secure files
diff --git a/doc/ci/secure_files/index.md b/doc/ci/secure_files/index.md
index e63b671ad2b..32bc4f2d758 100644
--- a/doc/ci/secure_files/index.md
+++ b/doc/ci/secure_files/index.md
@@ -7,24 +7,20 @@ type: reference
# Project-level Secure Files **(FREE)**
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/78227) in GitLab 14.8. [Deployed behind the `ci_secure_files` flag](../../administration/feature_flags.md), disabled by default.
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/78227) in GitLab 14.8. [Deployed behind the `ci_secure_files` flag](../../administration/feature_flags.md), disabled by default.
+> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/350748) in GitLab 15.7.
-FLAG:
-On self-managed GitLab, by default this feature is not available. To make it available,
-ask an administrator to [enable the feature flag](../../administration/feature_flags.md)
-named `ci_secure_files`. Limited to 100 secure files per project. Files must be smaller
-than 5 MB. Project-level Secure Files is an experimental feature developed by [GitLab Incubation Engineering](https://about.gitlab.com/handbook/engineering/incubation/).
-
-Project-level Secure Files is still in development, but you can:
+Project-level Secure Files is an experimental feature developed by [GitLab Incubation Engineering](https://about.gitlab.com/handbook/engineering/incubation/).
+The feature is still in development, but you can:
- [Request a feature](https://gitlab.com/gitlab-org/incubation-engineering/mobile-devops/feedback/-/issues/new?issuable_template=feature_request).
- [Report a bug](https://gitlab.com/gitlab-org/incubation-engineering/mobile-devops/feedback/-/issues/new?issuable_template=report_bug).
- [Share feedback](https://gitlab.com/gitlab-org/incubation-engineering/mobile-devops/feedback/-/issues/new?issuable_template=general_feedback).
-You can securely store files for use in CI/CD pipelines as "secure files". These files
+You can securely store up to 100 files for use in CI/CD pipelines as "secure files". These files
are stored securely outside of your project's repository, and are not version controlled.
It is safe to store sensitive information in these files. Secure files support both
-plain text and binary file types.
+plain text and binary file types, but must be 5 MB or less.
You can manage secure files in the project settings, or with the [secure files API](../../api/secure_files.md).
diff --git a/doc/development/api_graphql_styleguide.md b/doc/development/api_graphql_styleguide.md
index e0db0d7e34d..396bba2623e 100644
--- a/doc/development/api_graphql_styleguide.md
+++ b/doc/development/api_graphql_styleguide.md
@@ -785,7 +785,7 @@ The documentation will mention that the old Global ID style is now deprecated.
## Mark schema items as Alpha
You can mark GraphQL schema items (fields, arguments, enum values, and mutations) as
-[Alpha](https://about.gitlab.com/handbook/product/gitlab-the-product/#alpha-beta-ga).
+[Alpha](../policy/alpha-beta-support.md#alpha-features).
An item marked as Alpha is [exempt from the deprecation process](#breaking-change-exemptions) and can be removed
at any time without notice. Mark an item as Alpha when it is
diff --git a/doc/development/cached_queries.md b/doc/development/cached_queries.md
index e4625a50d79..1b590d68d18 100644
--- a/doc/development/cached_queries.md
+++ b/doc/development/cached_queries.md
@@ -37,9 +37,9 @@ in-memory objects whenever possible.
When you introduce a new feature, you should:
- Avoid N+1 queries.
-- Minimize the [query count](merge_request_performance_guidelines.md#query-counts).
+- Minimize the [query count](merge_request_concepts/performance.md#query-counts).
- Pay special attention to ensure
- [cached queries](merge_request_performance_guidelines.md#cached-queries) are not
+ [cached queries](merge_request_concepts/performance.md#cached-queries) are not
masking N+1 problems.
## How to detect cached queries
@@ -163,5 +163,5 @@ factors help improve the overall execution time:
## For more information
- [Metrics that would help us detect the potential N+1 Cached SQL calls](https://gitlab.com/gitlab-org/gitlab/-/issues/259007)
-- [Merge request performance guidelines for cached queries](merge_request_performance_guidelines.md#cached-queries)
+- [Merge request performance guidelines for cached queries](merge_request_concepts/performance.md#cached-queries)
- [Improvements for biggest offenders](https://gitlab.com/groups/gitlab-org/-/epics/4508)
diff --git a/doc/development/chatops_on_gitlabcom.md b/doc/development/chatops_on_gitlabcom.md
index c9cb2591e4e..378639c4a43 100644
--- a/doc/development/chatops_on_gitlabcom.md
+++ b/doc/development/chatops_on_gitlabcom.md
@@ -22,12 +22,12 @@ To request access to ChatOps on GitLab.com:
1. Sign in to [Internal GitLab for Operations](https://ops.gitlab.net/users/sign_in)
with one of the following methods (Okta is not supported):
- - The same username you use on GitLab.com. You may have to choose a different username later.
+ - The same username you use on GitLab.com.
- Selecting the **Sign in with Google** button to sign in with your GitLab.com email address.
1. Confirm that your username in [Internal GitLab for Operations](https://ops.gitlab.net/)
is the same as your username in [GitLab.com](https://gitlab.com/). If the usernames
- don't match, update the username in [User Settings/Account for the Ops instance](https://ops.gitlab.net/-/profile/account).
+ don't match, update the username in [User Settings/Account for the Ops instance](https://ops.gitlab.net/-/profile/account). Matching usernames are required to reduce the administrative effort of running multiple platforms. Matching usernames also help with tasks like managing access requests and offboarding.
1. Comment in your onboarding issue, and tag your onboarding buddy and your manager.
Request they add you to the `ops` ChatOps project by running this command
diff --git a/doc/development/code_review.md b/doc/development/code_review.md
index 9dc97a1aa27..4d7815b474b 100644
--- a/doc/development/code_review.md
+++ b/doc/development/code_review.md
@@ -200,7 +200,7 @@ See the [test engineering process](https://about.gitlab.com/handbook/engineering
##### Performance, reliability, and availability
-1. You are confident that this MR does not harm performance, or you have asked a reviewer to help assess the performance impact. ([Merge request performance guidelines](merge_request_performance_guidelines.md))
+1. You are confident that this MR does not harm performance, or you have asked a reviewer to help assess the performance impact. ([Merge request performance guidelines](merge_request_concepts/performance.md))
1. You have added [information for database reviewers in the MR description](database_review.md#required), or you have decided that it is unnecessary.
- [Does this MR have database-related changes?](database_review.md)
1. You have considered the availability and reliability risks of this change.
diff --git a/doc/development/contributing/merge_request_workflow.md b/doc/development/contributing/merge_request_workflow.md
index f06e8825660..ac3afa14b81 100644
--- a/doc/development/contributing/merge_request_workflow.md
+++ b/doc/development/contributing/merge_request_workflow.md
@@ -195,7 +195,7 @@ the contribution acceptance criteria below:
and testing future changes.
1. Changes do not degrade performance:
- Avoid repeated polling of endpoints that require a significant amount of overhead.
- - Check for N+1 queries via the SQL log or [`QueryRecorder`](../merge_request_performance_guidelines.md).
+ - Check for N+1 queries via the SQL log or [`QueryRecorder`](../merge_request_concepts/performance.md).
- Avoid repeated access of the file system.
- Use [polling with ETag caching](../polling.md) if needed to support real-time features.
1. If the merge request adds any new libraries (like gems or JavaScript libraries),
@@ -223,7 +223,7 @@ requirements.
1. Working and clean code that is commented where needed.
1. The change is evaluated to [limit the impact of far-reaching work](https://about.gitlab.com/handbook/engineering/development/#reducing-the-impact-of-far-reaching-work).
-1. [Performance guidelines](../merge_request_performance_guidelines.md) have been followed.
+1. [Performance guidelines](../merge_request_concepts/performance.md) have been followed.
1. [Secure coding guidelines](https://gitlab.com/gitlab-com/gl-security/security-guidelines) have been followed.
1. [Application and rate limit guidelines](../merge_request_application_and_rate_limit_guidelines.md) have been followed.
1. [Documented](../documentation/index.md) in the `/doc` directory.
diff --git a/doc/development/database/pagination_guidelines.md b/doc/development/database/pagination_guidelines.md
index 54b315b9dd9..aeab45e2158 100644
--- a/doc/development/database/pagination_guidelines.md
+++ b/doc/development/database/pagination_guidelines.md
@@ -62,7 +62,7 @@ Offset-based pagination is the easiest way to paginate over records, however, it
- Avoid presenting total counts, prefer limit counts.
- Example: count maximum 1001 records, and then on the UI show 1000+ if the count is 1001, show the actual number otherwise.
- - See the [badge counters approach](../merge_request_performance_guidelines.md#badge-counters) for more information.
+ - See the [badge counters approach](../merge_request_concepts/performance.md#badge-counters) for more information.
- Avoid using page numbers, use next and previous page buttons.
- Keyset pagination doesn't support page numbers.
- For APIs, advise against building URLs for the next page by "hand".
diff --git a/doc/development/database/query_recorder.md b/doc/development/database/query_recorder.md
index 84bd0fc938f..dfaaf8afcde 100644
--- a/doc/development/database/query_recorder.md
+++ b/doc/development/database/query_recorder.md
@@ -10,7 +10,7 @@ QueryRecorder is a tool for detecting the [N+1 queries problem](https://guides.r
> Implemented in [spec/support/query_recorder.rb](https://gitlab.com/gitlab-org/gitlab/-/blob/master/spec/support/helpers/query_recorder.rb) via [9c623e3e](https://gitlab.com/gitlab-org/gitlab-foss/commit/9c623e3e5d7434f2e30f7c389d13e5af4ede770a)
-As a rule, merge requests [should not increase query counts](../merge_request_performance_guidelines.md#query-counts). If you find yourself adding something like `.includes(:author, :assignee)` to avoid having `N+1` queries, consider using QueryRecorder to enforce this with a test. Without this, a new feature which causes an additional model to be accessed can silently reintroduce the problem.
+As a rule, merge requests [should not increase query counts](../merge_request_concepts/performance.md#query-counts). If you find yourself adding something like `.includes(:author, :assignee)` to avoid having `N+1` queries, consider using QueryRecorder to enforce this with a test. Without this, a new feature which causes an additional model to be accessed can silently reintroduce the problem.
## How it works
@@ -52,7 +52,7 @@ there are no N+1 queries. Rather than make an extra request to warm the cache, p
## Cached queries
-By default, QueryRecorder ignores [cached queries](../merge_request_performance_guidelines.md#cached-queries) in the count. However, it may be better to count
+By default, QueryRecorder ignores [cached queries](../merge_request_concepts/performance.md#cached-queries) in the count. However, it may be better to count
all queries to avoid introducing an N+1 query that may be masked by the statement cache.
To do this, this requires the `:use_sql_query_cache` flag to be set.
You should pass the `skip_cached` variable to `QueryRecorder` and use the `exceed_all_query_limit` matcher:
@@ -146,5 +146,5 @@ There are multiple ways to find the source of queries.
- [Bullet](../profiling.md#bullet) For finding `N+1` query problems
- [Performance guidelines](../performance.md)
-- [Merge request performance guidelines - Query counts](../merge_request_performance_guidelines.md#query-counts)
-- [Merge request performance guidelines - Cached queries](../merge_request_performance_guidelines.md#cached-queries)
+- [Merge request performance guidelines - Query counts](../merge_request_concepts/performance.md#query-counts)
+- [Merge request performance guidelines - Cached queries](../merge_request_concepts/performance.md#cached-queries)
diff --git a/doc/development/database_review.md b/doc/development/database_review.md
index e66be062986..048482960f4 100644
--- a/doc/development/database_review.md
+++ b/doc/development/database_review.md
@@ -275,4 +275,4 @@ Include in the MR description:
- [Check query plans](database/understanding_explain_plans.md) and suggest improvements
to queries (changing the query, schema or adding indexes and similar)
- General guideline is for queries to come in below [100ms execution time](database/query_performance.md#timing-guidelines-for-queries)
- - Avoid N+1 problems and minimize the [query count](merge_request_performance_guidelines.md#query-counts).
+ - Avoid N+1 problems and minimize the [query count](merge_request_concepts/performance.md#query-counts).
diff --git a/doc/development/diffs.md b/doc/development/diffs.md
index b38fcea4f00..c84bf57e085 100644
--- a/doc/development/diffs.md
+++ b/doc/development/diffs.md
@@ -1,199 +1,11 @@
---
-stage: Create
-group: Code Review
-info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
+redirect_to: 'merge_request_concepts/diffs/index.md'
+remove_date: '2023-04-10'
---
-# Working with diffs
+This document was moved to [another location](merge_request_concepts/diffs/index.md).
-We rely on different sources to present diffs. These include:
-
-- Gitaly service
-- Database (through `merge_request_diff_files`)
-- Redis (cached highlighted diffs)
-
-## Deep Dive
-
-<!-- vale gitlab.Spelling = NO -->
-
-In January 2019, Oswaldo Ferreira hosted a Deep Dive (GitLab team members only:
-`https://gitlab.com/gitlab-org/create-stage/issues/1`) on GitLab Diffs and Commenting on Diffs
-functionality to share domain-specific knowledge with anyone who may work in this part of the
-codebase in the future:
-
-<!-- vale gitlab.Spelling = YES -->
-
-- <i class="fa fa-youtube-play youtube" aria-hidden="true"></i>
- [Recording on YouTube](https://www.youtube.com/watch?v=K6G3gMcFyek)
-- Slides on [Google Slides](https://docs.google.com/presentation/d/1bGutFH2AT3bxOPZuLMGl1ANWHqFnrxwQwjiwAZkF-TU/edit)
-- [PDF slides](https://gitlab.com/gitlab-org/create-stage/uploads/b5ad2f336e0afcfe0f99db0af0ccc71a/)
-
-Everything covered in this deep dive was accurate as of GitLab 11.7, and while specific details may
-have changed since then, it should still serve as a good introduction.
-
-## Architecture overview
-
-### Merge request diffs
-
-When refreshing a merge request (pushing to a source branch, force-pushing to target branch, or if the target branch now contains any commits from the MR)
-we fetch the comparison information using `Gitlab::Git::Compare`, which fetches `base` and `head` data using Gitaly and diff between them through
-`Gitlab::Git::Diff.between`.
-The diffs fetching process _limits_ single file diff sizes and the overall size of the whole diff through a series of constant values. Raw diff files are
-then persisted on `merge_request_diff_files` table.
-
-Even though diffs larger than 10% of the value of `ApplicationSettings#diff_max_patch_bytes` are collapsed,
-we still keep them on PostgreSQL. However, diff files larger than defined _safety limits_
-(see the [Diff limits section](#diff-limits)) are _not_ persisted in the database.
-
-In order to present diffs information on the merge request diffs page, we:
-
-1. Fetch all diff files from database `merge_request_diff_files`
-1. Fetch the _old_ and _new_ file blobs in batch to:
- - Highlight old and new file content
- - Know which viewer it should use for each file (text, image, deleted, etc)
- - Know if the file content changed
- - Know if it was stored externally
- - Know if it had storage errors
-1. If the diff file is cacheable (text-based), it's cached on Redis
- using `Gitlab::Diff::FileCollection::MergeRequestDiff`
-
-### Note diffs
-
-When commenting on a diff (any comparison), we persist a truncated diff version
-on `NoteDiffFile` (which is associated with the actual `DiffNote`). So instead
-of hitting the repository every time we need the diff of the file, we:
-
-1. Check whether we have the `NoteDiffFile#diff` persisted and use it
-1. Otherwise, if it's a current MR revision, use the persisted
- `MergeRequestDiffFile#diff`
-1. In the last scenario, go the repository and fetch the diff
-
-## Diff limits
-
-As explained above, we limit single diff files and the size of the whole diff. There are scenarios where we collapse the diff file,
-and cases where the diff file is not presented at all, and the user is guided to the Blob view.
-
-### Diff collection limits
-
-Limits that act onto all diff files collection. Files number, lines number and files size are considered.
-
-```ruby
-Gitlab::Git::DiffCollection.collection_limits[:safe_max_files] = Gitlab::Git::DiffCollection::DEFAULT_LIMITS[:max_files] = 100
-```
-
-File diffs are collapsed (but are expandable) if 100 files have already been rendered.
-
-```ruby
-Gitlab::Git::DiffCollection.collection_limits[:safe_max_lines] = Gitlab::Git::DiffCollection::DEFAULT_LIMITS[:max_lines] = 5000
-```
-
-File diffs are collapsed (but be expandable) if 5000 lines have already been rendered.
-
-```ruby
-Gitlab::Git::DiffCollection.collection_limits[:safe_max_bytes] = Gitlab::Git::DiffCollection.collection_limits[:safe_max_files] * 5.kilobytes = 500.kilobytes
-```
-
-File diffs are collapsed (but be expandable) if 500 kilobytes have already been rendered.
-
-```ruby
-Gitlab::Git::DiffCollection.collection_limits[:max_files] = Commit::DIFF_HARD_LIMIT_FILES = 1000
-```
-
-No more files are rendered at all if 1000 files have already been rendered.
-
-```ruby
-Gitlab::Git::DiffCollection.collection_limits[:max_lines] = Commit::DIFF_HARD_LIMIT_LINES = 50000
-```
-
-No more files are rendered at all if 50,000 lines have already been rendered.
-
-```ruby
-Gitlab::Git::DiffCollection.collection_limits[:max_bytes] = Gitlab::Git::DiffCollection.collection_limits[:max_files] * 5.kilobytes = 5000.kilobytes
-```
-
-No more files are rendered at all if 5 megabytes have already been rendered.
-
-All collection limit parameters are sent and applied on Gitaly. That is, after the limit is surpassed,
-Gitaly only returns the safe amount of data to be persisted on `merge_request_diff_files`.
-
-### Individual diff file limits
-
-Limits that act onto each diff file of a collection. Files number, lines number and files size are considered.
-
-#### Expandable patches (collapsed)
-
-Diff patches are collapsed when surpassing 10% of the value set in `ApplicationSettings#diff_max_patch_bytes`.
-That is, it's equivalent to 10kb if the maximum allowed value is 100kb.
-The diff is persisted and expandable if the patch size doesn't
-surpass `ApplicationSettings#diff_max_patch_bytes`.
-
-Although this nomenclature (Collapsing) is also used on Gitaly, this limit is only used on GitLab (hardcoded - not sent to Gitaly).
-Gitaly only returns `Diff.Collapsed` (RPC) when surpassing collection limits.
-
-#### Not expandable patches (too large)
-
-The patch not be rendered if it's larger than `ApplicationSettings#diff_max_patch_bytes`.
-Users see a `Changes are too large to be shown.` message and a button to view only that file in that commit.
-
-```ruby
-Commit::DIFF_SAFE_LINES = Gitlab::Git::DiffCollection::DEFAULT_LIMITS[:max_lines] = 5000
-```
-
-File diff is suppressed (technically different from collapsed, but behaves the same, and is expandable) if it has more than 5000 lines.
-
-This limit is hardcoded and only applied on GitLab.
-
-## Viewers
-
-Diff Viewers, which can be found on `models/diff_viewer/*` are classes used to map metadata about each type of Diff File. It has information
-whether it's a binary, which partial should be used to render it or which File extensions this class accounts for.
-
-`DiffViewer::Base` validates _blobs_ (old and new versions) content, extension and file type to check if it can be rendered.
-
-## Merge request diffs against the `HEAD` of the target branch
-
-Historically, merge request diffs have been calculated by `git diff target...source` which compares the
-`HEAD` of the source branch with the merge base (or a common ancestor) of the target branch and the source's.
-This solution works well until the target branch starts containing some of the
-changes introduced by the source branch: Consider the following case, in which the source branch
-is `feature_a` and the target is `main`:
-
-1. Checkout a new branch `feature_a` from `main` and remove `file_a` and `file_b` in it.
-1. Add a commit that removes `file_a` to `main`.
-
-The merge request diff still contains the `file_a` removal while the actual diff compared to
-`main`'s `HEAD` has only the `file_b` removal. The diff with such redundant
-changes is harder to review.
-
-In order to display an up-to-date diff, in GitLab 12.9 we
-[introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/27008) merge request
-diffs compared against `HEAD` of the target branch: the
-target branch is artificially merged into the source branch, then the resulting
-merge ref is compared to the source branch to calculate an accurate
-diff.
-
-Until we complete the epics ["use merge refs for diffs"](https://gitlab.com/groups/gitlab-org/-/epics/854)
-and ["merge conflicts in diffs"](https://gitlab.com/groups/gitlab-org/-/epics/4893),
-both options `main (base)` and `main (HEAD)` are available to be displayed in merge requests:
-
-![Merge ref head options](img/merge_ref_head_options_v13_6.png)
-
-The `main (HEAD)` option is meant to replace `main (base)` in the future.
-
-In order to support comments for both options, diff note positions are stored for
-both `main (base)` and `main (HEAD)` versions ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/198457) in 12.10).
-The position for `main (base)` version is stored in `Note#position` and
-`Note#original_position` columns, for `main (HEAD)` version `DiffNotePosition`
-has been introduced.
-
-One of the key challenges to deal with when working on merge ref diffs are merge
-conflicts. If the target and source branch contains a merge conflict, the branches
-cannot be automatically merged. The
-<i class="fa fa-youtube-play youtube" aria-hidden="true"></i> [recording on YouTube](https://www.youtube.com/watch?v=GFXIFA4ZuZw&feature=youtu.be&ab_channel=GitLabUnfiltered)
-is a quick introduction to the problem and the motivation behind the [epic](https://gitlab.com/groups/gitlab-org/-/epics/854).
-
-In 13.5 a solution for both-modified merge
-conflict has been
-[introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/232484). However,
-there are more classes of merge conflicts that are to be
-[addressed](https://gitlab.com/groups/gitlab-org/-/epics/4893) in the future.
+<!-- This redirect file can be deleted after <2023-04-10>. -->
+<!-- Redirects that point to other docs in the same project expire in three months. -->
+<!-- Redirects that point to docs in a different project or site (for example, link is not relative and starts with `https:`) expire in one year. -->
+<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/redirects.html -->
diff --git a/doc/development/documentation/workflow.md b/doc/development/documentation/workflow.md
index 2effa21b266..3c73030aceb 100644
--- a/doc/development/documentation/workflow.md
+++ b/doc/development/documentation/workflow.md
@@ -21,7 +21,7 @@ If you are not a GitLab team member, or do not have the Developer role for the G
1. Select an [issue](https://about.gitlab.com/handbook/product/ux/technical-writing/#community-contribution-opportunities) you'd like to work on.
- You don't need an issue to open a merge request.
- - For a Hackathon, in the issue, in a comment, mention the person who opened the issue and ask for the issue to be assigned to you.
+ - For a Hackathon, mention `@docs-hackathon` in a comment and ask for the issue to be assigned to you.
To be fair to other contributors, if you see someone has already asked to work on the issue, choose another issue.
If you are looking for issues to work on and don't see any that suit you, you can always fix [Vale](testing.md#vale) issues.
1. Go to the [GitLab repository](https://gitlab.com/gitlab-org/gitlab).
diff --git a/doc/development/feature_development.md b/doc/development/feature_development.md
index b9e093b9479..874a56555fb 100644
--- a/doc/development/feature_development.md
+++ b/doc/development/feature_development.md
@@ -108,7 +108,7 @@ Consult these topics for information on contributing to specific GitLab features
- [Performance guidelines](performance.md) for writing code, benchmarks, and
certain patterns to avoid.
- [Caching guidelines](caching.md) for using caching in Rails under a GitLab environment.
-- [Merge request performance guidelines](merge_request_performance_guidelines.md)
+- [Merge request performance guidelines](merge_request_concepts/performance.md)
for ensuring merge requests do not negatively impact GitLab performance
- [Profiling](profiling.md) a URL or tracking down N+1 queries using Bullet.
- [Cached queries guidelines](cached_queries.md), for tracking down N+1 queries
diff --git a/doc/development/feature_flags/controls.md b/doc/development/feature_flags/controls.md
index 3e6491a92b5..f1eafc2a95a 100644
--- a/doc/development/feature_flags/controls.md
+++ b/doc/development/feature_flags/controls.md
@@ -328,6 +328,21 @@ If a project A has `:feature-set-1` enabled, there is no guarantee that project
For more detail, see [This is how percentages work in Flipper](https://www.hackwithpassion.com/this-is-how-percentages-work-in-flipper/).
+### Verifying metrics after enabling feature flag
+
+After turning on the feature flag, you need to [monitor the relevant graphs](https://about.gitlab.com/handbook/engineering/monitoring/) between each step:
+
+1. Go to [`dashboards.gitlab.net`](https://dashboards.gitlab.net).
+1. Turn on the `feature-flag`.
+1. Watch `Latency: Apdex` for services that might be impacted by your change
+ (like `sidekiq service`, `api service` or `web service`). Then check out more in-depth
+ dashboards by selecting `Service Overview Dashboards` and choosing a dashboard that might
+ be related to your change.
+
+In this illustration, you can see that the Apdex score started to decline after the feature flag was enabled at `09:46`. The feature flag was then deactivated at `10:31`, and the service returned to the original value:
+
+![Feature Flag Metrics](../img/feature-flag-metrics.png)
+
### Feature flag change logging
#### ChatOps level
diff --git a/doc/development/go_guide/go_upgrade.md b/doc/development/go_guide/go_upgrade.md
index d931140d9da..b3ec0a15c37 100644
--- a/doc/development/go_guide/go_upgrade.md
+++ b/doc/development/go_guide/go_upgrade.md
@@ -193,7 +193,7 @@ be included in the relevant issues as part of the definition of done:
Upstream component maintainers must validate their Go-based projects using:
- Established unit tests in the codebase.
-- Procedures established in [Merge Request Performance Guidelines](../merge_request_performance_guidelines.md).
+- Procedures established in [Merge Request Performance Guidelines](../merge_request_concepts/performance.md).
- Procedures established in [Performance, Reliability, and Availability guidelines](../code_review.md#performance-reliability-and-availability).
Upstream component maintainers should consider validating their Go-based
diff --git a/doc/development/img/feature-flag-metrics.png b/doc/development/img/feature-flag-metrics.png
new file mode 100644
index 00000000000..ce8702d47eb
--- /dev/null
+++ b/doc/development/img/feature-flag-metrics.png
Binary files differ
diff --git a/doc/development/merge_request_concepts/diffs/development.md b/doc/development/merge_request_concepts/diffs/development.md
new file mode 100644
index 00000000000..7a7d037ef9e
--- /dev/null
+++ b/doc/development/merge_request_concepts/diffs/development.md
@@ -0,0 +1,188 @@
+---
+stage: Create
+group: Code Review
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
+---
+
+# Merge request diffs development guide **(FREE)**
+
+This document explains the backend design and flow of merge request diffs.
+It should help contributors:
+
+- Understand the code design.
+- Identify areas for improvement through contribution.
+
+It's intentional that it doesn't contain too many implementation details, as they
+can change often. The code better explains these details. The components
+mentioned here are the major parts of the application for how merge request diffs
+are generated, stored, and returned to users.
+
+NOTE:
+This page is a living document. Update it accordingly when the parts
+of the codebase touched in this document are changed or removed, or when new components
+are added.
+
+## Data model
+
+Four main ActiveRecord models represent what we collectively refer to
+as _diffs._ These database-backed records replicate data contained in the
+project's Git repository, and are in part a cache against excessive access requests
+to [Gitaly](../../gitaly.md). Additionally, they provide a logical place for:
+
+- Calculated and retrieved metadata about the pieces of the diff.
+- General class- and instance- based logic.
+
+```mermaid
+erDiagram
+ MergeRequest ||--|{ MergeRequestDiff: ""
+ MergeRequestDiff |{--|{ MergeRequestDiffCommit: ""
+ MergeRequestDiff |{--|| MergeRequestDiffDetail: ""
+ MergeRequestDiff |{--|{ MergeRequestDiffFile: ""
+ MergeRequestDiffCommit |{--|| MergeRequestDiffCommitUser: ""
+```
+
+### `MergeRequestDiff`
+
+`MergeRequestDiff` is defined in `app/models/merge_request_diff.rb`. This
+class holds metadata and context related to the diff resulting from a set of
+commits. It defines methods that are the primary means for interacting with diff
+contents, individual commits, and the files containing changes.
+
+```ruby
+#<MergeRequestDiff:0x00007fd1ed63b4d0
+ id: 28,
+ state: "collected",
+ merge_request_id: 28,
+ created_at: Tue, 06 Sep 2022 18:56:02.509469000 UTC +00:00,
+ updated_at: Tue, 06 Sep 2022 18:56:02.754201000 UTC +00:00,
+ base_commit_sha: "ae73cb07c9eeaf35924a10f713b364d32b2dd34f",
+ real_size: "9",
+ head_commit_sha: "bb5206fee213d983da88c47f9cf4cc6caf9c66dc",
+ start_commit_sha: "0b4bc9a49b562e85de7cc9e834518ea6828729b9",
+ commits_count: 6,
+ external_diff: "diff-28",
+ external_diff_store: 1,
+ stored_externally: nil,
+ files_count: 9,
+ sorted: true,
+ diff_type: "regular",
+ verification_checksum: nil>
+```
+
+Diff content is usually accessed through this class. Logic is often applied
+to diff, file, and commit content before it is returned to a user.
+
+### `MergeRequestDiffCommit`
+
+`MergeRequestDiffCommit` is defined in `app/models/merge_request_diff_commit.rb`.
+This class corresponds to a single commit contained in its corresponding `MergeRequestDiff`,
+and holds header information about the commit.
+
+```ruby
+#<MergeRequestDiffCommit:0x00007fd1dfc6c4c0
+ authored_date: Wed, 06 Aug 2022 06:35:52.000000000 UTC +00:00,
+ committed_date: Wed, 06 Aug 2022 06:35:52.000000000 UTC +00:00,
+ merge_request_diff_id: 28,
+ relative_order: 0,
+ sha: "bb5206fee213d983da88c47f9cf4cc6caf9c66dc",
+ message: "Feature conflcit added\n\nSigned-off-by: Sample User <sample.user@example.com>\n",
+ trailers: {},
+ commit_author_id: 19,
+ committer_id: 19>
+```
+
+Every `MergeRequestDiffCommit` has a corresponding `MergeRequest::DiffCommitUser`
+record it `:belongs_to`, in ActiveRecord parlance. These records are `:commit_author`
+and `:committer`, and could be distinct individuals.
+
+### `MergeRequest::DiffCommitUser`
+
+`MergeRequest::DiffCommitUser` is defined in `app/models/merge_request/diff_commit_user.rb`.
+It captures the `name` and `email` of a given commit, but contains no connection
+itself to any `User` records.
+
+```ruby
+#<MergeRequest::DiffCommitUser:0x00007fd1dff7c930
+ id: 19,
+ name: "Sample User",
+ email: "sample.user@example.com">
+```
+
+### `MergeRequestDiffFile`
+
+`MergeRequestDiffFile` is defined in `app/models/merge_request_diff_file.rb`.
+This record of this class represents the diff of a single file contained in the
+`MergeRequestDiff`. It holds both meta and specific information about the file's
+relationship to the change, such as:
+
+- Whether it is added or renamed.
+- Its ordering in the diff.
+- The raw diff output itself.
+
+### `MergeRequestDiffDetail`
+
+`MergeRequestDiffDetail` is defined in `app/models/merge_request_diff_detail.rb`.
+This class provides verification information for Geo replication, but otherwise
+is not used for user-facing diffs.
+
+```ruby
+#<MergeRequestDiffFile:0x00007fd1ef7c9048
+ merge_request_diff_id: 28,
+ relative_order: 0,
+ new_file: true,
+ renamed_file: false,
+ deleted_file: false,
+ too_large: false,
+ a_mode: "0",
+ b_mode: "100644",
+ new_path: "files/ruby/feature.rb",
+ old_path: "files/ruby/feature.rb",
+ diff:
+ "@@ -0,0 +1,4 @@\n+# This file was changed in feature branch\n+# We put different code here to make merge conflict\n+class Conflict\n+end\n",
+ binary: false,
+ external_diff_offset: nil,
+ external_diff_size: nil>
+```
+
+## Flow
+
+These flowcharts should help explain the flow from the controllers down to the
+models for different features. This page is not intended to document the entirety
+of options for access and working with diffs, focusing solely on the most common.
+
+### `batch_diffs.json`
+
+The most common avenue for viewing diffs is the **Changes**
+tab in the top navigation bar of merge request pages in the GitLab UI. When selected, the
+diffs themselves are loaded via a paginated request to `/-/merge_requests/:id/batch_diffs.json`,
+which is served by [`Projects::MergeRequests::DiffsController#diffs_batch`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/controllers/projects/merge_requests/diffs_controller.rb):
+
+<!-- Don't delete the &nbsp; characters below. Mermaid returns a syntax error if they aren't included.-->
+
+```mermaid
+sequenceDiagram
+ Note over .#diffs_batch: Preload diffs and ivars
+ .#diffs_batch->>+.#define_diff_vars: &nbsp;
+ .#define_diff_vars ->>+ @merge_request: @merge_request_diffs =
+ Note right of @merge_request: An ordered collection of all diffs in MR
+ @merge_request-->>-.#define_diff_vars: &nbsp;
+ .#define_diff_vars ->>+ @merge_request: @merge_request_diff =
+ Note right of @merge_request: Most recent merge_request_diff (or commit)
+ @merge_request-->>-.#define_diff_vars: &nbsp;
+ .#define_diff_vars ->>+ .#define_diff_vars: @compare =
+ Note right of .#define_diff_vars:: param-filtered merge_request_diff(s)
+ .#define_diff_vars -->>- .#diffs_batch: &nbsp;
+ Note over .#diffs_batch: Preloading complete
+ .#diffs_batch->>+@merge_request: Calculate unfoldable diff lines
+ Note right of @merge_request: note_positions_for_paths.unfoldable
+ @merge_request-->>-.#diffs_batch: &nbsp;
+ Note over .#diffs_batch: Build options hash
+ Note over .#diffs_batch: Build cache_context
+ Note over .#diffs_batch: Unfold files in diff
+ .#diffs_batch->>+Gitlab_Diff_FileCollection_MergeRequestDiffBase: diffs.write_diff
+ Gitlab_Diff_FileCollection_MergeRequestDiffBase->>+Gitlab_Diff_HighlightCache: Highlight diff
+ Gitlab_Diff_HighlightCache -->>-Gitlab_Diff_FileCollection_MergeRequestDiffBase: Return highlighted diff
+ Note over Gitlab_Diff_FileCollection_MergeRequestDiffBase: Cache diff
+ Gitlab_Diff_FileCollection_MergeRequestDiffBase-->>-.#diffs_batch: &nbsp;
+ Note over .#diffs_batch: render JSON
+```
diff --git a/doc/development/merge_request_concepts/diffs/index.md b/doc/development/merge_request_concepts/diffs/index.md
new file mode 100644
index 00000000000..8ef3f01aba9
--- /dev/null
+++ b/doc/development/merge_request_concepts/diffs/index.md
@@ -0,0 +1,199 @@
+---
+stage: Create
+group: Code Review
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
+---
+
+# Working with diffs
+
+We rely on different sources to present diffs. These include:
+
+- Gitaly service
+- Database (through `merge_request_diff_files`)
+- Redis (cached highlighted diffs)
+
+## Deep Dive
+
+<!-- vale gitlab.Spelling = NO -->
+
+In January 2019, Oswaldo Ferreira hosted a Deep Dive (GitLab team members only:
+`https://gitlab.com/gitlab-org/create-stage/issues/1`) on GitLab Diffs and Commenting on Diffs
+functionality to share domain-specific knowledge with anyone who may work in this part of the
+codebase in the future:
+
+<!-- vale gitlab.Spelling = YES -->
+
+- <i class="fa fa-youtube-play youtube" aria-hidden="true"></i>
+ [Recording on YouTube](https://www.youtube.com/watch?v=K6G3gMcFyek)
+- Slides on [Google Slides](https://docs.google.com/presentation/d/1bGutFH2AT3bxOPZuLMGl1ANWHqFnrxwQwjiwAZkF-TU/edit)
+- [PDF slides](https://gitlab.com/gitlab-org/create-stage/uploads/b5ad2f336e0afcfe0f99db0af0ccc71a/)
+
+Everything covered in this deep dive was accurate as of GitLab 11.7, and while specific details may
+have changed since then, it should still serve as a good introduction.
+
+## Architecture overview
+
+### Merge request diffs
+
+When refreshing a merge request (pushing to a source branch, force-pushing to target branch, or if the target branch now contains any commits from the MR)
+we fetch the comparison information using `Gitlab::Git::Compare`, which fetches `base` and `head` data using Gitaly and diff between them through
+`Gitlab::Git::Diff.between`.
+The diffs fetching process _limits_ single file diff sizes and the overall size of the whole diff through a series of constant values. Raw diff files are
+then persisted on `merge_request_diff_files` table.
+
+Even though diffs larger than 10% of the value of `ApplicationSettings#diff_max_patch_bytes` are collapsed,
+we still keep them on PostgreSQL. However, diff files larger than defined _safety limits_
+(see the [Diff limits section](#diff-limits)) are _not_ persisted in the database.
+
+In order to present diffs information on the merge request diffs page, we:
+
+1. Fetch all diff files from database `merge_request_diff_files`
+1. Fetch the _old_ and _new_ file blobs in batch to:
+ - Highlight old and new file content
+ - Know which viewer it should use for each file (text, image, deleted, etc)
+ - Know if the file content changed
+ - Know if it was stored externally
+ - Know if it had storage errors
+1. If the diff file is cacheable (text-based), it's cached on Redis
+ using `Gitlab::Diff::FileCollection::MergeRequestDiff`
+
+### Note diffs
+
+When commenting on a diff (any comparison), we persist a truncated diff version
+on `NoteDiffFile` (which is associated with the actual `DiffNote`). So instead
+of hitting the repository every time we need the diff of the file, we:
+
+1. Check whether we have the `NoteDiffFile#diff` persisted and use it
+1. Otherwise, if it's a current MR revision, use the persisted
+ `MergeRequestDiffFile#diff`
+1. In the last scenario, go the repository and fetch the diff
+
+## Diff limits
+
+As explained above, we limit single diff files and the size of the whole diff. There are scenarios where we collapse the diff file,
+and cases where the diff file is not presented at all, and the user is guided to the Blob view.
+
+### Diff collection limits
+
+Limits that act onto all diff files collection. Files number, lines number and files size are considered.
+
+```ruby
+Gitlab::Git::DiffCollection.collection_limits[:safe_max_files] = Gitlab::Git::DiffCollection::DEFAULT_LIMITS[:max_files] = 100
+```
+
+File diffs are collapsed (but are expandable) if 100 files have already been rendered.
+
+```ruby
+Gitlab::Git::DiffCollection.collection_limits[:safe_max_lines] = Gitlab::Git::DiffCollection::DEFAULT_LIMITS[:max_lines] = 5000
+```
+
+File diffs are collapsed (but be expandable) if 5000 lines have already been rendered.
+
+```ruby
+Gitlab::Git::DiffCollection.collection_limits[:safe_max_bytes] = Gitlab::Git::DiffCollection.collection_limits[:safe_max_files] * 5.kilobytes = 500.kilobytes
+```
+
+File diffs are collapsed (but be expandable) if 500 kilobytes have already been rendered.
+
+```ruby
+Gitlab::Git::DiffCollection.collection_limits[:max_files] = Commit::DIFF_HARD_LIMIT_FILES = 1000
+```
+
+No more files are rendered at all if 1000 files have already been rendered.
+
+```ruby
+Gitlab::Git::DiffCollection.collection_limits[:max_lines] = Commit::DIFF_HARD_LIMIT_LINES = 50000
+```
+
+No more files are rendered at all if 50,000 lines have already been rendered.
+
+```ruby
+Gitlab::Git::DiffCollection.collection_limits[:max_bytes] = Gitlab::Git::DiffCollection.collection_limits[:max_files] * 5.kilobytes = 5000.kilobytes
+```
+
+No more files are rendered at all if 5 megabytes have already been rendered.
+
+All collection limit parameters are sent and applied on Gitaly. That is, after the limit is surpassed,
+Gitaly only returns the safe amount of data to be persisted on `merge_request_diff_files`.
+
+### Individual diff file limits
+
+Limits that act onto each diff file of a collection. Files number, lines number and files size are considered.
+
+#### Expandable patches (collapsed)
+
+Diff patches are collapsed when surpassing 10% of the value set in `ApplicationSettings#diff_max_patch_bytes`.
+That is, it's equivalent to 10kb if the maximum allowed value is 100kb.
+The diff is persisted and expandable if the patch size doesn't
+surpass `ApplicationSettings#diff_max_patch_bytes`.
+
+Although this nomenclature (Collapsing) is also used on Gitaly, this limit is only used on GitLab (hardcoded - not sent to Gitaly).
+Gitaly only returns `Diff.Collapsed` (RPC) when surpassing collection limits.
+
+#### Not expandable patches (too large)
+
+The patch not be rendered if it's larger than `ApplicationSettings#diff_max_patch_bytes`.
+Users see a `Changes are too large to be shown.` message and a button to view only that file in that commit.
+
+```ruby
+Commit::DIFF_SAFE_LINES = Gitlab::Git::DiffCollection::DEFAULT_LIMITS[:max_lines] = 5000
+```
+
+File diff is suppressed (technically different from collapsed, but behaves the same, and is expandable) if it has more than 5000 lines.
+
+This limit is hardcoded and only applied on GitLab.
+
+## Viewers
+
+Diff Viewers, which can be found on `models/diff_viewer/*` are classes used to map metadata about each type of Diff File. It has information
+whether it's a binary, which partial should be used to render it or which File extensions this class accounts for.
+
+`DiffViewer::Base` validates _blobs_ (old and new versions) content, extension and file type to check if it can be rendered.
+
+## Merge request diffs against the `HEAD` of the target branch
+
+Historically, merge request diffs have been calculated by `git diff target...source` which compares the
+`HEAD` of the source branch with the merge base (or a common ancestor) of the target branch and the source's.
+This solution works well until the target branch starts containing some of the
+changes introduced by the source branch: Consider the following case, in which the source branch
+is `feature_a` and the target is `main`:
+
+1. Checkout a new branch `feature_a` from `main` and remove `file_a` and `file_b` in it.
+1. Add a commit that removes `file_a` to `main`.
+
+The merge request diff still contains the `file_a` removal while the actual diff compared to
+`main`'s `HEAD` has only the `file_b` removal. The diff with such redundant
+changes is harder to review.
+
+In order to display an up-to-date diff, in GitLab 12.9 we
+[introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/27008) merge request
+diffs compared against `HEAD` of the target branch: the
+target branch is artificially merged into the source branch, then the resulting
+merge ref is compared to the source branch to calculate an accurate
+diff.
+
+Until we complete the epics ["use merge refs for diffs"](https://gitlab.com/groups/gitlab-org/-/epics/854)
+and ["merge conflicts in diffs"](https://gitlab.com/groups/gitlab-org/-/epics/4893),
+both options `main (base)` and `main (HEAD)` are available to be displayed in merge requests:
+
+![Merge ref head options](../img/merge_ref_head_options_v13_6.png)
+
+The `main (HEAD)` option is meant to replace `main (base)` in the future.
+
+In order to support comments for both options, diff note positions are stored for
+both `main (base)` and `main (HEAD)` versions ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/198457) in 12.10).
+The position for `main (base)` version is stored in `Note#position` and
+`Note#original_position` columns, for `main (HEAD)` version `DiffNotePosition`
+has been introduced.
+
+One of the key challenges to deal with when working on merge ref diffs are merge
+conflicts. If the target and source branch contains a merge conflict, the branches
+cannot be automatically merged. The
+<i class="fa fa-youtube-play youtube" aria-hidden="true"></i> [recording on YouTube](https://www.youtube.com/watch?v=GFXIFA4ZuZw&feature=youtu.be&ab_channel=GitLabUnfiltered)
+is a quick introduction to the problem and the motivation behind the [epic](https://gitlab.com/groups/gitlab-org/-/epics/854).
+
+In 13.5 a solution for both-modified merge
+conflict has been
+[introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/232484). However,
+there are more classes of merge conflicts that are to be
+[addressed](https://gitlab.com/groups/gitlab-org/-/epics/4893) in the future.
diff --git a/doc/development/img/merge_ref_head_options_v13_6.png b/doc/development/merge_request_concepts/img/merge_ref_head_options_v13_6.png
index 3134092cc92..3134092cc92 100644
--- a/doc/development/img/merge_ref_head_options_v13_6.png
+++ b/doc/development/merge_request_concepts/img/merge_ref_head_options_v13_6.png
Binary files differ
diff --git a/doc/development/merge_request_concepts/performance.md b/doc/development/merge_request_concepts/performance.md
new file mode 100644
index 00000000000..c1bdd45891d
--- /dev/null
+++ b/doc/development/merge_request_concepts/performance.md
@@ -0,0 +1,565 @@
+---
+stage: none
+group: unassigned
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
+---
+
+# Merge Request Performance Guidelines
+
+Each new introduced merge request **should be performant by default**.
+
+To ensure a merge request does not negatively impact performance of GitLab
+_every_ merge request **should** adhere to the guidelines outlined in this
+document. There are no exceptions to this rule unless specifically discussed
+with and agreed upon by backend maintainers and performance specialists.
+
+It's also highly recommended that you read the following guides:
+
+- [Performance Guidelines../performance.md)
+- [Avoiding downtime in migrations](../database/avoiding_downtime_in_migrations.md)
+
+## Definition
+
+The term `SHOULD` per the [RFC 2119](https://www.ietf.org/rfc/rfc2119.txt) means:
+
+> This word, or the adjective "RECOMMENDED", mean that there
+> may exist valid reasons in particular circumstances to ignore a
+> particular item, but the full implications must be understood and
+> carefully weighed before choosing a different course.
+
+Ideally, each of these tradeoffs should be documented
+in the separate issues, labeled accordingly and linked
+to original issue and epic.
+
+## Impact Analysis
+
+**Summary:** think about the impact your merge request may have on performance
+and those maintaining a GitLab setup.
+
+Any change submitted can have an impact not only on the application itself but
+also those maintaining it and those keeping it up and running (for example, production
+engineers). As a result you should think carefully about the impact of your
+merge request on not only the application but also on the people keeping it up
+and running.
+
+Can the queries used potentially take down any critical services and result in
+engineers being woken up in the night? Can a malicious user abuse the code to
+take down a GitLab instance? Do my changes make loading a certain page
+slower? Does execution time grow exponentially given enough load or data in the
+database?
+
+These are all questions one should ask themselves before submitting a merge
+request. It may sometimes be difficult to assess the impact, in which case you
+should ask a performance specialist to review your code. See the "Reviewing"
+section below for more information.
+
+## Performance Review
+
+**Summary:** ask performance specialists to review your code if you're not sure
+about the impact.
+
+Sometimes it's hard to assess the impact of a merge request. In this case you
+should ask one of the merge request reviewers to review your changes. You can
+find a list of these reviewers at <https://about.gitlab.com/company/team/>. A reviewer
+in turn can request a performance specialist to review the changes.
+
+## Think outside of the box
+
+Everyone has their own perception of how to use the new feature.
+Always consider how users might be using the feature instead. Usually,
+users test our features in a very unconventional way,
+like by brute forcing or abusing edge conditions that we have.
+
+## Data set
+
+The data set the merge request processes should be known
+and documented. The feature should clearly document what the expected
+data set is for this feature to process, and what problems it might cause.
+
+If you would think about the following example that puts
+a strong emphasis of data set being processed.
+The problem is simple: you want to filter a list of files from
+some Git repository. Your feature requests a list of all files
+from the repository and perform search for the set of files.
+As an author you should in context of that problem consider
+the following:
+
+1. What repositories are planned to be supported?
+1. How long it do big repositories like Linux kernel take?
+1. Is there something that we can do differently to not process such a
+ big data set?
+1. Should we build some fail-safe mechanism to contain
+ computational complexity? Usually it's better to degrade
+ the service for a single user instead of all users.
+
+## Query plans and database structure
+
+The query plan can tell us if we need additional
+indexes, or expensive filtering (such as using sequential scans).
+
+Each query plan should be run against substantial size of data set.
+For example, if you look for issues with specific conditions,
+you should consider validating a query against
+a small number (a few hundred) and a big number (100_000) of issues.
+See how the query behaves if the result is a few
+and a few thousand.
+
+This is needed as we have users using GitLab for very big projects and
+in a very unconventional way. Even if it seems that it's unlikely
+that such a big data set is used, it's still plausible that one
+of our customers could encounter a problem with the feature.
+
+Understanding ahead of time how it behaves at scale, even if we accept it,
+is the desired outcome. We should always have a plan or understanding of what is needed
+to optimize the feature for higher usage patterns.
+
+Every database structure should be optimized and sometimes even over-described
+in preparation for easy extension. The hardest part after some point is
+data migration. Migrating millions of rows is always troublesome and
+can have a negative impact on the application.
+
+To better understand how to get help with the query plan reviews
+read this section on [how to prepare the merge request for a database review../database_review.md#how-to-prepare-the-merge-request-for-a-database-review).
+
+## Query Counts
+
+**Summary:** a merge request **should not** increase the total number of executed SQL
+queries unless absolutely necessary.
+
+The total number of queries executed by the code modified or added by a merge request
+must not increase unless absolutely necessary. When building features it's
+entirely possible you need some extra queries, but you should try to keep
+this at a minimum.
+
+As an example, say you introduce a feature that updates a number of database
+rows with the same value. It may be very tempting (and easy) to write this using
+the following pseudo code:
+
+```ruby
+objects_to_update.each do |object|
+ object.some_field = some_value
+ object.save
+end
+```
+
+This means running one query for every object to update. This code can
+easily overload a database given enough rows to update or many instances of this
+code running in parallel. This particular problem is known as the
+["N+1 query problem"](https://guides.rubyonrails.org/active_record_querying.html#eager-loading-associations). You can write a test with [QueryRecorder](../database/query_recorder.md) to detect this and prevent regressions.
+
+In this particular case the workaround is fairly easy:
+
+```ruby
+objects_to_update.update_all(some_field: some_value)
+```
+
+This uses ActiveRecord's `update_all` method to update all rows in a single
+query. This in turn makes it much harder for this code to overload a database.
+
+## Use read replicas when possible
+
+In a DB cluster we have many read replicas and one primary. A classic use of scaling the DB is to have read-only actions be performed by the replicas. We use [load balancing](../../administration/postgresql/database_load_balancing.md) to distribute this load. This allows for the replicas to grow as the pressure on the DB grows.
+
+By default, queries use read-only replicas, but due to
+[primary sticking](../../administration/postgresql/database_load_balancing.md#primary-sticking), GitLab uses the
+primary for some time and reverts to secondaries after they have either caught up or after 30 seconds.
+Doing this can lead to a considerable amount of unnecessary load on the primary.
+To prevent switching to the primary [merge request 56849](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/56849) introduced the
+`without_sticky_writes` block. Typically, this method can be applied to prevent primary stickiness
+after a trivial or insignificant write which doesn't affect the following queries in the same session.
+
+To learn when a usage timestamp update can lead the session to stick to the primary and how to
+prevent it by using `without_sticky_writes`, see [merge request 57328](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/57328)
+
+As a counterpart of the `without_sticky_writes` utility,
+[merge request 59167](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/59167) introduced
+`use_replicas_for_read_queries`. This method forces all read-only queries inside its block to read
+replicas regardless of the current primary stickiness.
+This utility is reserved for cases where queries can tolerate replication lag.
+
+Internally, our database load balancer classifies the queries based on their main statement (`select`, `update`, `delete`, and so on). When in doubt, it redirects the queries to the primary database. Hence, there are some common cases the load balancer sends the queries to the primary unnecessarily:
+
+- Custom queries (via `exec_query`, `execute_statement`, `execute`, and so on)
+- Read-only transactions
+- In-flight connection configuration set
+- Sidekiq background jobs
+
+After the above queries are executed, GitLab
+[sticks to the primary](../../administration/postgresql/database_load_balancing.md#primary-sticking).
+To make the inside queries prefer using the replicas,
+[merge request 59086](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/59086) introduced
+`fallback_to_replicas_for_ambiguous_queries`. This MR is also an example of how we redirected a
+costly, time-consuming query to the replicas.
+
+## Use CTEs wisely
+
+Read about [complex queries on the relation object../database/iterating_tables_in_batches.md#complex-queries-on-the-relation-object) for considerations on how to use CTEs. We have found in some situations that CTEs can become problematic in use (similar to the n+1 problem above). In particular, hierarchical recursive CTE queries such as the CTE in [AuthorizedProjectsWorker](https://gitlab.com/gitlab-org/gitlab/-/issues/325688) are very difficult to optimize and don't scale. We should avoid them when implementing new features that require any kind of hierarchical structure.
+
+CTEs have been effectively used as an optimization fence in many simpler cases,
+such as this [example](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/43242#note_61416277).
+Beginning in PostgreSQL 12, CTEs are inlined then [optimized by default](https://paquier.xyz/postgresql-2/postgres-12-with-materialize/).
+Keeping the old behavior requires marking CTEs with the keyword `MATERIALIZED`.
+
+When building CTE statements, use the `Gitlab::SQL::CTE` class [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/56976) in GitLab 13.11.
+By default, this `Gitlab::SQL::CTE` class forces materialization through adding the `MATERIALIZED` keyword for PostgreSQL 12 and higher.
+`Gitlab::SQL::CTE` automatically omits materialization when PostgreSQL 11 is running
+(this behavior is implemented using a custom Arel node `Gitlab::Database::AsWithMaterialized` under the surface).
+
+WARNING:
+Upgrading to GitLab 14.0 requires PostgreSQL 12 or higher.
+
+## Cached Queries
+
+**Summary:** a merge request **should not** execute duplicated cached queries.
+
+Rails provides an [SQL Query Cache](../cached_queries.md#cached-queries-guidelines),
+used to cache the results of database queries for the duration of the request.
+
+See [why cached queries are considered bad](../cached_queries.md#why-cached-queries-are-considered-bad) and
+[how to detect them](../cached_queries.md#how-to-detect-cached-queries).
+
+The code introduced by a merge request, should not execute multiple duplicated cached queries.
+
+The total number of the queries (including cached ones) executed by the code modified or added by a merge request
+should not increase unless absolutely necessary.
+The number of executed queries (including cached queries) should not depend on
+collection size.
+You can write a test by passing the `skip_cached` variable to [QueryRecorder../database/query_recorder.md) to detect this and prevent regressions.
+
+As an example, say you have a CI pipeline. All pipeline builds belong to the same pipeline,
+thus they also belong to the same project (`pipeline.project`):
+
+```ruby
+pipeline_project = pipeline.project
+# Project Load (0.6ms) SELECT "projects".* FROM "projects" WHERE "projects"."id" = $1 LIMIT $2
+build = pipeline.builds.first
+
+build.project == pipeline_project
+# CACHE Project Load (0.0ms) SELECT "projects".* FROM "projects" WHERE "projects"."id" = $1 LIMIT $2
+# => true
+```
+
+When we call `build.project`, it doesn't hit the database, it uses the cached result, but it re-instantiates
+the same pipeline project object. It turns out that associated objects do not point to the same in-memory object.
+
+If we try to serialize each build:
+
+```ruby
+pipeline.builds.each do |build|
+ build.to_json(only: [:name], include: [project: { only: [:name]}])
+end
+```
+
+It re-instantiates project object for each build, instead of using the same in-memory object.
+
+In this particular case the workaround is fairly easy:
+
+```ruby
+ActiveRecord::Associations::Preloader.new.preload(pipeline, [builds: :project])
+
+pipeline.builds.each do |build|
+ build.to_json(only: [:name], include: [project: { only: [:name]}])
+end
+```
+
+`ActiveRecord::Associations::Preloader` uses the same in-memory object for the same project.
+This avoids the cached SQL query and also avoids re-instantiation of the project object for each build.
+
+## Executing Queries in Loops
+
+**Summary:** SQL queries **must not** be executed in a loop unless absolutely
+necessary.
+
+Executing SQL queries in a loop can result in many queries being executed
+depending on the number of iterations in a loop. This may work fine for a
+development environment with little data, but in a production environment this
+can quickly spiral out of control.
+
+There are some cases where this may be needed. If this is the case this should
+be clearly mentioned in the merge request description.
+
+## Batch process
+
+**Summary:** Iterating a single process to external services (for example, PostgreSQL, Redis, Object Storage)
+should be executed in a **batch-style** to reduce connection overheads.
+
+For fetching rows from various tables in a batch-style, please see [Eager Loading](#eager-loading) section.
+
+### Example: Delete multiple files from Object Storage
+
+When you delete multiple files from object storage, like GCS,
+executing a single REST API call multiple times is a quite expensive
+process. Ideally, this should be done in a batch-style, for example, S3 provides
+[batch deletion API](https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteObjects.html),
+so it'd be a good idea to consider such an approach.
+
+The `FastDestroyAll` module might help this situation. It's a
+small framework when you remove a bunch of database rows and its associated data
+in a batch style.
+
+## Timeout
+
+**Summary:** You should set a reasonable timeout when the system invokes HTTP calls
+to external services (such as Kubernetes), and it should be executed in Sidekiq, not
+in Puma threads.
+
+Often, GitLab needs to communicate with an external service such as Kubernetes
+clusters. In this case, it's hard to estimate when the external service finishes
+the requested process, for example, if it's a user-owned cluster that's inactive for some reason,
+GitLab might wait for the response forever ([Example](https://gitlab.com/gitlab-org/gitlab/-/issues/31475)).
+This could result in Puma timeout and should be avoided at all cost.
+
+You should set a reasonable timeout, gracefully handle exceptions and surface the
+errors in UI or logging internally.
+
+Using [`ReactiveCaching`../utilities.md#reactivecaching) is one of the best solutions to fetch external data.
+
+## Keep database transaction minimal
+
+**Summary:** You should avoid accessing to external services like Gitaly during database
+transactions, otherwise it leads to severe contention problems
+as an open transaction basically blocks the release of a PostgreSQL backend connection.
+
+For keeping transaction as minimal as possible, please consider using `AfterCommitQueue`
+module or `after_commit` AR hook.
+
+Here is [an example](https://gitlab.com/gitlab-org/gitlab/-/issues/36154#note_247228859)
+that one request to Gitaly instance during transaction triggered a ~"priority::1" issue.
+
+## Eager Loading
+
+**Summary:** always eager load associations when retrieving more than one row.
+
+When retrieving multiple database records for which you need to use any
+associations you **must** eager load these associations. For example, if you're
+retrieving a list of blog posts and you want to display their authors you
+**must** eager load the author associations.
+
+In other words, instead of this:
+
+```ruby
+Post.all.each do |post|
+ puts post.author.name
+end
+```
+
+You should use this:
+
+```ruby
+Post.all.includes(:author).each do |post|
+ puts post.author.name
+end
+```
+
+Also consider using [QueryRecoder tests](../database/query_recorder.md) to prevent a regression when eager loading.
+
+## Memory Usage
+
+**Summary:** merge requests **must not** increase memory usage unless absolutely
+necessary.
+
+A merge request must not increase the memory usage of GitLab by more than the
+absolute bare minimum required by the code. This means that if you have to parse
+some large document (for example, an HTML document) it's best to parse it as a stream
+whenever possible, instead of loading the entire input into memory. Sometimes
+this isn't possible, in that case this should be stated explicitly in the merge
+request.
+
+## Lazy Rendering of UI Elements
+
+**Summary:** only render UI elements when they are actually needed.
+
+Certain UI elements may not always be needed. For example, when hovering over a
+diff line there's a small icon displayed that can be used to create a new
+comment. Instead of always rendering these kind of elements they should only be
+rendered when actually needed. This ensures we don't spend time generating
+Haml/HTML when it's not used.
+
+## Use of Caching
+
+**Summary:** cache data in memory or in Redis when it's needed multiple times in
+a transaction or has to be kept around for a certain time period.
+
+Sometimes certain bits of data have to be re-used in different places during a
+transaction. In these cases this data should be cached in memory to remove the
+need for running complex operations to fetch the data. You should use Redis if
+data should be cached for a certain time period instead of the duration of the
+transaction.
+
+For example, say you process multiple snippets of text containing username
+mentions (for example, `Hello @alice` and `How are you doing @alice?`). By caching the
+user objects for every username we can remove the need for running the same
+query for every mention of `@alice`.
+
+Caching data per transaction can be done using
+[RequestStore](https://github.com/steveklabnik/request_store) (use
+`Gitlab::SafeRequestStore` to avoid having to remember to check
+`RequestStore.active?`). Caching data in Redis can be done using
+[Rails' caching system](https://guides.rubyonrails.org/caching_with_rails.html).
+
+## Pagination
+
+Each feature that renders a list of items as a table needs to include pagination.
+
+The main styles of pagination are:
+
+1. Offset-based pagination: user goes to a specific page, like 1. User sees the next page number,
+ and the total number of pages. This style is well supported by all components of GitLab.
+1. Offset-based pagination, but without the count: user goes to a specific page, like 1.
+ User sees only the next page number, but does not see the total amount of pages.
+1. Next page using keyset-based pagination: user can only go to next page, as we don't know how many pages
+ are available.
+1. Infinite scrolling pagination: user scrolls the page and next items are loaded asynchronously. This is ideal,
+ as it has exact same benefits as the previous one.
+
+The ultimately scalable solution for pagination is to use Keyset-based pagination.
+However, we don't have support for that at GitLab at that moment. You
+can follow the progress looking at [API: Keyset Pagination](https://gitlab.com/groups/gitlab-org/-/epics/2039).
+
+Take into consideration the following when choosing a pagination strategy:
+
+1. It's very inefficient to calculate amount of objects that pass the filtering,
+ this operation usually can take seconds, and can time out,
+1. It's very inefficient to get entries for page at higher ordinals, like 1000.
+ The database has to sort and iterate all previous items, and this operation usually
+ can result in substantial load put on database.
+
+You can find useful tips related to pagination in the [pagination guidelines../database/pagination_guidelines.md).
+
+## Badge counters
+
+Counters should always be truncated. It means that we don't want to present
+the exact number over some threshold. The reason for that is for the cases where we want
+to calculate exact number of items, we effectively need to filter each of them for
+the purpose of knowing the exact number of items matching.
+
+From ~UX perspective it's often acceptable to see that you have over 1000+ pipelines,
+instead of that you have 40000+ pipelines, but at a tradeoff of loading page for 2s longer.
+
+An example of this pattern is the list of pipelines and jobs. We truncate numbers to `1000+`,
+but we show an accurate number of running pipelines, which is the most interesting information.
+
+There's a helper method that can be used for that purpose - `NumbersHelper.limited_counter_with_delimiter` -
+that accepts an upper limit of counting rows.
+
+In some cases it's desired that badge counters are loaded asynchronously.
+This can speed up the initial page load and give a better user experience overall.
+
+## Usage of feature flags
+
+Each feature that has performance critical elements or has a known performance deficiency
+needs to come with feature flag to disable it.
+
+The feature flag makes our team more happy, because they can monitor the system and
+quickly react without our users noticing the problem.
+
+Performance deficiencies should be addressed right away after we merge initial
+changes.
+
+Read more about when and how feature flags should be used in
+[Feature flags in GitLab development](https://about.gitlab.com/handbook/product-development-flow/feature-flag-lifecycle/#feature-flags-in-gitlab-development).
+
+## Storage
+
+We can consider the following types of storages:
+
+- **Local temporary storage** (very-very short-term storage) This type of storage is system-provided storage, like a `/tmp` folder.
+ This is the type of storage that you should ideally use for all your temporary tasks.
+ The fact that each node has its own temporary storage makes scaling significantly easier.
+ This storage is also very often SSD-based, thus is significantly faster.
+ The local storage can easily be configured for the application with
+ the usage of `TMPDIR` variable.
+
+- **Shared temporary storage** (short-term storage) This type of storage is network-based temporary storage,
+ usually run with a common NFS server. As of Feb 2020, we still use this type of storage
+ for most of our implementations. Even though this allows the above limit to be significantly larger,
+ it does not really mean that you can use more. The shared temporary storage is shared by
+ all nodes. Thus, the job that uses significant amount of that space or performs a lot
+ of operations creates a contention on execution of all other jobs and request
+ across the whole application, this can easily impact stability of the whole GitLab.
+ Be respectful of that.
+
+- **Shared persistent storage** (long-term storage) This type of storage uses
+ shared network-based storage (for example, NFS). This solution is mostly used by customers running small
+ installations consisting of a few nodes. The files on shared storage are easily accessible,
+ but any job that is uploading or downloading data can create a serious contention to all other jobs.
+ This is also an approach by default used by Omnibus.
+
+- **Object-based persistent storage** (long term storage) this type of storage uses external
+ services like [AWS S3](https://en.wikipedia.org/wiki/Amazon_S3). The Object Storage
+ can be treated as infinitely scalable and redundant. Accessing this storage usually requires
+ downloading the file to manipulate it. The Object Storage can be considered as an ultimate
+ solution, as by definition it can be assumed that it can handle unlimited concurrent uploads
+ and downloads of files. This is also ultimate solution required to ensure that application can
+ run in containerized deployments (Kubernetes) at ease.
+
+### Temporary storage
+
+The storage on production nodes is really sparse. The application should be built
+in a way that accommodates running under very limited temporary storage.
+You can expect the system on which your code runs has a total of `1G-10G`
+of temporary storage. However, this storage is really shared across all
+jobs being run. If your job requires to use more than `100MB` of that space
+you should reconsider the approach you have taken.
+
+Whatever your needs are, you should clearly document if you need to process files.
+If you require more than `100MB`, consider asking for help from a maintainer
+to work with you to possibly discover a better solution.
+
+#### Local temporary storage
+
+The usage of local storage is a desired solution to use,
+especially since we work on deploying applications to Kubernetes clusters.
+When you would like to use `Dir.mktmpdir`? In a case when you want for example
+to extract/create archives, perform extensive manipulation of existing data, and so on.
+
+```ruby
+Dir.mktmpdir('designs') do |path|
+ # do manipulation on path
+ # the path will be removed once
+ # we go out of the block
+end
+```
+
+#### Shared temporary storage
+
+The usage of shared temporary storage is required if your intent
+is to persistent file for a disk-based storage, and not Object Storage.
+[Workhorse direct upload](../uploads/index.md#direct-upload) when accepting file
+can write it to shared storage, and later GitLab Rails can perform a move operation.
+The move operation on the same destination is instantaneous.
+The system instead of performing `copy` operation just re-attaches file into a new place.
+
+Since this introduces extra complexity into application, you should only try
+to re-use well established patterns (for example, `ObjectStorage` concern) instead of re-implementing it.
+
+The usage of shared temporary storage is otherwise deprecated for all other usages.
+
+### Persistent storage
+
+#### Object Storage
+
+It is required that all features holding persistent files support saving data
+to Object Storage. Having a persistent storage in the form of shared volume across nodes
+is not scalable, as it creates a contention on data access all nodes.
+
+GitLab offers the [ObjectStorage concern](https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/uploaders/object_storage.rb)
+that implements a seamless support for Shared and Object Storage-based persistent storage.
+
+#### Data access
+
+Each feature that accepts data uploads or allows to download them needs to use
+[Workhorse direct upload](../uploads/index.md#direct-upload). It means that uploads needs to be
+saved directly to Object Storage by Workhorse, and all downloads needs to be served
+by Workhorse.
+
+Performing uploads/downloads via Puma is an expensive operation,
+as it blocks the whole processing slot (thread) for the duration of the upload.
+
+Performing uploads/downloads via Puma also has a problem where the operation
+can time out, which is especially problematic for slow clients. If clients take a long time
+to upload/download the processing slot might be killed due to request processing
+timeout (usually between 30s-60s).
+
+For the above reasons it is required that [Workhorse direct upload../uploads/index.md#direct-upload) is implemented
+for all file uploads and downloads.
diff --git a/doc/development/merge_request_diffs.md b/doc/development/merge_request_diffs.md
index a3c8ada898e..9ec7e6cae8b 100644
--- a/doc/development/merge_request_diffs.md
+++ b/doc/development/merge_request_diffs.md
@@ -1,188 +1,11 @@
---
-stage: Create
-group: Code Review
-info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
+redirect_to: 'merge_request_concepts/diffs/development.md'
+remove_date: '2023-04-10'
---
-# Merge request diffs development guide **(FREE)**
+This document was moved to [another location](merge_request_concepts/diffs/development.md).
-This document explains the backend design and flow of merge request diffs.
-It should help contributors:
-
-- Understand the code design.
-- Identify areas for improvement through contribution.
-
-It's intentional that it doesn't contain too many implementation details, as they
-can change often. The code better explains these details. The components
-mentioned here are the major parts of the application for how merge request diffs
-are generated, stored, and returned to users.
-
-NOTE:
-This page is a living document. Update it accordingly when the parts
-of the codebase touched in this document are changed or removed, or when new components
-are added.
-
-## Data model
-
-Four main ActiveRecord models represent what we collectively refer to
-as _diffs._ These database-backed records replicate data contained in the
-project's Git repository, and are in part a cache against excessive access requests
-to [Gitaly](gitaly.md). Additionally, they provide a logical place for:
-
-- Calculated and retrieved metadata about the pieces of the diff.
-- General class- and instance- based logic.
-
-```mermaid
-erDiagram
- MergeRequest ||--|{ MergeRequestDiff: ""
- MergeRequestDiff |{--|{ MergeRequestDiffCommit: ""
- MergeRequestDiff |{--|| MergeRequestDiffDetail: ""
- MergeRequestDiff |{--|{ MergeRequestDiffFile: ""
- MergeRequestDiffCommit |{--|| MergeRequestDiffCommitUser: ""
-```
-
-### `MergeRequestDiff`
-
-`MergeRequestDiff` is defined in `app/models/merge_request_diff.rb`. This
-class holds metadata and context related to the diff resulting from a set of
-commits. It defines methods that are the primary means for interacting with diff
-contents, individual commits, and the files containing changes.
-
-```ruby
-#<MergeRequestDiff:0x00007fd1ed63b4d0
- id: 28,
- state: "collected",
- merge_request_id: 28,
- created_at: Tue, 06 Sep 2022 18:56:02.509469000 UTC +00:00,
- updated_at: Tue, 06 Sep 2022 18:56:02.754201000 UTC +00:00,
- base_commit_sha: "ae73cb07c9eeaf35924a10f713b364d32b2dd34f",
- real_size: "9",
- head_commit_sha: "bb5206fee213d983da88c47f9cf4cc6caf9c66dc",
- start_commit_sha: "0b4bc9a49b562e85de7cc9e834518ea6828729b9",
- commits_count: 6,
- external_diff: "diff-28",
- external_diff_store: 1,
- stored_externally: nil,
- files_count: 9,
- sorted: true,
- diff_type: "regular",
- verification_checksum: nil>
-```
-
-Diff content is usually accessed through this class. Logic is often applied
-to diff, file, and commit content before it is returned to a user.
-
-### `MergeRequestDiffCommit`
-
-`MergeRequestDiffCommit` is defined in `app/models/merge_request_diff_commit.rb`.
-This class corresponds to a single commit contained in its corresponding `MergeRequestDiff`,
-and holds header information about the commit.
-
-```ruby
-#<MergeRequestDiffCommit:0x00007fd1dfc6c4c0
- authored_date: Wed, 06 Aug 2022 06:35:52.000000000 UTC +00:00,
- committed_date: Wed, 06 Aug 2022 06:35:52.000000000 UTC +00:00,
- merge_request_diff_id: 28,
- relative_order: 0,
- sha: "bb5206fee213d983da88c47f9cf4cc6caf9c66dc",
- message: "Feature conflcit added\n\nSigned-off-by: Sample User <sample.user@example.com>\n",
- trailers: {},
- commit_author_id: 19,
- committer_id: 19>
-```
-
-Every `MergeRequestDiffCommit` has a corresponding `MergeRequest::DiffCommitUser`
-record it `:belongs_to`, in ActiveRecord parlance. These records are `:commit_author`
-and `:committer`, and could be distinct individuals.
-
-### `MergeRequest::DiffCommitUser`
-
-`MergeRequest::DiffCommitUser` is defined in `app/models/merge_request/diff_commit_user.rb`.
-It captures the `name` and `email` of a given commit, but contains no connection
-itself to any `User` records.
-
-```ruby
-#<MergeRequest::DiffCommitUser:0x00007fd1dff7c930
- id: 19,
- name: "Sample User",
- email: "sample.user@example.com">
-```
-
-### `MergeRequestDiffFile`
-
-`MergeRequestDiffFile` is defined in `app/models/merge_request_diff_file.rb`.
-This record of this class represents the diff of a single file contained in the
-`MergeRequestDiff`. It holds both meta and specific information about the file's
-relationship to the change, such as:
-
-- Whether it is added or renamed.
-- Its ordering in the diff.
-- The raw diff output itself.
-
-### `MergeRequestDiffDetail`
-
-`MergeRequestDiffDetail` is defined in `app/models/merge_request_diff_detail.rb`.
-This class provides verification information for Geo replication, but otherwise
-is not used for user-facing diffs.
-
-```ruby
-#<MergeRequestDiffFile:0x00007fd1ef7c9048
- merge_request_diff_id: 28,
- relative_order: 0,
- new_file: true,
- renamed_file: false,
- deleted_file: false,
- too_large: false,
- a_mode: "0",
- b_mode: "100644",
- new_path: "files/ruby/feature.rb",
- old_path: "files/ruby/feature.rb",
- diff:
- "@@ -0,0 +1,4 @@\n+# This file was changed in feature branch\n+# We put different code here to make merge conflict\n+class Conflict\n+end\n",
- binary: false,
- external_diff_offset: nil,
- external_diff_size: nil>
-```
-
-## Flow
-
-These flowcharts should help explain the flow from the controllers down to the
-models for different features. This page is not intended to document the entirety
-of options for access and working with diffs, focusing solely on the most common.
-
-### `batch_diffs.json`
-
-The most common avenue for viewing diffs is the **Changes**
-tab in the top navigation bar of merge request pages in the GitLab UI. When selected, the
-diffs themselves are loaded via a paginated request to `/-/merge_requests/:id/batch_diffs.json`,
-which is served by [`Projects::MergeRequests::DiffsController#diffs_batch`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/controllers/projects/merge_requests/diffs_controller.rb):
-
-<!-- Don't delete the &nbsp; characters below. Mermaid returns a syntax error if they aren't included.-->
-
-```mermaid
-sequenceDiagram
- Note over .#diffs_batch: Preload diffs and ivars
- .#diffs_batch->>+.#define_diff_vars: &nbsp;
- .#define_diff_vars ->>+ @merge_request: @merge_request_diffs =
- Note right of @merge_request: An ordered collection of all diffs in MR
- @merge_request-->>-.#define_diff_vars: &nbsp;
- .#define_diff_vars ->>+ @merge_request: @merge_request_diff =
- Note right of @merge_request: Most recent merge_request_diff (or commit)
- @merge_request-->>-.#define_diff_vars: &nbsp;
- .#define_diff_vars ->>+ .#define_diff_vars: @compare =
- Note right of .#define_diff_vars:: param-filtered merge_request_diff(s)
- .#define_diff_vars -->>- .#diffs_batch: &nbsp;
- Note over .#diffs_batch: Preloading complete
- .#diffs_batch->>+@merge_request: Calculate unfoldable diff lines
- Note right of @merge_request: note_positions_for_paths.unfoldable
- @merge_request-->>-.#diffs_batch: &nbsp;
- Note over .#diffs_batch: Build options hash
- Note over .#diffs_batch: Build cache_context
- Note over .#diffs_batch: Unfold files in diff
- .#diffs_batch->>+Gitlab_Diff_FileCollection_MergeRequestDiffBase: diffs.write_diff
- Gitlab_Diff_FileCollection_MergeRequestDiffBase->>+Gitlab_Diff_HighlightCache: Highlight diff
- Gitlab_Diff_HighlightCache -->>-Gitlab_Diff_FileCollection_MergeRequestDiffBase: Return highlighted diff
- Note over Gitlab_Diff_FileCollection_MergeRequestDiffBase: Cache diff
- Gitlab_Diff_FileCollection_MergeRequestDiffBase-->>-.#diffs_batch: &nbsp;
- Note over .#diffs_batch: render JSON
-```
+<!-- This redirect file can be deleted after <2023-04-10>. -->
+<!-- Redirects that point to other docs in the same project expire in three months. -->
+<!-- Redirects that point to docs in a different project or site (for example, link is not relative and starts with `https:`) expire in one year. -->
+<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/redirects.html -->
diff --git a/doc/development/merge_request_performance_guidelines.md b/doc/development/merge_request_performance_guidelines.md
index fd78d02202f..1af81a8af9f 100644
--- a/doc/development/merge_request_performance_guidelines.md
+++ b/doc/development/merge_request_performance_guidelines.md
@@ -1,565 +1,11 @@
---
-stage: none
-group: unassigned
-info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
+redirect_to: 'merge_request_concepts/performance.md'
+remove_date: '2023-04-10'
---
-# Merge Request Performance Guidelines
+This document was moved to [another location](merge_request_concepts/performance.md).
-Each new introduced merge request **should be performant by default**.
-
-To ensure a merge request does not negatively impact performance of GitLab
-_every_ merge request **should** adhere to the guidelines outlined in this
-document. There are no exceptions to this rule unless specifically discussed
-with and agreed upon by backend maintainers and performance specialists.
-
-It's also highly recommended that you read the following guides:
-
-- [Performance Guidelines](performance.md)
-- [Avoiding downtime in migrations](database/avoiding_downtime_in_migrations.md)
-
-## Definition
-
-The term `SHOULD` per the [RFC 2119](https://www.ietf.org/rfc/rfc2119.txt) means:
-
-> This word, or the adjective "RECOMMENDED", mean that there
-> may exist valid reasons in particular circumstances to ignore a
-> particular item, but the full implications must be understood and
-> carefully weighed before choosing a different course.
-
-Ideally, each of these tradeoffs should be documented
-in the separate issues, labeled accordingly and linked
-to original issue and epic.
-
-## Impact Analysis
-
-**Summary:** think about the impact your merge request may have on performance
-and those maintaining a GitLab setup.
-
-Any change submitted can have an impact not only on the application itself but
-also those maintaining it and those keeping it up and running (for example, production
-engineers). As a result you should think carefully about the impact of your
-merge request on not only the application but also on the people keeping it up
-and running.
-
-Can the queries used potentially take down any critical services and result in
-engineers being woken up in the night? Can a malicious user abuse the code to
-take down a GitLab instance? Do my changes make loading a certain page
-slower? Does execution time grow exponentially given enough load or data in the
-database?
-
-These are all questions one should ask themselves before submitting a merge
-request. It may sometimes be difficult to assess the impact, in which case you
-should ask a performance specialist to review your code. See the "Reviewing"
-section below for more information.
-
-## Performance Review
-
-**Summary:** ask performance specialists to review your code if you're not sure
-about the impact.
-
-Sometimes it's hard to assess the impact of a merge request. In this case you
-should ask one of the merge request reviewers to review your changes. You can
-find a list of these reviewers at <https://about.gitlab.com/company/team/>. A reviewer
-in turn can request a performance specialist to review the changes.
-
-## Think outside of the box
-
-Everyone has their own perception of how to use the new feature.
-Always consider how users might be using the feature instead. Usually,
-users test our features in a very unconventional way,
-like by brute forcing or abusing edge conditions that we have.
-
-## Data set
-
-The data set the merge request processes should be known
-and documented. The feature should clearly document what the expected
-data set is for this feature to process, and what problems it might cause.
-
-If you would think about the following example that puts
-a strong emphasis of data set being processed.
-The problem is simple: you want to filter a list of files from
-some Git repository. Your feature requests a list of all files
-from the repository and perform search for the set of files.
-As an author you should in context of that problem consider
-the following:
-
-1. What repositories are planned to be supported?
-1. How long it do big repositories like Linux kernel take?
-1. Is there something that we can do differently to not process such a
- big data set?
-1. Should we build some fail-safe mechanism to contain
- computational complexity? Usually it's better to degrade
- the service for a single user instead of all users.
-
-## Query plans and database structure
-
-The query plan can tell us if we need additional
-indexes, or expensive filtering (such as using sequential scans).
-
-Each query plan should be run against substantial size of data set.
-For example, if you look for issues with specific conditions,
-you should consider validating a query against
-a small number (a few hundred) and a big number (100_000) of issues.
-See how the query behaves if the result is a few
-and a few thousand.
-
-This is needed as we have users using GitLab for very big projects and
-in a very unconventional way. Even if it seems that it's unlikely
-that such a big data set is used, it's still plausible that one
-of our customers could encounter a problem with the feature.
-
-Understanding ahead of time how it behaves at scale, even if we accept it,
-is the desired outcome. We should always have a plan or understanding of what is needed
-to optimize the feature for higher usage patterns.
-
-Every database structure should be optimized and sometimes even over-described
-in preparation for easy extension. The hardest part after some point is
-data migration. Migrating millions of rows is always troublesome and
-can have a negative impact on the application.
-
-To better understand how to get help with the query plan reviews
-read this section on [how to prepare the merge request for a database review](database_review.md#how-to-prepare-the-merge-request-for-a-database-review).
-
-## Query Counts
-
-**Summary:** a merge request **should not** increase the total number of executed SQL
-queries unless absolutely necessary.
-
-The total number of queries executed by the code modified or added by a merge request
-must not increase unless absolutely necessary. When building features it's
-entirely possible you need some extra queries, but you should try to keep
-this at a minimum.
-
-As an example, say you introduce a feature that updates a number of database
-rows with the same value. It may be very tempting (and easy) to write this using
-the following pseudo code:
-
-```ruby
-objects_to_update.each do |object|
- object.some_field = some_value
- object.save
-end
-```
-
-This means running one query for every object to update. This code can
-easily overload a database given enough rows to update or many instances of this
-code running in parallel. This particular problem is known as the
-["N+1 query problem"](https://guides.rubyonrails.org/active_record_querying.html#eager-loading-associations). You can write a test with [QueryRecorder](database/query_recorder.md) to detect this and prevent regressions.
-
-In this particular case the workaround is fairly easy:
-
-```ruby
-objects_to_update.update_all(some_field: some_value)
-```
-
-This uses ActiveRecord's `update_all` method to update all rows in a single
-query. This in turn makes it much harder for this code to overload a database.
-
-## Use read replicas when possible
-
-In a DB cluster we have many read replicas and one primary. A classic use of scaling the DB is to have read-only actions be performed by the replicas. We use [load balancing](../administration/postgresql/database_load_balancing.md) to distribute this load. This allows for the replicas to grow as the pressure on the DB grows.
-
-By default, queries use read-only replicas, but due to
-[primary sticking](../administration/postgresql/database_load_balancing.md#primary-sticking), GitLab uses the
-primary for some time and reverts to secondaries after they have either caught up or after 30 seconds.
-Doing this can lead to a considerable amount of unnecessary load on the primary.
-To prevent switching to the primary [merge request 56849](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/56849) introduced the
-`without_sticky_writes` block. Typically, this method can be applied to prevent primary stickiness
-after a trivial or insignificant write which doesn't affect the following queries in the same session.
-
-To learn when a usage timestamp update can lead the session to stick to the primary and how to
-prevent it by using `without_sticky_writes`, see [merge request 57328](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/57328)
-
-As a counterpart of the `without_sticky_writes` utility,
-[merge request 59167](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/59167) introduced
-`use_replicas_for_read_queries`. This method forces all read-only queries inside its block to read
-replicas regardless of the current primary stickiness.
-This utility is reserved for cases where queries can tolerate replication lag.
-
-Internally, our database load balancer classifies the queries based on their main statement (`select`, `update`, `delete`, and so on). When in doubt, it redirects the queries to the primary database. Hence, there are some common cases the load balancer sends the queries to the primary unnecessarily:
-
-- Custom queries (via `exec_query`, `execute_statement`, `execute`, and so on)
-- Read-only transactions
-- In-flight connection configuration set
-- Sidekiq background jobs
-
-After the above queries are executed, GitLab
-[sticks to the primary](../administration/postgresql/database_load_balancing.md#primary-sticking).
-To make the inside queries prefer using the replicas,
-[merge request 59086](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/59086) introduced
-`fallback_to_replicas_for_ambiguous_queries`. This MR is also an example of how we redirected a
-costly, time-consuming query to the replicas.
-
-## Use CTEs wisely
-
-Read about [complex queries on the relation object](database/iterating_tables_in_batches.md#complex-queries-on-the-relation-object) for considerations on how to use CTEs. We have found in some situations that CTEs can become problematic in use (similar to the n+1 problem above). In particular, hierarchical recursive CTE queries such as the CTE in [AuthorizedProjectsWorker](https://gitlab.com/gitlab-org/gitlab/-/issues/325688) are very difficult to optimize and don't scale. We should avoid them when implementing new features that require any kind of hierarchical structure.
-
-CTEs have been effectively used as an optimization fence in many simpler cases,
-such as this [example](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/43242#note_61416277).
-Beginning in PostgreSQL 12, CTEs are inlined then [optimized by default](https://paquier.xyz/postgresql-2/postgres-12-with-materialize/).
-Keeping the old behavior requires marking CTEs with the keyword `MATERIALIZED`.
-
-When building CTE statements, use the `Gitlab::SQL::CTE` class [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/56976) in GitLab 13.11.
-By default, this `Gitlab::SQL::CTE` class forces materialization through adding the `MATERIALIZED` keyword for PostgreSQL 12 and higher.
-`Gitlab::SQL::CTE` automatically omits materialization when PostgreSQL 11 is running
-(this behavior is implemented using a custom Arel node `Gitlab::Database::AsWithMaterialized` under the surface).
-
-WARNING:
-Upgrading to GitLab 14.0 requires PostgreSQL 12 or higher.
-
-## Cached Queries
-
-**Summary:** a merge request **should not** execute duplicated cached queries.
-
-Rails provides an [SQL Query Cache](cached_queries.md#cached-queries-guidelines),
-used to cache the results of database queries for the duration of the request.
-
-See [why cached queries are considered bad](cached_queries.md#why-cached-queries-are-considered-bad) and
-[how to detect them](cached_queries.md#how-to-detect-cached-queries).
-
-The code introduced by a merge request, should not execute multiple duplicated cached queries.
-
-The total number of the queries (including cached ones) executed by the code modified or added by a merge request
-should not increase unless absolutely necessary.
-The number of executed queries (including cached queries) should not depend on
-collection size.
-You can write a test by passing the `skip_cached` variable to [QueryRecorder](database/query_recorder.md) to detect this and prevent regressions.
-
-As an example, say you have a CI pipeline. All pipeline builds belong to the same pipeline,
-thus they also belong to the same project (`pipeline.project`):
-
-```ruby
-pipeline_project = pipeline.project
-# Project Load (0.6ms) SELECT "projects".* FROM "projects" WHERE "projects"."id" = $1 LIMIT $2
-build = pipeline.builds.first
-
-build.project == pipeline_project
-# CACHE Project Load (0.0ms) SELECT "projects".* FROM "projects" WHERE "projects"."id" = $1 LIMIT $2
-# => true
-```
-
-When we call `build.project`, it doesn't hit the database, it uses the cached result, but it re-instantiates
-the same pipeline project object. It turns out that associated objects do not point to the same in-memory object.
-
-If we try to serialize each build:
-
-```ruby
-pipeline.builds.each do |build|
- build.to_json(only: [:name], include: [project: { only: [:name]}])
-end
-```
-
-It re-instantiates project object for each build, instead of using the same in-memory object.
-
-In this particular case the workaround is fairly easy:
-
-```ruby
-ActiveRecord::Associations::Preloader.new.preload(pipeline, [builds: :project])
-
-pipeline.builds.each do |build|
- build.to_json(only: [:name], include: [project: { only: [:name]}])
-end
-```
-
-`ActiveRecord::Associations::Preloader` uses the same in-memory object for the same project.
-This avoids the cached SQL query and also avoids re-instantiation of the project object for each build.
-
-## Executing Queries in Loops
-
-**Summary:** SQL queries **must not** be executed in a loop unless absolutely
-necessary.
-
-Executing SQL queries in a loop can result in many queries being executed
-depending on the number of iterations in a loop. This may work fine for a
-development environment with little data, but in a production environment this
-can quickly spiral out of control.
-
-There are some cases where this may be needed. If this is the case this should
-be clearly mentioned in the merge request description.
-
-## Batch process
-
-**Summary:** Iterating a single process to external services (for example, PostgreSQL, Redis, Object Storage)
-should be executed in a **batch-style** to reduce connection overheads.
-
-For fetching rows from various tables in a batch-style, please see [Eager Loading](#eager-loading) section.
-
-### Example: Delete multiple files from Object Storage
-
-When you delete multiple files from object storage, like GCS,
-executing a single REST API call multiple times is a quite expensive
-process. Ideally, this should be done in a batch-style, for example, S3 provides
-[batch deletion API](https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteObjects.html),
-so it'd be a good idea to consider such an approach.
-
-The `FastDestroyAll` module might help this situation. It's a
-small framework when you remove a bunch of database rows and its associated data
-in a batch style.
-
-## Timeout
-
-**Summary:** You should set a reasonable timeout when the system invokes HTTP calls
-to external services (such as Kubernetes), and it should be executed in Sidekiq, not
-in Puma threads.
-
-Often, GitLab needs to communicate with an external service such as Kubernetes
-clusters. In this case, it's hard to estimate when the external service finishes
-the requested process, for example, if it's a user-owned cluster that's inactive for some reason,
-GitLab might wait for the response forever ([Example](https://gitlab.com/gitlab-org/gitlab/-/issues/31475)).
-This could result in Puma timeout and should be avoided at all cost.
-
-You should set a reasonable timeout, gracefully handle exceptions and surface the
-errors in UI or logging internally.
-
-Using [`ReactiveCaching`](utilities.md#reactivecaching) is one of the best solutions to fetch external data.
-
-## Keep database transaction minimal
-
-**Summary:** You should avoid accessing to external services like Gitaly during database
-transactions, otherwise it leads to severe contention problems
-as an open transaction basically blocks the release of a PostgreSQL backend connection.
-
-For keeping transaction as minimal as possible, please consider using `AfterCommitQueue`
-module or `after_commit` AR hook.
-
-Here is [an example](https://gitlab.com/gitlab-org/gitlab/-/issues/36154#note_247228859)
-that one request to Gitaly instance during transaction triggered a ~"priority::1" issue.
-
-## Eager Loading
-
-**Summary:** always eager load associations when retrieving more than one row.
-
-When retrieving multiple database records for which you need to use any
-associations you **must** eager load these associations. For example, if you're
-retrieving a list of blog posts and you want to display their authors you
-**must** eager load the author associations.
-
-In other words, instead of this:
-
-```ruby
-Post.all.each do |post|
- puts post.author.name
-end
-```
-
-You should use this:
-
-```ruby
-Post.all.includes(:author).each do |post|
- puts post.author.name
-end
-```
-
-Also consider using [QueryRecoder tests](database/query_recorder.md) to prevent a regression when eager loading.
-
-## Memory Usage
-
-**Summary:** merge requests **must not** increase memory usage unless absolutely
-necessary.
-
-A merge request must not increase the memory usage of GitLab by more than the
-absolute bare minimum required by the code. This means that if you have to parse
-some large document (for example, an HTML document) it's best to parse it as a stream
-whenever possible, instead of loading the entire input into memory. Sometimes
-this isn't possible, in that case this should be stated explicitly in the merge
-request.
-
-## Lazy Rendering of UI Elements
-
-**Summary:** only render UI elements when they are actually needed.
-
-Certain UI elements may not always be needed. For example, when hovering over a
-diff line there's a small icon displayed that can be used to create a new
-comment. Instead of always rendering these kind of elements they should only be
-rendered when actually needed. This ensures we don't spend time generating
-Haml/HTML when it's not used.
-
-## Use of Caching
-
-**Summary:** cache data in memory or in Redis when it's needed multiple times in
-a transaction or has to be kept around for a certain time period.
-
-Sometimes certain bits of data have to be re-used in different places during a
-transaction. In these cases this data should be cached in memory to remove the
-need for running complex operations to fetch the data. You should use Redis if
-data should be cached for a certain time period instead of the duration of the
-transaction.
-
-For example, say you process multiple snippets of text containing username
-mentions (for example, `Hello @alice` and `How are you doing @alice?`). By caching the
-user objects for every username we can remove the need for running the same
-query for every mention of `@alice`.
-
-Caching data per transaction can be done using
-[RequestStore](https://github.com/steveklabnik/request_store) (use
-`Gitlab::SafeRequestStore` to avoid having to remember to check
-`RequestStore.active?`). Caching data in Redis can be done using
-[Rails' caching system](https://guides.rubyonrails.org/caching_with_rails.html).
-
-## Pagination
-
-Each feature that renders a list of items as a table needs to include pagination.
-
-The main styles of pagination are:
-
-1. Offset-based pagination: user goes to a specific page, like 1. User sees the next page number,
- and the total number of pages. This style is well supported by all components of GitLab.
-1. Offset-based pagination, but without the count: user goes to a specific page, like 1.
- User sees only the next page number, but does not see the total amount of pages.
-1. Next page using keyset-based pagination: user can only go to next page, as we don't know how many pages
- are available.
-1. Infinite scrolling pagination: user scrolls the page and next items are loaded asynchronously. This is ideal,
- as it has exact same benefits as the previous one.
-
-The ultimately scalable solution for pagination is to use Keyset-based pagination.
-However, we don't have support for that at GitLab at that moment. You
-can follow the progress looking at [API: Keyset Pagination](https://gitlab.com/groups/gitlab-org/-/epics/2039).
-
-Take into consideration the following when choosing a pagination strategy:
-
-1. It's very inefficient to calculate amount of objects that pass the filtering,
- this operation usually can take seconds, and can time out,
-1. It's very inefficient to get entries for page at higher ordinals, like 1000.
- The database has to sort and iterate all previous items, and this operation usually
- can result in substantial load put on database.
-
-You can find useful tips related to pagination in the [pagination guidelines](database/pagination_guidelines.md).
-
-## Badge counters
-
-Counters should always be truncated. It means that we don't want to present
-the exact number over some threshold. The reason for that is for the cases where we want
-to calculate exact number of items, we effectively need to filter each of them for
-the purpose of knowing the exact number of items matching.
-
-From ~UX perspective it's often acceptable to see that you have over 1000+ pipelines,
-instead of that you have 40000+ pipelines, but at a tradeoff of loading page for 2s longer.
-
-An example of this pattern is the list of pipelines and jobs. We truncate numbers to `1000+`,
-but we show an accurate number of running pipelines, which is the most interesting information.
-
-There's a helper method that can be used for that purpose - `NumbersHelper.limited_counter_with_delimiter` -
-that accepts an upper limit of counting rows.
-
-In some cases it's desired that badge counters are loaded asynchronously.
-This can speed up the initial page load and give a better user experience overall.
-
-## Usage of feature flags
-
-Each feature that has performance critical elements or has a known performance deficiency
-needs to come with feature flag to disable it.
-
-The feature flag makes our team more happy, because they can monitor the system and
-quickly react without our users noticing the problem.
-
-Performance deficiencies should be addressed right away after we merge initial
-changes.
-
-Read more about when and how feature flags should be used in
-[Feature flags in GitLab development](https://about.gitlab.com/handbook/product-development-flow/feature-flag-lifecycle/#feature-flags-in-gitlab-development).
-
-## Storage
-
-We can consider the following types of storages:
-
-- **Local temporary storage** (very-very short-term storage) This type of storage is system-provided storage, like a `/tmp` folder.
- This is the type of storage that you should ideally use for all your temporary tasks.
- The fact that each node has its own temporary storage makes scaling significantly easier.
- This storage is also very often SSD-based, thus is significantly faster.
- The local storage can easily be configured for the application with
- the usage of `TMPDIR` variable.
-
-- **Shared temporary storage** (short-term storage) This type of storage is network-based temporary storage,
- usually run with a common NFS server. As of Feb 2020, we still use this type of storage
- for most of our implementations. Even though this allows the above limit to be significantly larger,
- it does not really mean that you can use more. The shared temporary storage is shared by
- all nodes. Thus, the job that uses significant amount of that space or performs a lot
- of operations creates a contention on execution of all other jobs and request
- across the whole application, this can easily impact stability of the whole GitLab.
- Be respectful of that.
-
-- **Shared persistent storage** (long-term storage) This type of storage uses
- shared network-based storage (for example, NFS). This solution is mostly used by customers running small
- installations consisting of a few nodes. The files on shared storage are easily accessible,
- but any job that is uploading or downloading data can create a serious contention to all other jobs.
- This is also an approach by default used by Omnibus.
-
-- **Object-based persistent storage** (long term storage) this type of storage uses external
- services like [AWS S3](https://en.wikipedia.org/wiki/Amazon_S3). The Object Storage
- can be treated as infinitely scalable and redundant. Accessing this storage usually requires
- downloading the file to manipulate it. The Object Storage can be considered as an ultimate
- solution, as by definition it can be assumed that it can handle unlimited concurrent uploads
- and downloads of files. This is also ultimate solution required to ensure that application can
- run in containerized deployments (Kubernetes) at ease.
-
-### Temporary storage
-
-The storage on production nodes is really sparse. The application should be built
-in a way that accommodates running under very limited temporary storage.
-You can expect the system on which your code runs has a total of `1G-10G`
-of temporary storage. However, this storage is really shared across all
-jobs being run. If your job requires to use more than `100MB` of that space
-you should reconsider the approach you have taken.
-
-Whatever your needs are, you should clearly document if you need to process files.
-If you require more than `100MB`, consider asking for help from a maintainer
-to work with you to possibly discover a better solution.
-
-#### Local temporary storage
-
-The usage of local storage is a desired solution to use,
-especially since we work on deploying applications to Kubernetes clusters.
-When you would like to use `Dir.mktmpdir`? In a case when you want for example
-to extract/create archives, perform extensive manipulation of existing data, and so on.
-
-```ruby
-Dir.mktmpdir('designs') do |path|
- # do manipulation on path
- # the path will be removed once
- # we go out of the block
-end
-```
-
-#### Shared temporary storage
-
-The usage of shared temporary storage is required if your intent
-is to persistent file for a disk-based storage, and not Object Storage.
-[Workhorse direct upload](uploads/index.md#direct-upload) when accepting file
-can write it to shared storage, and later GitLab Rails can perform a move operation.
-The move operation on the same destination is instantaneous.
-The system instead of performing `copy` operation just re-attaches file into a new place.
-
-Since this introduces extra complexity into application, you should only try
-to re-use well established patterns (for example, `ObjectStorage` concern) instead of re-implementing it.
-
-The usage of shared temporary storage is otherwise deprecated for all other usages.
-
-### Persistent storage
-
-#### Object Storage
-
-It is required that all features holding persistent files support saving data
-to Object Storage. Having a persistent storage in the form of shared volume across nodes
-is not scalable, as it creates a contention on data access all nodes.
-
-GitLab offers the [ObjectStorage concern](https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/uploaders/object_storage.rb)
-that implements a seamless support for Shared and Object Storage-based persistent storage.
-
-#### Data access
-
-Each feature that accepts data uploads or allows to download them needs to use
-[Workhorse direct upload](uploads/index.md#direct-upload). It means that uploads needs to be
-saved directly to Object Storage by Workhorse, and all downloads needs to be served
-by Workhorse.
-
-Performing uploads/downloads via Puma is an expensive operation,
-as it blocks the whole processing slot (thread) for the duration of the upload.
-
-Performing uploads/downloads via Puma also has a problem where the operation
-can time out, which is especially problematic for slow clients. If clients take a long time
-to upload/download the processing slot might be killed due to request processing
-timeout (usually between 30s-60s).
-
-For the above reasons it is required that [Workhorse direct upload](uploads/index.md#direct-upload) is implemented
-for all file uploads and downloads.
+<!-- This redirect file can be deleted after <2023-04-10>. -->
+<!-- Redirects that point to other docs in the same project expire in three months. -->
+<!-- Redirects that point to docs in a different project or site (for example, link is not relative and starts with `https:`) expire in one year. -->
+<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/redirects.html -->
diff --git a/doc/development/pages/index.md b/doc/development/pages/index.md
index 5e56264330a..05eeb1965d1 100644
--- a/doc/development/pages/index.md
+++ b/doc/development/pages/index.md
@@ -147,7 +147,7 @@ GitLab Pages access control is disabled by default. To enable it:
1. Restart GitLab (if running through the GDK, run `gdk restart`). Running
`gdk reconfigure` overwrites the value of `access_control` in `config/gitlab.yml`.
1. In your local GitLab instance, in the browser go to `http://gdk.test:3000/admin/applications`.
-1. Create an [Instance-wide OAuth application](../../integration/oauth_provider.md#instance-wide-applications)
+1. Create an [Instance-wide OAuth application](../../integration/oauth_provider.md#create-an-instance-wide-application)
with the `api` scope.
1. Set the value of your `redirect-uri` to the `pages-domain` authorization endpoint
(for example, `http://pages.gdk.test:3010/auth`).
diff --git a/doc/development/performance.md b/doc/development/performance.md
index 127cade9fee..21f80364358 100644
--- a/doc/development/performance.md
+++ b/doc/development/performance.md
@@ -14,7 +14,7 @@ consistent performance of GitLab. Refer to the [Index](#performance-documentatio
- General:
- [Solving performance issues](#workflow)
- [Handbook performance page](https://about.gitlab.com/handbook/engineering/performance/)
- - [Merge request performance guidelines](../development/merge_request_performance_guidelines.md)
+ - [Merge request performance guidelines](merge_request_concepts/performance.md)
- Backend:
- [Tooling](#tooling)
- Database:
@@ -51,7 +51,7 @@ The process of solving performance problems is roughly as follows:
1. Add your findings based on the measurement period (screenshots of graphs,
timings, etc) to the issue mentioned in step 1.
1. Solve the problem.
-1. Create a merge request, assign the "Performance" label and follow the [performance review process](merge_request_performance_guidelines.md).
+1. Create a merge request, assign the "Performance" label and follow the [performance review process](merge_request_concepts/performance.md).
1. Once a change has been deployed make sure to _again_ measure for at least 24
hours to see if your changes have any impact on the production environment.
1. Repeat until you're done.
diff --git a/doc/development/value_stream_analytics/value_stream_analytics_aggregated_backend.md b/doc/development/value_stream_analytics/value_stream_analytics_aggregated_backend.md
index 5bcadc6f39b..3d1286a5809 100644
--- a/doc/development/value_stream_analytics/value_stream_analytics_aggregated_backend.md
+++ b/doc/development/value_stream_analytics/value_stream_analytics_aggregated_backend.md
@@ -273,7 +273,7 @@ attributes.
- `stages` - Load the stages for the currently selected value stream.
- `median` - For each stage, request the median duration.
- `count` - For each stage, request the number of items in the stage (this is a
-[limit count](../merge_request_performance_guidelines.md#badge-counters), maximum 1000 rows).
+[limit count](../merge_request_concepts/performance.md#badge-counters), maximum 1000 rows).
- `average_duration_chart` - Data for the duration chart.
- `summary`, `time_summary` - Top-level aggregations, most of the metrics are using different APIs/
finders and not invoking the aggregated backend.
diff --git a/doc/integration/jira/dvcs.md b/doc/integration/jira/dvcs.md
index 982c8203904..7f431de1323 100644
--- a/doc/integration/jira/dvcs.md
+++ b/doc/integration/jira/dvcs.md
@@ -72,14 +72,14 @@ you can still perform multiple actions in a single commit. For example:
## Configure a GitLab application for DVCS
-For projects in a single group we recommend you create a [group application](../oauth_provider.md#group-owned-applications).
+For projects in a single group we recommend you create a [group application](../oauth_provider.md#create-a-group-owned-application).
For projects across multiple groups we recommend you create and use a `jira` user in GitLab, and use the account
only for integration work. A separate account ensures regular account
maintenance does not affect your integration. If a `jira` user or group application is not feasible,
-you can set up this integration as an [instance-wide application](../oauth_provider.md#instance-wide-applications)
-or with a [user owned application](../oauth_provider.md#user-owned-applications) instead.
+you can set up this integration as an [instance-wide application](../oauth_provider.md#create-an-instance-wide-application)
+or with a [user owned application](../oauth_provider.md#create-a-user-owned-application) instead.
-1. Navigate to the [appropriate **Applications** section](../oauth_provider.md#introduction-to-oauth).
+1. Navigate to the [appropriate **Applications** section](../oauth_provider.md).
1. In the **Name** field, enter a descriptive name for the integration, such as `Jira`.
1. In the **Redirect URI** field, enter the URI appropriate for your version of GitLab,
replacing `<gitlab.example.com>` with your GitLab instance domain:
diff --git a/doc/integration/oauth_provider.md b/doc/integration/oauth_provider.md
index 53d61ef8021..95910e29463 100644
--- a/doc/integration/oauth_provider.md
+++ b/doc/integration/oauth_provider.md
@@ -6,17 +6,6 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# Configure GitLab as an OAuth 2.0 authentication identity provider
-This document describes how you can use GitLab as an OAuth 2.0 authentication identity provider.
-
-- OAuth 2 applications can be created and managed using the GitLab UI (described below)
- or managed using the [Applications API](../api/applications.md).
-- After an application is created, external services can manage access tokens using the
- [OAuth 2 API](../api/oauth2.md).
-- To allow users to sign in to GitLab using third-party OAuth 2 providers, see
- [OmniAuth documentation](omniauth.md).
-
-## Introduction to OAuth
-
[OAuth 2](https://oauth.net/2/) provides to client applications a 'secure delegated
access' to server resources on behalf of a resource owner. OAuth 2 allows
authorization servers to issue access tokens to third-party clients with the approval
@@ -33,21 +22,30 @@ to repositories without sharing user credentials to your GitLab.com account.
GitLab supports several ways of adding a new OAuth 2 application to an instance:
-- [User owned applications](#user-owned-applications)
-- [Group owned applications](#group-owned-applications)
-- [Instance-wide applications](#instance-wide-applications)
+- [User owned applications](#create-a-user-owned-application)
+- [Group owned applications](#create-a-group-owned-application)
+- [Instance-wide applications](#create-an-instance-wide-application)
The only difference between these methods is the [permission](../user/permissions.md)
levels. The default callback URL is `https://your-gitlab.example.com/users/auth/gitlab/callback` (you can also use a non-SSL URL, but you should use SSL URLs).
-## User owned applications
+This document describes how you can use GitLab as an OAuth 2.0 authentication identity provider.
+
+- OAuth 2 applications can be created and managed using the GitLab UI (described below)
+ or managed using the [Applications API](../api/applications.md).
+- After an application is created, external services can manage access tokens using the
+ [OAuth 2 API](../api/oauth2.md).
+- To allow users to sign in to GitLab using third-party OAuth 2 providers, see
+ [OmniAuth documentation](omniauth.md).
+
+## Create a user-owned application
To add a new application for your user:
1. In the top-right corner, select your avatar.
1. Select **Edit profile**.
1. On the left sidebar, select **Applications**.
-1. Enter a **Name**, **Redirect URI** and OAuth 2 scopes as defined in [Authorized Applications](#authorized-applications).
+1. Enter a **Name**, **Redirect URI** and OAuth 2 scopes as defined in [Authorized Applications](#view-all-authorized-applications).
The **Redirect URI** is the URL where users are sent after they authorize with GitLab.
1. Select **Save application**. GitLab provides:
@@ -57,7 +55,7 @@ To add a new application for your user:
- By selecting **Copy** in the **Secret** field
[in GitLab 14.2 and later](https://gitlab.com/gitlab-org/gitlab/-/issues/332844).
-## Group owned applications
+## Create a group-owned application
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/16227) in GitLab 13.11.
@@ -65,7 +63,7 @@ To add a new application for a group:
1. Go to the desired group.
1. On the left sidebar, select **Settings > Applications**.
-1. Enter a **Name**, **Redirect URI** and OAuth 2 scopes as defined in [Authorized Applications](#authorized-applications).
+1. Enter a **Name**, **Redirect URI** and OAuth 2 scopes as defined in [Authorized Applications](#view-all-authorized-applications).
The **Redirect URI** is the URL where users are sent after they authorize with GitLab.
1. Select **Save application**. GitLab provides:
@@ -75,7 +73,7 @@ To add a new application for a group:
- By selecting **Copy** in the **Secret** field
[in GitLab 14.2 and later](https://gitlab.com/gitlab-org/gitlab/-/issues/332844).
-## Instance-wide applications
+## Create an instance-wide application
To create an application for your GitLab instance:
@@ -86,22 +84,7 @@ To create an application for your GitLab instance:
When creating application in the **Admin Area** , you can mark it as _trusted_.
The user authorization step is automatically skipped for this application.
-## Access token expiration
-
-> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/21745) in GitLab 14.3, with the ability to opt out.
-> - Ability to opt-out of expiring access token [removed](https://gitlab.com/gitlab-org/gitlab/-/issues/340848) in GitLab 15.0.
-
-WARNING:
-The ability to opt-out of expiring access tokens was [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/340848)
-in GitLab 14.3 and [removed](https://gitlab.com/gitlab-org/gitlab/-/issues/340848) in 15.0. All
-existing integrations must be updated to support access token refresh.
-
-Access tokens expire after two hours. Integrations that use access tokens must generate new ones at least every
-two hours.
-
-When applications are deleted, all grants and tokens associated with the application are also deleted.
-
-## Authorized applications
+## View all authorized applications
To see all the application you've authorized with your GitLab credentials:
@@ -128,6 +111,21 @@ application can perform. Available scopes are depicted in the following table.
At any time you can revoke any access by selecting **Revoke**.
+## Access token expiration
+
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/21745) in GitLab 14.3, with the ability to opt out.
+> - Ability to opt-out of expiring access token [removed](https://gitlab.com/gitlab-org/gitlab/-/issues/340848) in GitLab 15.0.
+
+WARNING:
+The ability to opt out of expiring access tokens was [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/340848)
+in GitLab 14.3 and [removed](https://gitlab.com/gitlab-org/gitlab/-/issues/340848) in 15.0. All
+existing integrations must be updated to support access token refresh.
+
+Access tokens expire after two hours. Integrations that use access tokens must generate new ones at least every
+two hours.
+
+When applications are deleted, all grants and tokens associated with the application are also deleted.
+
## Hashed OAuth application secrets
> Introduced in GitLab 15.4 [with a flag](../administration/feature_flags.md) named `hash_oauth_secrets`. Disabled by default.
diff --git a/doc/policy/alpha-beta-support.md b/doc/policy/alpha-beta-support.md
index 8d9b1f270f3..5a7c1962d12 100644
--- a/doc/policy/alpha-beta-support.md
+++ b/doc/policy/alpha-beta-support.md
@@ -4,39 +4,60 @@ group: Distribution
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
---
-<!-- any changes made to this page should be reflected in https://about.gitlab.com/support/statement-of-support/#alpha-beta-features and https://about.gitlab.com/handbook/product/gitlab-the-product/#alpha-beta-ga -->
+# Support for Alpha, Beta, Limited Availability, and Generally Available features **(PREMIUM)**
-# Support for Alpha, Beta, Limited Availability, and Generally Available Features **(PREMIUM)**
+Some GitLab features are released as Alpha or Beta versions and are
+[not fully supported](https://about.gitlab.com/support/statement-of-support/#alpha-beta-features).
+All other features are considered to be Generally Available (GA).
-Some GitLab features are released as [Alpha or Beta versions](https://about.gitlab.com/support/statement-of-support/#alpha-beta-features) which are not fully supported. All other features are considered to be Generally Available (GA).
+## Alpha features
-## Alpha Features
+Support is **not** provided for Alpha features and issues with them should be opened in the [GitLab issue tracker](https://gitlab.com/gitlab-org/gitlab/issues).
-Characteristics of alpha features:
+Characteristics of Alpha features:
- Not ready for production use.
- Unstable and can cause performance and stability issues.
- Configuration and dependencies are likely to change.
- Features and functions may be removed.
- Data loss can occur (be that through bugs or updates).
+- Documentation reflects the Alpha status.
+- Behind flags that are off by default.
+- Not announced in release posts.
-Support is **not** provided for Alpha features and issues with them should be opened in the [GitLab issue tracker](https://gitlab.com/gitlab-org/gitlab/issues).
+## Beta features
+
+Your Support Contract provides **commercially-reasonable effort** support for Beta features, with the expectation that issues require extra time and assistance from development to troubleshoot.
-## Beta Features
+### Closed Beta features
-Characteristics of beta features:
+Closed Beta features are available to selected users only.
- Not ready for production use.
- Unstable and can cause performance and stability issues.
-- Configuration and dependencies are not likely to change.
-- Features and functions are not likely to change.
-- Data loss is not likely.
+- Configuration and dependencies unlikely to change.
+- Features and functions unlikely to change.
+- Data loss less likely.
+- Behind a feature flag that is off by default and the UI reflects Beta status.
+- Documentation reflects Beta status.
+- Can be announced in a release post that reflects Beta status.
-Your Support Contract provides **commercially-reasonable effort** support for Beta features, with the expectation that issues require extra time and assistance from development to troubleshoot.
+### Open Beta features
+
+- Not ready for production use.
+- Unstable and can cause performance and stability issues.
+- Configuration and dependencies unlikely to change.
+- Features and functions unlikely to change.
+- Data loss not likely.
+- Support on a commercially-reasonable effort basis.
+- Documentation reflects Beta status.
+- Behind a feature flag that is on by default and the UI reflects Beta status.
+- Behind a toggle that is off by default and the UI reflects Beta status.
+- Can be announced in a release post that reflects Beta status.
## Limited Availability (LA)
-Characteristics of limited availability features:
+Characteristics of Limited Availability features:
- Ready for production use by a small set of customers.
- Can be booked by Deal Desk as part of an order.
diff --git a/doc/security/rate_limits.md b/doc/security/rate_limits.md
index 929609cd4a4..5784863e681 100644
--- a/doc/security/rate_limits.md
+++ b/doc/security/rate_limits.md
@@ -74,13 +74,7 @@ For configuration information, see
### Git operations using SSH
-> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/78373) in GitLab 14.7 [with a flag](../administration/feature_flags.md) named `rate_limit_gitlab_shell`. Disabled by default.
-> - [Enabled on GitLab.com and self-managed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/79419) in GitLab 14.8.
-
-FLAG:
-On self-managed GitLab, by default this feature is available. To disable the feature, ask an administrator to
-[disable the feature flag](../administration/feature_flags.md) named `rate_limit_gitlab_shell`. On GitLab.com, this feature
-is available.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/78373) in GitLab 14.7 [with a flag](../administration/feature_flags.md) named `rate_limit_gitlab_shell`. Available by default without a feature flag from 15.8.
GitLab applies rate limits to Git operations that use SSH by user account and project. When the rate limit is exceeded, GitLab rejects
further connection requests from that user for the project.
diff --git a/doc/topics/authentication/index.md b/doc/topics/authentication/index.md
index 763e16bc31d..77a83b10df9 100644
--- a/doc/topics/authentication/index.md
+++ b/doc/topics/authentication/index.md
@@ -17,7 +17,7 @@ This page gathers all the resources for the topic **Authentication** within GitL
- [Support for Universal 2nd Factor Authentication - YubiKeys](https://about.gitlab.com/blog/2016/06/22/gitlab-adds-support-for-u2f/)
- [Security Webcast with Yubico](https://about.gitlab.com/blog/2016/08/31/gitlab-and-yubico-security-webcast/)
- **Integrations:**
- - [GitLab as OAuth2 authentication service provider](../../integration/oauth_provider.md#introduction-to-oauth)
+ - [GitLab as OAuth2 authentication service provider](../../integration/oauth_provider.md)
- [GitLab as OpenID Connect identity provider](../../integration/openid_connect_provider.md)
## GitLab administrators
diff --git a/doc/user/application_security/secret_detection/post_processing.md b/doc/user/application_security/secret_detection/post_processing.md
index 9c74467bce5..e026c663854 100644
--- a/doc/user/application_security/secret_detection/post_processing.md
+++ b/doc/user/application_security/secret_detection/post_processing.md
@@ -11,7 +11,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
FLAG:
By default, auto revocation of GitLab personal access tokens is not available. To opt-in on GitLab.com
-during the [Beta period](https://about.gitlab.com/handbook/product/gitlab-the-product/#alpha-beta-ga), please
+during the [Beta period](../../../policy/alpha-beta-support.md#beta-features), please
[let us know by completing this form](https://docs.google.com/forms/d/e/1FAIpQLSdRbFhvA5jvI-Rt_Qnl1PQ1znOXKK8m6lRtmM0uva4upetKvQ/viewform).
GitLab supports running post-processing hooks after detecting a secret. These
diff --git a/doc/user/profile/index.md b/doc/user/profile/index.md
index 170f6a67093..f977e529c5c 100644
--- a/doc/user/profile/index.md
+++ b/doc/user/profile/index.md
@@ -391,7 +391,7 @@ Without the `config.extend_remember_period` flag, you would be forced to sign in
- Receive emails for:
- [Sign-ins from unknown IP addresses or devices](notifications.md#notifications-for-unknown-sign-ins)
- [Attempted sign-ins using wrong two-factor authentication codes](notifications.md#notifications-for-attempted-sign-in-using-wrong-two-factor-authentication-codes)
-- Manage applications that can [use GitLab as an OAuth provider](../../integration/oauth_provider.md#introduction-to-oauth)
+- Manage applications that can [use GitLab as an OAuth provider](../../integration/oauth_provider.md)
- Manage [personal access tokens](personal_access_tokens.md) to access your account via API and authorized applications
- Manage [SSH keys](../ssh.md) to access your account via SSH
- Change your [syntax highlighting theme](preferences.md#syntax-highlighting-theme)
diff --git a/doc/user/project/merge_requests/versions.md b/doc/user/project/merge_requests/versions.md
index a864a9849b0..120e300da5e 100644
--- a/doc/user/project/merge_requests/versions.md
+++ b/doc/user/project/merge_requests/versions.md
@@ -64,7 +64,7 @@ branches. The new mode is available from the comparison target drop down
by selecting **main (HEAD)**. In GitLab 13.9, it
[replaced](https://gitlab.com/gitlab-org/gitlab/-/issues/198458) the
old default comparison. For technical details, additional information is available in the
-[developer documentation](../../../development/diffs.md#merge-request-diffs-against-the-head-of-the-target-branch).
+[developer documentation](../../../development/merge_request_concepts/diffs/index.md#merge-request-diffs-against-the-head-of-the-target-branch).
![Merge request versions compare HEAD](img/versions_compare_head_v12_10.png)
diff --git a/lib/api/internal/base.rb b/lib/api/internal/base.rb
index 39f39a5514e..3f6e052f7b6 100644
--- a/lib/api/internal/base.rb
+++ b/lib/api/internal/base.rb
@@ -44,9 +44,7 @@ module API
# This is a separate method so that EE can alter its behaviour more
# easily.
- if Feature.enabled?(:rate_limit_gitlab_shell)
- check_rate_limit!(:gitlab_shell_operation, scope: [params[:action], params[:project], actor.key_or_user])
- end
+ check_rate_limit!(:gitlab_shell_operation, scope: [params[:action], params[:project], actor.key_or_user])
rate_limiter = Gitlab::Auth::IpRateLimiter.new(request.ip)
diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb
index 7e8f9c76dea..c97ef5a10ef 100644
--- a/lib/gitlab/auth.rb
+++ b/lib/gitlab/auth.rb
@@ -31,6 +31,7 @@ module Gitlab
# Scopes used for GitLab as admin
SUDO_SCOPE = :sudo
+ ADMIN_MODE_SCOPE = :admin_mode
ADMIN_SCOPES = [SUDO_SCOPE].freeze
# Default scopes for OAuth applications that don't define their own
@@ -366,6 +367,7 @@ module Gitlab
def available_scopes_for(current_user)
scopes = non_admin_available_scopes
scopes += ADMIN_SCOPES if current_user.admin?
+
scopes
end
diff --git a/lib/gitlab/background_migration/backfill_admin_mode_scope_for_personal_access_tokens.rb b/lib/gitlab/background_migration/backfill_admin_mode_scope_for_personal_access_tokens.rb
new file mode 100644
index 00000000000..82e607ac7a7
--- /dev/null
+++ b/lib/gitlab/background_migration/backfill_admin_mode_scope_for_personal_access_tokens.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module BackgroundMigration
+ # Backfill `admin_mode` scope for a range of personal access tokens
+ class BackfillAdminModeScopeForPersonalAccessTokens < ::Gitlab::BackgroundMigration::BatchedMigrationJob
+ scope_to ->(relation) do
+ relation.joins('INNER JOIN users ON personal_access_tokens.user_id = users.id')
+ .where(users: { admin: true })
+ .where(revoked: [false, nil])
+ .where.not('expires_at IS NOT NULL AND expires_at <= ?', Time.current)
+ end
+
+ operation_name :update_all
+ feature_category :authentication_and_authorization
+
+ ADMIN_MODE_SCOPE = ['admin_mode'].freeze
+
+ def perform
+ each_sub_batch do |sub_batch|
+ sub_batch.each do |token|
+ token.update!(scopes: (YAML.safe_load(token.scopes) + ADMIN_MODE_SCOPE).uniq.to_yaml)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/database/migration_helpers.rb b/lib/gitlab/database/migration_helpers.rb
index 4858a96c173..1ab68b20da4 100644
--- a/lib/gitlab/database/migration_helpers.rb
+++ b/lib/gitlab/database/migration_helpers.rb
@@ -357,10 +357,28 @@ module Gitlab
end
def foreign_key_exists?(source, target = nil, **options)
- foreign_keys(source).any? do |foreign_key|
- tables_match?(target.to_s, foreign_key.to_table.to_s) &&
- options_match?(foreign_key.options, options)
+ # This if block is necessary because foreign_key_exists? is called in down migrations that may execute before
+ # the postgres_foreign_keys view had necessary columns added, or even before the view existed.
+ # In that case, we revert to the previous behavior of this method.
+ # The behavior in the if block has a bug: it always returns false if the fk being checked has multiple columns.
+ # This can be removed after init_schema.rb passes 20221122210711_add_columns_to_postgres_foreign_keys.rb
+ # Tracking issue: https://gitlab.com/gitlab-org/gitlab/-/issues/386796
+ if ActiveRecord::Migrator.current_version < 20221122210711
+ return foreign_keys(source).any? do |foreign_key|
+ tables_match?(target.to_s, foreign_key.to_table.to_s) &&
+ options_match?(foreign_key.options, options)
+ end
end
+
+ fks = Gitlab::Database::PostgresForeignKey.by_constrained_table_name(source)
+
+ fks = fks.by_referenced_table_name(target) if target
+ fks = fks.by_name(options[:name]) if options[:name]
+ fks = fks.by_constrained_columns(options[:column]) if options[:column]
+ fks = fks.by_referenced_columns(options[:primary_key]) if options[:primary_key]
+ fks = fks.by_on_delete_action(options[:on_delete]) if options[:on_delete]
+
+ fks.exists?
end
# Returns the name for a concurrent foreign key.
diff --git a/lib/gitlab/database/postgres_foreign_key.rb b/lib/gitlab/database/postgres_foreign_key.rb
index dc331075942..d3ede45fe86 100644
--- a/lib/gitlab/database/postgres_foreign_key.rb
+++ b/lib/gitlab/database/postgres_foreign_key.rb
@@ -9,7 +9,7 @@ module Gitlab
enum on_delete_action: {
restrict: 'r',
cascade: 'c',
- set_null: 'n',
+ nullify: 'n',
set_default: 'd',
no_action: 'a'
}
@@ -20,13 +20,29 @@ module Gitlab
where(referenced_table_identifier: identifier)
end
+ scope :by_referenced_table_name, ->(name) { where(referenced_table_name: name) }
+
scope :by_constrained_table_identifier, ->(identifier) do
raise ArgumentError, "Constrained table name is not fully qualified with a schema: #{identifier}" unless identifier =~ /^\w+\.\w+$/
where(constrained_table_identifier: identifier)
end
+ scope :by_constrained_table_name, ->(name) { where(constrained_table_name: name) }
+
scope :not_inherited, -> { where(is_inherited: false) }
+
+ scope :by_name, ->(name) { where(name: name) }
+
+ scope :by_constrained_columns, ->(cols) { where(constrained_columns: Array.wrap(cols)) }
+
+ scope :by_referenced_columns, ->(cols) { where(referenced_columns: Array.wrap(cols)) }
+
+ scope :by_on_delete_action, ->(on_delete) do
+ raise ArgumentError, "Invalid on_delete action #{on_delete}" unless on_delete_actions.key?(on_delete)
+
+ where(on_delete_action: on_delete)
+ end
end
end
end
diff --git a/lib/gitlab/hotlinking_detector.rb b/lib/gitlab/hotlinking_detector.rb
index dd58f6aca26..b5000777010 100644
--- a/lib/gitlab/hotlinking_detector.rb
+++ b/lib/gitlab/hotlinking_detector.rb
@@ -12,8 +12,6 @@ module Gitlab
def intercept_hotlinking?(request)
request_accepts = parse_request_accepts(request)
- return false unless Feature.enabled?(:repository_archive_hotlinking_interception)
-
# Block attempts to embed as JS
return true if sec_fetch_invalid?(request)
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 91de67a8611..cfc98c7b64f 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -16662,7 +16662,7 @@ msgstr ""
msgid "Expires:"
msgstr ""
-msgid "Explain the problem. If appropriate, provide a link to the relevant issue or comment."
+msgid "Explain why you're reporting the user."
msgstr ""
msgid "Explore"
@@ -24423,9 +24423,6 @@ msgstr ""
msgid "Last successful update %{time}."
msgstr ""
-msgid "Last time verified"
-msgstr ""
-
msgid "Last update"
msgstr ""
@@ -31423,6 +31420,9 @@ msgstr ""
msgid "Please type %{phrase_code} to proceed or close this modal to cancel."
msgstr ""
+msgid "Please use this form to report to the administrator users who create spam issues, comments or behave inappropriately."
+msgstr ""
+
msgid "Please wait a few moments while we load the file history for this line."
msgstr ""
@@ -35847,9 +35847,6 @@ msgstr ""
msgid "Resume"
msgstr ""
-msgid "Resync"
-msgstr ""
-
msgid "Retrieving the compliance report failed. Refresh the page and try again."
msgstr ""
@@ -45380,9 +45377,6 @@ msgstr ""
msgid "Use the search bar on the top of this page"
msgstr ""
-msgid "Use this form to report to the administrator users who create spam issues, comments or behave inappropriately."
-msgstr ""
-
msgid "Use this token to validate received payloads."
msgstr ""
@@ -48215,9 +48209,6 @@ msgid_plural "You have %{pendingMembersCount} pending members that need approval
msgstr[0] ""
msgstr[1] ""
-msgid "You have already reported this user"
-msgstr ""
-
msgid "You have been granted %{access_level} access to the %{source_link} %{source_type}."
msgstr ""
@@ -49821,6 +49812,9 @@ msgstr[1] ""
msgid "has already been linked to another vulnerability"
msgstr ""
+msgid "has already been reported for abuse"
+msgstr ""
+
msgid "has already been taken"
msgstr ""
diff --git a/qa/qa/specs/features/browser_ui/1_manage/user/parent_group_access_termination_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/user/parent_group_access_termination_spec.rb
index 6bc695487ee..a72d1bfb4ba 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/user/parent_group_access_termination_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/user/parent_group_access_termination_spec.rb
@@ -34,6 +34,7 @@ module QA
Page::Group::Menu.perform(&:click_subgroup_members_item)
Page::Group::Members.perform do |members_page|
+ members_page.search_member(user.username)
members_page.remove_member(user.username)
end
end
diff --git a/spec/factories/abuse_reports.rb b/spec/factories/abuse_reports.rb
index 4174faae1ed..4ae9b4def8e 100644
--- a/spec/factories/abuse_reports.rb
+++ b/spec/factories/abuse_reports.rb
@@ -5,5 +5,6 @@ FactoryBot.define do
reporter factory: :user
user
message { 'User sends spam' }
+ reported_from_url { 'http://gitlab.com' }
end
end
diff --git a/spec/features/abuse_report_spec.rb b/spec/features/abuse_report_spec.rb
index 591b397119c..f9fa3e6e109 100644
--- a/spec/features/abuse_report_spec.rb
+++ b/spec/features/abuse_report_spec.rb
@@ -2,130 +2,119 @@
require 'spec_helper'
-RSpec.describe 'Abuse reports', feature_category: :insider_threat do
- let_it_be(:abusive_user) { create(:user, username: 'abuser_mcabusive') }
- let_it_be(:reporter1) { create(:user, username: 'reporter_mcreporty') }
- let_it_be(:reporter2) { create(:user) }
+RSpec.describe 'Abuse reports', :js, feature_category: :insider_threat do
+ let_it_be(:abusive_user) { create(:user) }
+
+ let_it_be(:reporter1) { create(:user) }
+
let_it_be(:project) { create(:project, :public) }
let_it_be(:issue) { create(:issue, project: project, author: abusive_user) }
- let!(:group) do
- create(:group).tap do |g|
- g.add_owner(reporter1)
- g.add_developer(abusive_user)
- end
- end
-
before do
sign_in(reporter1)
end
- it 'allows a user to be reported for abuse from an issue', :js do
- visit project_issue_path(project, issue)
+ context 'when reporting an issue for abuse' do
+ it 'allows a user to be reported for abuse from an issue', :js do
+ visit project_issue_path(project, issue)
- click_button 'Issue actions'
- click_link 'Report abuse to administrator'
+ click_button 'Issue actions'
+ click_link 'Report abuse to administrator'
- wait_for_requests
+ wait_for_requests
- fill_and_submit_form
+ fill_and_submit_form
- expect(page).to have_content 'Thank you for your report'
- end
+ expect(page).to have_content 'Thank you for your report'
+ end
- it 'allows a user to be reported for abuse from their profile', :js do
- visit user_path(abusive_user)
+ it 'redirects backs to the issue when cancel button is clicked' do
+ visit project_issue_path(project, issue)
- click_button 'Report abuse to administrator'
+ click_button 'Issue actions'
+ click_link 'Report abuse to administrator'
- choose "They're posting spam."
- click_button 'Next'
+ wait_for_requests
- wait_for_requests
+ click_link 'Cancel'
- fill_and_submit_form
+ expect(page).to have_current_path(project_issue_path(project, issue))
+ end
+ end
- expect(page).to have_content 'Thank you for your report'
+ context 'when reporting a user profile for abuse' do
+ let_it_be(:reporter2) { create(:user) }
- visit user_path(abusive_user)
+ it 'allows a user to be reported for abuse from their profile' do
+ visit user_path(abusive_user)
- click_button 'Report abuse to administrator'
+ click_button 'Report abuse to administrator'
- choose "They're posting spam."
- click_button 'Next'
+ choose "They're posting spam."
+ click_button 'Next'
- fill_and_submit_form
+ wait_for_requests
- expect(page).to have_content 'You have already reported this user'
- end
+ fill_and_submit_form
- it 'allows multiple users to report a user', :js do
- visit user_path(abusive_user)
+ expect(page).to have_content 'Thank you for your report'
- click_button 'Report abuse to administrator'
+ visit user_path(abusive_user)
- choose "They're posting spam."
- click_button 'Next'
+ click_button 'Report abuse to administrator'
- wait_for_requests
+ choose "They're posting spam."
+ click_button 'Next'
- fill_and_submit_form
+ fill_and_submit_form
- expect(page).to have_content 'Thank you for your report'
+ expect(page).to have_content 'User has already been reported for abuse'
+ end
- sign_out(reporter1)
- sign_in(reporter2)
+ it 'allows multiple users to report a user' do
+ visit user_path(abusive_user)
- visit user_path(abusive_user)
+ click_button 'Report abuse to administrator'
- click_button 'Report abuse to administrator'
+ choose "They're posting spam."
+ click_button 'Next'
- choose "They're posting spam."
- click_button 'Next'
+ wait_for_requests
- wait_for_requests
+ fill_and_submit_form
- fill_and_submit_form
+ expect(page).to have_content 'Thank you for your report'
- expect(page).to have_content 'Thank you for your report'
- end
+ gitlab_sign_out
+ gitlab_sign_in(reporter2)
- describe 'Cancel', :js do
- context 'when ref_url is not present (e.g. visit user page then click on report abuse)' do
- it 'links the user back to where abuse report was triggered' do
- origin_url = user_path(abusive_user)
+ visit user_path(abusive_user)
- visit origin_url
+ click_button 'Report abuse to administrator'
- click_button 'Report abuse to administrator'
- choose "They're posting spam."
- click_button 'Next'
+ choose "They're posting spam."
+ click_button 'Next'
- wait_for_requests
+ wait_for_requests
- click_link 'Cancel'
+ fill_and_submit_form
- expect(page).to have_current_path(origin_url)
- end
+ expect(page).to have_content 'Thank you for your report'
end
- context 'when ref_url is present (e.g. user is reported from one of their MRs)' do
- it 'links the user back to ref_url' do
- ref_url = group_group_members_path(group)
+ it 'redirects backs to user profile when cancel button is clicked' do
+ visit user_path(abusive_user)
- visit ref_url
+ click_button 'Report abuse to administrator'
- # visit abusive user's profile page
- page.first('.js-user-link').click
+ choose "They're posting spam."
+ click_button 'Next'
- click_button 'Report abuse to administrator'
- choose "They're posting spam."
- click_button 'Next'
+ wait_for_requests
- click_link 'Cancel'
+ click_link 'Cancel'
- expect(page).to have_current_path(ref_url)
- end
+ expect(page).to have_current_path(user_path(abusive_user))
end
end
diff --git a/spec/features/merge_request/user_sees_discussions_navigation_spec.rb b/spec/features/merge_request/user_sees_discussions_navigation_spec.rb
index 2afe7e3edba..d75a8049925 100644
--- a/spec/features/merge_request/user_sees_discussions_navigation_spec.rb
+++ b/spec/features/merge_request/user_sees_discussions_navigation_spec.rb
@@ -194,29 +194,10 @@ RSpec.describe 'Merge request > User sees discussions navigation', :js, feature_
end
def goto_next_thread
- begin
- # this is required when moved_mr_sidebar is enabled
- page.within('.issue-sticky-header') do
- click_button 'Go to next unresolved thread'
- end
- rescue StandardError
- click_button 'Go to next unresolved thread'
- end
- wait_for_scroll_end
+ click_button 'Go to next unresolved thread'
end
def goto_previous_thread
- begin
- page.within('.issue-sticky-header') do
- click_button 'Go to previous unresolved thread'
- end
- rescue StandardError
- click_button 'Go to previous unresolved thread'
- end
- wait_for_scroll_end
- end
-
- def wait_for_scroll_end
- sleep(1)
+ click_button 'Go to previous unresolved thread'
end
end
diff --git a/spec/frontend/abuse_reports/components/abuse_category_selector_spec.js b/spec/frontend/abuse_reports/components/abuse_category_selector_spec.js
index 4f66348f9cd..69c4717e52b 100644
--- a/spec/frontend/abuse_reports/components/abuse_category_selector_spec.js
+++ b/spec/frontend/abuse_reports/components/abuse_category_selector_spec.js
@@ -112,7 +112,7 @@ describe('AbuseCategorySelector', () => {
it('renders referer as a hidden fields', () => {
expect(findReferer().attributes()).toMatchObject({
type: 'hidden',
- name: 'ref_url',
+ name: 'abuse_report[reported_from_url]',
value: REPORTED_FROM_URL,
});
});
diff --git a/spec/frontend/constants_spec.js b/spec/frontend/constants_spec.js
new file mode 100644
index 00000000000..b596b62f72c
--- /dev/null
+++ b/spec/frontend/constants_spec.js
@@ -0,0 +1,30 @@
+import * as constants from '~/constants';
+
+describe('Global JS constants', () => {
+ describe('getModifierKey()', () => {
+ afterEach(() => {
+ delete window.gl;
+ });
+
+ it.each`
+ isMac | removeSuffix | expectedKey
+ ${true} | ${false} | ${'⌘'}
+ ${false} | ${false} | ${'Ctrl+'}
+ ${true} | ${true} | ${'⌘'}
+ ${false} | ${true} | ${'Ctrl'}
+ `(
+ 'returns correct keystroke when isMac=$isMac and removeSuffix=$removeSuffix',
+ ({ isMac, removeSuffix, expectedKey }) => {
+ Object.assign(window, {
+ gl: {
+ client: {
+ isMac,
+ },
+ },
+ });
+
+ expect(constants.getModifierKey(removeSuffix)).toBe(expectedKey);
+ },
+ );
+ });
+});
diff --git a/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/tags_list_row_spec.js b/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/tags_list_row_spec.js
index 96c670eaad2..fa0d76762df 100644
--- a/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/tags_list_row_spec.js
+++ b/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/tags_list_row_spec.js
@@ -335,10 +335,10 @@ describe('tags list row', () => {
});
describe.each`
- name | finderFunction | text | icon | clipboard
- ${'published date detail'} | ${findPublishedDateDetail} | ${'Published to the gitlab-org/gitlab-test/rails-12009 image repository at 01:29 UTC on 2020-11-03'} | ${'clock'} | ${false}
- ${'manifest detail'} | ${findManifestDetail} | ${'Manifest digest: sha256:2cf3d2fdac1b04a14301d47d51cb88dcd26714c74f91440eeee99ce399089062'} | ${'log'} | ${true}
- ${'configuration detail'} | ${findConfigurationDetail} | ${'Configuration digest: sha256:c2613843ab33aabf847965442b13a8b55a56ae28837ce182627c0716eb08c02b'} | ${'cloud-gear'} | ${true}
+ name | finderFunction | text | icon | clipboard
+ ${'published date detail'} | ${findPublishedDateDetail} | ${'Published to the gitlab-org/gitlab-test/rails-12009 image repository at 13:29:38 UTC on 2020-11-03'} | ${'clock'} | ${false}
+ ${'manifest detail'} | ${findManifestDetail} | ${'Manifest digest: sha256:2cf3d2fdac1b04a14301d47d51cb88dcd26714c74f91440eeee99ce399089062'} | ${'log'} | ${true}
+ ${'configuration detail'} | ${findConfigurationDetail} | ${'Configuration digest: sha256:c2613843ab33aabf847965442b13a8b55a56ae28837ce182627c0716eb08c02b'} | ${'cloud-gear'} | ${true}
`('$name details row', ({ finderFunction, text, icon, clipboard }) => {
it(`has ${text} as text`, async () => {
mountComponent();
diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/package_history_spec.js b/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/package_history_spec.js
index c6b5138639e..0cbe2755f7e 100644
--- a/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/package_history_spec.js
+++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/package_history_spec.js
@@ -61,14 +61,14 @@ describe('Package History', () => {
);
});
describe.each`
- name | amount | icon | text | timeAgoTooltip | link
- ${'created-on'} | ${HISTORY_PIPELINES_LIMIT + 2} | ${'clock'} | ${'Test package version 1.0.0 was first created'} | ${mavenPackage.created_at} | ${null}
- ${'first-pipeline-commit'} | ${HISTORY_PIPELINES_LIMIT + 2} | ${'commit'} | ${'Created by commit #sha-baz on branch branch-name'} | ${null} | ${mockPipelineInfo.project.commit_url}
- ${'first-pipeline-pipeline'} | ${HISTORY_PIPELINES_LIMIT + 2} | ${'pipeline'} | ${'Built by pipeline #1 triggered by foo'} | ${mockPipelineInfo.created_at} | ${mockPipelineInfo.project.pipeline_url}
- ${'published'} | ${HISTORY_PIPELINES_LIMIT + 2} | ${'package'} | ${'Published to the baz project Package Registry'} | ${mavenPackage.created_at} | ${null}
- ${'archived'} | ${HISTORY_PIPELINES_LIMIT + 2} | ${'history'} | ${'Package has 1 archived update'} | ${null} | ${null}
- ${'archived'} | ${HISTORY_PIPELINES_LIMIT + 3} | ${'history'} | ${'Package has 2 archived updates'} | ${null} | ${null}
- ${'pipeline-entry'} | ${HISTORY_PIPELINES_LIMIT + 2} | ${'pencil'} | ${'Package updated by commit #sha-baz on branch branch-name, built by pipeline #3, and published to the registry'} | ${mavenPackage.created_at} | ${mockPipelineInfo.project.commit_url}
+ name | amount | icon | text | timeAgoTooltip | link
+ ${'created-on'} | ${HISTORY_PIPELINES_LIMIT + 2} | ${'clock'} | ${'Test package version 1.0.0 was first created'} | ${mavenPackage.created_at} | ${null}
+ ${'first-pipeline-commit'} | ${HISTORY_PIPELINES_LIMIT + 2} | ${'commit'} | ${'Created by commit sha-baz on branch branch-name'} | ${null} | ${mockPipelineInfo.project.commit_url}
+ ${'first-pipeline-pipeline'} | ${HISTORY_PIPELINES_LIMIT + 2} | ${'pipeline'} | ${'Built by pipeline #1 triggered by foo'} | ${mockPipelineInfo.created_at} | ${mockPipelineInfo.project.pipeline_url}
+ ${'published'} | ${HISTORY_PIPELINES_LIMIT + 2} | ${'package'} | ${'Published to the baz project Package Registry'} | ${mavenPackage.created_at} | ${null}
+ ${'archived'} | ${HISTORY_PIPELINES_LIMIT + 2} | ${'history'} | ${'Package has 1 archived update'} | ${null} | ${null}
+ ${'archived'} | ${HISTORY_PIPELINES_LIMIT + 3} | ${'history'} | ${'Package has 2 archived updates'} | ${null} | ${null}
+ ${'pipeline-entry'} | ${HISTORY_PIPELINES_LIMIT + 2} | ${'pencil'} | ${'Package updated by commit sha-baz on branch branch-name, built by pipeline #3, and published to the registry'} | ${mavenPackage.created_at} | ${mockPipelineInfo.project.commit_url}
`(
'with $amount pipelines history element $name',
({ name, icon, text, timeAgoTooltip, link, amount }) => {
diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/package_history_spec.js b/spec/frontend/packages_and_registries/package_registry/components/details/package_history_spec.js
index ec2e833552a..bb2fa9eb6f5 100644
--- a/spec/frontend/packages_and_registries/package_registry/components/details/package_history_spec.js
+++ b/spec/frontend/packages_and_registries/package_registry/components/details/package_history_spec.js
@@ -131,14 +131,14 @@ describe('Package History', () => {
});
describe.each`
- name | amount | icon | text | timeAgoTooltip | link
- ${'created-on'} | ${HISTORY_PIPELINES_LIMIT + 2} | ${'clock'} | ${'@gitlab-org/package-15 version 1.0.0 was first created'} | ${packageData().createdAt} | ${null}
- ${'first-pipeline-commit'} | ${HISTORY_PIPELINES_LIMIT + 2} | ${'commit'} | ${'Created by commit #b83d6e39 on branch master'} | ${null} | ${onePipeline.commitPath}
- ${'first-pipeline-pipeline'} | ${HISTORY_PIPELINES_LIMIT + 2} | ${'pipeline'} | ${'Built by pipeline #1 triggered by Administrator'} | ${onePipeline.createdAt} | ${onePipeline.path}
- ${'published'} | ${HISTORY_PIPELINES_LIMIT + 2} | ${'package'} | ${'Published to the baz project Package Registry'} | ${packageData().createdAt} | ${null}
- ${'archived'} | ${HISTORY_PIPELINES_LIMIT + 2} | ${'history'} | ${'Package has 1 archived update'} | ${null} | ${null}
- ${'archived'} | ${HISTORY_PIPELINES_LIMIT + 3} | ${'history'} | ${'Package has 2 archived updates'} | ${null} | ${null}
- ${'pipeline-entry'} | ${HISTORY_PIPELINES_LIMIT + 2} | ${'pencil'} | ${'Package updated by commit #b83d6e39 on branch master, built by pipeline #3, and published to the registry'} | ${packageData().createdAt} | ${onePipeline.commitPath}
+ name | amount | icon | text | timeAgoTooltip | link
+ ${'created-on'} | ${HISTORY_PIPELINES_LIMIT + 2} | ${'clock'} | ${'@gitlab-org/package-15 version 1.0.0 was first created'} | ${packageData().createdAt} | ${null}
+ ${'first-pipeline-commit'} | ${HISTORY_PIPELINES_LIMIT + 2} | ${'commit'} | ${'Created by commit b83d6e39 on branch master'} | ${null} | ${onePipeline.commitPath}
+ ${'first-pipeline-pipeline'} | ${HISTORY_PIPELINES_LIMIT + 2} | ${'pipeline'} | ${'Built by pipeline #1 triggered by Administrator'} | ${onePipeline.createdAt} | ${onePipeline.path}
+ ${'published'} | ${HISTORY_PIPELINES_LIMIT + 2} | ${'package'} | ${'Published to the baz project Package Registry'} | ${packageData().createdAt} | ${null}
+ ${'archived'} | ${HISTORY_PIPELINES_LIMIT + 2} | ${'history'} | ${'Package has 1 archived update'} | ${null} | ${null}
+ ${'archived'} | ${HISTORY_PIPELINES_LIMIT + 3} | ${'history'} | ${'Package has 2 archived updates'} | ${null} | ${null}
+ ${'pipeline-entry'} | ${HISTORY_PIPELINES_LIMIT + 2} | ${'pencil'} | ${'Package updated by commit b83d6e39 on branch master, built by pipeline #3, and published to the registry'} | ${packageData().createdAt} | ${onePipeline.commitPath}
`(
'with $amount pipelines history element $name',
({ name, icon, text, timeAgoTooltip, link, amount }) => {
diff --git a/spec/frontend/users/profile/components/report_abuse_button_spec.js b/spec/frontend/users/profile/components/report_abuse_button_spec.js
index bd39a089473..a09a437a9cc 100644
--- a/spec/frontend/users/profile/components/report_abuse_button_spec.js
+++ b/spec/frontend/users/profile/components/report_abuse_button_spec.js
@@ -1,5 +1,6 @@
import { GlButton } from '@gitlab/ui';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import { BV_HIDE_TOOLTIP } from '~/lib/utils/constants';
import ReportAbuseButton from '~/users/profile/components/report_abuse_button.vue';
import AbuseCategorySelector from '~/abuse_reports/components/abuse_category_selector.vue';
@@ -69,4 +70,14 @@ describe('ReportAbuseButton', () => {
expect(findAbuseCategorySelector().props('showDrawer')).toBe(false);
});
});
+
+ describe('when user hovers out of the button', () => {
+ it(`should emit ${BV_HIDE_TOOLTIP} to close the tooltip`, () => {
+ jest.spyOn(wrapper.vm.$root, '$emit');
+
+ findReportAbuseButton().vm.$emit('mouseout');
+
+ expect(wrapper.vm.$root.$emit).toHaveBeenCalledWith(BV_HIDE_TOOLTIP);
+ });
+ });
});
diff --git a/spec/frontend/work_items/components/work_item_notes_spec.js b/spec/frontend/work_items/components/work_item_notes_spec.js
index 98b2e34b860..63947e66392 100644
--- a/spec/frontend/work_items/components/work_item_notes_spec.js
+++ b/spec/frontend/work_items/components/work_item_notes_spec.js
@@ -6,6 +6,7 @@ import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import SystemNote from '~/work_items/components/notes/system_note.vue';
import WorkItemNotes from '~/work_items/components/work_item_notes.vue';
+import WorkItemCommentForm from '~/work_items/components/work_item_comment_form.vue';
import ActivityFilter from '~/work_items/components/notes/activity_filter.vue';
import workItemNotesQuery from '~/work_items/graphql/work_item_notes.query.graphql';
import workItemNotesByIidQuery from '~/work_items/graphql/work_item_notes_by_iid.query.graphql';
@@ -40,6 +41,7 @@ describe('WorkItemNotes component', () => {
const findAllSystemNotes = () => wrapper.findAllComponents(SystemNote);
const findActivityLabel = () => wrapper.find('label');
+ const findWorkItemCommentForm = () => wrapper.findComponent(WorkItemCommentForm);
const findSkeletonLoader = () => wrapper.findComponent(GlSkeletonLoader);
const findIntersectionObserver = () => wrapper.findComponent(GlIntersectionObserver);
const findSortingFilter = () => wrapper.findComponent(ActivityFilter);
@@ -85,6 +87,13 @@ describe('WorkItemNotes component', () => {
expect(findActivityLabel().exists()).toBe(true);
});
+ it('passes correct props to comment form component', async () => {
+ createComponent({ workItemId: mockWorkItemId, fetchByIid: false });
+ await waitForPromises();
+
+ expect(findWorkItemCommentForm().props('fetchByIid')).toEqual(false);
+ });
+
describe('when notes are loading', () => {
it('renders skeleton loader', () => {
expect(findSkeletonLoader().exists()).toBe(true);
@@ -117,6 +126,10 @@ describe('WorkItemNotes component', () => {
mockNotesByIidWidgetResponse.discussions.nodes.length,
);
});
+
+ it('passes correct props to comment form component', () => {
+ expect(findWorkItemCommentForm().props('fetchByIid')).toEqual(true);
+ });
});
describe('Pagination', () => {
diff --git a/spec/lib/gitlab/background_migration/backfill_admin_mode_scope_for_personal_access_tokens_spec.rb b/spec/lib/gitlab/background_migration/backfill_admin_mode_scope_for_personal_access_tokens_spec.rb
new file mode 100644
index 00000000000..7075d4694ae
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/backfill_admin_mode_scope_for_personal_access_tokens_spec.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::BackgroundMigration::BackfillAdminModeScopeForPersonalAccessTokens,
+ :migration, schema: 20221228103133, feature_category: :authentication_and_authorization do
+ let(:users) { table(:users) }
+ let(:personal_access_tokens) { table(:personal_access_tokens) }
+
+ let(:admin) { users.create!(name: 'admin', email: 'admin@example.com', projects_limit: 1, admin: true) }
+ let(:user) { users.create!(name: 'user', email: 'user@example.com', projects_limit: 1) }
+
+ let!(:pat_admin_1) { personal_access_tokens.create!(name: 'admin 1', user_id: admin.id, scopes: "---\n- api\n") }
+ let!(:pat_user) { personal_access_tokens.create!(name: 'user 1', user_id: user.id, scopes: "---\n- api\n") }
+ let!(:pat_revoked) do
+ personal_access_tokens.create!(name: 'admin 2', user_id: admin.id, scopes: "---\n- api\n", revoked: true)
+ end
+
+ let!(:pat_expired) do
+ personal_access_tokens.create!(name: 'admin 3', user_id: admin.id, scopes: "---\n- api\n", expires_at: 1.day.ago)
+ end
+
+ let!(:pat_admin_mode) do
+ personal_access_tokens.create!(name: 'admin 4', user_id: admin.id, scopes: "---\n- admin_mode\n")
+ end
+
+ let!(:pat_admin_2) { personal_access_tokens.create!(name: 'admin 5', user_id: admin.id, scopes: "---\n- read_api\n") }
+ let!(:pat_not_in_range) { personal_access_tokens.create!(name: 'admin 6', user_id: admin.id, scopes: "---\n- api\n") }
+
+ subject do
+ described_class.new(
+ start_id: pat_admin_1.id,
+ end_id: pat_admin_2.id,
+ batch_table: :personal_access_tokens,
+ batch_column: :id,
+ sub_batch_size: 1,
+ pause_ms: 0,
+ connection: ApplicationRecord.connection
+ )
+ end
+
+ it "adds `admin_mode` scope to active personal access tokens of administrators" do
+ subject.perform
+
+ expect(pat_admin_1.reload.scopes).to eq("---\n- api\n- admin_mode\n")
+ expect(pat_user.reload.scopes).to eq("---\n- api\n")
+ expect(pat_revoked.reload.scopes).to eq("---\n- api\n")
+ expect(pat_expired.reload.scopes).to eq("---\n- api\n")
+ expect(pat_admin_mode.reload.scopes).to eq("---\n- admin_mode\n")
+ expect(pat_admin_2.reload.scopes).to eq("---\n- read_api\n- admin_mode\n")
+ expect(pat_not_in_range.reload.scopes).to eq("---\n- api\n")
+ end
+end
diff --git a/spec/lib/gitlab/database/migration_helpers_spec.rb b/spec/lib/gitlab/database/migration_helpers_spec.rb
index 8923187142f..a1433b9a1d2 100644
--- a/spec/lib/gitlab/database/migration_helpers_spec.rb
+++ b/spec/lib/gitlab/database/migration_helpers_spec.rb
@@ -973,58 +973,58 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
describe '#foreign_key_exists?' do
before do
- key = ActiveRecord::ConnectionAdapters::ForeignKeyDefinition.new(
- :projects, :users,
- {
- column: :non_standard_id,
- name: :fk_projects_users_non_standard_id,
- on_delete: :cascade,
- primary_key: :id
- }
- )
- allow(model).to receive(:foreign_keys).with(:projects).and_return([key])
+ model.connection.execute(<<~SQL)
+ create table referenced (
+ id bigserial primary key not null
+ );
+ create table referencing (
+ id bigserial primary key not null,
+ non_standard_id bigint not null,
+ constraint fk_referenced foreign key (non_standard_id) references referenced(id) on delete cascade
+ );
+ SQL
end
shared_examples_for 'foreign key checks' do
it 'finds existing foreign keys by column' do
- expect(model.foreign_key_exists?(:projects, target_table, column: :non_standard_id)).to be_truthy
+ expect(model.foreign_key_exists?(:referencing, target_table, column: :non_standard_id)).to be_truthy
end
it 'finds existing foreign keys by name' do
- expect(model.foreign_key_exists?(:projects, target_table, name: :fk_projects_users_non_standard_id)).to be_truthy
+ expect(model.foreign_key_exists?(:referencing, target_table, name: :fk_referenced)).to be_truthy
end
it 'finds existing foreign_keys by name and column' do
- expect(model.foreign_key_exists?(:projects, target_table, name: :fk_projects_users_non_standard_id, column: :non_standard_id)).to be_truthy
+ expect(model.foreign_key_exists?(:referencing, target_table, name: :fk_referenced, column: :non_standard_id)).to be_truthy
end
it 'finds existing foreign_keys by name, column and on_delete' do
- expect(model.foreign_key_exists?(:projects, target_table, name: :fk_projects_users_non_standard_id, column: :non_standard_id, on_delete: :cascade)).to be_truthy
+ expect(model.foreign_key_exists?(:referencing, target_table, name: :fk_referenced, column: :non_standard_id, on_delete: :cascade)).to be_truthy
end
it 'finds existing foreign keys by target table only' do
- expect(model.foreign_key_exists?(:projects, target_table)).to be_truthy
+ expect(model.foreign_key_exists?(:referencing, target_table)).to be_truthy
end
it 'compares by column name if given' do
- expect(model.foreign_key_exists?(:projects, target_table, column: :user_id)).to be_falsey
+ expect(model.foreign_key_exists?(:referencing, target_table, column: :user_id)).to be_falsey
end
it 'compares by target column name if given' do
- expect(model.foreign_key_exists?(:projects, target_table, primary_key: :user_id)).to be_falsey
- expect(model.foreign_key_exists?(:projects, target_table, primary_key: :id)).to be_truthy
+ expect(model.foreign_key_exists?(:referencing, target_table, primary_key: :user_id)).to be_falsey
+ expect(model.foreign_key_exists?(:referencing, target_table, primary_key: :id)).to be_truthy
end
it 'compares by foreign key name if given' do
- expect(model.foreign_key_exists?(:projects, target_table, name: :non_existent_foreign_key_name)).to be_falsey
+ expect(model.foreign_key_exists?(:referencing, target_table, name: :non_existent_foreign_key_name)).to be_falsey
end
it 'compares by foreign key name and column if given' do
- expect(model.foreign_key_exists?(:projects, target_table, name: :non_existent_foreign_key_name, column: :non_standard_id)).to be_falsey
+ expect(model.foreign_key_exists?(:referencing, target_table, name: :non_existent_foreign_key_name, column: :non_standard_id)).to be_falsey
end
it 'compares by foreign key name, column and on_delete if given' do
- expect(model.foreign_key_exists?(:projects, target_table, name: :fk_projects_users_non_standard_id, column: :non_standard_id, on_delete: :nullify)).to be_falsey
+ expect(model.foreign_key_exists?(:referencing, target_table, name: :fk_referenced, column: :non_standard_id, on_delete: :nullify)).to be_falsey
end
end
@@ -1035,7 +1035,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
end
context 'specifying a target table' do
- let(:target_table) { :users }
+ let(:target_table) { :referenced }
it_behaves_like 'foreign key checks'
end
@@ -1044,59 +1044,66 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
expect(model.foreign_key_exists?(:projects, :other_table)).to be_falsey
end
+ it 'raises an error if an invalid on_delete is specified' do
+ # The correct on_delete key is "nullify"
+ expect { model.foreign_key_exists?(:referenced, on_delete: :set_null) }.to raise_error(ArgumentError)
+ end
+
context 'with foreign key using multiple columns' do
before do
- key = ActiveRecord::ConnectionAdapters::ForeignKeyDefinition.new(
- :projects, :users,
- {
- column: [:partition_number, :id],
- name: :fk_projects_users_partition_number_id,
- on_delete: :cascade,
- primary_key: [:partition_number, :id]
- }
- )
- allow(model).to receive(:foreign_keys).with(:projects).and_return([key])
+ model.connection.execute(<<~SQL)
+ create table p_referenced (
+ id bigserial not null,
+ partition_number bigint not null default 100,
+ primary key (partition_number, id)
+ );
+ create table p_referencing (
+ id bigserial primary key not null,
+ partition_number bigint not null,
+ constraint fk_partitioning foreign key (partition_number, id) references p_referenced(partition_number, id) on delete cascade
+ );
+ SQL
end
it 'finds existing foreign keys by columns' do
- expect(model.foreign_key_exists?(:projects, :users, column: [:partition_number, :id])).to be_truthy
+ expect(model.foreign_key_exists?(:p_referencing, :p_referenced, column: [:partition_number, :id])).to be_truthy
end
it 'finds existing foreign keys by name' do
- expect(model.foreign_key_exists?(:projects, :users, name: :fk_projects_users_partition_number_id)).to be_truthy
+ expect(model.foreign_key_exists?(:p_referencing, :p_referenced, name: :fk_partitioning)).to be_truthy
end
it 'finds existing foreign_keys by name and column' do
- expect(model.foreign_key_exists?(:projects, :users, name: :fk_projects_users_partition_number_id, column: [:partition_number, :id])).to be_truthy
+ expect(model.foreign_key_exists?(:p_referencing, :p_referenced, name: :fk_partitioning, column: [:partition_number, :id])).to be_truthy
end
it 'finds existing foreign_keys by name, column and on_delete' do
- expect(model.foreign_key_exists?(:projects, :users, name: :fk_projects_users_partition_number_id, column: [:partition_number, :id], on_delete: :cascade)).to be_truthy
+ expect(model.foreign_key_exists?(:p_referencing, :p_referenced, name: :fk_partitioning, column: [:partition_number, :id], on_delete: :cascade)).to be_truthy
end
it 'finds existing foreign keys by target table only' do
- expect(model.foreign_key_exists?(:projects, :users)).to be_truthy
+ expect(model.foreign_key_exists?(:p_referencing, :p_referenced)).to be_truthy
end
it 'compares by column name if given' do
- expect(model.foreign_key_exists?(:projects, :users, column: :id)).to be_falsey
+ expect(model.foreign_key_exists?(:p_referencing, :p_referenced, column: :id)).to be_falsey
end
it 'compares by target column name if given' do
- expect(model.foreign_key_exists?(:projects, :users, primary_key: :user_id)).to be_falsey
- expect(model.foreign_key_exists?(:projects, :users, primary_key: [:partition_number, :id])).to be_truthy
+ expect(model.foreign_key_exists?(:p_referencing, :p_referenced, primary_key: :user_id)).to be_falsey
+ expect(model.foreign_key_exists?(:p_referencing, :p_referenced, primary_key: [:partition_number, :id])).to be_truthy
end
it 'compares by foreign key name if given' do
- expect(model.foreign_key_exists?(:projects, :users, name: :non_existent_foreign_key_name)).to be_falsey
+ expect(model.foreign_key_exists?(:p_referencing, :p_referenced, name: :non_existent_foreign_key_name)).to be_falsey
end
it 'compares by foreign key name and column if given' do
- expect(model.foreign_key_exists?(:projects, :users, name: :non_existent_foreign_key_name, column: [:partition_number, :id])).to be_falsey
+ expect(model.foreign_key_exists?(:p_referencing, :p_referenced, name: :non_existent_foreign_key_name, column: [:partition_number, :id])).to be_falsey
end
it 'compares by foreign key name, column and on_delete if given' do
- expect(model.foreign_key_exists?(:projects, :users, name: :fk_projects_users_partition_number_id, column: [:partition_number, :id], on_delete: :nullify)).to be_falsey
+ expect(model.foreign_key_exists?(:p_referencing, :p_referenced, name: :fk_partitioning, column: [:partition_number, :id], on_delete: :nullify)).to be_falsey
end
end
end
diff --git a/spec/lib/gitlab/database/postgres_foreign_key_spec.rb b/spec/lib/gitlab/database/postgres_foreign_key_spec.rb
index 16180da3e3a..a8dbc4be16f 100644
--- a/spec/lib/gitlab/database/postgres_foreign_key_spec.rb
+++ b/spec/lib/gitlab/database/postgres_foreign_key_spec.rb
@@ -43,6 +43,14 @@ RSpec.describe Gitlab::Database::PostgresForeignKey, type: :model, feature_categ
end
end
+ describe '#by_referenced_table_name' do
+ it 'finds the foreign keys for the referenced table' do
+ expected = described_class.find_by!(name: 'fk_constrained_to_referenced')
+
+ expect(described_class.by_referenced_table_name('referenced_table')).to contain_exactly(expected)
+ end
+ end
+
describe '#by_constrained_table_identifier' do
it 'throws an error when the identifier name is not fully qualified' do
expect { described_class.by_constrained_table_identifier('constrained_table') }.to raise_error(ArgumentError, /not fully qualified/)
@@ -55,9 +63,25 @@ RSpec.describe Gitlab::Database::PostgresForeignKey, type: :model, feature_categ
end
end
+ describe '#by_constrained_table_name' do
+ it 'finds the foreign keys for the constrained table' do
+ expected = described_class.where(name: %w[fk_constrained_to_referenced fk_constrained_to_other_referenced]).to_a
+
+ expect(described_class.by_constrained_table_name('constrained_table')).to match_array(expected)
+ end
+ end
+
+ describe '#by_name' do
+ it 'finds foreign keys by name' do
+ expect(described_class.by_name('fk_constrained_to_referenced').pluck(:name)).to contain_exactly('fk_constrained_to_referenced')
+ end
+ end
+
context 'when finding columns for foreign keys' do
using RSpec::Parameterized::TableSyntax
+ let(:fks) { described_class.by_constrained_table_name('constrained_table') }
+
where(:fk, :expected_constrained, :expected_referenced) do
lazy { described_class.find_by(name: 'fk_constrained_to_referenced') } | %w[referenced_table_id referenced_table_id_b] | %w[id id_b]
lazy { described_class.find_by(name: 'fk_constrained_to_other_referenced') } | %w[other_referenced_table_id] | %w[id]
@@ -71,23 +95,70 @@ RSpec.describe Gitlab::Database::PostgresForeignKey, type: :model, feature_categ
it 'finds the correct referenced column names' do
expect(fk.referenced_columns).to eq(expected_referenced)
end
+
+ describe '#by_constrained_columns' do
+ it 'finds the correct foreign key' do
+ expect(fks.by_constrained_columns(expected_constrained)).to contain_exactly(fk)
+ end
+ end
+
+ describe '#by_referenced_columns' do
+ it 'finds the correct foreign key' do
+ expect(fks.by_referenced_columns(expected_referenced)).to contain_exactly(fk)
+ end
+ end
end
end
describe '#on_delete_action' do
+ before do
+ ApplicationRecord.connection.execute(<<~SQL)
+ create table public.referenced_table_all_on_delete_actions (
+ id bigserial primary key not null
+ );
+
+ create table public.constrained_table_all_on_delete_actions (
+ id bigserial primary key not null,
+ ref_id_no_action bigint not null constraint fk_no_action references referenced_table_all_on_delete_actions(id),
+ ref_id_restrict bigint not null constraint fk_restrict references referenced_table_all_on_delete_actions(id) on delete restrict,
+ ref_id_nullify bigint not null constraint fk_nullify references referenced_table_all_on_delete_actions(id) on delete set null,
+ ref_id_cascade bigint not null constraint fk_cascade references referenced_table_all_on_delete_actions(id) on delete cascade,
+ ref_id_set_default bigint not null constraint fk_set_default references referenced_table_all_on_delete_actions(id) on delete set default
+ )
+ SQL
+ end
+
+ let(:fks) { described_class.by_constrained_table_name('constrained_table_all_on_delete_actions') }
+
+ context 'with an invalid on_delete_action' do
+ it 'raises an error' do
+ # the correct value is :nullify, not :set_null
+ expect { fks.by_on_delete_action(:set_null) }.to raise_error(ArgumentError)
+ end
+ end
+
where(:fk_name, :expected_on_delete_action) do
[
- %w[fk_constrained_to_referenced restrict],
- %w[fk_constrained_to_other_referenced no_action]
+ %w[fk_no_action no_action],
+ %w[fk_restrict restrict],
+ %w[fk_nullify nullify],
+ %w[fk_cascade cascade],
+ %w[fk_set_default set_default]
]
end
with_them do
- subject(:fk) { described_class.find_by(name: fk_name) }
+ subject(:fk) { fks.find_by(name: fk_name) }
it 'has the appropriate on delete action' do
expect(fk.on_delete_action).to eq(expected_on_delete_action)
end
+
+ describe '#by_on_delete_action' do
+ it 'finds the key by on delete action' do
+ expect(fks.by_on_delete_action(expected_on_delete_action)).to contain_exactly(fk)
+ end
+ end
end
end
diff --git a/spec/migrations/queue_backfill_admin_mode_scope_for_personal_access_tokens_spec.rb b/spec/migrations/queue_backfill_admin_mode_scope_for_personal_access_tokens_spec.rb
new file mode 100644
index 00000000000..8209f317550
--- /dev/null
+++ b/spec/migrations/queue_backfill_admin_mode_scope_for_personal_access_tokens_spec.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require_migration!
+
+RSpec.describe QueueBackfillAdminModeScopeForPersonalAccessTokens,
+ feature_category: :authentication_and_authorization do
+ describe '#up' do
+ it 'schedules background migration' do
+ migrate!
+
+ expect(described_class::MIGRATION).to have_scheduled_batched_migration(
+ table_name: :personal_access_tokens,
+ column_name: :id,
+ interval: described_class::DELAY_INTERVAL)
+ end
+ end
+end
diff --git a/spec/models/abuse_report_spec.rb b/spec/models/abuse_report_spec.rb
index 95e61488c80..29baccfe0ee 100644
--- a/spec/models/abuse_report_spec.rb
+++ b/spec/models/abuse_report_spec.rb
@@ -20,6 +20,11 @@ RSpec.describe AbuseReport, feature_category: :insider_threat do
end
describe 'validations' do
+ let(:http) { 'http://gitlab.com' }
+ let(:https) { 'https://gitlab.com' }
+ let(:ftp) { 'ftp://example.com' }
+ let(:javascript) { 'javascript:alert(window.opener.document.location)' }
+
it { is_expected.to validate_presence_of(:reporter) }
it { is_expected.to validate_presence_of(:user) }
it { is_expected.to validate_presence_of(:message) }
@@ -28,8 +33,16 @@ RSpec.describe AbuseReport, feature_category: :insider_threat do
it do
is_expected.to validate_uniqueness_of(:user_id)
.scoped_to(:reporter_id)
- .with_message('You have already reported this user')
+ .with_message('has already been reported for abuse')
end
+
+ it { is_expected.to validate_length_of(:reported_from_url).is_at_most(512).allow_blank }
+ it { is_expected.to allow_value(http).for(:reported_from_url) }
+ it { is_expected.to allow_value(https).for(:reported_from_url) }
+ it { is_expected.not_to allow_value(ftp).for(:reported_from_url) }
+ it { is_expected.not_to allow_value(javascript).for(:reported_from_url) }
+ it { is_expected.to allow_value('http://localhost:9000').for(:reported_from_url) }
+ it { is_expected.to allow_value('https://gitlab.com').for(:reported_from_url) }
end
describe '#remove_user' do
diff --git a/spec/models/integrations/apple_app_store_spec.rb b/spec/models/integrations/apple_app_store_spec.rb
index dde26e383c7..1a57f556895 100644
--- a/spec/models/integrations/apple_app_store_spec.rb
+++ b/spec/models/integrations/apple_app_store_spec.rb
@@ -29,7 +29,7 @@ RSpec.describe Integrations::AppleAppStore, feature_category: :mobile_devops do
describe '#fields' do
it 'returns custom fields' do
expect(apple_app_store_integration.fields.pluck(:name)).to eq(%w[app_store_issuer_id app_store_key_id
- app_store_private_key])
+ app_store_private_key])
end
end
diff --git a/spec/models/personal_access_token_spec.rb b/spec/models/personal_access_token_spec.rb
index 9d4c53f8d55..f65b5ff824b 100644
--- a/spec/models/personal_access_token_spec.rb
+++ b/spec/models/personal_access_token_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe PersonalAccessToken do
+RSpec.describe PersonalAccessToken, feature_category: :authentication_and_authorization do
subject { described_class }
describe '.build' do
@@ -210,6 +210,12 @@ RSpec.describe PersonalAccessToken do
expect(personal_access_token).to be_valid
end
+ it "allows creating a token with `admin_mode` scope" do
+ personal_access_token.scopes = [:api, :admin_mode]
+
+ expect(personal_access_token).to be_valid
+ end
+
context 'when registry is disabled' do
before do
stub_container_registry_config(enabled: false)
@@ -340,4 +346,27 @@ RSpec.describe PersonalAccessToken do
end
end
end
+
+ # During the implementation of Admin Mode for API, tokens of
+ # administrators should automatically get the `admin_mode` scope as well
+ # See https://gitlab.com/gitlab-org/gitlab/-/issues/42692
+ describe '`admin_mode scope' do
+ subject { create(:personal_access_token, user: user, scopes: ['api']) }
+
+ context 'with administrator user' do
+ let_it_be(:user) { create(:user, :admin) }
+
+ it 'adds `admin_mode` scope before created' do
+ expect(subject.scopes).to contain_exactly('api', 'admin_mode')
+ end
+ end
+
+ context 'with normal user' do
+ let_it_be(:user) { create(:user) }
+
+ it 'does not add `admin_mode` scope before created' do
+ expect(subject.scopes).to contain_exactly('api')
+ end
+ end
+ end
end
diff --git a/spec/requests/abuse_reports_controller_spec.rb b/spec/requests/abuse_reports_controller_spec.rb
index 71ecf8444bf..49a80689c65 100644
--- a/spec/requests/abuse_reports_controller_spec.rb
+++ b/spec/requests/abuse_reports_controller_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe AbuseReportsController, feature_category: :users do
+RSpec.describe AbuseReportsController, feature_category: :insider_threat do
let(:reporter) { create(:user) }
let(:user) { create(:user) }
let(:attrs) do
@@ -16,6 +16,18 @@ RSpec.describe AbuseReportsController, feature_category: :users do
end
describe 'GET new' do
+ let(:ref_url) { 'http://example.com' }
+
+ it 'sets the instance variables' do
+ get new_abuse_report_path(user_id: user.id, ref_url: ref_url)
+
+ expect(assigns(:abuse_report)).to be_kind_of(AbuseReport)
+ expect(assigns(:abuse_report)).to have_attributes(
+ user_id: user.id,
+ reported_from_url: ref_url
+ )
+ end
+
context 'when the user has already been deleted' do
it 'redirects the reporter to root_path' do
user_id = user.id
@@ -47,7 +59,9 @@ RSpec.describe AbuseReportsController, feature_category: :users do
context 'when user is reported for abuse' do
let(:ref_url) { 'http://example.com' }
- let(:request_params) { { user_id: user.id, abuse_report: { category: abuse_category }, ref_url: ref_url } }
+ let(:request_params) do
+ { user_id: user.id, abuse_report: { category: abuse_category, reported_from_url: ref_url } }
+ end
it 'renders new template' do
subject
@@ -62,9 +76,9 @@ RSpec.describe AbuseReportsController, feature_category: :users do
expect(assigns(:abuse_report)).to be_kind_of(AbuseReport)
expect(assigns(:abuse_report)).to have_attributes(
user_id: user.id,
- category: abuse_category
+ category: abuse_category,
+ reported_from_url: ref_url
)
- expect(assigns(:ref_url)).to eq(ref_url)
end
end
diff --git a/spec/requests/api/internal/base_spec.rb b/spec/requests/api/internal/base_spec.rb
index 35909906e45..767f3e8b5b5 100644
--- a/spec/requests/api/internal/base_spec.rb
+++ b/spec/requests/api/internal/base_spec.rb
@@ -453,16 +453,10 @@ RSpec.describe API::Internal::Base, feature_category: :authentication_and_author
expect(json_response['message']['error']).to eq('This endpoint has been requested too many times. Try again later.')
end
- context 'when rate_limit_gitlab_shell feature flag is disabled' do
- before do
- stub_feature_flags(rate_limit_gitlab_shell: false)
- end
-
- it 'is not throttled by rate limiter' do
- expect(::Gitlab::ApplicationRateLimiter).not_to receive(:throttled?)
+ it 'is not throttled by rate limiter' do
+ expect(::Gitlab::ApplicationRateLimiter).not_to receive(:throttled?)
- subject
- end
+ subject
end
context 'when the IP is in a trusted range' do
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index 6e8168c0ee1..4f7fad0bc69 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -1169,7 +1169,7 @@ RSpec.describe API::Projects do
expect(response).to have_gitlab_http_status(:bad_request)
end
- it "assigns attributes to project", :aggregate_failures do
+ it 'assigns attributes to project', :aggregate_failures do
project = attributes_for(:project, {
path: 'camelCasePath',
issues_enabled: false,
@@ -1198,6 +1198,11 @@ RSpec.describe API::Projects do
attrs[:feature_flags_access_level] = 'disabled'
attrs[:infrastructure_access_level] = 'disabled'
attrs[:monitor_access_level] = 'disabled'
+ attrs[:snippets_access_level] = 'disabled'
+ attrs[:wiki_access_level] = 'disabled'
+ attrs[:builds_access_level] = 'disabled'
+ attrs[:merge_requests_access_level] = 'disabled'
+ attrs[:issues_access_level] = 'disabled'
end
post api('/projects', user), params: project
@@ -1228,6 +1233,11 @@ RSpec.describe API::Projects do
expect(project.project_feature.feature_flags_access_level).to eq(ProjectFeature::DISABLED)
expect(project.project_feature.infrastructure_access_level).to eq(ProjectFeature::DISABLED)
expect(project.project_feature.monitor_access_level).to eq(ProjectFeature::DISABLED)
+ expect(project.project_feature.wiki_access_level).to eq(ProjectFeature::DISABLED)
+ expect(project.project_feature.builds_access_level).to eq(ProjectFeature::DISABLED)
+ expect(project.project_feature.merge_requests_access_level).to eq(ProjectFeature::DISABLED)
+ expect(project.project_feature.issues_access_level).to eq(ProjectFeature::DISABLED)
+ expect(project.project_feature.snippets_access_level).to eq(ProjectFeature::DISABLED)
end
it 'assigns container_registry_enabled to project', :aggregate_failures do
diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb
index 393ada1da4f..555ba2bc978 100644
--- a/spec/requests/api/repositories_spec.rb
+++ b/spec/requests/api/repositories_spec.rb
@@ -353,15 +353,9 @@ RSpec.describe API::Repositories, feature_category: :source_code_management do
expect(response).to have_gitlab_http_status(:too_many_requests)
end
- context "when hotlinking detection is enabled" do
- before do
- stub_feature_flags(repository_archive_hotlinking_interception: true)
- end
-
- it_behaves_like "hotlink interceptor" do
- let(:http_request) do
- get api(route, current_user), headers: headers
- end
+ it_behaves_like "hotlink interceptor" do
+ let(:http_request) do
+ get api(route, current_user), headers: headers
end
end
end
diff --git a/spec/support/shared_examples/features/reportable_note_shared_examples.rb b/spec/support/shared_examples/features/reportable_note_shared_examples.rb
index c35f711111b..9d859403465 100644
--- a/spec/support/shared_examples/features/reportable_note_shared_examples.rb
+++ b/spec/support/shared_examples/features/reportable_note_shared_examples.rb
@@ -36,7 +36,7 @@ RSpec.shared_examples 'reportable note' do |type|
dropdown.click_link('Report abuse to administrator')
expect(find('#user_name')['value']).to match(note.author.username)
- expect(find('#abuse_report_message')['value']).to match(noteable_note_url(note))
+ expect(find('#abuse_report_reported_from_url')['value']).to match(noteable_note_url(note))
end
def open_dropdown(dropdown)
diff --git a/workhorse/go.mod b/workhorse/go.mod
index b72e7e9ba8e..77c06a622a7 100644
--- a/workhorse/go.mod
+++ b/workhorse/go.mod
@@ -17,7 +17,7 @@ require (
github.com/gorilla/websocket v1.5.0
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0
- github.com/johannesboyne/gofakes3 v0.0.0-20230104192229-1065b17a924f
+ github.com/johannesboyne/gofakes3 v0.0.0-20230108161031-df26ca44a1e9
github.com/jpillora/backoff v1.0.0
github.com/mitchellh/copystructure v1.2.0
github.com/prometheus/client_golang v1.14.0
diff --git a/workhorse/go.sum b/workhorse/go.sum
index eed04007d1b..d75e6e01992 100644
--- a/workhorse/go.sum
+++ b/workhorse/go.sum
@@ -981,8 +981,8 @@ github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHW
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/joefitzgerald/rainbow-reporter v0.1.0/go.mod h1:481CNgqmVHQZzdIbN52CupLJyoVwB10FQ/IQlF1pdL8=
-github.com/johannesboyne/gofakes3 v0.0.0-20230104192229-1065b17a924f h1:m/Y0+A6QxQ00DPR0XmpOzoCmBOWO9J4XqBGiHonbMj8=
-github.com/johannesboyne/gofakes3 v0.0.0-20230104192229-1065b17a924f/go.mod h1:Cnosl0cRZIfKjTMuH49sQog2LeNsU5Hf4WnPIDWIDV0=
+github.com/johannesboyne/gofakes3 v0.0.0-20230108161031-df26ca44a1e9 h1:PqhUbDge60cL99naOP9m3W0MiQtWc5kwteQQ9oU36PA=
+github.com/johannesboyne/gofakes3 v0.0.0-20230108161031-df26ca44a1e9/go.mod h1:Cnosl0cRZIfKjTMuH49sQog2LeNsU5Hf4WnPIDWIDV0=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=