summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab/issue_templates/Navigation - Left Sidebar Proposals.md2
-rw-r--r--GITLAB_PAGES_VERSION2
-rw-r--r--app/assets/javascripts/content_editor/components/bubble_menus/formatting.vue2
-rw-r--r--app/assets/javascripts/content_editor/extensions/reference_definition.js29
-rw-r--r--app/assets/javascripts/content_editor/extensions/sourcemap.js2
-rw-r--r--app/assets/javascripts/content_editor/services/create_content_editor.js2
-rw-r--r--app/assets/javascripts/content_editor/services/markdown_serializer.js20
-rw-r--r--app/assets/javascripts/content_editor/services/remark_markdown_deserializer.js12
-rw-r--r--app/assets/javascripts/content_editor/services/serialization_helpers.js17
-rw-r--r--app/assets/javascripts/lib/gfm/index.js10
-rw-r--r--app/assets/javascripts/pages/projects/pipeline_schedules/index/index.js41
-rw-r--r--app/assets/javascripts/pipeline_schedules/components/take_ownership_modal.vue52
-rw-r--r--app/events/projects/project_transfered_event.rb25
-rw-r--r--app/helpers/commits_helper.rb7
-rw-r--r--app/models/blob.rb4
-rw-r--r--app/services/projects/transfer_service.rb14
-rw-r--r--app/services/protected_branches/base_service.rb4
-rw-r--r--app/services/protected_branches/cache_service.rb47
-rw-r--r--app/services/protected_branches/create_service.rb2
-rw-r--r--app/services/protected_branches/destroy_service.rb2
-rw-r--r--app/services/protected_branches/update_service.rb2
-rw-r--r--app/views/admin/application_settings/_runner_registrars_form.html.haml2
-rw-r--r--app/views/admin/application_settings/_usage.html.haml4
-rw-r--r--app/views/layouts/group.html.haml2
-rw-r--r--app/views/layouts/profile.html.haml1
-rw-r--r--app/views/layouts/project.html.haml2
-rw-r--r--app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml2
-rw-r--r--app/views/projects/pipeline_schedules/index.html.haml2
-rw-r--r--config/metrics/aggregates/common.yml10
-rw-r--r--config/webpack.vendor.config.js1
-rw-r--r--doc/administration/audit_events.md2
-rw-r--r--doc/administration/auth/ldap/ldap_synchronization.md2
-rw-r--r--doc/administration/compliance.md2
-rw-r--r--doc/api/groups.md8
-rw-r--r--doc/integration/saml.md2
-rw-r--r--doc/security/two_factor_authentication.md2
-rw-r--r--doc/subscriptions/bronze_starter.md10
-rw-r--r--doc/user/admin_area/settings/usage_statistics.md2
-rw-r--r--doc/user/group/access_and_permissions.md244
-rw-r--r--doc/user/group/index.md245
-rw-r--r--doc/user/permissions.md6
-rw-r--r--doc/user/project/members/index.md4
-rw-r--r--doc/user/project/members/share_project_with_groups.md2
-rw-r--r--doc/user/project/repository/push_rules.md2
-rw-r--r--glfm_specification/example_snapshots/html.yml93
-rw-r--r--glfm_specification/example_snapshots/prosemirror_json.yml1187
-rw-r--r--lib/api/helpers.rb4
-rw-r--r--lib/gitlab/diff/file.rb8
-rw-r--r--lib/gitlab/memory/reports_daemon.rb23
-rw-r--r--lib/gitlab/usage_data_counters/hll_redis_counter.rb6
-rw-r--r--lib/gitlab/usage_data_counters/known_events/common.yml20
-rw-r--r--lib/gitlab/utils/batch_loader.rb23
-rw-r--r--locale/gitlab.pot18
-rw-r--r--package.json1
-rw-r--r--spec/controllers/projects/commit_controller_spec.rb16
-rw-r--r--spec/controllers/projects/compare_controller_spec.rb19
-rw-r--r--spec/events/projects/project_transfered_event_spec.rb46
-rw-r--r--spec/features/projects/pipeline_schedules_spec.rb7
-rw-r--r--spec/frontend/content_editor/remark_markdown_processing_spec.js30
-rw-r--r--spec/frontend/content_editor/render_html_and_json_for_all_examples.js2
-rw-r--r--spec/frontend/content_editor/services/markdown_serializer_spec.js36
-rw-r--r--spec/frontend/lib/gfm/index_spec.js64
-rw-r--r--spec/frontend/pipeline_schedules/components/take_ownership_modal_spec.js54
-rw-r--r--spec/frontend_integration/content_editor/content_editor_integration_spec.js49
-rw-r--r--spec/helpers/commits_helper_spec.rb18
-rw-r--r--spec/lib/gitlab/auth/o_auth/user_spec.rb1
-rw-r--r--spec/lib/gitlab/memory/reports_daemon_spec.rb1
-rw-r--r--spec/lib/gitlab/usage_data_counters/hll_redis_counter_spec.rb25
-rw-r--r--spec/lib/gitlab/utils/batch_loader_spec.rb82
-rw-r--r--spec/requests/api/ci/runner/jobs_artifacts_spec.rb6
-rw-r--r--spec/services/projects/transfer_service_spec.rb27
-rw-r--r--spec/services/protected_branches/cache_service_spec.rb81
-rw-r--r--spec/services/protected_branches/create_service_spec.rb8
-rw-r--r--spec/services/protected_branches/destroy_service_spec.rb8
-rw-r--r--spec/services/protected_branches/update_service_spec.rb8
-rw-r--r--spec/views/projects/pipeline_schedules/_pipeline_schedule.html.haml_spec.rb4
-rw-r--r--yarn.lock60
77 files changed, 2435 insertions, 457 deletions
diff --git a/.gitlab/issue_templates/Navigation - Left Sidebar Proposals.md b/.gitlab/issue_templates/Navigation - Left Sidebar Proposals.md
index e9e510da11e..3939fca44f1 100644
--- a/.gitlab/issue_templates/Navigation - Left Sidebar Proposals.md
+++ b/.gitlab/issue_templates/Navigation - Left Sidebar Proposals.md
@@ -6,7 +6,7 @@
### Checklist
-- [ ] If your proposal includes changes to the top-level menu items within the left sidebar, engage the [Foundations Product Design Manager](https://about.gitlab.com/handbook/product/categories/#foundations-group) for approval. The Foundations DRI will work with UX partners in product design, research, and technical writing, as applicable.
+- [ ] If your proposal includes changes to the menu items within the left sidebar, engage the [Foundations Product Manager](https://about.gitlab.com/handbook/product/categories/#foundations-group) for approval. The Foundations DRI will work with UX partners in product design, research, and technical writing, as applicable.
- [ ] Follow the [product development workflow](https://about.gitlab.com/handbook/product-development-flow/#validation-phase-2-problem-validation) validation process to ensure you are solving a well understood problem and that the proposed change is understandable and non-disruptive to users. Navigation-specific research is strongly encouraged.
- [ ] Engage the [Foundations](https://about.gitlab.com/handbook/product/categories/#foundations-group) team to ensure your proposal is in alignment with holistic changes happening to the left side bar.
- [ ] Consider whether you need to communicate the change somehow, or if you will have an interim period in the UI where your nav item will live in more than one place.
diff --git a/GITLAB_PAGES_VERSION b/GITLAB_PAGES_VERSION
index 30e298c7494..76d05362056 100644
--- a/GITLAB_PAGES_VERSION
+++ b/GITLAB_PAGES_VERSION
@@ -1 +1 @@
-1.61.1
+1.62.0
diff --git a/app/assets/javascripts/content_editor/components/bubble_menus/formatting.vue b/app/assets/javascripts/content_editor/components/bubble_menus/formatting.vue
index f0726ff3e63..8caf12eac5f 100644
--- a/app/assets/javascripts/content_editor/components/bubble_menus/formatting.vue
+++ b/app/assets/javascripts/content_editor/components/bubble_menus/formatting.vue
@@ -10,6 +10,7 @@ import Code from '../../extensions/code';
import CodeBlockHighlight from '../../extensions/code_block_highlight';
import Diagram from '../../extensions/diagram';
import Frontmatter from '../../extensions/frontmatter';
+import ReferenceDefinition from '../../extensions/reference_definition';
import ToolbarButton from '../toolbar_button.vue';
export default {
@@ -35,6 +36,7 @@ export default {
Image.name,
Audio.name,
Video.name,
+ ReferenceDefinition.name,
];
return !exclude.some((type) => editor.isActive(type));
diff --git a/app/assets/javascripts/content_editor/extensions/reference_definition.js b/app/assets/javascripts/content_editor/extensions/reference_definition.js
new file mode 100644
index 00000000000..e2762fe9fd9
--- /dev/null
+++ b/app/assets/javascripts/content_editor/extensions/reference_definition.js
@@ -0,0 +1,29 @@
+import { Node } from '@tiptap/core';
+
+export default Node.create({
+ name: 'referenceDefinition',
+
+ group: 'block',
+
+ content: 'text*',
+
+ marks: '',
+
+ addAttributes() {
+ return {
+ identifier: {
+ default: null,
+ },
+ url: {
+ default: null,
+ },
+ title: {
+ default: null,
+ },
+ };
+ },
+
+ renderHTML() {
+ return ['pre', {}, 0];
+ },
+});
diff --git a/app/assets/javascripts/content_editor/extensions/sourcemap.js b/app/assets/javascripts/content_editor/extensions/sourcemap.js
index 618f17b1c5e..fdac852e55c 100644
--- a/app/assets/javascripts/content_editor/extensions/sourcemap.js
+++ b/app/assets/javascripts/content_editor/extensions/sourcemap.js
@@ -16,6 +16,7 @@ import Link from './link';
import ListItem from './list_item';
import OrderedList from './ordered_list';
import Paragraph from './paragraph';
+import ReferenceDefinition from './reference_definition';
import Strike from './strike';
import TaskList from './task_list';
import TaskItem from './task_item';
@@ -45,6 +46,7 @@ export default Extension.create({
ListItem.name,
OrderedList.name,
Paragraph.name,
+ ReferenceDefinition.name,
Strike.name,
TaskList.name,
TaskItem.name,
diff --git a/app/assets/javascripts/content_editor/services/create_content_editor.js b/app/assets/javascripts/content_editor/services/create_content_editor.js
index 795355abdab..7a289df94ea 100644
--- a/app/assets/javascripts/content_editor/services/create_content_editor.js
+++ b/app/assets/javascripts/content_editor/services/create_content_editor.js
@@ -42,6 +42,7 @@ import OrderedList from '../extensions/ordered_list';
import Paragraph from '../extensions/paragraph';
import PasteMarkdown from '../extensions/paste_markdown';
import Reference from '../extensions/reference';
+import ReferenceDefinition from '../extensions/reference_definition';
import Sourcemap from '../extensions/sourcemap';
import Strike from '../extensions/strike';
import Subscript from '../extensions/subscript';
@@ -128,6 +129,7 @@ export const createContentEditor = ({
Paragraph,
PasteMarkdown,
Reference,
+ ReferenceDefinition,
Sourcemap,
Strike,
Subscript,
diff --git a/app/assets/javascripts/content_editor/services/markdown_serializer.js b/app/assets/javascripts/content_editor/services/markdown_serializer.js
index c1c7af6b1af..1511f122a15 100644
--- a/app/assets/javascripts/content_editor/services/markdown_serializer.js
+++ b/app/assets/javascripts/content_editor/services/markdown_serializer.js
@@ -33,6 +33,7 @@ import MathInline from '../extensions/math_inline';
import OrderedList from '../extensions/ordered_list';
import Paragraph from '../extensions/paragraph';
import Reference from '../extensions/reference';
+import ReferenceDefinition from '../extensions/reference_definition';
import Strike from '../extensions/strike';
import Subscript from '../extensions/subscript';
import Superscript from '../extensions/superscript';
@@ -177,6 +178,25 @@ const defaultSerializerConfig = {
[Reference.name]: (state, node) => {
state.write(node.attrs.originalText || node.attrs.text);
},
+ [ReferenceDefinition.name]: preserveUnchanged({
+ render: (state, node, parent, index, same, sourceMarkdown) => {
+ const nextSibling = parent.maybeChild(index + 1);
+
+ state.text(same ? sourceMarkdown : node.textContent, false);
+
+ /**
+ * Do not insert a blank line between reference definitions
+ * because it isn’t necessary and a more compact text format
+ * is preferred.
+ */
+ if (!nextSibling || nextSibling.type.name !== ReferenceDefinition.name) {
+ state.closeBlock(node);
+ } else {
+ state.ensureNewLine();
+ }
+ },
+ overwriteSourcePreservationStrategy: true,
+ }),
[TableOfContents.name]: (state, node) => {
state.write('[[_TOC_]]');
state.closeBlock(node);
diff --git a/app/assets/javascripts/content_editor/services/remark_markdown_deserializer.js b/app/assets/javascripts/content_editor/services/remark_markdown_deserializer.js
index 8e2c066e011..e38be672063 100644
--- a/app/assets/javascripts/content_editor/services/remark_markdown_deserializer.js
+++ b/app/assets/javascripts/content_editor/services/remark_markdown_deserializer.js
@@ -170,6 +170,16 @@ const factorySpecs = {
type: 'ignore',
selector: (hastNode) => hastNode.type === 'comment',
},
+
+ referenceDefinition: {
+ type: 'block',
+ selector: 'referencedefinition',
+ getAttrs: (hastNode) => ({
+ title: hastNode.properties.title,
+ url: hastNode.properties.url,
+ identifier: hastNode.properties.identifier,
+ }),
+ },
};
export default () => {
@@ -185,7 +195,7 @@ export default () => {
wrappableTags,
markdown,
}),
- skipRendering: ['footnoteReference', 'footnoteDefinition', 'code'],
+ skipRendering: ['footnoteReference', 'footnoteDefinition', 'code', 'definition'],
});
return { document };
diff --git a/app/assets/javascripts/content_editor/services/serialization_helpers.js b/app/assets/javascripts/content_editor/services/serialization_helpers.js
index 7d5e718b41c..97bad375aea 100644
--- a/app/assets/javascripts/content_editor/services/serialization_helpers.js
+++ b/app/assets/javascripts/content_editor/services/serialization_helpers.js
@@ -1,4 +1,4 @@
-import { uniq, isString, omit } from 'lodash';
+import { uniq, isString, omit, isFunction } from 'lodash';
const defaultAttrs = {
td: { colspan: 1, rowspan: 1, colwidth: null },
@@ -327,16 +327,25 @@ export function renderCodeBlock(state, node) {
state.closeBlock(node);
}
-export function preserveUnchanged(render) {
+const expandPreserveUnchangedConfig = (configOrRender) =>
+ isFunction(configOrRender)
+ ? { render: configOrRender, overwriteSourcePreservationStrategy: false }
+ : configOrRender;
+
+export function preserveUnchanged(configOrRender) {
return (state, node, parent, index) => {
+ const { render, overwriteSourcePreservationStrategy } = expandPreserveUnchangedConfig(
+ configOrRender,
+ );
+
const { sourceMarkdown } = node.attrs;
const same = state.options.changeTracker.get(node);
- if (same) {
+ if (same && !overwriteSourcePreservationStrategy) {
state.write(sourceMarkdown);
state.closeBlock(node);
} else {
- render(state, node, parent, index);
+ render(state, node, parent, index, same, sourceMarkdown);
}
};
}
diff --git a/app/assets/javascripts/lib/gfm/index.js b/app/assets/javascripts/lib/gfm/index.js
index 92118c8929f..05534401a2a 100644
--- a/app/assets/javascripts/lib/gfm/index.js
+++ b/app/assets/javascripts/lib/gfm/index.js
@@ -19,6 +19,16 @@ const skipRenderingHandlers = {
h(node.position, 'codeBlock', { language: node.lang, meta: node.meta }, [
{ type: 'text', value: node.value },
]),
+ definition: (h, node) => {
+ const title = node.title ? ` "${node.title}"` : '';
+
+ return h(
+ node.position,
+ 'referenceDefinition',
+ { identifier: node.identifier, url: node.url, title: node.title },
+ [{ type: 'text', value: `[${node.identifier}]: ${node.url}${title}` }],
+ );
+ },
};
const createParser = ({ skipRendering = [] }) => {
diff --git a/app/assets/javascripts/pages/projects/pipeline_schedules/index/index.js b/app/assets/javascripts/pages/projects/pipeline_schedules/index/index.js
index cd4bc35e74e..9513f42d9c9 100644
--- a/app/assets/javascripts/pages/projects/pipeline_schedules/index/index.js
+++ b/app/assets/javascripts/pages/projects/pipeline_schedules/index/index.js
@@ -1,4 +1,6 @@
import Vue from 'vue';
+import { BV_SHOW_MODAL } from '~/lib/utils/constants';
+import PipelineSchedulesTakeOwnershipModal from '~/pipeline_schedules/components/take_ownership_modal.vue';
import PipelineSchedulesCallout from '../shared/components/pipeline_schedules_callout.vue';
function initPipelineSchedules() {
@@ -23,4 +25,43 @@ function initPipelineSchedules() {
});
}
+function initTakeownershipModal() {
+ const modalId = 'pipeline-take-ownership-modal';
+ const buttonSelector = 'js-take-ownership-button';
+ const el = document.getElementById(modalId);
+ const takeOwnershipButtons = document.querySelectorAll(`.${buttonSelector}`);
+
+ if (!el) {
+ return;
+ }
+
+ // eslint-disable-next-line no-new
+ new Vue({
+ el,
+ data() {
+ return {
+ url: '',
+ };
+ },
+ mounted() {
+ takeOwnershipButtons.forEach((button) => {
+ button.addEventListener('click', () => {
+ const { url } = button.dataset;
+
+ this.url = url;
+ this.$root.$emit(BV_SHOW_MODAL, modalId, `.${buttonSelector}`);
+ });
+ });
+ },
+ render(createElement) {
+ return createElement(PipelineSchedulesTakeOwnershipModal, {
+ props: {
+ ownershipUrl: this.url,
+ },
+ });
+ },
+ });
+}
+
initPipelineSchedules();
+initTakeownershipModal();
diff --git a/app/assets/javascripts/pipeline_schedules/components/take_ownership_modal.vue b/app/assets/javascripts/pipeline_schedules/components/take_ownership_modal.vue
new file mode 100644
index 00000000000..7ded3945a32
--- /dev/null
+++ b/app/assets/javascripts/pipeline_schedules/components/take_ownership_modal.vue
@@ -0,0 +1,52 @@
+<script>
+import { GlModal } from '@gitlab/ui';
+import { __, s__ } from '~/locale';
+
+export default {
+ components: {
+ GlModal,
+ },
+ props: {
+ ownershipUrl: {
+ type: String,
+ required: true,
+ },
+ },
+ modalId: 'pipeline-take-ownership-modal',
+ i18n: {
+ takeOwnership: s__('PipelineSchedules|Take ownership'),
+ ownershipMessage: s__(
+ 'PipelineSchedules|Only the owner of a pipeline schedule can make changes to it. Do you want to take ownership of this schedule?',
+ ),
+ cancelLabel: __('Cancel'),
+ },
+ computed: {
+ actionCancel() {
+ return { text: this.$options.i18n.cancelLabel };
+ },
+ actionPrimary() {
+ return {
+ text: this.$options.i18n.takeOwnership,
+ attributes: [
+ {
+ variant: 'confirm',
+ category: 'primary',
+ href: this.ownershipUrl,
+ 'data-method': 'post',
+ },
+ ],
+ };
+ },
+ },
+};
+</script>
+<template>
+ <gl-modal
+ :modal-id="$options.modalId"
+ :action-primary="actionPrimary"
+ :action-cancel="actionCancel"
+ :title="$options.i18n.takeOwnership"
+ >
+ <p>{{ $options.i18n.ownershipMessage }}</p>
+ </gl-modal>
+</template>
diff --git a/app/events/projects/project_transfered_event.rb b/app/events/projects/project_transfered_event.rb
new file mode 100644
index 00000000000..14cc53daabb
--- /dev/null
+++ b/app/events/projects/project_transfered_event.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+module Projects
+ class ProjectTransferedEvent < ::Gitlab::EventStore::Event
+ def schema
+ {
+ 'type' => 'object',
+ 'properties' => {
+ 'project_id' => { 'type' => 'integer' },
+ 'old_namespace_id' => { 'type' => 'integer' },
+ 'old_root_namespace_id' => { 'type' => 'integer' },
+ 'new_namespace_id' => { 'type' => 'integer' },
+ 'new_root_namespace_id' => { 'type' => 'integer' }
+ },
+ 'required' => %w[
+ project_id
+ old_namespace_id
+ old_root_namespace_id
+ new_namespace_id
+ new_root_namespace_id
+ ]
+ }
+ end
+ end
+end
diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb
index 33b771eef69..d574aa51ec4 100644
--- a/app/helpers/commits_helper.rb
+++ b/app/helpers/commits_helper.rb
@@ -137,7 +137,12 @@ module CommitsHelper
def conditionally_paginate_diff_files(diffs, paginate:, page:, per:)
if paginate
- Kaminari.paginate_array(diffs.diff_files.to_a).page(page).per(per)
+ diff_files = diffs.diff_files.to_a
+ Gitlab::Utils::BatchLoader.clear_key([:repository_blobs, diffs.project.repository])
+
+ Kaminari.paginate_array(diff_files).page(page).per(per).tap do |diff_files|
+ diff_files.each(&:add_blobs_to_batch_loader)
+ end
else
diffs.diff_files
end
diff --git a/app/models/blob.rb b/app/models/blob.rb
index a12d856dc36..20d7c230aa2 100644
--- a/app/models/blob.rb
+++ b/app/models/blob.rb
@@ -93,8 +93,8 @@ class Blob < SimpleDelegator
end
def self.lazy(repository, commit_id, path, blob_size_limit: Gitlab::Git::Blob::MAX_DATA_DISPLAY_SIZE)
- BatchLoader.for([commit_id, path]).batch(key: repository) do |items, loader, args|
- args[:key].blobs_at(items, blob_size_limit: blob_size_limit).each do |blob|
+ BatchLoader.for([commit_id, path]).batch(key: [:repository_blobs, repository]) do |items, loader, args|
+ args[:key].last.blobs_at(items, blob_size_limit: blob_size_limit).each do |blob|
loader.call([blob.commit_id, blob.path], blob) if blob
end
end
diff --git a/app/services/projects/transfer_service.rb b/app/services/projects/transfer_service.rb
index 666227951c6..3cb5a564ba5 100644
--- a/app/services/projects/transfer_service.rb
+++ b/app/services/projects/transfer_service.rb
@@ -121,6 +121,8 @@ module Projects
# Overridden in EE
def post_update_hooks(project)
ensure_personal_project_owner_membership(project)
+
+ publish_event
end
# Overridden in EE
@@ -268,6 +270,18 @@ module Projects
CustomerRelations::IssueContact.delete_for_project(project.id)
end
+
+ def publish_event
+ event = ::Projects::ProjectTransferedEvent.new(data: {
+ project_id: project.id,
+ old_namespace_id: old_namespace.id,
+ old_root_namespace_id: old_namespace.root_ancestor.id,
+ new_namespace_id: new_namespace.id,
+ new_root_namespace_id: new_namespace.root_ancestor.id
+ })
+
+ Gitlab::EventStore.publish(event)
+ end
end
end
diff --git a/app/services/protected_branches/base_service.rb b/app/services/protected_branches/base_service.rb
index f48e02ab4b5..d26c1b148bf 100644
--- a/app/services/protected_branches/base_service.rb
+++ b/app/services/protected_branches/base_service.rb
@@ -13,5 +13,9 @@ module ProtectedBranches
def after_execute(*)
# overridden in EE::ProtectedBranches module
end
+
+ def refresh_cache
+ CacheService.new(@project, @current_user, @params).refresh
+ end
end
end
diff --git a/app/services/protected_branches/cache_service.rb b/app/services/protected_branches/cache_service.rb
new file mode 100644
index 00000000000..fed5dca2581
--- /dev/null
+++ b/app/services/protected_branches/cache_service.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+module ProtectedBranches
+ class CacheService < ProtectedBranches::BaseService
+ CACHE_ROOT_KEY = 'cache:gitlab:protected_branch'
+ TTL_UNSET = -1
+ CACHE_EXPIRE_IN = 1.day
+ CACHE_LIMIT = 1000
+
+ def fetch(ref_name)
+ record = OpenSSL::Digest::SHA256.hexdigest(ref_name)
+
+ Gitlab::Redis::Cache.with do |redis|
+ cached_result = redis.hget(redis_key, record)
+
+ break Gitlab::Redis::Boolean.decode(cached_result) unless cached_result.nil?
+
+ value = yield
+
+ redis.hset(redis_key, record, Gitlab::Redis::Boolean.encode(value))
+
+ # We don't want to extend cache expiration time
+ if redis.ttl(redis_key) == TTL_UNSET
+ redis.expire(redis_key, CACHE_EXPIRE_IN)
+ end
+
+ # If the cache record has too many elements, then something went wrong and
+ # it's better to drop the cache key.
+ if redis.hlen(redis_key) > CACHE_LIMIT
+ redis.unlink(redis_key)
+ end
+
+ value
+ end
+ end
+
+ def refresh
+ Gitlab::Redis::Cache.with { |redis| redis.unlink(redis_key) }
+ end
+
+ private
+
+ def redis_key
+ @redis_key ||= [CACHE_ROOT_KEY, @project.id].join(':')
+ end
+ end
+end
diff --git a/app/services/protected_branches/create_service.rb b/app/services/protected_branches/create_service.rb
index dada449989a..903addf7afc 100644
--- a/app/services/protected_branches/create_service.rb
+++ b/app/services/protected_branches/create_service.rb
@@ -7,6 +7,8 @@ module ProtectedBranches
save_protected_branch
+ refresh_cache
+
protected_branch
end
diff --git a/app/services/protected_branches/destroy_service.rb b/app/services/protected_branches/destroy_service.rb
index 47332ace417..01d3b68314f 100644
--- a/app/services/protected_branches/destroy_service.rb
+++ b/app/services/protected_branches/destroy_service.rb
@@ -5,7 +5,7 @@ module ProtectedBranches
def execute(protected_branch)
raise Gitlab::Access::AccessDeniedError unless can?(current_user, :destroy_protected_branch, protected_branch)
- protected_branch.destroy
+ protected_branch.destroy.tap { refresh_cache }
end
end
end
diff --git a/app/services/protected_branches/update_service.rb b/app/services/protected_branches/update_service.rb
index 1e70f2d9793..c155e0022f5 100644
--- a/app/services/protected_branches/update_service.rb
+++ b/app/services/protected_branches/update_service.rb
@@ -10,6 +10,8 @@ module ProtectedBranches
if protected_branch.update(params)
after_execute(protected_branch: protected_branch, old_merge_access_levels: old_merge_access_levels, old_push_access_levels: old_push_access_levels)
+
+ refresh_cache
end
protected_branch
diff --git a/app/views/admin/application_settings/_runner_registrars_form.html.haml b/app/views/admin/application_settings/_runner_registrars_form.html.haml
index 1d6051a06ea..7781db29bab 100644
--- a/app/views/admin/application_settings/_runner_registrars_form.html.haml
+++ b/app/views/admin/application_settings/_runner_registrars_form.html.haml
@@ -1,5 +1,5 @@
= gitlab_ui_form_for @application_setting, url: ci_cd_admin_application_settings_path(anchor: 'js-runner-settings'), html: { class: 'fieldset-form' } do |f|
- = form_errors(@application_setting)
+ = form_errors(@application_setting, pajamas_alert: true)
%fieldset
.gl-form-group
diff --git a/app/views/admin/application_settings/_usage.html.haml b/app/views/admin/application_settings/_usage.html.haml
index c9ed2309cec..7326a63f8c2 100644
--- a/app/views/admin/application_settings/_usage.html.haml
+++ b/app/views/admin/application_settings/_usage.html.haml
@@ -3,7 +3,7 @@
- link_end = '</a>'.html_safe
= gitlab_ui_form_for @application_setting, url: metrics_and_profiling_admin_application_settings_path(anchor: 'js-usage-settings'), html: { class: 'fieldset-form' } do |f|
- = form_errors(@application_setting)
+ = form_errors(@application_setting, pajamas_alert: true)
%fieldset
.form-group
@@ -38,7 +38,7 @@
%p.gl-mb-3= s_('AdminSettings|Registration Features include:')
- email_from_gitlab_path = help_page_path('user/admin_area/email_from_gitlab')
- repo_size_limit_path = help_page_path('user/admin_area/settings/account_and_limit_settings', anchor: 'repository-size-limit')
- - restrict_ip_path = help_page_path('user/group/index', anchor: 'restrict-group-access-by-ip-address')
+ - restrict_ip_path = help_page_path('user/group/access_and_permissions', anchor: 'restrict-group-access-by-ip-address')
- email_from_gitlab_link = link_start % { url: email_from_gitlab_path }
- repo_size_limit_link = link_start % { url: repo_size_limit_path }
- restrict_ip_link = link_start % { url: restrict_ip_path }
diff --git a/app/views/layouts/group.html.haml b/app/views/layouts/group.html.haml
index 655375e4d18..67809cbc608 100644
--- a/app/views/layouts/group.html.haml
+++ b/app/views/layouts/group.html.haml
@@ -7,7 +7,7 @@
- content_for :flash_message do
= render "layouts/header/storage_enforcement_banner", context: @group
- = dispensable_render_if_exists "shared/namespace_storage_limit_alert"
+ = dispensable_render_if_exists "shared/namespace_storage_limit_alert", context: @group
- content_for :page_specific_javascripts do
- if current_user
diff --git a/app/views/layouts/profile.html.haml b/app/views/layouts/profile.html.haml
index 43ec2ceff99..1ec839ef642 100644
--- a/app/views/layouts/profile.html.haml
+++ b/app/views/layouts/profile.html.haml
@@ -8,5 +8,6 @@
- content_for :flash_message do
= render "layouts/header/storage_enforcement_banner", context: current_user.namespace
+ = dispensable_render_if_exists "shared/namespace_storage_limit_alert", context: current_user.namespace
= render template: "layouts/application"
diff --git a/app/views/layouts/project.html.haml b/app/views/layouts/project.html.haml
index 614f247bb1c..9503e874fd0 100644
--- a/app/views/layouts/project.html.haml
+++ b/app/views/layouts/project.html.haml
@@ -9,7 +9,7 @@
- content_for :flash_message do
= render "layouts/header/storage_enforcement_banner", context: @project
- = dispensable_render_if_exists "shared/namespace_storage_limit_alert"
+ = dispensable_render_if_exists "shared/namespace_storage_limit_alert", context: @project
- content_for :project_javascripts do
- project = @target_project || @project
diff --git a/app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml b/app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml
index edcd44563f7..c36c3ae5adf 100644
--- a/app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml
+++ b/app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml
@@ -33,7 +33,7 @@
= link_to play_pipeline_schedule_path(pipeline_schedule), method: :post, title: _('Play'), class: 'btn gl-button btn-default btn-icon' do
= sprite_icon('play')
- if can?(current_user, :take_ownership_pipeline_schedule, pipeline_schedule)
- = link_to take_ownership_pipeline_schedule_path(pipeline_schedule), method: :post, title: s_('PipelineSchedules|Take ownership'), class: 'btn gl-button btn-default' do
+ = render Pajamas::ButtonComponent.new(button_options: { class: 'js-take-ownership-button has-tooltip', title: s_('PipelineSchedule|Take ownership to edit'), data: { url: take_ownership_pipeline_schedule_path(pipeline_schedule) } }) do
= s_('PipelineSchedules|Take ownership')
- if can?(current_user, :update_pipeline_schedule, pipeline_schedule)
= link_to edit_pipeline_schedule_path(pipeline_schedule), title: _('Edit'), class: 'btn gl-button btn-default btn-icon' do
diff --git a/app/views/projects/pipeline_schedules/index.html.haml b/app/views/projects/pipeline_schedules/index.html.haml
index a56e8f7f5c7..661cf465081 100644
--- a/app/views/projects/pipeline_schedules/index.html.haml
+++ b/app/views/projects/pipeline_schedules/index.html.haml
@@ -18,3 +18,5 @@
- else
.card.bg-light.gl-mt-3
.nothing-here-block= _("No schedules")
+
+#pipeline-take-ownership-modal
diff --git a/config/metrics/aggregates/common.yml b/config/metrics/aggregates/common.yml
index 6396187a26a..b8f2d179741 100644
--- a/config/metrics/aggregates/common.yml
+++ b/config/metrics/aggregates/common.yml
@@ -17,16 +17,6 @@
# Corresponding feature flag should have `default_enabled` attribute set to `false`.
# This attribute is OPTIONAL and can be omitted, when `feature_flag` is missing no feature flag will be checked.
---
-- name: compliance_features_track_unique_visits_union
- operator: OR
- source: redis
- time_frame: [7d, 28d]
- events:
- - 'g_compliance_audit_events'
- - 'g_compliance_dashboard'
- - 'i_compliance_audit_events'
- - 'a_compliance_audit_events_api'
- - 'i_compliance_credential_inventory'
- name: incident_management_alerts_total_unique_counts
operator: OR
source: redis
diff --git a/config/webpack.vendor.config.js b/config/webpack.vendor.config.js
index ff38497a05e..903607679a4 100644
--- a/config/webpack.vendor.config.js
+++ b/config/webpack.vendor.config.js
@@ -41,7 +41,6 @@ module.exports = {
'three',
'select2',
'moment-mini',
- 'aws-sdk',
'dompurify',
'bootstrap/dist/js/bootstrap.js',
'sortablejs/modular/sortable.esm.js',
diff --git a/doc/administration/audit_events.md b/doc/administration/audit_events.md
index 8bcd78391cb..3282bb5195d 100644
--- a/doc/administration/audit_events.md
+++ b/doc/administration/audit_events.md
@@ -115,7 +115,7 @@ From there, you can see the following actions:
- Instance administrator started or stopped impersonation of a group member. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/300961) in GitLab 14.8.
- Group deploy token was successfully created, revoked, or deleted. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/353452) in GitLab 14.9.
- Failed attempt to create a group deploy token. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/353452) in GitLab 14.9.
-- [IP restrictions](../user/group/index.md#group-access-restriction-by-ip-address) changed. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/358986) in GitLab 15.0.
+- [IP restrictions](../user/group/access_and_permissions.md#restrict-group-access-by-ip-address) changed. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/358986) in GitLab 15.0.
- Changes to push rules. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/227629) in GitLab 15.0.
- [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/356152) in GitLab 15.1, changes to the following merge request approvals settings:
- Prevent approval by author.
diff --git a/doc/administration/auth/ldap/ldap_synchronization.md b/doc/administration/auth/ldap/ldap_synchronization.md
index b0ada1c11dd..62706a9e3b9 100644
--- a/doc/administration/auth/ldap/ldap_synchronization.md
+++ b/doc/administration/auth/ldap/ldap_synchronization.md
@@ -128,7 +128,7 @@ To take advantage of group sync, group Owners or users with the [Maintainer role
### Add group links
For information on adding group links by using CNs and filters, refer to the
-[GitLab groups documentation](../../../user/group/index.md#manage-group-memberships-via-ldap).
+[GitLab groups documentation](../../../user/group/access_and_permissions.md#manage-group-memberships-via-ldap).
### Administrator sync
diff --git a/doc/administration/compliance.md b/doc/administration/compliance.md
index 869de929eb8..8c7f8bf766d 100644
--- a/doc/administration/compliance.md
+++ b/doc/administration/compliance.md
@@ -97,7 +97,7 @@ These features can also help with compliance requirements:
projects): Search dependencies for their licenses. This lets you determine if
the licenses of your project's dependencies are compatible with your project's
license.
-- [**Lock project membership to group**](../user/group/index.md#prevent-members-from-being-added-to-projects-in-a-group)
+- [**Lock project membership to group**](../user/group/access_and_permissions.md#prevent-members-from-being-added-to-projects-in-a-group)
(for groups): Group owners can prevent new members from being added to projects
within a group.
- [**LDAP group sync**](auth/ldap/ldap_synchronization.md#group-sync) (for
diff --git a/doc/api/groups.md b/doc/api/groups.md
index 6e6149e935b..bafb6ed3e7e 100644
--- a/doc/api/groups.md
+++ b/doc/api/groups.md
@@ -926,7 +926,7 @@ PUT /groups/:id
| `emails_disabled` | boolean | no | Disable email notifications. |
| `lfs_enabled` | boolean | no | Enable/disable Large File Storage (LFS) for the projects in this group. |
| `mentions_disabled` | boolean | no | Disable the capability of a group from getting mentioned. |
-| `prevent_sharing_groups_outside_hierarchy` | boolean | no | See [Prevent group sharing outside the group hierarchy](../user/group/index.md#prevent-group-sharing-outside-the-group-hierarchy). This attribute is only available on top-level groups. [Introduced in GitLab 14.1](https://gitlab.com/gitlab-org/gitlab/-/issues/333721) |
+| `prevent_sharing_groups_outside_hierarchy` | boolean | no | See [Prevent group sharing outside the group hierarchy](../user/group/access_and_permissions.md#prevent-group-sharing-outside-the-group-hierarchy). This attribute is only available on top-level groups. [Introduced in GitLab 14.1](https://gitlab.com/gitlab-org/gitlab/-/issues/333721) |
| `project_creation_level` | string | no | Determine if developers can create projects in the group. Can be `noone` (No one), `maintainer` (users with the Maintainer role), or `developer` (users with the Developer or Maintainer role). |
| `request_access_enabled` | boolean | no | Allow users to request member access. |
| `require_two_factor_authentication` | boolean | no | Require all users in this group to setup Two-factor authentication. |
@@ -1481,7 +1481,7 @@ DELETE /groups/:id/share/:group_id
### Get group push rules **(PREMIUM)**
-Get the [push rules](../user/group/index.md#group-push-rules) of a group.
+Get the [push rules](../user/group/access_and_permissions.md#group-push-rules) of a group.
Only available to group owners and administrators.
@@ -1524,7 +1524,7 @@ the `commit_committer_check` and `reject_unsigned_commits` parameters:
### Add group push rule **(PREMIUM)**
-Adds [push rules](../user/group/index.md#group-push-rules) to the specified group.
+Adds [push rules](../user/group/access_and_permissions.md#group-push-rules) to the specified group.
Only available to group owners and administrators.
@@ -1618,7 +1618,7 @@ Response:
### Delete group push rule **(PREMIUM)**
-Deletes the [push rules](../user/group/index.md#group-push-rules) of a group.
+Deletes the [push rules](../user/group/access_and_permissions.md#group-push-rules) of a group.
Only available to group owners and administrators.
diff --git a/doc/integration/saml.md b/doc/integration/saml.md
index 97ff2a7df80..8a900975a3a 100644
--- a/doc/integration/saml.md
+++ b/doc/integration/saml.md
@@ -756,7 +756,7 @@ Group SAML on a self-managed instance is limited when compared to the recommende
[instance-wide SAML](../user/group/saml_sso/index.md). The recommended solution allows you to take advantage of:
- [LDAP compatibility](../administration/auth/ldap/index.md).
-- [LDAP Group Sync](../user/group/index.md#manage-group-memberships-via-ldap).
+- [LDAP Group Sync](../user/group/access_and_permissions.md#manage-group-memberships-via-ldap).
- [Required groups](#required-groups).
- [Administrator groups](#administrator-groups).
- [Auditor groups](#auditor-groups).
diff --git a/doc/security/two_factor_authentication.md b/doc/security/two_factor_authentication.md
index 5907860f5cc..008aef69691 100644
--- a/doc/security/two_factor_authentication.md
+++ b/doc/security/two_factor_authentication.md
@@ -71,7 +71,7 @@ The following are important notes about 2FA:
2FA for the project. For example, if project *P* belongs to 2FA-enabled group *A* and
is shared with 2FA-disabled group *B*, members of group *B* can access project *P*
without 2FA. To ensure this scenario doesn't occur,
- [prevent sharing of projects](../user/group/index.md#prevent-a-project-from-being-shared-with-groups)
+ [prevent sharing of projects](../user/group/access_and_permissions.md#prevent-a-project-from-being-shared-with-groups)
for the 2FA-enabled group.
- If you add additional members to a project within a group or subgroup that has
2FA enabled, 2FA is **not** required for those individually added members.
diff --git a/doc/subscriptions/bronze_starter.md b/doc/subscriptions/bronze_starter.md
index 2649249f8ec..62e045a7593 100644
--- a/doc/subscriptions/bronze_starter.md
+++ b/doc/subscriptions/bronze_starter.md
@@ -23,11 +23,11 @@ the tiers are no longer mentioned in GitLab documentation:
- [Setting a default template for merge requests and issues](../user/project/description_templates.md#set-a-default-template-for-merge-requests-and-issues)
- [Email from GitLab](../user/admin_area/email_from_gitlab.md)
- Groups:
- - [Creating group memberships via CN](../user/group/index.md#create-group-links-via-cn)
- - [Group push rules](../user/group/index.md#group-push-rules)
- - [Managing group memberships via LDAP](../user/group/index.md#manage-group-memberships-via-ldap)
- - [Member locking](../user/group/index.md#prevent-members-from-being-added-to-projects-in-a-group)
- - [Overriding user permissions](../user/group/index.md#override-user-permissions)
+ - [Creating group memberships via CN](../user/group/access_and_permissions.md#create-group-links-via-cn)
+ - [Group push rules](../user/group/access_and_permissions.md#group-push-rules)
+ - [Managing group memberships via LDAP](../user/group/access_and_permissions.md#manage-group-memberships-via-ldap)
+ - [Member locking](../user/group/access_and_permissions.md#prevent-members-from-being-added-to-projects-in-a-group)
+ - [Overriding user permissions](../user/group/access_and_permissions.md#override-user-permissions)
- [User contribution analytics](../user/group/contribution_analytics/index.md)
- [Kerberos integration](../integration/kerberos.md)
- Issue boards:
diff --git a/doc/user/admin_area/settings/usage_statistics.md b/doc/user/admin_area/settings/usage_statistics.md
index 65712a9a85c..afb937494e0 100644
--- a/doc/user/admin_area/settings/usage_statistics.md
+++ b/doc/user/admin_area/settings/usage_statistics.md
@@ -48,7 +48,7 @@ tier. Users can continue to access the features in a paid tier without sharing u
### Features available in 14.4 and later
- [Repository size limit](../settings/account_and_limit_settings.md#repository-size-limit).
-- [Group access restriction by IP address](../../group/index.md#group-access-restriction-by-ip-address).
+- [Group access restriction by IP address](../../group/access_and_permissions.md#restrict-group-access-by-ip-address).
NOTE:
Registration is not yet required for participation, but may be added in a future milestone.
diff --git a/doc/user/group/access_and_permissions.md b/doc/user/group/access_and_permissions.md
index 1ed5f4b2eb7..03b638f8902 100644
--- a/doc/user/group/access_and_permissions.md
+++ b/doc/user/group/access_and_permissions.md
@@ -6,6 +6,246 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# Group access and permissions
-Use Groups to manage one or more related projects at the same time.
+Configure your groups to control group permissions and access.
-For instructions on how to configure access and permissions for groups, see [Groups](index.md).
+## Group push rules **(PREMIUM)**
+
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/34370) in GitLab 12.8.
+> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/224129) in GitLab 13.4.
+
+Group push rules allow group maintainers to set
+[push rules](../project/repository/push_rules.md) for newly created projects in the specific group.
+
+To configure push rules for a group:
+
+1. Go to the groups's **Push Rules** page.
+1. Select the settings you want.
+1. Select **Save Push Rules**.
+
+The group's new subgroups have push rules set for them based on either:
+
+- The closest parent group with push rules defined.
+- Push rules set at the instance level, if no parent groups have push rules defined.
+
+## Restrict group access by IP address **(PREMIUM)**
+
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/1985) in GitLab 12.0.
+> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/215410) from GitLab Ultimate to GitLab Premium in 13.1.
+
+To ensure only people from your organization can access particular
+resources, you can restrict access to groups by IP address. This group-level setting
+applies to:
+
+- The GitLab UI, including subgroups, projects, and issues.
+- [In GitLab 12.3 and later](https://gitlab.com/gitlab-org/gitlab/-/issues/12874), the API.
+
+### Security implications
+
+You should consider some security implications before configuring IP address restrictions.
+
+- Restricting HTTP traffic on GitLab.com with IP address restrictions causes SSH requests (including Git operations over
+ SSH) to fail. For more information, see [the relevant issue](https://gitlab.com/gitlab-org/gitlab/-/issues/271673).
+- Administrators and group owners can access group settings from any IP address, regardless of IP restriction. However:
+ - Groups owners cannot access projects belonging to the group when accessing from a disallowed IP address.
+ - Administrators can access projects belonging to the group when accessing from a disallowed IP address.
+ Access to projects includes cloning code from them.
+ - Users can still see group and project names and hierarchies. Only the following are restricted:
+ - [Groups](../../api/groups.md), including all [group resources](../../api/api_resources.md#group-resources).
+ - [Project](../../api/projects.md), including all [project resources](../../api/api_resources.md#project-resources).
+- When you register a runner, it is not bound by the IP restrictions. When the runner requests a new job or an update to
+ a job's state, it is also not bound by the IP restrictions. But when the running CI/CD job sends Git requests from a
+ restricted IP address, the IP restriction prevents code from being cloned.
+- Users may still see some events from the IP restricted groups and projects on their dashboard. Activity may include
+ push, merge, issue, or comment events.
+
+### Restrict group access by IP address
+
+To restrict group access by IP address:
+
+1. Go to the group's **Settings > General** page.
+1. Expand the **Permissions and group features** section.
+1. In the **Allow access to the following IP addresses** field, enter IPv4 or IPv6 address ranges in CIDR notation.
+1. Select **Save changes**.
+
+In self-managed installations of GitLab 15.1 and later, you can also configure
+[globally-allowed IP address ranges](../admin_area/settings/visibility_and_access_controls.md#configure-globally-allowed-ip-address-ranges)
+at the group level.
+
+## Restrict group access by domain **(PREMIUM)**
+
+> - Support for specifying multiple email domains [added](https://gitlab.com/gitlab-org/gitlab/-/issues/33143) in GitLab 13.1.
+> - Support for restricting access to projects in the group [added](https://gitlab.com/gitlab-org/gitlab/-/issues/14004) in GitLab 14.1.2.
+
+You can prevent users with email addresses in specific domains from being added to a group and its projects.
+
+To restrict group access by domain:
+
+1. Go to the group's **Settings > General** page.
+1. Expand the **Permissions and group features** section.
+1. In the **Restrict membership by email** field, enter the domain names.
+1. Select **Save changes**.
+
+Any time you attempt to add a new user, the user's [primary email](../profile/index.md#change-your-primary-email) is compared against this list.
+Only users with a [primary email](../profile/index.md#change-your-primary-email) that matches any of the configured email domain restrictions
+can be added to the group.
+
+The most popular public email domains cannot be restricted, such as:
+
+- `gmail.com`, `yahoo.com`, `aol.com`, `icloud.com`
+- `hotmail.com`, `hotmail.co.uk`, `hotmail.fr`
+- `msn.com`, `live.com`, `outlook.com`
+
+## Prevent group sharing outside the group hierarchy
+
+You can configure a top-level group so its subgroups and projects
+cannot invite other groups outside of the top-level group's hierarchy.
+This option is only available for top-level groups.
+
+For example, in the following group and project hierarchy:
+
+- **Animals > Dogs > Dog Project**
+- **Animals > Cats**
+- **Plants > Trees**
+
+If you prevent group sharing outside the hierarchy for the **Animals** group:
+
+- **Dogs** can invite the group **Cats**.
+- **Dogs** cannot invite the group **Trees**.
+- **Dog Project** can invite the group **Cats**.
+- **Dog Project** cannot invite the group **Trees**.
+
+To prevent sharing outside of the group's hierarchy:
+
+1. On the top bar, select **Menu > Groups** and find your group.
+1. On the left sidebar, select **Settings > General**.
+1. Expand **Permissions and group features**.
+1. Select **Prevent members from sending invitations to groups outside of `<group_name>` and its subgroups**.
+1. Select **Save changes**.
+
+## Prevent a project from being shared with groups
+
+Prevent projects in a group from [sharing
+a project with another group](../project/members/share_project_with_groups.md) to enable tighter control over project access.
+
+To prevent a project from being shared with other groups:
+
+1. Go to the group's **Settings > General** page.
+1. Expand the **Permissions and group features** section.
+1. Select **Prevent sharing a project in `<group_name>` with other groups**.
+1. Select **Save changes**.
+
+This setting applies to all subgroups unless overridden by a group owner. Groups already
+added to a project lose access when the setting is enabled.
+
+## Prevent users from requesting access to a group
+
+As a group owner, you can prevent non-members from requesting access to
+your group.
+
+1. On the top bar, select **Menu > Groups**.
+1. Select **Your Groups**.
+1. Find the group and select it.
+1. From the left menu, select **Settings > General**.
+1. Expand the **Permissions and group features** section.
+1. Clear the **Allow users to request access** checkbox.
+1. Select **Save changes**.
+
+## Prevent project forking outside group **(PREMIUM)**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/216987) in GitLab 13.3.
+
+By default, projects in a group can be forked.
+Optionally, on [GitLab Premium](https://about.gitlab.com/pricing/) or higher tiers,
+you can prevent the projects in a group from being forked outside of the current top-level group.
+
+This setting will be removed from the SAML setting page, and migrated to the
+group settings page. In the interim period, both of these settings are taken into consideration.
+If even one is set to `true`, then the group does not allow outside forks.
+
+To prevent projects from being forked outside the group:
+
+1. Go to the top-level group's **Settings > General** page.
+1. Expand the **Permissions and group features** section.
+1. Check **Prevent project forking outside current group**.
+1. Select **Save changes**.
+
+Existing forks are not removed.
+
+## Prevent members from being added to projects in a group **(PREMIUM)**
+
+As a group owner, you can prevent any new project membership for all
+projects in a group, allowing tighter control over project membership.
+
+For example, if you want to lock the group for an [Audit Event](../../administration/audit_events.md),
+you can guarantee that project membership cannot be modified during the audit.
+
+You can still invite groups or to add members to groups, implicitly giving members access to projects in the **locked** group.
+
+The setting does not cascade. Projects in subgroups observe the subgroup configuration, ignoring the parent group.
+
+To prevent members from being added to projects in a group:
+
+1. Go to the group's **Settings > General** page.
+1. Expand the **Permissions and group features** section.
+1. Under **Membership**, select **Prevent adding new members to projects within this group**.
+1. Select **Save changes**.
+
+All users who previously had permissions can no longer add members to a group.
+API requests to add a new user to a project are not possible.
+
+## Manage group memberships via LDAP **(PREMIUM SELF)**
+
+Group syncing allows LDAP groups to be mapped to GitLab groups. This provides more control over per-group user management. To configure group syncing, edit the `group_base` **DN** (`'OU=Global Groups,OU=GitLab INT,DC=GitLab,DC=org'`). This **OU** contains all groups that are associated with GitLab groups.
+
+Group links can be created by using either a CN or a filter. To create these group links, go to the group's **Settings > LDAP Synchronization** page. After configuring the link, it may take more than an hour for the users to sync with the GitLab group.
+
+For more information on the administration of LDAP and group sync, refer to the [main LDAP documentation](../../administration/auth/ldap/ldap_synchronization.md#group-sync).
+
+NOTE:
+When you add LDAP synchronization, if an LDAP user is a group member and they are not part of the LDAP group, they are removed from the group.
+
+### Create group links via CN **(PREMIUM SELF)**
+
+To create group links via CN:
+
+<!-- vale gitlab.Spelling = NO -->
+
+1. Select the **LDAP Server** for the link.
+1. As the **Sync method**, select `LDAP Group cn`.
+1. In the **LDAP Group cn** field, begin typing the CN of the group. There is a dropdown list with matching CNs in the configured `group_base`. Select your CN from this list.
+1. In the **LDAP Access** section, select the [permission level](../permissions.md) for users synced in this group.
+1. Select **Add Synchronization**.
+
+<!-- vale gitlab.Spelling = YES -->
+
+### Create group links via filter **(PREMIUM SELF)**
+
+To create group links via filter:
+
+1. Select the **LDAP Server** for the link.
+1. As the **Sync method**, select `LDAP user filter`.
+1. Input your filter in the **LDAP User filter** box. Follow the [documentation on user filters](../../administration/auth/ldap/index.md#set-up-ldap-user-filter).
+1. In the **LDAP Access** section, select the [permission level](../permissions.md) for users synced in this group.
+1. Select **Add Synchronization**.
+
+### Override user permissions **(PREMIUM SELF)**
+
+LDAP user permissions can be manually overridden by an administrator. To override a user's permissions:
+
+1. Go to your group's **Group information > Members** page.
+1. In the row for the user you are editing, select the pencil (**{pencil}**) icon.
+1. Select **Edit permissions** in the modal.
+
+Now you can edit the user's permissions from the **Members** page.
+
+## Troubleshooting
+
+### Verify if access is blocked by IP restriction
+
+If a user sees a 404 when they would normally expect access, and the problem is limited to a specific group, search the `auth.log` rails log for one or more of the following:
+
+- `json.message`: `'Attempting to access IP restricted group'`
+- `json.allowed`: `false`
+
+In viewing the log entries, compare the `remote.ip` with the list of
+[allowed IPs](#restrict-group-access-by-ip-address) for the group.
diff --git a/doc/user/group/index.md b/doc/user/group/index.md
index 420e06c3678..3e2672e725d 100644
--- a/doc/user/group/index.md
+++ b/doc/user/group/index.md
@@ -48,19 +48,6 @@ For example, consider a user named Alex:
| Alex creates a group for their team with the group name `alex-team`. The group and its projects are available at: `https://gitlab.example.com/alex-team`. | The namespace in this case is `alex-team`. |
| Alex creates a subgroup of `alex-team` with the subgroup name `marketing`. The subgroup and its projects are available at: `https://gitlab.example.com/alex-team/marketing`. | The namespace in this case is `alex-team/marketing`. |
-## Prevent users from requesting access to a group
-
-As a group owner, you can prevent non-members from requesting access to
-your group.
-
-1. On the top bar, select **Menu > Groups**.
-1. Select **Your Groups**.
-1. Find the group and select it.
-1. From the left menu, select **Settings > General**.
-1. Expand the **Permissions and group features** section.
-1. Clear the **Allow users to request access** checkbox.
-1. Select **Save changes**.
-
## Mention a group in an issue or merge request
When you mention a group in a comment, every member of the group gets a to-do item
@@ -73,115 +60,6 @@ added to their To-do list.
A to-do item is created for all the group and subgroup members.
-## Manage group memberships via LDAP **(PREMIUM SELF)**
-
-Group syncing allows LDAP groups to be mapped to GitLab groups. This provides more control over per-group user management. To configure group syncing, edit the `group_base` **DN** (`'OU=Global Groups,OU=GitLab INT,DC=GitLab,DC=org'`). This **OU** contains all groups that are associated with GitLab groups.
-
-Group links can be created by using either a CN or a filter. To create these group links, go to the group's **Settings > LDAP Synchronization** page. After configuring the link, it may take more than an hour for the users to sync with the GitLab group.
-
-For more information on the administration of LDAP and group sync, refer to the [main LDAP documentation](../../administration/auth/ldap/ldap_synchronization.md#group-sync).
-
-NOTE:
-When you add LDAP synchronization, if an LDAP user is a group member and they are not part of the LDAP group, they are removed from the group.
-
-### Create group links via CN **(PREMIUM SELF)**
-
-To create group links via CN:
-
-<!-- vale gitlab.Spelling = NO -->
-
-1. Select the **LDAP Server** for the link.
-1. As the **Sync method**, select `LDAP Group cn`.
-1. In the **LDAP Group cn** field, begin typing the CN of the group. There is a dropdown list with matching CNs in the configured `group_base`. Select your CN from this list.
-1. In the **LDAP Access** section, select the [permission level](../permissions.md) for users synced in this group.
-1. Select **Add Synchronization**.
-
-<!-- vale gitlab.Spelling = YES -->
-
-### Create group links via filter **(PREMIUM SELF)**
-
-To create group links via filter:
-
-1. Select the **LDAP Server** for the link.
-1. As the **Sync method**, select `LDAP user filter`.
-1. Input your filter in the **LDAP User filter** box. Follow the [documentation on user filters](../../administration/auth/ldap/index.md#set-up-ldap-user-filter).
-1. In the **LDAP Access** section, select the [permission level](../permissions.md) for users synced in this group.
-1. Select **Add Synchronization**.
-
-### Override user permissions **(PREMIUM SELF)**
-
-LDAP user permissions can be manually overridden by an administrator. To override a user's permissions:
-
-1. Go to your group's **Group information > Members** page.
-1. In the row for the user you are editing, select the pencil (**{pencil}**) icon.
-1. Select **Edit permissions** in the modal.
-
-Now you can edit the user's permissions from the **Members** page.
-
-## Prevent group sharing outside the group hierarchy
-
-You can configure a top-level group so its subgroups and projects
-cannot invite other groups outside of the top-level group's hierarchy.
-This option is only available for top-level groups.
-
-For example, in the following group and project hierarchy:
-
-- **Animals > Dogs > Dog Project**
-- **Animals > Cats**
-- **Plants > Trees**
-
-If you prevent group sharing outside the hierarchy for the **Animals** group:
-
-- **Dogs** can invite the group **Cats**.
-- **Dogs** cannot invite the group **Trees**.
-- **Dog Project** can invite the group **Cats**.
-- **Dog Project** cannot invite the group **Trees**.
-
-To prevent sharing outside of the group's hierarchy:
-
-1. On the top bar, select **Menu > Groups** and find your group.
-1. On the left sidebar, select **Settings > General**.
-1. Expand **Permissions and group features**.
-1. Select **Prevent members from sending invitations to groups outside of `<group_name>` and its subgroups**.
-1. Select **Save changes**.
-
-## Prevent a project from being shared with groups
-
-Prevent projects in a group from [sharing
-a project with another group](../project/members/share_project_with_groups.md) to enable tighter control over project access.
-
-To prevent a project from being shared with other groups:
-
-1. Go to the group's **Settings > General** page.
-1. Expand the **Permissions and group features** section.
-1. Select **Prevent sharing a project in `<group_name>` with other groups**.
-1. Select **Save changes**.
-
-This setting applies to all subgroups unless overridden by a group owner. Groups already
-added to a project lose access when the setting is enabled.
-
-## Prevent members from being added to projects in a group **(PREMIUM)**
-
-As a group owner, you can prevent any new project membership for all
-projects in a group, allowing tighter control over project membership.
-
-For example, if you want to lock the group for an [Audit Event](../../administration/audit_events.md),
-you can guarantee that project membership cannot be modified during the audit.
-
-You can still invite groups or to add members to groups, implicitly giving members access to projects in the **locked** group.
-
-The setting does not cascade. Projects in subgroups observe the subgroup configuration, ignoring the parent group.
-
-To prevent members from being added to projects in a group:
-
-1. Go to the group's **Settings > General** page.
-1. Expand the **Permissions and group features** section.
-1. Under **Membership**, select **Prevent adding new members to projects within this group**.
-1. Select **Save changes**.
-
-All users who previously had permissions can no longer add members to a group.
-API requests to add a new user to a project are not possible.
-
## Export members as CSV **(PREMIUM)**
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/287940) in GitLab 14.2.
@@ -193,115 +71,6 @@ You can export a list of members in a group or subgroup as a CSV.
1. Select **Export as CSV**.
1. After the CSV file has been generated, it is emailed as an attachment to the user that requested it.
-## Group access restriction by IP address **(PREMIUM)**
-
-> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/1985) in GitLab 12.0.
-> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/215410) from GitLab Ultimate to GitLab Premium in 13.1.
-
-To ensure only people from your organization can access particular
-resources, you can restrict access to groups by IP address. This group-level setting
-applies to:
-
-- The GitLab UI, including subgroups, projects, and issues.
-- [In GitLab 12.3 and later](https://gitlab.com/gitlab-org/gitlab/-/issues/12874), the API.
-
-### Security implications
-
-You should consider some security implications before configuring IP address restrictions.
-
-- Restricting HTTP traffic on GitLab.com with IP address restrictions causes SSH requests (including Git operations over
- SSH) to fail. For more information, see [the relevant issue](https://gitlab.com/gitlab-org/gitlab/-/issues/271673).
-- Administrators and group owners can access group settings from any IP address, regardless of IP restriction. However:
- - Groups owners cannot access projects belonging to the group when accessing from a disallowed IP address.
- - Administrators can access projects belonging to the group when accessing from a disallowed IP address.
- Access to projects includes cloning code from them.
- - Users can still see group and project names and hierarchies. Only the following are restricted:
- - [Groups](../../api/groups.md), including all [group resources](../../api/api_resources.md#group-resources).
- - [Project](../../api/projects.md), including all [project resources](../../api/api_resources.md#project-resources).
-- When you register a runner, it is not bound by the IP restrictions. When the runner requests a new job or an update to
- a job's state, it is also not bound by the IP restrictions. But when the running CI/CD job sends Git requests from a
- restricted IP address, the IP restriction prevents code from being cloned.
-- Users may still see some events from the IP restricted groups and projects on their dashboard. Activity may include
- push, merge, issue, or comment events.
-
-### Restrict group access by IP address
-
-To restrict group access by IP address:
-
-1. Go to the group's **Settings > General** page.
-1. Expand the **Permissions and group features** section.
-1. In the **Allow access to the following IP addresses** field, enter IPv4 or IPv6 address ranges in CIDR notation.
-1. Select **Save changes**.
-
-In self-managed installations of GitLab 15.1 and later, you can also configure
-[globally-allowed IP address ranges](../admin_area/settings/visibility_and_access_controls.md#configure-globally-allowed-ip-address-ranges)
-at the group level.
-
-## Restrict group access by domain **(PREMIUM)**
-
-> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/7297) in GitLab 12.2.
-> - Support for specifying multiple email domains [added](https://gitlab.com/gitlab-org/gitlab/-/issues/33143) in GitLab 13.1.
-> - Support for restricting access to projects in the group [added](https://gitlab.com/gitlab-org/gitlab/-/issues/14004) in GitLab 14.1.2.
-
-You can prevent users with email addresses in specific domains from being added to a group and its projects.
-
-To restrict group access by domain:
-
-1. Go to the group's **Settings > General** page.
-1. Expand the **Permissions and group features** section.
-1. In the **Restrict membership by email** field, enter the domain names.
-1. Select **Save changes**.
-
-Any time you attempt to add a new user, the user's [primary email](../profile/index.md#change-your-primary-email) is compared against this list.
-Only users with a [primary email](../profile/index.md#change-your-primary-email) that matches any of the configured email domain restrictions
-can be added to the group.
-
-The most popular public email domains cannot be restricted, such as:
-
-- `gmail.com`, `yahoo.com`, `aol.com`, `icloud.com`
-- `hotmail.com`, `hotmail.co.uk`, `hotmail.fr`
-- `msn.com`, `live.com`, `outlook.com`
-
-## Prevent project forking outside group **(PREMIUM)**
-
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/216987) in GitLab 13.3.
-
-By default, projects in a group can be forked.
-Optionally, on [GitLab Premium](https://about.gitlab.com/pricing/) or higher tiers,
-you can prevent the projects in a group from being forked outside of the current top-level group.
-
-This setting will be removed from the SAML setting page, and migrated to the
-group settings page. In the interim period, both of these settings are taken into consideration.
-If even one is set to `true`, then the group does not allow outside forks.
-
-To prevent projects from being forked outside the group:
-
-1. Go to the top-level group's **Settings > General** page.
-1. Expand the **Permissions and group features** section.
-1. Check **Prevent project forking outside current group**.
-1. Select **Save changes**.
-
-Existing forks are not removed.
-
-## Group push rules **(PREMIUM)**
-
-> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/34370) in GitLab 12.8.
-> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/224129) in GitLab 13.4.
-
-Group push rules allow group maintainers to set
-[push rules](../project/repository/push_rules.md) for newly created projects in the specific group.
-
-To configure push rules for a group:
-
-1. Go to the groups's **Push Rules** page.
-1. Select the settings you want.
-1. Select **Save Push Rules**.
-
-The group's new subgroups have push rules set for them based on either:
-
-- The closest parent group with push rules defined.
-- Push rules set at the instance level, if no parent groups have push rules defined.
-
## Related topics
- [Group wikis](../project/wiki/index.md)
@@ -323,20 +92,8 @@ The group's new subgroups have push rules set for them based on either:
- [Integrations](../admin_area/settings/project_integration_management.md).
- [Transfer a project into a group](../project/settings/index.md#transfer-a-project-to-another-namespace).
- [Share a project with a group](../project/members/share_project_with_groups.md): Give all group members access to the project at once.
-- [Lock the sharing with group feature](#prevent-a-project-from-being-shared-with-groups).
+- [Lock the sharing with group feature](access_and_permissions.md#prevent-a-project-from-being-shared-with-groups).
- [Enforce two-factor authentication (2FA)](../../security/two_factor_authentication.md#enforce-2fa-for-all-users-in-a-group): Enforce 2FA
for all group members.
- Namespaces [API](../../api/namespaces.md) and [Rake tasks](../../raketasks/index.md).
- [Control access and visibility](../admin_area/settings/visibility_and_access_controls.md).
-
-## Troubleshooting
-
-### Verify if access is blocked by IP restriction
-
-If a user sees a 404 when they would normally expect access, and the problem is limited to a specific group, search the `auth.log` rails log for one or more of the following:
-
-- `json.message`: `'Attempting to access IP restricted group'`
-- `json.allowed`: `false`
-
-In viewing the log entries, compare the `remote.ip` with the list of
-[allowed IPs](#group-access-restriction-by-ip-address) for the group.
diff --git a/doc/user/permissions.md b/doc/user/permissions.md
index 33c1584cb72..07b78dc7788 100644
--- a/doc/user/permissions.md
+++ b/doc/user/permissions.md
@@ -217,7 +217,7 @@ The following table lists project permissions available for each role:
4. If the [branch is protected](project/protected_branches.md), this depends on the access Developers and Maintainers are given.
5. Guest users can access GitLab [**Releases**](project/releases/index.md) for downloading assets but are not allowed to download the source code nor see [repository information like commits and release evidence](project/releases/index.md#view-a-release-and-download-assets).
6. Actions are limited only to records owned (referenced) by user.
-7. When [Share Group Lock](group/index.md#prevent-a-project-from-being-shared-with-groups) is enabled the project can't be shared with other groups. It does not affect group with group sharing.
+7. When [Share Group Lock](group/access_and_permissions.md#prevent-a-project-from-being-shared-with-groups) is enabled the project can't be shared with other groups. It does not affect group with group sharing.
8. For information on eligible approvers for merge requests, see
[Eligible approvers](project/merge_requests/approvals/rules.md#eligible-approvers).
9. Applies only to comments on [Design Management](project/issues/design_management.md) designs.
@@ -412,7 +412,7 @@ The following table lists group permissions available for each role:
| Delete [group wiki](project/wiki/group.md) pages | | | ✓ | ✓ | ✓ |
| Edit [epic](group/epics/index.md) comments (posted by any user) | | | | ✓ (2) | ✓ (2) |
| List group deploy tokens | | | | ✓ | ✓ |
-| Manage [group push rules](group/index.md#group-push-rules) | | | | ✓ | ✓ |
+| Manage [group push rules](group/access_and_permissions.md#group-push-rules) | | | | ✓ | ✓ |
| View/manage group-level Kubernetes cluster | | | | ✓ | ✓ |
| Create and manage compliance frameworks | | | | | ✓ |
| Create/Delete group deploy tokens | | | | | ✓ |
@@ -600,7 +600,7 @@ for more information.
## LDAP users permissions
LDAP user permissions can be manually overridden by an administrator.
-Read through the documentation on [LDAP users permissions](group/index.md#manage-group-memberships-via-ldap) to learn more.
+Read through the documentation on [LDAP users permissions](group/access_and_permissions.md#manage-group-memberships-via-ldap) to learn more.
## Project aliases
diff --git a/doc/user/project/members/index.md b/doc/user/project/members/index.md
index 2f12a963754..2fccf8bbe5c 100644
--- a/doc/user/project/members/index.md
+++ b/doc/user/project/members/index.md
@@ -91,7 +91,7 @@ Each user's access is based on:
Prerequisite:
- You must have the Maintainer or Owner role.
-- Sharing the project with other groups must not be [prevented](../../group/index.md#prevent-a-project-from-being-shared-with-groups).
+- Sharing the project with other groups must not be [prevented](../../group/access_and_permissions.md#prevent-a-project-from-being-shared-with-groups).
To add groups to a project:
@@ -173,7 +173,7 @@ To remove a member from a project:
user has not forked the private repository or created webhooks. Existing forks continue to receive
changes from the upstream project, and webhooks continue to receive updates. You may also want to configure your project
to prevent projects in a group
- [from being forked outside their group](../../group/index.md#prevent-project-forking-outside-group).
+ [from being forked outside their group](../../group/access_and_permissions.md#prevent-project-forking-outside-group).
1. Select **Remove member**.
## Filter and sort members
diff --git a/doc/user/project/members/share_project_with_groups.md b/doc/user/project/members/share_project_with_groups.md
index bb8ae6ae5d2..cf03cc122f2 100644
--- a/doc/user/project/members/share_project_with_groups.md
+++ b/doc/user/project/members/share_project_with_groups.md
@@ -81,4 +81,4 @@ It is possible to prevent projects in a group from [sharing
a project with another group](../members/share_project_with_groups.md).
This allows for tighter control over project access.
-Learn more about [Share with group lock](../../group/index.md#prevent-a-project-from-being-shared-with-groups).
+Learn more about [Share with group lock](../../group/access_and_permissions.md#prevent-a-project-from-being-shared-with-groups).
diff --git a/doc/user/project/repository/push_rules.md b/doc/user/project/repository/push_rules.md
index 592aff85434..46a9585604e 100644
--- a/doc/user/project/repository/push_rules.md
+++ b/doc/user/project/repository/push_rules.md
@@ -25,7 +25,7 @@ For custom push rules use [server hooks](../../../administration/server_hooks.md
## Enable global push rules
You can create push rules for all new projects to inherit, but they can be overridden
-at the project level or the [group level](../../group/index.md#group-push-rules).
+at the project level or the [group level](../../group/access_and_permissions.md#group-push-rules).
All projects created after you configure global push rules inherit this
configuration. However, each existing project must be updated manually, using the
process described in [Override global push rules per project](#override-global-push-rules-per-project).
diff --git a/glfm_specification/example_snapshots/html.yml b/glfm_specification/example_snapshots/html.yml
index 834dd49a934..105a0d26409 100644
--- a/glfm_specification/example_snapshots/html.yml
+++ b/glfm_specification/example_snapshots/html.yml
@@ -2144,6 +2144,7 @@
static: |-
<p data-sourcepos="3:1-3:5" dir="auto"><a href="/url" title="title">foo</a></p>
wysiwyg: |-
+ <pre>[foo]: /url "title"</pre>
<p><a target="_blank" rel="noopener noreferrer nofollow" href="/url" title="title">foo</a></p>
04_07__leaf_blocks__link_reference_definitions__002:
canonical: |
@@ -2151,6 +2152,7 @@
static: |-
<p data-sourcepos="5:1-5:5" dir="auto"><a href="/url" title="the title">foo</a></p>
wysiwyg: |-
+ <pre>[foo]: /url "the title"</pre>
<p><a target="_blank" rel="noopener noreferrer nofollow" href="/url" title="the title">foo</a></p>
04_07__leaf_blocks__link_reference_definitions__003:
canonical: |
@@ -2158,6 +2160,7 @@
static: |-
<p data-sourcepos="3:1-3:11" dir="auto"><a href="my_(url)" title="title (with parens)">Foo*bar]</a></p>
wysiwyg: |-
+ <pre>[foo*bar\]]: my_(url) "title (with parens)"</pre>
<p><a target="_blank" rel="noopener noreferrer nofollow" href="my_(url)" title="title (with parens)">Foo*bar]</a></p>
04_07__leaf_blocks__link_reference_definitions__004:
canonical: |
@@ -2165,6 +2168,7 @@
static: |-
<p data-sourcepos="5:1-5:9" dir="auto"><a href="my%20url" title="title">Foo bar</a></p>
wysiwyg: |-
+ <pre>[foo bar]: my url "title"</pre>
<p><a target="_blank" rel="noopener noreferrer nofollow" href="my%20url" title="title">Foo bar</a></p>
04_07__leaf_blocks__link_reference_definitions__005:
canonical: |
@@ -2180,6 +2184,11 @@
line2
">foo</a></p>
wysiwyg: |-
+ <pre>[foo]: /url "
+ title
+ line1
+ line2
+ "</pre>
<p><a target="_blank" rel="noopener noreferrer nofollow" href="/url" title="
title
line1
@@ -2204,6 +2213,7 @@
static: |-
<p data-sourcepos="4:1-4:5" dir="auto"><a href="/url">foo</a></p>
wysiwyg: |-
+ <pre>[foo]: /url</pre>
<p><a target="_blank" rel="noopener noreferrer nofollow" href="/url">foo</a></p>
04_07__leaf_blocks__link_reference_definitions__008:
canonical: |
@@ -2221,6 +2231,7 @@
static: |-
<p data-sourcepos="3:1-3:5" dir="auto"><a href="">foo</a></p>
wysiwyg: |-
+ <pre>[foo]: </pre>
<p><a target="_blank" rel="noopener noreferrer nofollow" href="">foo</a></p>
04_07__leaf_blocks__link_reference_definitions__010:
canonical: |
@@ -2238,6 +2249,7 @@
static: |-
<p data-sourcepos="3:1-3:5" dir="auto"><a href="/url%5Cbar*baz" title='foo"bar\baz'>foo</a></p>
wysiwyg: |-
+ <pre>[foo]: /url\bar*baz "foo"bar\baz"</pre>
<p><a target="_blank" rel="noopener noreferrer nofollow" href="/url%5Cbar*baz" title="foo&quot;bar\baz">foo</a></p>
04_07__leaf_blocks__link_reference_definitions__012:
canonical: |
@@ -2246,6 +2258,7 @@
<p data-sourcepos="1:1-1:5" dir="auto"><a href="url">foo</a></p>
wysiwyg: |-
<p><a target="_blank" rel="noopener noreferrer nofollow" href="url">foo</a></p>
+ <pre>[foo]: url</pre>
04_07__leaf_blocks__link_reference_definitions__013:
canonical: |
<p><a href="first">foo</a></p>
@@ -2253,12 +2266,15 @@
<p data-sourcepos="1:1-1:5" dir="auto"><a href="first">foo</a></p>
wysiwyg: |-
<p><a target="_blank" rel="noopener noreferrer nofollow" href="first">foo</a></p>
+ <pre>[foo]: first</pre>
+ <pre>[foo]: second</pre>
04_07__leaf_blocks__link_reference_definitions__014:
canonical: |
<p><a href="/url">Foo</a></p>
static: |-
<p data-sourcepos="3:1-3:5" dir="auto"><a href="/url">Foo</a></p>
wysiwyg: |-
+ <pre>[foo]: /url</pre>
<p><a target="_blank" rel="noopener noreferrer nofollow" href="/url">Foo</a></p>
04_07__leaf_blocks__link_reference_definitions__015:
canonical: |
@@ -2266,18 +2282,20 @@
static: |-
<p data-sourcepos="3:1-3:8" dir="auto"><a href="/%CF%86%CE%BF%CF%85">αγω</a></p>
wysiwyg: |-
+ <pre>[αγω]: /φου</pre>
<p><a target="_blank" rel="noopener noreferrer nofollow" href="/%CF%86%CE%BF%CF%85">αγω</a></p>
04_07__leaf_blocks__link_reference_definitions__016:
canonical: ""
static: ""
wysiwyg: |-
- <p></p>
+ <pre>[foo]: /url</pre>
04_07__leaf_blocks__link_reference_definitions__017:
canonical: |
<p>bar</p>
static: |-
<p data-sourcepos="1:1-4:3" dir="auto">bar</p>
wysiwyg: |-
+ <pre>[foo]: /url</pre>
<p>bar</p>
04_07__leaf_blocks__link_reference_definitions__018:
canonical: |
@@ -2292,6 +2310,7 @@
static: |-
<p data-sourcepos="1:1-2:10" dir="auto">"title" ok</p>
wysiwyg: |-
+ <pre>[foo]: /url</pre>
<p>"title" ok</p>
04_07__leaf_blocks__link_reference_definitions__020:
canonical: |
@@ -2349,6 +2368,7 @@
</blockquote>
wysiwyg: |-
<h1><a target="_blank" rel="noopener noreferrer nofollow" href="/url">Foo</a></h1>
+ <pre>[foo]: /url</pre>
<blockquote multiline="false"><p>bar</p></blockquote>
04_07__leaf_blocks__link_reference_definitions__024:
canonical: |
@@ -2359,6 +2379,7 @@
<a id="user-content-bar" class="anchor" href="#bar" aria-hidden="true"></a>bar</h1>
<p data-sourcepos="4:1-4:5" dir="auto"><a href="/url">foo</a></p>
wysiwyg: |-
+ <pre>[foo]: /url</pre>
<h1>bar</h1>
<p><a target="_blank" rel="noopener noreferrer nofollow" href="/url">foo</a></p>
04_07__leaf_blocks__link_reference_definitions__025:
@@ -2369,6 +2390,7 @@
<p data-sourcepos="1:1-3:5" dir="auto">===
<a href="/url">foo</a></p>
wysiwyg: |-
+ <pre>[foo]: /url</pre>
<p>===
<a target="_blank" rel="noopener noreferrer nofollow" href="/url">foo</a></p>
04_07__leaf_blocks__link_reference_definitions__026:
@@ -2381,6 +2403,9 @@
<a href="/bar-url" title="bar">bar</a>,
<a href="/baz-url">baz</a></p>
wysiwyg: |-
+ <pre>[foo]: /foo-url "foo"</pre>
+ <pre>[bar]: /bar-url "bar"</pre>
+ <pre>[baz]: /baz-url</pre>
<p><a target="_blank" rel="noopener noreferrer nofollow" href="/foo-url" title="foo">foo</a>,
<a target="_blank" rel="noopener noreferrer nofollow" href="/bar-url" title="bar">bar</a>,
<a target="_blank" rel="noopener noreferrer nofollow" href="/baz-url">baz</a></p>
@@ -2395,12 +2420,12 @@
</blockquote>
wysiwyg: |-
<p><a target="_blank" rel="noopener noreferrer nofollow" href="/url">foo</a></p>
- <blockquote multiline="false"><p></p></blockquote>
+ <blockquote multiline="false"><pre>[foo]: /url</pre></blockquote>
04_07__leaf_blocks__link_reference_definitions__028:
canonical: ""
static: ""
wysiwyg: |-
- <p></p>
+ <pre>[foo]: /url</pre>
04_08__leaf_blocks__paragraphs__001:
canonical: |
<p>aaa</p>
@@ -4543,7 +4568,7 @@
</li>
</ul>
wysiwyg: |-
- <ul bullet="*"><li><p>a</p></li><li><p>b</p></li><li><p>d</p></li></ul>
+ <ul bullet="*"><li><p>a</p></li><li><p>b</p><pre>[ref]: /url</pre></li><li><p>d</p></li></ul>
05_04__container_blocks__lists__018:
canonical: |
<ul>
@@ -4880,6 +4905,7 @@
<p data-sourcepos="1:1-1:5" dir="auto"><a href="/bar*" title="ti*tle">foo</a></p>
wysiwyg: |-
<p><a target="_blank" rel="noopener noreferrer nofollow" href="/bar*" title="ti*tle">foo</a></p>
+ <pre>[foo]: /bar* "ti*tle"</pre>
06_02__inlines__backslash_escapes__013:
canonical: |
<pre><code class="language-foo+bar">foo
@@ -4969,6 +4995,7 @@
<p data-sourcepos="1:1-1:5" dir="auto"><a href="/f%C3%B6%C3%B6" title="föö">foo</a></p>
wysiwyg: |-
<p><a target="_blank" rel="noopener noreferrer nofollow" href="/f%C3%B6%C3%B6" title="föö">foo</a></p>
+ <pre>[foo]: /föö "föö"</pre>
06_03__inlines__entity_and_numeric_character_references__010:
canonical: |
<pre><code class="language-föö">foo
@@ -6475,6 +6502,7 @@
<p data-sourcepos="1:1-1:10" dir="auto"><a href="/url" title="title">foo</a></p>
wysiwyg: |-
<p><a target="_blank" rel="noopener noreferrer nofollow" href="/url" title="title">foo</a></p>
+ <pre>[bar]: /url "title"</pre>
06_07__inlines__links__044:
canonical: |
<p><a href="/uri">link [foo [bar]]</a></p>
@@ -6482,6 +6510,7 @@
<p data-sourcepos="1:1-1:23" dir="auto"><a href="/uri">link [foo [bar]]</a></p>
wysiwyg: |-
<p><a target="_blank" rel="noopener noreferrer nofollow" href="/uri">link [foo [bar]]</a></p>
+ <pre>[ref]: /uri</pre>
06_07__inlines__links__045:
canonical: |
<p><a href="/uri">link [bar</a></p>
@@ -6489,6 +6518,7 @@
<p data-sourcepos="1:1-1:17" dir="auto"><a href="/uri">link [bar</a></p>
wysiwyg: |-
<p><a target="_blank" rel="noopener noreferrer nofollow" href="/uri">link [bar</a></p>
+ <pre>[ref]: /uri</pre>
06_07__inlines__links__046:
canonical: |
<p><a href="/uri">link <em>foo <strong>bar</strong> <code>#</code></em></a></p>
@@ -6496,6 +6526,7 @@
<p data-sourcepos="1:1-1:29" dir="auto"><a href="/uri">link <em>foo <strong>bar</strong> <code>#</code></em></a></p>
wysiwyg: |-
<p><a target="_blank" rel="noopener noreferrer nofollow" href="/uri">link <em>foo </em><strong><em>bar<code>#</code></em></strong></a></p>
+ <pre>[ref]: /uri</pre>
06_07__inlines__links__047:
canonical: |
<p><a href="/uri"><img src="moon.jpg" alt="moon" /></a></p>
@@ -6503,6 +6534,7 @@
<p data-sourcepos="1:1-1:24" dir="auto"><a href="/uri"><img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="moon" decoding="async" class="lazy" data-src="moon.jpg"></a></p>
wysiwyg: |-
<p><a target="_blank" rel="noopener noreferrer nofollow" href="/uri"><img src="moon.jpg" alt="moon"></a></p>
+ <pre>[ref]: /uri</pre>
06_07__inlines__links__048:
canonical: |
<p>[foo <a href="/uri">bar</a>]<a href="/uri">ref</a></p>
@@ -6510,6 +6542,7 @@
<p data-sourcepos="1:1-1:22" dir="auto">[foo <a href="/uri">bar</a>]<a href="/uri">ref</a></p>
wysiwyg: |-
<p>[foo <a target="_blank" rel="noopener noreferrer nofollow" href="/uri">bar</a>]<a target="_blank" rel="noopener noreferrer nofollow" href="/uri">ref</a></p>
+ <pre>[ref]: /uri</pre>
06_07__inlines__links__049:
canonical: |
<p>[foo <em>bar <a href="/uri">baz</a></em>]<a href="/uri">ref</a></p>
@@ -6517,6 +6550,7 @@
<p data-sourcepos="1:1-1:27" dir="auto">[foo <em>bar <a href="/uri">baz</a></em>]<a href="/uri">ref</a></p>
wysiwyg: |-
<p>[foo <em>bar </em><a target="_blank" rel="noopener noreferrer nofollow" href="/uri"><em>baz</em></a>]<a target="_blank" rel="noopener noreferrer nofollow" href="/uri">ref</a></p>
+ <pre>[ref]: /uri</pre>
06_07__inlines__links__050:
canonical: |
<p>*<a href="/uri">foo*</a></p>
@@ -6524,6 +6558,7 @@
<p data-sourcepos="1:1-1:12" dir="auto">*<a href="/uri">foo*</a></p>
wysiwyg: |-
<p>*<a target="_blank" rel="noopener noreferrer nofollow" href="/uri">foo*</a></p>
+ <pre>[ref]: /uri</pre>
06_07__inlines__links__051:
canonical: |
<p><a href="/uri">foo *bar</a></p>
@@ -6531,6 +6566,7 @@
<p data-sourcepos="1:1-1:15" dir="auto"><a href="/uri">foo *bar</a></p>
wysiwyg: |-
<p><a target="_blank" rel="noopener noreferrer nofollow" href="/uri">foo *bar</a></p>
+ <pre>[ref]: /uri</pre>
06_07__inlines__links__052:
canonical: |
<p>[foo <bar attr="][ref]"></p>
@@ -6538,6 +6574,7 @@
<p data-sourcepos="1:1-1:24" dir="auto">[foo </p>
wysiwyg: |-
<p>[foo </p>
+ <pre>[ref]: /uri</pre>
06_07__inlines__links__053:
canonical: |
<p>[foo<code>][ref]</code></p>
@@ -6545,6 +6582,7 @@
<p data-sourcepos="1:1-1:12" dir="auto">[foo<code>][ref]</code></p>
wysiwyg: |-
<p>[foo<code>][ref]</code></p>
+ <pre>[ref]: /uri</pre>
06_07__inlines__links__054:
canonical: |
<p>[foo<a href="http://example.com/?search=%5D%5Bref%5D">http://example.com/?search=][ref]</a></p>
@@ -6552,6 +6590,7 @@
<p data-sourcepos="1:1-1:39" dir="auto">[foo<a href="http://example.com/?search=%5D%5Bref%5D" rel="nofollow noreferrer noopener" target="_blank">http://example.com/?search=][ref]</a></p>
wysiwyg: |-
<p>[foo<a target="_blank" rel="noopener noreferrer nofollow" href="http://example.com/?search=%5D%5Bref%5D">http://example.com/?search=][ref]</a></p>
+ <pre>[ref]: /uri</pre>
06_07__inlines__links__055:
canonical: |
<p><a href="/url" title="title">foo</a></p>
@@ -6559,6 +6598,7 @@
<p data-sourcepos="1:1-1:10" dir="auto"><a href="/url" title="title">foo</a></p>
wysiwyg: |-
<p><a target="_blank" rel="noopener noreferrer nofollow" href="/url" title="title">foo</a></p>
+ <pre>[bar]: /url "title"</pre>
06_07__inlines__links__056:
canonical: |
<p><a href="/url">Толпой</a> is a Russian word.</p>
@@ -6566,12 +6606,14 @@
<p data-sourcepos="1:1-1:47" dir="auto"><a href="/url">Толпой</a> is a Russian word.</p>
wysiwyg: |-
<p><a target="_blank" rel="noopener noreferrer nofollow" href="/url">Толпой</a> is a Russian word.</p>
+ <pre>[толпой]: /url</pre>
06_07__inlines__links__057:
canonical: |
<p><a href="/url">Baz</a></p>
static: |-
<p data-sourcepos="4:1-4:14" dir="auto"><a href="/url">Baz</a></p>
wysiwyg: |-
+ <pre>[foo bar]: /url</pre>
<p><a target="_blank" rel="noopener noreferrer nofollow" href="/url">Baz</a></p>
06_07__inlines__links__058:
canonical: |
@@ -6580,6 +6622,7 @@
<p data-sourcepos="1:1-1:11" dir="auto">[foo] <a href="/url" title="title">bar</a></p>
wysiwyg: |-
<p>[foo] <a target="_blank" rel="noopener noreferrer nofollow" href="/url" title="title">bar</a></p>
+ <pre>[bar]: /url "title"</pre>
06_07__inlines__links__059:
canonical: |
<p>[foo]
@@ -6590,12 +6633,15 @@
wysiwyg: |-
<p>[foo]
<a target="_blank" rel="noopener noreferrer nofollow" href="/url" title="title">bar</a></p>
+ <pre>[bar]: /url "title"</pre>
06_07__inlines__links__060:
canonical: |
<p><a href="/url1">bar</a></p>
static: |-
<p data-sourcepos="5:1-5:10" dir="auto"><a href="/url1">bar</a></p>
wysiwyg: |-
+ <pre>[foo]: /url1</pre>
+ <pre>[foo]: /url2</pre>
<p><a target="_blank" rel="noopener noreferrer nofollow" href="/url1">bar</a></p>
06_07__inlines__links__061:
canonical: |
@@ -6604,6 +6650,7 @@
<p data-sourcepos="1:1-1:32" dir="auto">[bar][foo<span>!</span>]</p>
wysiwyg: |-
<p>[bar][foo!]</p>
+ <pre>[foo!]: /url</pre>
06_07__inlines__links__062:
canonical: |
<p>[foo][ref[]</p>
@@ -6641,12 +6688,14 @@
<p data-sourcepos="1:1-1:12" dir="auto"><a href="/uri">foo</a></p>
wysiwyg: |-
<p><a target="_blank" rel="noopener noreferrer nofollow" href="/uri">foo</a></p>
+ <pre>[ref\[]: /uri</pre>
06_07__inlines__links__066:
canonical: |
<p><a href="/uri">bar\</a></p>
static: |-
<p data-sourcepos="3:1-3:7" dir="auto"><a href="/uri">bar\</a></p>
wysiwyg: |-
+ <pre>[bar\\]: /uri</pre>
<p><a target="_blank" rel="noopener noreferrer nofollow" href="/uri">bar\</a></p>
06_07__inlines__links__067:
canonical: |
@@ -6681,6 +6730,7 @@
<p data-sourcepos="1:1-1:7" dir="auto"><a href="/url" title="title">foo</a></p>
wysiwyg: |-
<p><a target="_blank" rel="noopener noreferrer nofollow" href="/url" title="title">foo</a></p>
+ <pre>[foo]: /url "title"</pre>
06_07__inlines__links__070:
canonical: |
<p><a href="/url" title="title"><em>foo</em> bar</a></p>
@@ -6688,6 +6738,7 @@
<p data-sourcepos="1:1-1:13" dir="auto"><a href="/url" title="title"><em>foo</em> bar</a></p>
wysiwyg: |-
<p><a target="_blank" rel="noopener noreferrer nofollow" href="/url" title="title"><em>foo</em> bar</a></p>
+ <pre>[*foo* bar]: /url "title"</pre>
06_07__inlines__links__071:
canonical: |
<p><a href="/url" title="title">Foo</a></p>
@@ -6695,6 +6746,7 @@
<p data-sourcepos="1:1-1:7" dir="auto"><a href="/url" title="title">Foo</a></p>
wysiwyg: |-
<p><a target="_blank" rel="noopener noreferrer nofollow" href="/url" title="title">Foo</a></p>
+ <pre>[foo]: /url "title"</pre>
06_07__inlines__links__072:
canonical: |
<p><a href="/url" title="title">foo</a>
@@ -6705,6 +6757,7 @@
wysiwyg: |-
<p><a target="_blank" rel="noopener noreferrer nofollow" href="/url" title="title">foo</a>
[]</p>
+ <pre>[foo]: /url "title"</pre>
06_07__inlines__links__073:
canonical: |
<p><a href="/url" title="title">foo</a></p>
@@ -6712,6 +6765,7 @@
<p data-sourcepos="1:1-1:5" dir="auto"><a href="/url" title="title">foo</a></p>
wysiwyg: |-
<p><a target="_blank" rel="noopener noreferrer nofollow" href="/url" title="title">foo</a></p>
+ <pre>[foo]: /url "title"</pre>
06_07__inlines__links__074:
canonical: |
<p><a href="/url" title="title"><em>foo</em> bar</a></p>
@@ -6719,6 +6773,7 @@
<p data-sourcepos="1:1-1:11" dir="auto"><a href="/url" title="title"><em>foo</em> bar</a></p>
wysiwyg: |-
<p><a target="_blank" rel="noopener noreferrer nofollow" href="/url" title="title"><em>foo</em> bar</a></p>
+ <pre>[*foo* bar]: /url "title"</pre>
06_07__inlines__links__075:
canonical: |
<p>[<a href="/url" title="title"><em>foo</em> bar</a>]</p>
@@ -6726,6 +6781,7 @@
<p data-sourcepos="1:1-1:13" dir="auto">[<a href="/url" title="title"><em>foo</em> bar</a>]</p>
wysiwyg: |-
<p>[<a target="_blank" rel="noopener noreferrer nofollow" href="/url" title="title"><em>foo</em> bar</a>]</p>
+ <pre>[*foo* bar]: /url "title"</pre>
06_07__inlines__links__076:
canonical: |
<p>[[bar <a href="/url">foo</a></p>
@@ -6733,6 +6789,7 @@
<p data-sourcepos="1:1-1:11" dir="auto">[[bar <a href="/url">foo</a></p>
wysiwyg: |-
<p>[[bar <a target="_blank" rel="noopener noreferrer nofollow" href="/url">foo</a></p>
+ <pre>[foo]: /url</pre>
06_07__inlines__links__077:
canonical: |
<p><a href="/url" title="title">Foo</a></p>
@@ -6740,6 +6797,7 @@
<p data-sourcepos="1:1-1:5" dir="auto"><a href="/url" title="title">Foo</a></p>
wysiwyg: |-
<p><a target="_blank" rel="noopener noreferrer nofollow" href="/url" title="title">Foo</a></p>
+ <pre>[foo]: /url "title"</pre>
06_07__inlines__links__078:
canonical: |
<p><a href="/url">foo</a> bar</p>
@@ -6747,6 +6805,7 @@
<p data-sourcepos="1:1-1:9" dir="auto"><a href="/url">foo</a> bar</p>
wysiwyg: |-
<p><a target="_blank" rel="noopener noreferrer nofollow" href="/url">foo</a> bar</p>
+ <pre>[foo]: /url</pre>
06_07__inlines__links__079:
canonical: |
<p>[foo]</p>
@@ -6754,12 +6813,14 @@
<p data-sourcepos="1:1-1:6" dir="auto">[foo]</p>
wysiwyg: |-
<p>[foo]</p>
+ <pre>[foo]: /url "title"</pre>
06_07__inlines__links__080:
canonical: |
<p>*<a href="/url">foo*</a></p>
static: |-
<p data-sourcepos="3:1-3:7" dir="auto">*<a href="/url">foo*</a></p>
wysiwyg: |-
+ <pre>[foo*]: /url</pre>
<p>*<a target="_blank" rel="noopener noreferrer nofollow" href="/url">foo*</a></p>
06_07__inlines__links__081:
canonical: |
@@ -6768,6 +6829,8 @@
<p data-sourcepos="1:1-1:10" dir="auto"><a href="/url2">foo</a></p>
wysiwyg: |-
<p><a target="_blank" rel="noopener noreferrer nofollow" href="/url2">foo</a></p>
+ <pre>[foo]: /url1</pre>
+ <pre>[bar]: /url2</pre>
06_07__inlines__links__082:
canonical: |
<p><a href="/url1">foo</a></p>
@@ -6775,6 +6838,7 @@
<p data-sourcepos="1:1-1:7" dir="auto"><a href="/url1">foo</a></p>
wysiwyg: |-
<p><a target="_blank" rel="noopener noreferrer nofollow" href="/url1">foo</a></p>
+ <pre>[foo]: /url1</pre>
06_07__inlines__links__083:
canonical: |
<p><a href="">foo</a></p>
@@ -6782,6 +6846,7 @@
<p data-sourcepos="1:1-1:7" dir="auto"><a href="">foo</a></p>
wysiwyg: |-
<p><a target="_blank" rel="noopener noreferrer nofollow" href="">foo</a></p>
+ <pre>[foo]: /url1</pre>
06_07__inlines__links__084:
canonical: |
<p><a href="/url1">foo</a>(not a link)</p>
@@ -6789,6 +6854,7 @@
<p data-sourcepos="1:1-1:17" dir="auto"><a href="/url1">foo</a>(not a link)</p>
wysiwyg: |-
<p><a target="_blank" rel="noopener noreferrer nofollow" href="/url1">foo</a>(not a link)</p>
+ <pre>[foo]: /url1</pre>
06_07__inlines__links__085:
canonical: |
<p>[foo]<a href="/url">bar</a></p>
@@ -6796,6 +6862,7 @@
<p data-sourcepos="1:1-1:15" dir="auto">[foo]<a href="/url">bar</a></p>
wysiwyg: |-
<p>[foo]<a target="_blank" rel="noopener noreferrer nofollow" href="/url">bar</a></p>
+ <pre>[baz]: /url</pre>
06_07__inlines__links__086:
canonical: |
<p><a href="/url2">foo</a><a href="/url1">baz</a></p>
@@ -6803,6 +6870,8 @@
<p data-sourcepos="1:1-1:15" dir="auto"><a href="/url2">foo</a><a href="/url1">baz</a></p>
wysiwyg: |-
<p><a target="_blank" rel="noopener noreferrer nofollow" href="/url2">foo</a><a target="_blank" rel="noopener noreferrer nofollow" href="/url1">baz</a></p>
+ <pre>[baz]: /url1</pre>
+ <pre>[bar]: /url2</pre>
06_07__inlines__links__087:
canonical: |
<p>[foo]<a href="/url1">bar</a></p>
@@ -6810,6 +6879,8 @@
<p data-sourcepos="1:1-1:15" dir="auto">[foo]<a href="/url1">bar</a></p>
wysiwyg: |-
<p>[foo]<a target="_blank" rel="noopener noreferrer nofollow" href="/url1">bar</a></p>
+ <pre>[baz]: /url1</pre>
+ <pre>[foo]: /url2</pre>
06_08__inlines__images__001:
canonical: |
<p><img src="/url" alt="foo" title="title" /></p>
@@ -6824,6 +6895,7 @@
<p data-sourcepos="1:1-1:12" dir="auto"><a class="no-attachment-icon" href="train.jpg" target="_blank" rel="noopener noreferrer"><img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="foo bar" title="train &amp; tracks" decoding="async" class="lazy" data-src="train.jpg"></a></p>
wysiwyg: |-
<p><img src="train.jpg" alt="foo bar" title="train &amp; tracks"></p>
+ <pre>[foo *bar*]: train.jpg "train &amp; tracks"</pre>
06_08__inlines__images__003:
canonical: |
<p><img src="/url2" alt="foo bar" /></p>
@@ -6845,6 +6917,7 @@
<p data-sourcepos="1:1-1:14" dir="auto"><a class="no-attachment-icon" href="train.jpg" target="_blank" rel="noopener noreferrer"><img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="foo bar" title="train &amp; tracks" decoding="async" class="lazy" data-src="train.jpg"></a></p>
wysiwyg: |-
<p><img src="train.jpg" alt="foo bar" title="train &amp; tracks"></p>
+ <pre>[foo *bar*]: train.jpg "train &amp; tracks"</pre>
06_08__inlines__images__006:
canonical: |
<p><img src="train.jpg" alt="foo bar" title="train &amp; tracks" /></p>
@@ -6852,6 +6925,7 @@
<p data-sourcepos="1:1-1:20" dir="auto"><a class="no-attachment-icon" href="train.jpg" target="_blank" rel="noopener noreferrer"><img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="foo bar" title="train &amp; tracks" decoding="async" class="lazy" data-src="train.jpg"></a></p>
wysiwyg: |-
<p><img src="train.jpg" alt="foo bar" title="train &amp; tracks"></p>
+ <pre>[foobar]: train.jpg "train &amp; tracks"</pre>
06_08__inlines__images__007:
canonical: |
<p><img src="train.jpg" alt="foo" /></p>
@@ -6887,6 +6961,7 @@
<p data-sourcepos="1:1-1:11" dir="auto"><a class="no-attachment-icon" href="/url" target="_blank" rel="noopener noreferrer"><img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="foo" decoding="async" class="lazy" data-src="/url"></a></p>
wysiwyg: |-
<p><img src="/url" alt="foo"></p>
+ <pre>[bar]: /url</pre>
06_08__inlines__images__012:
canonical: |
<p><img src="/url" alt="foo" /></p>
@@ -6894,6 +6969,7 @@
<p data-sourcepos="1:1-1:11" dir="auto"><a class="no-attachment-icon" href="/url" target="_blank" rel="noopener noreferrer"><img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="foo" decoding="async" class="lazy" data-src="/url"></a></p>
wysiwyg: |-
<p><img src="/url" alt="foo"></p>
+ <pre>[bar]: /url</pre>
06_08__inlines__images__013:
canonical: |
<p><img src="/url" alt="foo" title="title" /></p>
@@ -6901,6 +6977,7 @@
<p data-sourcepos="1:1-1:8" dir="auto"><a class="no-attachment-icon" href="/url" target="_blank" rel="noopener noreferrer"><img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="foo" title="title" decoding="async" class="lazy" data-src="/url"></a></p>
wysiwyg: |-
<p><img src="/url" alt="foo" title="title"></p>
+ <pre>[foo]: /url "title"</pre>
06_08__inlines__images__014:
canonical: |
<p><img src="/url" alt="foo bar" title="title" /></p>
@@ -6908,6 +6985,7 @@
<p data-sourcepos="1:1-1:14" dir="auto"><a class="no-attachment-icon" href="/url" target="_blank" rel="noopener noreferrer"><img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="foo bar" title="title" decoding="async" class="lazy" data-src="/url"></a></p>
wysiwyg: |-
<p><img src="/url" alt="foo bar" title="title"></p>
+ <pre>[*foo* bar]: /url "title"</pre>
06_08__inlines__images__015:
canonical: |
<p><img src="/url" alt="Foo" title="title" /></p>
@@ -6915,6 +6993,7 @@
<p data-sourcepos="1:1-1:8" dir="auto"><a class="no-attachment-icon" href="/url" target="_blank" rel="noopener noreferrer"><img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Foo" title="title" decoding="async" class="lazy" data-src="/url"></a></p>
wysiwyg: |-
<p><img src="/url" alt="Foo" title="title"></p>
+ <pre>[foo]: /url "title"</pre>
06_08__inlines__images__016:
canonical: |
<p><img src="/url" alt="foo" title="title" />
@@ -6925,6 +7004,7 @@
wysiwyg: |-
<p><img src="/url" alt="foo" title="title">
[]</p>
+ <pre>[foo]: /url "title"</pre>
06_08__inlines__images__017:
canonical: |
<p><img src="/url" alt="foo" title="title" /></p>
@@ -6932,6 +7012,7 @@
<p data-sourcepos="1:1-1:6" dir="auto"><a class="no-attachment-icon" href="/url" target="_blank" rel="noopener noreferrer"><img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="foo" title="title" decoding="async" class="lazy" data-src="/url"></a></p>
wysiwyg: |-
<p><img src="/url" alt="foo" title="title"></p>
+ <pre>[foo]: /url "title"</pre>
06_08__inlines__images__018:
canonical: |
<p><img src="/url" alt="foo bar" title="title" /></p>
@@ -6939,6 +7020,7 @@
<p data-sourcepos="1:1-1:12" dir="auto"><a class="no-attachment-icon" href="/url" target="_blank" rel="noopener noreferrer"><img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="foo bar" title="title" decoding="async" class="lazy" data-src="/url"></a></p>
wysiwyg: |-
<p><img src="/url" alt="foo bar" title="title"></p>
+ <pre>[*foo* bar]: /url "title"</pre>
06_08__inlines__images__019:
canonical: |
<p>![[foo]]</p>
@@ -6956,6 +7038,7 @@
<p data-sourcepos="1:1-1:6" dir="auto"><a class="no-attachment-icon" href="/url" target="_blank" rel="noopener noreferrer"><img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Foo" title="title" decoding="async" class="lazy" data-src="/url"></a></p>
wysiwyg: |-
<p><img src="/url" alt="Foo" title="title"></p>
+ <pre>[foo]: /url "title"</pre>
06_08__inlines__images__021:
canonical: |
<p>![foo]</p>
@@ -6963,6 +7046,7 @@
<p data-sourcepos="1:1-1:7" dir="auto">![foo]</p>
wysiwyg: |-
<p>![foo]</p>
+ <pre>[foo]: /url "title"</pre>
06_08__inlines__images__022:
canonical: |
<p>!<a href="/url" title="title">foo</a></p>
@@ -6970,6 +7054,7 @@
<p data-sourcepos="1:1-1:27" dir="auto"><span>!</span><a href="/url" title="title">foo</a></p>
wysiwyg: |-
<p>!<a target="_blank" rel="noopener noreferrer nofollow" href="/url" title="title">foo</a></p>
+ <pre>[foo]: /url "title"</pre>
06_09__inlines__autolinks__001:
canonical: |
<p><a href="http://foo.bar.baz">http://foo.bar.baz</a></p>
diff --git a/glfm_specification/example_snapshots/prosemirror_json.yml b/glfm_specification/example_snapshots/prosemirror_json.yml
index f770d341c42..7cb4757b368 100644
--- a/glfm_specification/example_snapshots/prosemirror_json.yml
+++ b/glfm_specification/example_snapshots/prosemirror_json.yml
@@ -3884,6 +3884,20 @@
"type": "doc",
"content": [
{
+ "type": "referenceDefinition",
+ "attrs": {
+ "identifier": "foo",
+ "url": "/url",
+ "title": "title"
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "[foo]: /url \"title\""
+ }
+ ]
+ },
+ {
"type": "paragraph",
"content": [
{
@@ -3911,6 +3925,20 @@
"type": "doc",
"content": [
{
+ "type": "referenceDefinition",
+ "attrs": {
+ "identifier": "foo",
+ "url": "/url",
+ "title": "the title"
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "[foo]: /url \"the title\""
+ }
+ ]
+ },
+ {
"type": "paragraph",
"content": [
{
@@ -3938,6 +3966,20 @@
"type": "doc",
"content": [
{
+ "type": "referenceDefinition",
+ "attrs": {
+ "identifier": "foo*bar\\]",
+ "url": "my_(url)",
+ "title": "title (with parens)"
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "[foo*bar\\]]: my_(url) \"title (with parens)\""
+ }
+ ]
+ },
+ {
"type": "paragraph",
"content": [
{
@@ -3965,6 +4007,20 @@
"type": "doc",
"content": [
{
+ "type": "referenceDefinition",
+ "attrs": {
+ "identifier": "foo bar",
+ "url": "my url",
+ "title": "title"
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "[foo bar]: my url \"title\""
+ }
+ ]
+ },
+ {
"type": "paragraph",
"content": [
{
@@ -3992,6 +4048,20 @@
"type": "doc",
"content": [
{
+ "type": "referenceDefinition",
+ "attrs": {
+ "identifier": "foo",
+ "url": "/url",
+ "title": "\ntitle\nline1\nline2\n"
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "[foo]: /url \"\ntitle\nline1\nline2\n\""
+ }
+ ]
+ },
+ {
"type": "paragraph",
"content": [
{
@@ -4052,6 +4122,20 @@
"type": "doc",
"content": [
{
+ "type": "referenceDefinition",
+ "attrs": {
+ "identifier": "foo",
+ "url": "/url",
+ "title": null
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "[foo]: /url"
+ }
+ ]
+ },
+ {
"type": "paragraph",
"content": [
{
@@ -4103,6 +4187,20 @@
"type": "doc",
"content": [
{
+ "type": "referenceDefinition",
+ "attrs": {
+ "identifier": "foo",
+ "url": "",
+ "title": null
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "[foo]: "
+ }
+ ]
+ },
+ {
"type": "paragraph",
"content": [
{
@@ -4154,6 +4252,20 @@
"type": "doc",
"content": [
{
+ "type": "referenceDefinition",
+ "attrs": {
+ "identifier": "foo",
+ "url": "/url\\bar*baz",
+ "title": "foo\"bar\\baz"
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "[foo]: /url\\bar*baz \"foo\"bar\\baz\""
+ }
+ ]
+ },
+ {
"type": "paragraph",
"content": [
{
@@ -4200,6 +4312,20 @@
"text": "foo"
}
]
+ },
+ {
+ "type": "referenceDefinition",
+ "attrs": {
+ "identifier": "foo",
+ "url": "url",
+ "title": null
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "[foo]: url"
+ }
+ ]
}
]
}
@@ -4227,6 +4353,34 @@
"text": "foo"
}
]
+ },
+ {
+ "type": "referenceDefinition",
+ "attrs": {
+ "identifier": "foo",
+ "url": "first",
+ "title": null
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "[foo]: first"
+ }
+ ]
+ },
+ {
+ "type": "referenceDefinition",
+ "attrs": {
+ "identifier": "foo",
+ "url": "second",
+ "title": null
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "[foo]: second"
+ }
+ ]
}
]
}
@@ -4235,6 +4389,20 @@
"type": "doc",
"content": [
{
+ "type": "referenceDefinition",
+ "attrs": {
+ "identifier": "foo",
+ "url": "/url",
+ "title": null
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "[foo]: /url"
+ }
+ ]
+ },
+ {
"type": "paragraph",
"content": [
{
@@ -4262,6 +4430,20 @@
"type": "doc",
"content": [
{
+ "type": "referenceDefinition",
+ "attrs": {
+ "identifier": "αγω",
+ "url": "/φου",
+ "title": null
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "[αγω]: /φου"
+ }
+ ]
+ },
+ {
"type": "paragraph",
"content": [
{
@@ -4289,7 +4471,18 @@
"type": "doc",
"content": [
{
- "type": "paragraph"
+ "type": "referenceDefinition",
+ "attrs": {
+ "identifier": "foo",
+ "url": "/url",
+ "title": null
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "[foo]: /url"
+ }
+ ]
}
]
}
@@ -4298,6 +4491,20 @@
"type": "doc",
"content": [
{
+ "type": "referenceDefinition",
+ "attrs": {
+ "identifier": "foo",
+ "url": "/url",
+ "title": null
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "[foo]: /url"
+ }
+ ]
+ },
+ {
"type": "paragraph",
"content": [
{
@@ -4328,6 +4535,20 @@
"type": "doc",
"content": [
{
+ "type": "referenceDefinition",
+ "attrs": {
+ "identifier": "foo",
+ "url": "/url",
+ "title": null
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "[foo]: /url"
+ }
+ ]
+ },
+ {
"type": "paragraph",
"content": [
{
@@ -4447,6 +4668,20 @@
]
},
{
+ "type": "referenceDefinition",
+ "attrs": {
+ "identifier": "foo",
+ "url": "/url",
+ "title": null
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "[foo]: /url"
+ }
+ ]
+ },
+ {
"type": "blockquote",
"attrs": {
"multiline": false
@@ -4470,6 +4705,20 @@
"type": "doc",
"content": [
{
+ "type": "referenceDefinition",
+ "attrs": {
+ "identifier": "foo",
+ "url": "/url",
+ "title": null
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "[foo]: /url"
+ }
+ ]
+ },
+ {
"type": "heading",
"attrs": {
"level": 1
@@ -4509,6 +4758,20 @@
"type": "doc",
"content": [
{
+ "type": "referenceDefinition",
+ "attrs": {
+ "identifier": "foo",
+ "url": "/url",
+ "title": null
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "[foo]: /url"
+ }
+ ]
+ },
+ {
"type": "paragraph",
"content": [
{
@@ -4540,6 +4803,48 @@
"type": "doc",
"content": [
{
+ "type": "referenceDefinition",
+ "attrs": {
+ "identifier": "foo",
+ "url": "/foo-url",
+ "title": "foo"
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "[foo]: /foo-url \"foo\""
+ }
+ ]
+ },
+ {
+ "type": "referenceDefinition",
+ "attrs": {
+ "identifier": "bar",
+ "url": "/bar-url",
+ "title": "bar"
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "[bar]: /bar-url \"bar\""
+ }
+ ]
+ },
+ {
+ "type": "referenceDefinition",
+ "attrs": {
+ "identifier": "baz",
+ "url": "/baz-url",
+ "title": null
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "[baz]: /baz-url"
+ }
+ ]
+ },
+ {
"type": "paragraph",
"content": [
{
@@ -4634,7 +4939,18 @@
},
"content": [
{
- "type": "paragraph"
+ "type": "referenceDefinition",
+ "attrs": {
+ "identifier": "foo",
+ "url": "/url",
+ "title": null
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "[foo]: /url"
+ }
+ ]
}
]
}
@@ -4645,7 +4961,18 @@
"type": "doc",
"content": [
{
- "type": "paragraph"
+ "type": "referenceDefinition",
+ "attrs": {
+ "identifier": "foo",
+ "url": "/url",
+ "title": null
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "[foo]: /url"
+ }
+ ]
}
]
}
@@ -9572,6 +9899,20 @@
"text": "b"
}
]
+ },
+ {
+ "type": "referenceDefinition",
+ "attrs": {
+ "identifier": "ref",
+ "url": "/url",
+ "title": null
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "[ref]: /url"
+ }
+ ]
}
]
},
@@ -10416,6 +10757,20 @@
"text": "foo"
}
]
+ },
+ {
+ "type": "referenceDefinition",
+ "attrs": {
+ "identifier": "foo",
+ "url": "/bar*",
+ "title": "ti*tle"
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "[foo]: /bar* \"ti*tle\""
+ }
+ ]
}
]
}
@@ -10588,6 +10943,20 @@
"text": "foo"
}
]
+ },
+ {
+ "type": "referenceDefinition",
+ "attrs": {
+ "identifier": "foo",
+ "url": "/föö",
+ "title": "föö"
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "[foo]: /föö \"föö\""
+ }
+ ]
}
]
}
@@ -15592,6 +15961,20 @@
"text": "foo"
}
]
+ },
+ {
+ "type": "referenceDefinition",
+ "attrs": {
+ "identifier": "bar",
+ "url": "/url",
+ "title": "title"
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "[bar]: /url \"title\""
+ }
+ ]
}
]
}
@@ -15619,6 +16002,20 @@
"text": "link [foo [bar]]"
}
]
+ },
+ {
+ "type": "referenceDefinition",
+ "attrs": {
+ "identifier": "ref",
+ "url": "/uri",
+ "title": null
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "[ref]: /uri"
+ }
+ ]
}
]
}
@@ -15646,6 +16043,20 @@
"text": "link [bar"
}
]
+ },
+ {
+ "type": "referenceDefinition",
+ "attrs": {
+ "identifier": "ref",
+ "url": "/uri",
+ "title": null
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "[ref]: /uri"
+ }
+ ]
}
]
}
@@ -15739,6 +16150,20 @@
"text": "#"
}
]
+ },
+ {
+ "type": "referenceDefinition",
+ "attrs": {
+ "identifier": "ref",
+ "url": "/uri",
+ "title": null
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "[ref]: /uri"
+ }
+ ]
}
]
}
@@ -15772,6 +16197,20 @@
]
}
]
+ },
+ {
+ "type": "referenceDefinition",
+ "attrs": {
+ "identifier": "ref",
+ "url": "/uri",
+ "title": null
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "[ref]: /uri"
+ }
+ ]
}
]
}
@@ -15823,6 +16262,20 @@
"text": "ref"
}
]
+ },
+ {
+ "type": "referenceDefinition",
+ "attrs": {
+ "identifier": "ref",
+ "url": "/uri",
+ "title": null
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "[ref]: /uri"
+ }
+ ]
}
]
}
@@ -15886,6 +16339,20 @@
"text": "ref"
}
]
+ },
+ {
+ "type": "referenceDefinition",
+ "attrs": {
+ "identifier": "ref",
+ "url": "/uri",
+ "title": null
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "[ref]: /uri"
+ }
+ ]
}
]
}
@@ -15917,6 +16384,20 @@
"text": "foo*"
}
]
+ },
+ {
+ "type": "referenceDefinition",
+ "attrs": {
+ "identifier": "ref",
+ "url": "/uri",
+ "title": null
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "[ref]: /uri"
+ }
+ ]
}
]
}
@@ -15944,6 +16425,20 @@
"text": "foo *bar"
}
]
+ },
+ {
+ "type": "referenceDefinition",
+ "attrs": {
+ "identifier": "ref",
+ "url": "/uri",
+ "title": null
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "[ref]: /uri"
+ }
+ ]
}
]
}
@@ -15959,6 +16454,20 @@
"text": "[foo "
}
]
+ },
+ {
+ "type": "referenceDefinition",
+ "attrs": {
+ "identifier": "ref",
+ "url": "/uri",
+ "title": null
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "[ref]: /uri"
+ }
+ ]
}
]
}
@@ -15983,6 +16492,20 @@
"text": "][ref]"
}
]
+ },
+ {
+ "type": "referenceDefinition",
+ "attrs": {
+ "identifier": "ref",
+ "url": "/uri",
+ "title": null
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "[ref]: /uri"
+ }
+ ]
}
]
}
@@ -16014,6 +16537,20 @@
"text": "http://example.com/?search=][ref]"
}
]
+ },
+ {
+ "type": "referenceDefinition",
+ "attrs": {
+ "identifier": "ref",
+ "url": "/uri",
+ "title": null
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "[ref]: /uri"
+ }
+ ]
}
]
}
@@ -16041,6 +16578,20 @@
"text": "foo"
}
]
+ },
+ {
+ "type": "referenceDefinition",
+ "attrs": {
+ "identifier": "bar",
+ "url": "/url",
+ "title": "title"
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "[bar]: /url \"title\""
+ }
+ ]
}
]
}
@@ -16072,6 +16623,20 @@
"text": " is a Russian word."
}
]
+ },
+ {
+ "type": "referenceDefinition",
+ "attrs": {
+ "identifier": "толпой",
+ "url": "/url",
+ "title": null
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "[толпой]: /url"
+ }
+ ]
}
]
}
@@ -16080,6 +16645,20 @@
"type": "doc",
"content": [
{
+ "type": "referenceDefinition",
+ "attrs": {
+ "identifier": "foo bar",
+ "url": "/url",
+ "title": null
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "[foo bar]: /url"
+ }
+ ]
+ },
+ {
"type": "paragraph",
"content": [
{
@@ -16130,6 +16709,20 @@
"text": "bar"
}
]
+ },
+ {
+ "type": "referenceDefinition",
+ "attrs": {
+ "identifier": "bar",
+ "url": "/url",
+ "title": "title"
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "[bar]: /url \"title\""
+ }
+ ]
}
]
}
@@ -16161,6 +16754,20 @@
"text": "bar"
}
]
+ },
+ {
+ "type": "referenceDefinition",
+ "attrs": {
+ "identifier": "bar",
+ "url": "/url",
+ "title": "title"
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "[bar]: /url \"title\""
+ }
+ ]
}
]
}
@@ -16169,6 +16776,34 @@
"type": "doc",
"content": [
{
+ "type": "referenceDefinition",
+ "attrs": {
+ "identifier": "foo",
+ "url": "/url1",
+ "title": null
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "[foo]: /url1"
+ }
+ ]
+ },
+ {
+ "type": "referenceDefinition",
+ "attrs": {
+ "identifier": "foo",
+ "url": "/url2",
+ "title": null
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "[foo]: /url2"
+ }
+ ]
+ },
+ {
"type": "paragraph",
"content": [
{
@@ -16203,6 +16838,20 @@
"text": "[bar][foo!]"
}
]
+ },
+ {
+ "type": "referenceDefinition",
+ "attrs": {
+ "identifier": "foo!",
+ "url": "/url",
+ "title": null
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "[foo!]: /url"
+ }
+ ]
}
]
}
@@ -16302,6 +16951,20 @@
"text": "foo"
}
]
+ },
+ {
+ "type": "referenceDefinition",
+ "attrs": {
+ "identifier": "ref\\[",
+ "url": "/uri",
+ "title": null
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "[ref\\[]: /uri"
+ }
+ ]
}
]
}
@@ -16310,6 +16973,20 @@
"type": "doc",
"content": [
{
+ "type": "referenceDefinition",
+ "attrs": {
+ "identifier": "bar\\\\",
+ "url": "/uri",
+ "title": null
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "[bar\\\\]: /uri"
+ }
+ ]
+ },
+ {
"type": "paragraph",
"content": [
{
@@ -16404,6 +17081,20 @@
"text": "foo"
}
]
+ },
+ {
+ "type": "referenceDefinition",
+ "attrs": {
+ "identifier": "foo",
+ "url": "/url",
+ "title": "title"
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "[foo]: /url \"title\""
+ }
+ ]
}
]
}
@@ -16450,6 +17141,20 @@
"text": " bar"
}
]
+ },
+ {
+ "type": "referenceDefinition",
+ "attrs": {
+ "identifier": "*foo* bar",
+ "url": "/url",
+ "title": "title"
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "[*foo* bar]: /url \"title\""
+ }
+ ]
}
]
}
@@ -16477,6 +17182,20 @@
"text": "Foo"
}
]
+ },
+ {
+ "type": "referenceDefinition",
+ "attrs": {
+ "identifier": "foo",
+ "url": "/url",
+ "title": "title"
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "[foo]: /url \"title\""
+ }
+ ]
}
]
}
@@ -16508,6 +17227,20 @@
"text": "\n[]"
}
]
+ },
+ {
+ "type": "referenceDefinition",
+ "attrs": {
+ "identifier": "foo",
+ "url": "/url",
+ "title": "title"
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "[foo]: /url \"title\""
+ }
+ ]
}
]
}
@@ -16535,6 +17268,20 @@
"text": "foo"
}
]
+ },
+ {
+ "type": "referenceDefinition",
+ "attrs": {
+ "identifier": "foo",
+ "url": "/url",
+ "title": "title"
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "[foo]: /url \"title\""
+ }
+ ]
}
]
}
@@ -16581,6 +17328,20 @@
"text": " bar"
}
]
+ },
+ {
+ "type": "referenceDefinition",
+ "attrs": {
+ "identifier": "*foo* bar",
+ "url": "/url",
+ "title": "title"
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "[*foo* bar]: /url \"title\""
+ }
+ ]
}
]
}
@@ -16635,6 +17396,20 @@
"text": "]"
}
]
+ },
+ {
+ "type": "referenceDefinition",
+ "attrs": {
+ "identifier": "*foo* bar",
+ "url": "/url",
+ "title": "title"
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "[*foo* bar]: /url \"title\""
+ }
+ ]
}
]
}
@@ -16666,6 +17441,20 @@
"text": "foo"
}
]
+ },
+ {
+ "type": "referenceDefinition",
+ "attrs": {
+ "identifier": "foo",
+ "url": "/url",
+ "title": null
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "[foo]: /url"
+ }
+ ]
}
]
}
@@ -16693,6 +17482,20 @@
"text": "Foo"
}
]
+ },
+ {
+ "type": "referenceDefinition",
+ "attrs": {
+ "identifier": "foo",
+ "url": "/url",
+ "title": "title"
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "[foo]: /url \"title\""
+ }
+ ]
}
]
}
@@ -16724,6 +17527,20 @@
"text": " bar"
}
]
+ },
+ {
+ "type": "referenceDefinition",
+ "attrs": {
+ "identifier": "foo",
+ "url": "/url",
+ "title": null
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "[foo]: /url"
+ }
+ ]
}
]
}
@@ -16739,6 +17556,20 @@
"text": "[foo]"
}
]
+ },
+ {
+ "type": "referenceDefinition",
+ "attrs": {
+ "identifier": "foo",
+ "url": "/url",
+ "title": "title"
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "[foo]: /url \"title\""
+ }
+ ]
}
]
}
@@ -16747,6 +17578,20 @@
"type": "doc",
"content": [
{
+ "type": "referenceDefinition",
+ "attrs": {
+ "identifier": "foo*",
+ "url": "/url",
+ "title": null
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "[foo*]: /url"
+ }
+ ]
+ },
+ {
"type": "paragraph",
"content": [
{
@@ -16797,6 +17642,34 @@
"text": "foo"
}
]
+ },
+ {
+ "type": "referenceDefinition",
+ "attrs": {
+ "identifier": "foo",
+ "url": "/url1",
+ "title": null
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "[foo]: /url1"
+ }
+ ]
+ },
+ {
+ "type": "referenceDefinition",
+ "attrs": {
+ "identifier": "bar",
+ "url": "/url2",
+ "title": null
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "[bar]: /url2"
+ }
+ ]
}
]
}
@@ -16824,6 +17697,20 @@
"text": "foo"
}
]
+ },
+ {
+ "type": "referenceDefinition",
+ "attrs": {
+ "identifier": "foo",
+ "url": "/url1",
+ "title": null
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "[foo]: /url1"
+ }
+ ]
}
]
}
@@ -16851,6 +17738,20 @@
"text": "foo"
}
]
+ },
+ {
+ "type": "referenceDefinition",
+ "attrs": {
+ "identifier": "foo",
+ "url": "/url1",
+ "title": null
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "[foo]: /url1"
+ }
+ ]
}
]
}
@@ -16882,6 +17783,20 @@
"text": "(not a link)"
}
]
+ },
+ {
+ "type": "referenceDefinition",
+ "attrs": {
+ "identifier": "foo",
+ "url": "/url1",
+ "title": null
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "[foo]: /url1"
+ }
+ ]
}
]
}
@@ -16913,6 +17828,20 @@
"text": "bar"
}
]
+ },
+ {
+ "type": "referenceDefinition",
+ "attrs": {
+ "identifier": "baz",
+ "url": "/url",
+ "title": null
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "[baz]: /url"
+ }
+ ]
}
]
}
@@ -16956,6 +17885,34 @@
"text": "baz"
}
]
+ },
+ {
+ "type": "referenceDefinition",
+ "attrs": {
+ "identifier": "baz",
+ "url": "/url1",
+ "title": null
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "[baz]: /url1"
+ }
+ ]
+ },
+ {
+ "type": "referenceDefinition",
+ "attrs": {
+ "identifier": "bar",
+ "url": "/url2",
+ "title": null
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "[bar]: /url2"
+ }
+ ]
}
]
}
@@ -16987,6 +17944,34 @@
"text": "bar"
}
]
+ },
+ {
+ "type": "referenceDefinition",
+ "attrs": {
+ "identifier": "baz",
+ "url": "/url1",
+ "title": null
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "[baz]: /url1"
+ }
+ ]
+ },
+ {
+ "type": "referenceDefinition",
+ "attrs": {
+ "identifier": "foo",
+ "url": "/url2",
+ "title": null
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "[foo]: /url2"
+ }
+ ]
}
]
}
@@ -17029,6 +18014,20 @@
}
}
]
+ },
+ {
+ "type": "referenceDefinition",
+ "attrs": {
+ "identifier": "foo *bar*",
+ "url": "train.jpg",
+ "title": "train & tracks"
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "[foo *bar*]: train.jpg \"train & tracks\""
+ }
+ ]
}
]
}
@@ -17092,6 +18091,20 @@
}
}
]
+ },
+ {
+ "type": "referenceDefinition",
+ "attrs": {
+ "identifier": "foo *bar*",
+ "url": "train.jpg",
+ "title": "train & tracks"
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "[foo *bar*]: train.jpg \"train & tracks\""
+ }
+ ]
}
]
}
@@ -17113,6 +18126,20 @@
}
}
]
+ },
+ {
+ "type": "referenceDefinition",
+ "attrs": {
+ "identifier": "foobar",
+ "url": "train.jpg",
+ "title": "train & tracks"
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "[foobar]: train.jpg \"train & tracks\""
+ }
+ ]
}
]
}
@@ -17222,6 +18249,20 @@
}
}
]
+ },
+ {
+ "type": "referenceDefinition",
+ "attrs": {
+ "identifier": "bar",
+ "url": "/url",
+ "title": null
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "[bar]: /url"
+ }
+ ]
}
]
}
@@ -17243,6 +18284,20 @@
}
}
]
+ },
+ {
+ "type": "referenceDefinition",
+ "attrs": {
+ "identifier": "bar",
+ "url": "/url",
+ "title": null
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "[bar]: /url"
+ }
+ ]
}
]
}
@@ -17264,6 +18319,20 @@
}
}
]
+ },
+ {
+ "type": "referenceDefinition",
+ "attrs": {
+ "identifier": "foo",
+ "url": "/url",
+ "title": "title"
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "[foo]: /url \"title\""
+ }
+ ]
}
]
}
@@ -17285,6 +18354,20 @@
}
}
]
+ },
+ {
+ "type": "referenceDefinition",
+ "attrs": {
+ "identifier": "*foo* bar",
+ "url": "/url",
+ "title": "title"
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "[*foo* bar]: /url \"title\""
+ }
+ ]
}
]
}
@@ -17306,6 +18389,20 @@
}
}
]
+ },
+ {
+ "type": "referenceDefinition",
+ "attrs": {
+ "identifier": "foo",
+ "url": "/url",
+ "title": "title"
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "[foo]: /url \"title\""
+ }
+ ]
}
]
}
@@ -17331,6 +18428,20 @@
"text": "\n[]"
}
]
+ },
+ {
+ "type": "referenceDefinition",
+ "attrs": {
+ "identifier": "foo",
+ "url": "/url",
+ "title": "title"
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "[foo]: /url \"title\""
+ }
+ ]
}
]
}
@@ -17352,6 +18463,20 @@
}
}
]
+ },
+ {
+ "type": "referenceDefinition",
+ "attrs": {
+ "identifier": "foo",
+ "url": "/url",
+ "title": "title"
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "[foo]: /url \"title\""
+ }
+ ]
}
]
}
@@ -17373,6 +18498,20 @@
}
}
]
+ },
+ {
+ "type": "referenceDefinition",
+ "attrs": {
+ "identifier": "*foo* bar",
+ "url": "/url",
+ "title": "title"
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "[*foo* bar]: /url \"title\""
+ }
+ ]
}
]
}
@@ -17418,6 +18557,20 @@
}
}
]
+ },
+ {
+ "type": "referenceDefinition",
+ "attrs": {
+ "identifier": "foo",
+ "url": "/url",
+ "title": "title"
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "[foo]: /url \"title\""
+ }
+ ]
}
]
}
@@ -17433,6 +18586,20 @@
"text": "![foo]"
}
]
+ },
+ {
+ "type": "referenceDefinition",
+ "attrs": {
+ "identifier": "foo",
+ "url": "/url",
+ "title": "title"
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "[foo]: /url \"title\""
+ }
+ ]
}
]
}
@@ -17464,6 +18631,20 @@
"text": "foo"
}
]
+ },
+ {
+ "type": "referenceDefinition",
+ "attrs": {
+ "identifier": "foo",
+ "url": "/url",
+ "title": "title"
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "[foo]: /url \"title\""
+ }
+ ]
}
]
}
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index 0bc643b40a3..a71a1cc40e6 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -584,12 +584,12 @@ module API
end
end
- def log_artifact_size(file)
+ def log_artifact_file_size(file)
Gitlab::ApplicationContext.push(artifact: file.model)
end
def present_artifacts_file!(file, **args)
- log_artifact_size(file) if file
+ log_artifact_file_size(file) if file
present_carrierwave_file!(file, **args)
end
diff --git a/lib/gitlab/diff/file.rb b/lib/gitlab/diff/file.rb
index 8c55652da43..5583c896803 100644
--- a/lib/gitlab/diff/file.rb
+++ b/lib/gitlab/diff/file.rb
@@ -41,8 +41,7 @@ module Gitlab
@unfolded = false
# Ensure items are collected in the the batch
- new_blob_lazy
- old_blob_lazy
+ add_blobs_to_batch_loader
end
def use_semantic_ipynb_diff?
@@ -382,6 +381,11 @@ module Gitlab
file_path.ends_with?('.ipynb')
end
+ def add_blobs_to_batch_loader
+ new_blob_lazy
+ old_blob_lazy
+ end
+
private
def diffable_by_attribute?
diff --git a/lib/gitlab/memory/reports_daemon.rb b/lib/gitlab/memory/reports_daemon.rb
index 779c19b3a1e..8788bee27a6 100644
--- a/lib/gitlab/memory/reports_daemon.rb
+++ b/lib/gitlab/memory/reports_daemon.rb
@@ -36,12 +36,16 @@ module Gitlab
sleep interval_with_jitter
reports.select(&:active?).each do |report|
- tms = Benchmark.measure do
- report.run
- end
+ start_monotonic_time = Gitlab::Metrics::System.monotonic_time
+ start_thread_cpu_time = Gitlab::Metrics::System.thread_cpu_time
- log_report(report_label(report), tms)
- @report_duration_counter.increment({ report: report_label(report) }, tms.real)
+ report.run
+
+ cpu_s = Gitlab::Metrics::System.thread_cpu_duration(start_thread_cpu_time)
+ duration_s = Gitlab::Metrics::System.monotonic_time - start_monotonic_time
+
+ log_report(label: report_label(report), cpu_s: cpu_s, duration_s: duration_s)
+ @report_duration_counter.increment({ report: report_label(report) }, duration_s)
sleep sleep_between_reports_s
end
@@ -58,15 +62,14 @@ module Gitlab
sleep_s + rand(sleep_max_delta_s)
end
- def log_report(report_label, tms)
+ def log_report(label:, duration_s:, cpu_s:)
Gitlab::AppLogger.info(
message: 'finished',
pid: $$,
worker_id: worker_id,
- perf_report: report_label,
- duration_s: tms.real.round(2),
- cpu_s: tms.utime.round(2),
- sys_cpu_s: tms.stime.round(2)
+ perf_report: label,
+ duration_s: duration_s.round(2),
+ cpu_s: cpu_s.round(2)
)
end
diff --git a/lib/gitlab/usage_data_counters/hll_redis_counter.rb b/lib/gitlab/usage_data_counters/hll_redis_counter.rb
index 40581bda81b..4647b19554d 100644
--- a/lib/gitlab/usage_data_counters/hll_redis_counter.rb
+++ b/lib/gitlab/usage_data_counters/hll_redis_counter.rb
@@ -114,6 +114,10 @@ module Gitlab
@categories ||= known_events.map { |event| event[:category] }.uniq
end
+ def categories_collected_from_metrics_definitions
+ CATEGORIES_COLLECTED_FROM_METRICS_DEFINITIONS
+ end
+
# @param category [String] the category name
# @return [Array<String>] list of event names for given category
def events_for_category(category)
@@ -164,7 +168,7 @@ module Gitlab
def categories_pending_migration
if ::Feature.enabled?(:use_redis_hll_instrumentation_classes)
- (categories - CATEGORIES_COLLECTED_FROM_METRICS_DEFINITIONS)
+ (categories - categories_collected_from_metrics_definitions)
else
categories
end
diff --git a/lib/gitlab/usage_data_counters/known_events/common.yml b/lib/gitlab/usage_data_counters/known_events/common.yml
index 88c9f44c165..d647573ec9f 100644
--- a/lib/gitlab/usage_data_counters/known_events/common.yml
+++ b/lib/gitlab/usage_data_counters/known_events/common.yml
@@ -1,25 +1,5 @@
---
# Compliance category
-- name: g_compliance_dashboard
- redis_slot: compliance
- category: compliance
- aggregation: weekly
-- name: g_compliance_audit_events
- category: compliance
- redis_slot: compliance
- aggregation: weekly
-- name: i_compliance_audit_events
- category: compliance
- redis_slot: compliance
- aggregation: weekly
-- name: i_compliance_credential_inventory
- category: compliance
- redis_slot: compliance
- aggregation: weekly
-- name: a_compliance_audit_events_api
- category: compliance
- redis_slot: compliance
- aggregation: weekly
- name: g_edit_by_web_ide
category: ide_edit
redis_slot: edit
diff --git a/lib/gitlab/utils/batch_loader.rb b/lib/gitlab/utils/batch_loader.rb
new file mode 100644
index 00000000000..67ade0633e2
--- /dev/null
+++ b/lib/gitlab/utils/batch_loader.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Utils
+ module BatchLoader
+ # Clears batched items under the specified batch key
+ # https://github.com/exAspArk/batch-loader#batch-key
+ def self.clear_key(batch_key)
+ return if ::BatchLoader::Executor.current.nil?
+
+ items_to_clear = ::BatchLoader::Executor.current.items_by_block.select do |k, v|
+ # The Hash key here is [source_location, batch_key], so we just check k[1]
+ k[1] == batch_key
+ end
+
+ items_to_clear.each do |k, v|
+ ::BatchLoader::Executor.current.items_by_block.delete(k)
+ ::BatchLoader::Executor.current.loaded_values_by_block.delete(k)
+ end
+ end
+ end
+ end
+end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 958d0a7489f..4dc897d3ddf 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -11694,9 +11694,21 @@ msgstr ""
msgid "DastConfig|Enable DAST to automatically test for vulnerabilities in your project's running application, website, or API, in the CI/CD pipeline. Configuration changes must be applied to your .gitlab-ci.yml file to take effect. For details of all configuration options, see the %{linkStart}GitLab DAST documentation%{linkEnd}."
msgstr ""
+msgid "DastConfig|Enabled"
+msgstr ""
+
msgid "DastConfig|Generate code snippet"
msgstr ""
+msgid "DastConfig|Last scan triggered %{runTimeAgo} in pipeline "
+msgstr ""
+
+msgid "DastConfig|No previous scans found for this project"
+msgstr ""
+
+msgid "DastConfig|Not enabled"
+msgstr ""
+
msgid "DastProfiles|A passive scan monitors all HTTP messages (requests and responses) sent to the target. An active scan attacks the target to find potential vulnerabilities."
msgstr ""
@@ -28580,6 +28592,9 @@ msgstr ""
msgid "PipelineSchedules|None"
msgstr ""
+msgid "PipelineSchedules|Only the owner of a pipeline schedule can make changes to it. Do you want to take ownership of this schedule?"
+msgstr ""
+
msgid "PipelineSchedules|Provide a short description for this pipeline"
msgstr ""
@@ -28592,6 +28607,9 @@ msgstr ""
msgid "PipelineSchedules|Variables"
msgstr ""
+msgid "PipelineSchedule|Take ownership to edit"
+msgstr ""
+
msgid "PipelineSource|API"
msgstr ""
diff --git a/package.json b/package.json
index 4ba8caee76e..d30b3f803c9 100644
--- a/package.json
+++ b/package.json
@@ -91,7 +91,6 @@
"@tiptap/vue-2": "^2.0.0-beta.84",
"apollo-upload-client": "15.0.0",
"autosize": "^5.0.1",
- "aws-sdk": "^2.637.0",
"axios": "^0.24.0",
"babel-loader": "^8.2.5",
"babel-plugin-lodash": "^3.3.4",
diff --git a/spec/controllers/projects/commit_controller_spec.rb b/spec/controllers/projects/commit_controller_spec.rb
index 59b2168c41a..edb07bbdce6 100644
--- a/spec/controllers/projects/commit_controller_spec.rb
+++ b/spec/controllers/projects/commit_controller_spec.rb
@@ -82,6 +82,22 @@ RSpec.describe Projects::CommitController do
expect(response).to be_successful
end
+ it 'only loads blobs in the current page' do
+ stub_feature_flags(async_commit_diff_files: false)
+ stub_const('Projects::CommitController::COMMIT_DIFFS_PER_PAGE', 1)
+
+ commit = project.commit('1a0b36b3cdad1d2ee32457c102a8c0b7056fa863')
+
+ expect_next_instance_of(Repository) do |repository|
+ # This commit contains 3 changed files but we expect only the blobs for the first one to be loaded
+ expect(repository).to receive(:blobs_at).with([[commit.id, '.gitignore']], anything).and_call_original
+ end
+
+ go(id: commit.id)
+
+ expect(response).to be_ok
+ end
+
shared_examples "export as" do |format|
it "does generally work" do
go(id: commit.id, format: format)
diff --git a/spec/controllers/projects/compare_controller_spec.rb b/spec/controllers/projects/compare_controller_spec.rb
index e6e0307d0ca..6ed6f7017e3 100644
--- a/spec/controllers/projects/compare_controller_spec.rb
+++ b/spec/controllers/projects/compare_controller_spec.rb
@@ -226,8 +226,8 @@ RSpec.describe Projects::CompareController do
context 'when page is valid' do
let(:from_project_id) { nil }
- let(:from_ref) { '08f22f25' }
- let(:to_ref) { '66eceea0' }
+ let(:from_ref) { '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9' }
+ let(:to_ref) { '5937ac0a7beb003549fc5fd26fc247adbce4a52e' }
let(:page) { 1 }
it 'shows the diff' do
@@ -237,6 +237,21 @@ RSpec.describe Projects::CompareController do
expect(assigns(:diffs).diff_files.first).to be_present
expect(assigns(:commits).length).to be >= 1
end
+
+ it 'only loads blobs in the current page' do
+ stub_const('Projects::CompareController::COMMIT_DIFFS_PER_PAGE', 1)
+
+ expect_next_instance_of(Repository) do |repository|
+ # This comparison contains 4 changed files but we expect only the blobs for the first one to be loaded
+ expect(repository).to receive(:blobs_at).with(
+ contain_exactly([from_ref, '.gitmodules'], [to_ref, '.gitmodules']), anything
+ ).and_call_original
+ end
+
+ show_request
+
+ expect(response).to be_successful
+ end
end
context 'when page is not valid' do
diff --git a/spec/events/projects/project_transfered_event_spec.rb b/spec/events/projects/project_transfered_event_spec.rb
new file mode 100644
index 00000000000..51c8bf4765f
--- /dev/null
+++ b/spec/events/projects/project_transfered_event_spec.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Projects::ProjectTransferedEvent do
+ where(:data, :valid) do
+ valid_event = {
+ project_id: 1,
+ old_namespace_id: 2,
+ old_root_namespace_id: 3,
+ new_namespace_id: 4,
+ new_root_namespace_id: 5
+ }
+
+ # All combinations of missing keys
+ with_missing_keys = 0.upto(valid_event.size - 1)
+ .flat_map { |size| valid_event.keys.combination(size).to_a }
+ .map { |keys| [valid_event.slice(*keys), false] }
+
+ [
+ [valid_event, true],
+ *with_missing_keys,
+ [{ project_id: 'foo', namespace_id: 2 }, false],
+ [{ project_id: 1, namespace_id: 'foo' }, false],
+ [{ project_id: [], namespace_id: 2 }, false],
+ [{ project_id: 1, namespace_id: [] }, false],
+ [{ project_id: {}, namespace_id: 2 }, false],
+ [{ project_id: 1, namespace_id: {} }, false],
+ ['foo', false],
+ [123, false],
+ [[], false]
+ ]
+ end
+
+ with_them do
+ it 'validates data' do
+ constructor = -> { described_class.new(data: data) }
+
+ if valid
+ expect { constructor.call }.not_to raise_error
+ else
+ expect { constructor.call }.to raise_error(Gitlab::EventStore::InvalidEvent)
+ end
+ end
+ end
+end
diff --git a/spec/features/projects/pipeline_schedules_spec.rb b/spec/features/projects/pipeline_schedules_spec.rb
index 8cf6d5bd29b..0711a30e974 100644
--- a/spec/features/projects/pipeline_schedules_spec.rb
+++ b/spec/features/projects/pipeline_schedules_spec.rb
@@ -109,7 +109,12 @@ RSpec.describe 'Pipeline Schedules', :js do
end
it 'changes ownership of the pipeline' do
- click_link 'Take ownership'
+ click_button 'Take ownership'
+
+ page.within('#pipeline-take-ownership-modal') do
+ click_link 'Take ownership'
+ end
+
page.within('.pipeline-schedule-table-row') do
expect(page).not_to have_content('No owner')
expect(page).to have_link('Sidney Jones')
diff --git a/spec/frontend/content_editor/remark_markdown_processing_spec.js b/spec/frontend/content_editor/remark_markdown_processing_spec.js
index 48adceaab58..ddf49a4c18e 100644
--- a/spec/frontend/content_editor/remark_markdown_processing_spec.js
+++ b/spec/frontend/content_editor/remark_markdown_processing_spec.js
@@ -15,6 +15,7 @@ import Link from '~/content_editor/extensions/link';
import ListItem from '~/content_editor/extensions/list_item';
import OrderedList from '~/content_editor/extensions/ordered_list';
import Paragraph from '~/content_editor/extensions/paragraph';
+import ReferenceDefinition from '~/content_editor/extensions/reference_definition';
import Sourcemap from '~/content_editor/extensions/sourcemap';
import Strike from '~/content_editor/extensions/strike';
import Table from '~/content_editor/extensions/table';
@@ -45,6 +46,7 @@ const tiptapEditor = createTestEditor({
Link,
ListItem,
OrderedList,
+ ReferenceDefinition,
Sourcemap,
Strike,
Table,
@@ -78,6 +80,7 @@ const {
listItem,
orderedList,
pre,
+ referenceDefinition,
strike,
table,
tableRow,
@@ -105,6 +108,7 @@ const {
listItem: { nodeType: ListItem.name },
orderedList: { nodeType: OrderedList.name },
paragraph: { nodeType: Paragraph.name },
+ referenceDefinition: { nodeType: ReferenceDefinition.name },
strike: { nodeType: Strike.name },
table: { nodeType: Table.name },
tableCell: { nodeType: TableCell.name },
@@ -1079,6 +1083,32 @@ _world_.
),
),
},
+ {
+ markdown: `
+[GitLab][gitlab-url]
+
+[gitlab-url]: https://gitlab.com "GitLab"
+
+ `,
+ expectedDoc: doc(
+ paragraph(
+ source('[GitLab][gitlab-url]'),
+ link(
+ { ...source('[GitLab][gitlab-url]'), href: 'https://gitlab.com', title: 'GitLab' },
+ 'GitLab',
+ ),
+ ),
+ referenceDefinition(
+ {
+ ...source('[gitlab-url]: https://gitlab.com "GitLab"'),
+ identifier: 'gitlab-url',
+ url: 'https://gitlab.com',
+ title: 'GitLab',
+ },
+ '[gitlab-url]: https://gitlab.com "GitLab"',
+ ),
+ ),
+ },
];
const runOnly = examples.find((example) => example.only === true);
diff --git a/spec/frontend/content_editor/render_html_and_json_for_all_examples.js b/spec/frontend/content_editor/render_html_and_json_for_all_examples.js
index 116a26cf7d5..782158810dd 100644
--- a/spec/frontend/content_editor/render_html_and_json_for_all_examples.js
+++ b/spec/frontend/content_editor/render_html_and_json_for_all_examples.js
@@ -26,6 +26,7 @@ import Italic from '~/content_editor/extensions/italic';
import Link from '~/content_editor/extensions/link';
import ListItem from '~/content_editor/extensions/list_item';
import OrderedList from '~/content_editor/extensions/ordered_list';
+import ReferenceDefinition from '~/content_editor/extensions/reference_definition';
import Strike from '~/content_editor/extensions/strike';
import Table from '~/content_editor/extensions/table';
import TableCell from '~/content_editor/extensions/table_cell';
@@ -63,6 +64,7 @@ const tiptapEditor = createTestEditor({
Link,
ListItem,
OrderedList,
+ ReferenceDefinition,
Strike,
Table,
TableCell,
diff --git a/spec/frontend/content_editor/services/markdown_serializer_spec.js b/spec/frontend/content_editor/services/markdown_serializer_spec.js
index 29f1f219971..422eb3f311b 100644
--- a/spec/frontend/content_editor/services/markdown_serializer_spec.js
+++ b/spec/frontend/content_editor/services/markdown_serializer_spec.js
@@ -24,6 +24,7 @@ import Link from '~/content_editor/extensions/link';
import ListItem from '~/content_editor/extensions/list_item';
import OrderedList from '~/content_editor/extensions/ordered_list';
import Paragraph from '~/content_editor/extensions/paragraph';
+import ReferenceDefinition from '~/content_editor/extensions/reference_definition';
import Sourcemap from '~/content_editor/extensions/sourcemap';
import Strike from '~/content_editor/extensions/strike';
import Table from '~/content_editor/extensions/table';
@@ -63,6 +64,7 @@ const tiptapEditor = createTestEditor({
Link,
ListItem,
OrderedList,
+ ReferenceDefinition,
Sourcemap,
Strike,
Table,
@@ -104,6 +106,7 @@ const {
listItem,
orderedList,
paragraph,
+ referenceDefinition,
strike,
table,
tableCell,
@@ -139,6 +142,7 @@ const {
listItem: { nodeType: ListItem.name },
orderedList: { nodeType: OrderedList.name },
paragraph: { nodeType: Paragraph.name },
+ referenceDefinition: { nodeType: ReferenceDefinition.name },
strike: { markType: Strike.name },
table: { nodeType: Table.name },
tableCell: { nodeType: TableCell.name },
@@ -1163,6 +1167,38 @@ Oranges are orange [^1]
);
});
+ it('correctly serializes reference definition', () => {
+ expect(
+ serialize(
+ referenceDefinition('[gitlab]: https://gitlab.com'),
+ referenceDefinition('[foobar]: foobar.com'),
+ ),
+ ).toBe(
+ `
+[gitlab]: https://gitlab.com
+[foobar]: foobar.com`.trimLeft(),
+ );
+ });
+
+ it('correctly adds a space between a reference definition and a block content', () => {
+ expect(
+ serialize(
+ paragraph('paragraph'),
+ referenceDefinition('[gitlab]: https://gitlab.com'),
+ referenceDefinition('[foobar]: foobar.com'),
+ heading({ level: 2 }, 'heading'),
+ ),
+ ).toBe(
+ `
+paragraph
+
+[gitlab]: https://gitlab.com
+[foobar]: foobar.com
+
+## heading`.trimLeft(),
+ );
+ });
+
const defaultEditAction = (initialContent) => {
tiptapEditor.chain().setContent(initialContent.toJSON()).insertContent(' modified').run();
};
diff --git a/spec/frontend/lib/gfm/index_spec.js b/spec/frontend/lib/gfm/index_spec.js
index b722315d63a..624744b9a92 100644
--- a/spec/frontend/lib/gfm/index_spec.js
+++ b/spec/frontend/lib/gfm/index_spec.js
@@ -96,28 +96,60 @@ describe('gfm', () => {
);
});
});
- });
- describe('when skipping the rendering of code blocks', () => {
- it('transforms code nodes into codeblock html tags', async () => {
- const result = await markdownToAST(
- `
+ describe('when skipping the rendering of code blocks', () => {
+ it('transforms code nodes into codeblock html tags', async () => {
+ const result = await markdownToAST(
+ `
\`\`\`javascript
console.log('Hola');
\`\`\`\
`,
- ['code'],
- );
+ ['code'],
+ );
- expectInRoot(
- result,
- expect.objectContaining({
- tagName: 'codeblock',
- properties: {
- language: 'javascript',
- },
- }),
- );
+ expectInRoot(
+ result,
+ expect.objectContaining({
+ tagName: 'codeblock',
+ properties: {
+ language: 'javascript',
+ },
+ }),
+ );
+ });
+ });
+
+ describe('when skipping the rendering of reference definitions', () => {
+ it('transforms code nodes into codeblock html tags', async () => {
+ const result = await markdownToAST(
+ `
+[gitlab][gitlab]
+
+[gitlab]: https://gitlab.com "GitLab"
+ `,
+ ['definition'],
+ );
+
+ expectInRoot(
+ result,
+ expect.objectContaining({
+ type: 'element',
+ tagName: 'referencedefinition',
+ properties: {
+ identifier: 'gitlab',
+ title: 'GitLab',
+ url: 'https://gitlab.com',
+ },
+ children: [
+ {
+ type: 'text',
+ value: '[gitlab]: https://gitlab.com "GitLab"',
+ },
+ ],
+ }),
+ );
+ });
});
});
});
diff --git a/spec/frontend/pipeline_schedules/components/take_ownership_modal_spec.js b/spec/frontend/pipeline_schedules/components/take_ownership_modal_spec.js
new file mode 100644
index 00000000000..23bfdef13c6
--- /dev/null
+++ b/spec/frontend/pipeline_schedules/components/take_ownership_modal_spec.js
@@ -0,0 +1,54 @@
+import { GlModal } from '@gitlab/ui';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import TakeOwnershipModal from '~/pipeline_schedules/components/take_ownership_modal.vue';
+
+describe('Take ownership modal', () => {
+ let wrapper;
+ const url = `/root/job-log-tester/-/pipeline_schedules/3/take_ownership`;
+
+ const createComponent = (props = {}) => {
+ wrapper = shallowMountExtended(TakeOwnershipModal, {
+ propsData: {
+ ownershipUrl: url,
+ ...props,
+ },
+ });
+ };
+
+ const findModal = () => wrapper.findComponent(GlModal);
+
+ beforeEach(() => {
+ createComponent();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('has a primary action set to a url and a post data-method', () => {
+ const actionPrimary = findModal().props('actionPrimary');
+
+ expect(actionPrimary.attributes).toEqual(
+ expect.objectContaining([
+ {
+ category: 'primary',
+ variant: 'confirm',
+ href: url,
+ 'data-method': 'post',
+ },
+ ]),
+ );
+ });
+
+ it('shows a take ownership message', () => {
+ expect(findModal().text()).toBe(
+ 'Only the owner of a pipeline schedule can make changes to it. Do you want to take ownership of this schedule?',
+ );
+ });
+
+ it('emits the cancel event when clicking on cancel', async () => {
+ findModal().vm.$emit('cancel');
+
+ expect(findModal().emitted('cancel')).toBeTruthy();
+ });
+});
diff --git a/spec/frontend_integration/content_editor/content_editor_integration_spec.js b/spec/frontend_integration/content_editor/content_editor_integration_spec.js
index 89b8d8d6d94..4d400a383e3 100644
--- a/spec/frontend_integration/content_editor/content_editor_integration_spec.js
+++ b/spec/frontend_integration/content_editor/content_editor_integration_spec.js
@@ -61,29 +61,38 @@ describe('content_editor', () => {
});
});
- it('renders footnote ids alongside the footnote definition', async () => {
- buildWrapper();
-
- renderMarkdown.mockResolvedValue(`
- <p data-sourcepos="3:1-3:56" dir="auto">
- This reference tag is a mix of letters and numbers. <sup class="footnote-ref"><a href="#fn-footnote-2717" id="fnref-footnote-2717" data-footnote-ref="">2</a></sup>
- </p>
- <section class="footnotes" data-footnotes>
- <ol>
- <li id="fn-footnote-2717">
- <p data-sourcepos="6:7-6:31">This is another footnote. <a href="#fnref-footnote-2717" aria-label="Back to content" class="footnote-backref" data-footnote-backref=""><gl-emoji title="leftwards arrow with hook" data-name="leftwards_arrow_with_hook" data-unicode-version="1.1">↩</gl-emoji></a></p>
- </li>
- </ol>
- </section>
- `);
+ describe('when preserveUnchangedMarkdown feature flag is enabled', () => {
+ beforeEach(() => {
+ gon.features = { preserveUnchangedMarkdown: true };
+ });
+ afterEach(() => {
+ gon.features = { preserveUnchangedMarkdown: false };
+ });
- await contentEditorService.setSerializedContent(`
- This reference tag is a mix of letters and numbers [^footnote].
+ it('processes and renders footnote ids alongside the footnote definition', async () => {
+ buildWrapper();
- [^footnote]: This is another footnote.
+ await contentEditorService.setSerializedContent(`
+This reference tag is a mix of letters and numbers [^footnote].
+
+[^footnote]: This is another footnote.
`);
- await nextTick();
+ await nextTick();
+
+ expect(wrapper.text()).toContain('footnote: This is another footnote');
+ });
+
+ it('processes and displays reference definitions', async () => {
+ buildWrapper();
+
+ await contentEditorService.setSerializedContent(`
+[GitLab][gitlab]
- expect(wrapper.text()).toContain('footnote: This is another footnote');
+[gitlab]: https://gitlab.com
+ `);
+ await nextTick();
+
+ expect(wrapper.find('pre').text()).toContain('[gitlab]: https://gitlab.com');
+ });
});
});
diff --git a/spec/helpers/commits_helper_spec.rb b/spec/helpers/commits_helper_spec.rb
index b5b572e9719..b27954de0d4 100644
--- a/spec/helpers/commits_helper_spec.rb
+++ b/spec/helpers/commits_helper_spec.rb
@@ -153,16 +153,24 @@ RSpec.describe CommitsHelper do
end
describe "#conditionally_paginate_diff_files" do
- let(:diffs_collection) { instance_double(Gitlab::Diff::FileCollection::Commit, diff_files: diff_files) }
- let(:diff_files) { Gitlab::Git::DiffCollection.new(files) }
- let(:page) { nil }
+ let_it_be(:project) { create(:project, :repository) }
+
+ let(:diffs_collection) { instance_double(Gitlab::Diff::FileCollection::Commit, diff_files: decorated_diff_files, project: project) }
+ let(:decorated_diff_files) do
+ diffs.map do |diff|
+ Gitlab::Diff::File.new(diff, repository: project.repository)
+ end
+ end
+ let(:diffs) { Gitlab::Git::DiffCollection.new(files) }
let(:files) do
Array.new(85).map do
{ too_large: false, diff: "" }
end
end
+ let(:page) { nil }
+
subject { helper.conditionally_paginate_diff_files(diffs_collection, paginate: paginate, page: page, per: Projects::CommitController::COMMIT_DIFFS_PER_PAGE) }
before do
@@ -203,8 +211,8 @@ RSpec.describe CommitsHelper do
context "pagination is disabled" do
let(:paginate) { false }
- it "returns a standard DiffCollection" do
- expect(subject).to be_a(Gitlab::Git::DiffCollection)
+ it "returns the unpaginated collection" do
+ expect(subject.size).to eq(85)
end
end
end
diff --git a/spec/lib/gitlab/auth/o_auth/user_spec.rb b/spec/lib/gitlab/auth/o_auth/user_spec.rb
index 5f5e7f211f8..b160f322fb8 100644
--- a/spec/lib/gitlab/auth/o_auth/user_spec.rb
+++ b/spec/lib/gitlab/auth/o_auth/user_spec.rb
@@ -727,6 +727,7 @@ RSpec.describe Gitlab::Auth::OAuth::User do
context 'signup with linked omniauth and LDAP account' do
before do
stub_omniauth_config(auto_link_ldap_user: true)
+ stub_ldap_setting(enabled: true)
allow(ldap_user).to receive(:uid) { uid }
allow(ldap_user).to receive(:username) { uid }
allow(ldap_user).to receive(:email) { ['johndoe@example.com', 'john2@example.com'] }
diff --git a/spec/lib/gitlab/memory/reports_daemon_spec.rb b/spec/lib/gitlab/memory/reports_daemon_spec.rb
index 75334834c5b..6af317f2771 100644
--- a/spec/lib/gitlab/memory/reports_daemon_spec.rb
+++ b/spec/lib/gitlab/memory/reports_daemon_spec.rb
@@ -32,7 +32,6 @@ RSpec.describe Gitlab::Memory::ReportsDaemon do
hash_including(
:duration_s,
:cpu_s,
- :sys_cpu_s,
message: 'finished',
pid: Process.pid,
worker_id: 'worker_1',
diff --git a/spec/lib/gitlab/usage_data_counters/hll_redis_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/hll_redis_counter_spec.rb
index 54d49b432f4..d45ee0113e8 100644
--- a/spec/lib/gitlab/usage_data_counters/hll_redis_counter_spec.rb
+++ b/spec/lib/gitlab/usage_data_counters/hll_redis_counter_spec.rb
@@ -82,7 +82,7 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s
stub_feature_flags(use_redis_hll_instrumentation_classes: true)
expect(described_class.unique_events_data.keys)
- .not_to include(*described_class::CATEGORIES_COLLECTED_FROM_METRICS_DEFINITIONS)
+ .not_to include(*described_class.categories_collected_from_metrics_definitions)
end
end
@@ -91,18 +91,17 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s
stub_feature_flags(use_redis_hll_instrumentation_classes: false)
expect(described_class.unique_events_data.keys)
- .to include(*described_class::CATEGORIES_COLLECTED_FROM_METRICS_DEFINITIONS)
+ .to include(*described_class.categories_collected_from_metrics_definitions)
end
end
end
end
describe '.categories' do
- it 'gets all unique category names' do
- expect(described_class.categories).to contain_exactly(
+ it 'gets CE unique category names' do
+ expect(described_class.categories).to include(
'deploy_token_packages',
'user_packages',
- 'compliance',
'ecosystem',
'analytics',
'ide_edit',
@@ -483,7 +482,7 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s
describe '.weekly_redis_keys' do
using RSpec::Parameterized::TableSyntax
- let(:weekly_event) { 'g_compliance_dashboard' }
+ let(:weekly_event) { 'i_search_total' }
let(:redis_event) { described_class.send(:event_for, weekly_event) }
subject(:weekly_redis_keys) { described_class.send(:weekly_redis_keys, events: [redis_event], start_date: DateTime.parse(start_date), end_date: DateTime.parse(end_date)) }
@@ -493,13 +492,13 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s
'2020-12-21' | '2020-12-20' | []
'2020-12-21' | '2020-11-21' | []
'2021-01-01' | '2020-12-28' | []
- '2020-12-21' | '2020-12-28' | ['g_{compliance}_dashboard-2020-52']
- '2020-12-21' | '2021-01-01' | ['g_{compliance}_dashboard-2020-52']
- '2020-12-27' | '2021-01-01' | ['g_{compliance}_dashboard-2020-52']
- '2020-12-26' | '2021-01-04' | ['g_{compliance}_dashboard-2020-52', 'g_{compliance}_dashboard-2020-53']
- '2020-12-26' | '2021-01-11' | ['g_{compliance}_dashboard-2020-52', 'g_{compliance}_dashboard-2020-53', 'g_{compliance}_dashboard-2021-01']
- '2020-12-26' | '2021-01-17' | ['g_{compliance}_dashboard-2020-52', 'g_{compliance}_dashboard-2020-53', 'g_{compliance}_dashboard-2021-01']
- '2020-12-26' | '2021-01-18' | ['g_{compliance}_dashboard-2020-52', 'g_{compliance}_dashboard-2020-53', 'g_{compliance}_dashboard-2021-01', 'g_{compliance}_dashboard-2021-02']
+ '2020-12-21' | '2020-12-28' | ['i_{search}_total-2020-52']
+ '2020-12-21' | '2021-01-01' | ['i_{search}_total-2020-52']
+ '2020-12-27' | '2021-01-01' | ['i_{search}_total-2020-52']
+ '2020-12-26' | '2021-01-04' | ['i_{search}_total-2020-52', 'i_{search}_total-2020-53']
+ '2020-12-26' | '2021-01-11' | ['i_{search}_total-2020-52', 'i_{search}_total-2020-53', 'i_{search}_total-2021-01']
+ '2020-12-26' | '2021-01-17' | ['i_{search}_total-2020-52', 'i_{search}_total-2020-53', 'i_{search}_total-2021-01']
+ '2020-12-26' | '2021-01-18' | ['i_{search}_total-2020-52', 'i_{search}_total-2020-53', 'i_{search}_total-2021-01', 'i_{search}_total-2021-02']
end
with_them do
diff --git a/spec/lib/gitlab/utils/batch_loader_spec.rb b/spec/lib/gitlab/utils/batch_loader_spec.rb
new file mode 100644
index 00000000000..c1f6d6df07a
--- /dev/null
+++ b/spec/lib/gitlab/utils/batch_loader_spec.rb
@@ -0,0 +1,82 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+require 'batch-loader'
+
+RSpec.describe Gitlab::Utils::BatchLoader do
+ let(:stubbed_loader) do
+ double( # rubocop:disable RSpec/VerifiedDoubles
+ 'Loader',
+ load_lazy_method: [],
+ load_lazy_method_same_batch_key: [],
+ load_lazy_method_other_batch_key: []
+ )
+ end
+
+ let(:test_module) do
+ Module.new do
+ def self.lazy_method(id)
+ BatchLoader.for(id).batch(key: :my_batch_name) do |ids, loader|
+ stubbed_loader.load_lazy_method(ids)
+
+ ids.each { |id| loader.call(id, id) }
+ end
+ end
+
+ def self.lazy_method_same_batch_key(id)
+ BatchLoader.for(id).batch(key: :my_batch_name) do |ids, loader|
+ stubbed_loader.load_lazy_method_same_batch_key(ids)
+
+ ids.each { |id| loader.call(id, id) }
+ end
+ end
+
+ def self.lazy_method_other_batch_key(id)
+ BatchLoader.for(id).batch(key: :other_batch_name) do |ids, loader|
+ stubbed_loader.load_lazy_method_other_batch_key(ids)
+
+ ids.each { |id| loader.call(id, id) }
+ end
+ end
+ end
+ end
+
+ before do
+ BatchLoader::Executor.clear_current
+ allow(test_module).to receive(:stubbed_loader).and_return(stubbed_loader)
+ end
+
+ describe '.clear_key' do
+ it 'clears batched items which match the specified batch key' do
+ test_module.lazy_method(1)
+ test_module.lazy_method_same_batch_key(2)
+ test_module.lazy_method_other_batch_key(3)
+
+ described_class.clear_key(:my_batch_name)
+
+ test_module.lazy_method(4).to_i
+ test_module.lazy_method_same_batch_key(5).to_i
+ test_module.lazy_method_other_batch_key(6).to_i
+
+ expect(stubbed_loader).to have_received(:load_lazy_method).with([4])
+ expect(stubbed_loader).to have_received(:load_lazy_method_same_batch_key).with([5])
+ expect(stubbed_loader).to have_received(:load_lazy_method_other_batch_key).with([3, 6])
+ end
+
+ it 'clears loaded values which match the specified batch key' do
+ test_module.lazy_method(1).to_i
+ test_module.lazy_method_same_batch_key(2).to_i
+ test_module.lazy_method_other_batch_key(3).to_i
+
+ described_class.clear_key(:my_batch_name)
+
+ test_module.lazy_method(1).to_i
+ test_module.lazy_method_same_batch_key(2).to_i
+ test_module.lazy_method_other_batch_key(3).to_i
+
+ expect(stubbed_loader).to have_received(:load_lazy_method).with([1]).twice
+ expect(stubbed_loader).to have_received(:load_lazy_method_same_batch_key).with([2]).twice
+ expect(stubbed_loader).to have_received(:load_lazy_method_other_batch_key).with([3])
+ end
+ end
+end
diff --git a/spec/requests/api/ci/runner/jobs_artifacts_spec.rb b/spec/requests/api/ci/runner/jobs_artifacts_spec.rb
index 5767fa4326e..cd8c3dd2806 100644
--- a/spec/requests/api/ci/runner/jobs_artifacts_spec.rb
+++ b/spec/requests/api/ci/runner/jobs_artifacts_spec.rb
@@ -844,7 +844,13 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
'Content-Disposition' => %q(attachment; filename="ci_build_artifacts.zip"; filename*=UTF-8''ci_build_artifacts.zip) }
end
+ before do
+ allow(Gitlab::ApplicationContext).to receive(:push).and_call_original
+ end
+
it 'downloads artifacts' do
+ expect(Gitlab::ApplicationContext).to receive(:push).with(artifact: an_instance_of(Ci::JobArtifact)).once.and_call_original
+
download_artifact
expect(response).to have_gitlab_http_status(:ok)
diff --git a/spec/services/projects/transfer_service_spec.rb b/spec/services/projects/transfer_service_spec.rb
index ecf9f92d74f..49073931c02 100644
--- a/spec/services/projects/transfer_service_spec.rb
+++ b/spec/services/projects/transfer_service_spec.rb
@@ -64,6 +64,28 @@ RSpec.describe Projects::TransferService do
expect(project.namespace).to eq(group)
end
+ context 'EventStore' do
+ let(:group) do
+ create(:group, :nested).tap { |g| g.add_owner(user) }
+ end
+
+ let(:target) do
+ create(:group, :nested).tap { |g| g.add_owner(user) }
+ end
+
+ it 'publishes a ProjectTransferedEvent' do
+ expect { execute_transfer }
+ .to publish_event(Projects::ProjectTransferedEvent)
+ .with(
+ project_id: kind_of(Numeric),
+ old_namespace_id: group.id,
+ old_root_namespace_id: group.parent_id,
+ new_namespace_id: target.id,
+ new_root_namespace_id: target.parent_id
+ )
+ end
+ end
+
context 'when project has an associated project namespace' do
it 'keeps project namespace in sync with project' do
transfer_result = execute_transfer
@@ -299,6 +321,11 @@ RSpec.describe Projects::TransferService do
)
end
+ it 'does not publish a ProjectTransferedEvent' do
+ expect { attempt_project_transfer }
+ .not_to publish_event(Projects::ProjectTransferedEvent)
+ end
+
context 'when project has pending builds', :sidekiq_inline do
let!(:other_project) { create(:project) }
let!(:pending_build) { create(:ci_pending_build, project: project.reload) }
diff --git a/spec/services/protected_branches/cache_service_spec.rb b/spec/services/protected_branches/cache_service_spec.rb
new file mode 100644
index 00000000000..4c05d6dcbd9
--- /dev/null
+++ b/spec/services/protected_branches/cache_service_spec.rb
@@ -0,0 +1,81 @@
+# frozen_string_literal: true
+# rubocop:disable Style/RedundantFetchBlock
+#
+require 'spec_helper'
+
+RSpec.describe ProtectedBranches::CacheService, :clean_gitlab_redis_cache do
+ subject(:service) { described_class.new(project, user) }
+
+ let_it_be(:project) { create(:project) }
+ let_it_be(:user) { project.first_owner }
+
+ let(:immediate_expiration) { 0 }
+
+ describe '#fetch' do
+ it 'caches the value' do
+ expect(service.fetch('main') { true }).to eq(true)
+ expect(service.fetch('not-found') { false }).to eq(false)
+
+ # Uses cached values
+ expect(service.fetch('main') { false }).to eq(true)
+ expect(service.fetch('not-found') { true }).to eq(false)
+ end
+
+ it 'sets expiry on the key' do
+ stub_const("#{described_class.name}::CACHE_EXPIRE_IN", immediate_expiration)
+
+ expect(service.fetch('main') { true }).to eq(true)
+ expect(service.fetch('not-found') { false }).to eq(false)
+
+ expect(service.fetch('main') { false }).to eq(false)
+ expect(service.fetch('not-found') { true }).to eq(true)
+ end
+
+ it 'does not set an expiry on the key after the hash is already created' do
+ expect(service.fetch('main') { true }).to eq(true)
+
+ stub_const("#{described_class.name}::CACHE_EXPIRE_IN", immediate_expiration)
+
+ expect(service.fetch('not-found') { false }).to eq(false)
+
+ expect(service.fetch('main') { false }).to eq(true)
+ expect(service.fetch('not-found') { true }).to eq(false)
+ end
+
+ context 'when CACHE_LIMIT is exceeded' do
+ before do
+ stub_const("#{described_class.name}::CACHE_LIMIT", 2)
+ end
+
+ it 'recreates cache' do
+ expect(service.fetch('main') { true }).to eq(true)
+ expect(service.fetch('not-found') { false }).to eq(false)
+
+ # Uses cached values
+ expect(service.fetch('main') { false }).to eq(true)
+ expect(service.fetch('not-found') { true }).to eq(false)
+
+ # Overflow
+ expect(service.fetch('new-branch') { true }).to eq(true)
+
+ # Refreshes values
+ expect(service.fetch('main') { false }).to eq(false)
+ expect(service.fetch('not-found') { true }).to eq(true)
+ end
+ end
+ end
+
+ describe '#refresh' do
+ it 'clears cached values' do
+ expect(service.fetch('main') { true }).to eq(true)
+ expect(service.fetch('not-found') { false }).to eq(false)
+
+ service.refresh
+
+ # Recreates cache
+ expect(service.fetch('main') { false }).to eq(false)
+ expect(service.fetch('not-found') { true }).to eq(true)
+ end
+ end
+end
+# rubocop:enable Style/RedundantFetchBlock
diff --git a/spec/services/protected_branches/create_service_spec.rb b/spec/services/protected_branches/create_service_spec.rb
index 3ac42d41377..5a371b68f0c 100644
--- a/spec/services/protected_branches/create_service_spec.rb
+++ b/spec/services/protected_branches/create_service_spec.rb
@@ -24,6 +24,14 @@ RSpec.describe ProtectedBranches::CreateService do
expect(project.protected_branches.last.merge_access_levels.map(&:access_level)).to eq([Gitlab::Access::MAINTAINER])
end
+ it 'refreshes the cache' do
+ expect_next_instance_of(ProtectedBranches::CacheService) do |cache_service|
+ expect(cache_service).to receive(:refresh)
+ end
+
+ service.execute
+ end
+
context 'when protecting a branch with a name that contains HTML tags' do
let(:name) { 'foo<b>bar<\b>' }
diff --git a/spec/services/protected_branches/destroy_service_spec.rb b/spec/services/protected_branches/destroy_service_spec.rb
index 4e55c72f312..492b91dbb8b 100644
--- a/spec/services/protected_branches/destroy_service_spec.rb
+++ b/spec/services/protected_branches/destroy_service_spec.rb
@@ -16,6 +16,14 @@ RSpec.describe ProtectedBranches::DestroyService do
expect(protected_branch).to be_destroyed
end
+ it 'refreshes the cache' do
+ expect_next_instance_of(ProtectedBranches::CacheService) do |cache_service|
+ expect(cache_service).to receive(:refresh)
+ end
+
+ service.execute(protected_branch)
+ end
+
context 'when a policy restricts rule deletion' do
before do
policy = instance_double(ProtectedBranchPolicy, allowed?: false)
diff --git a/spec/services/protected_branches/update_service_spec.rb b/spec/services/protected_branches/update_service_spec.rb
index 4405af35c37..ee61382bd89 100644
--- a/spec/services/protected_branches/update_service_spec.rb
+++ b/spec/services/protected_branches/update_service_spec.rb
@@ -18,6 +18,14 @@ RSpec.describe ProtectedBranches::UpdateService do
expect(result.reload.name).to eq(params[:name])
end
+ it 'refreshes the cache' do
+ expect_next_instance_of(ProtectedBranches::CacheService) do |cache_service|
+ expect(cache_service).to receive(:refresh)
+ end
+
+ result
+ end
+
context 'when updating name of a protected branch to one that contains HTML tags' do
let(:new_name) { 'foo<b>bar<\b>' }
let(:result) { service.execute(protected_branch) }
diff --git a/spec/views/projects/pipeline_schedules/_pipeline_schedule.html.haml_spec.rb b/spec/views/projects/pipeline_schedules/_pipeline_schedule.html.haml_spec.rb
index e650e183bc8..37c9908af1d 100644
--- a/spec/views/projects/pipeline_schedules/_pipeline_schedule.html.haml_spec.rb
+++ b/spec/views/projects/pipeline_schedules/_pipeline_schedule.html.haml_spec.rb
@@ -28,7 +28,7 @@ RSpec.describe 'projects/pipeline_schedules/_pipeline_schedule' do
it 'non-owner can take ownership of pipeline' do
render
- expect(rendered).to have_link('Take ownership')
+ expect(rendered).to have_button('Take ownership')
end
end
@@ -42,7 +42,7 @@ RSpec.describe 'projects/pipeline_schedules/_pipeline_schedule' do
it 'owner cannot take ownership of pipeline' do
render
- expect(rendered).not_to have_link('Take ownership')
+ expect(rendered).not_to have_button('Take ownership')
end
end
end
diff --git a/yarn.lock b/yarn.lock
index ca755429f19..4d7aae26d5b 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2744,21 +2744,6 @@ autosize@^5.0.1:
resolved "https://registry.yarnpkg.com/autosize/-/autosize-5.0.1.tgz#ed269b0fa9b7eb47627048a1bb3299e99e003a0f"
integrity sha512-UIWUlE4TOVPNNj2jjrU39wI4hEYbneUypEqcyRmRFIx5CC2gNdg3rQr+Zh7/3h6egbBvm33TDQjNQKtj9Tk1HA==
-aws-sdk@^2.637.0:
- version "2.637.0"
- resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.637.0.tgz#810e25e53acf2250d35fc74498f9d4492e154217"
- integrity sha512-e7EYX5rNtQyEaleQylUtLSNKXOmvOwfifQ4bYkfF80mFsVI3DSydczLHXrqPzXoEJaS/GI/9HqVnlQcPs6Q3ew==
- dependencies:
- buffer "4.9.1"
- events "1.1.1"
- ieee754 "1.1.13"
- jmespath "0.15.0"
- querystring "0.2.0"
- sax "1.2.1"
- url "0.10.3"
- uuid "3.3.2"
- xml2js "0.4.19"
-
axios-mock-adapter@^1.15.0:
version "1.15.0"
resolved "https://registry.yarnpkg.com/axios-mock-adapter/-/axios-mock-adapter-1.15.0.tgz#fbc06825d8302c95c3334d21023bba996255d45d"
@@ -3137,7 +3122,7 @@ buffer-xor@^1.0.3:
resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9"
integrity sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=
-buffer@4.9.1, buffer@^4.3.0:
+buffer@^4.3.0:
version "4.9.1"
resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.1.tgz#6d1bb601b07a4efced97094132093027c95bc298"
integrity sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=
@@ -5444,11 +5429,6 @@ eventemitter3@^4.0.0:
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.0.tgz#d65176163887ee59f386d64c82610b696a4a74eb"
integrity sha512-qerSRB0p+UDEssxTtm6EDKcE7W4OaoisfIMl4CngyEhjpYglocpNg6UEqCvemdGhosAsg4sO2dXJOdyBifPGCg==
-events@1.1.1:
- version "1.1.1"
- resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924"
- integrity sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=
-
events@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/events/-/events-3.0.0.tgz#9a0a0dfaf62893d92b875b8f2698ca4114973e88"
@@ -6582,7 +6562,7 @@ icss-utils@^4.1.0:
dependencies:
postcss "^7.0.14"
-ieee754@1.1.13, ieee754@^1.1.13, ieee754@^1.1.4:
+ieee754@^1.1.13, ieee754@^1.1.4:
version "1.1.13"
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84"
integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==
@@ -7528,11 +7508,6 @@ jest@^26.5.2:
import-local "^3.0.2"
jest-cli "^26.5.2"
-jmespath@0.15.0:
- version "0.15.0"
- resolved "https://registry.yarnpkg.com/jmespath/-/jmespath-0.15.0.tgz#a3f222a9aae9f966f5d27c796510e28091764217"
- integrity sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc=
-
jquery.caret@^0.3.1:
version "0.3.1"
resolved "https://registry.yarnpkg.com/jquery.caret/-/jquery.caret-0.3.1.tgz#9c093318faf327eff322e826ca9f3241368bc7b8"
@@ -10598,11 +10573,6 @@ sass@^1.49.9:
immutable "^4.0.0"
source-map-js ">=0.6.2 <2.0.0"
-sax@1.2.1, sax@>=0.6.0:
- version "1.2.1"
- resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.1.tgz#7b8e656190b228e81a66aea748480d828cd2d37a"
- integrity sha1-e45lYZCyKOgaZq6nSEgNgozS03o=
-
saxes@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/saxes/-/saxes-5.0.1.tgz#eebab953fa3b7608dbe94e5dadb15c888fa6696d"
@@ -12004,14 +11974,6 @@ url-loader@^4.1.1:
mime-types "^2.1.27"
schema-utils "^3.0.0"
-url@0.10.3:
- version "0.10.3"
- resolved "https://registry.yarnpkg.com/url/-/url-0.10.3.tgz#021e4d9c7705f21bbf37d03ceb58767402774c64"
- integrity sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=
- dependencies:
- punycode "1.3.2"
- querystring "0.2.0"
-
url@^0.11.0:
version "0.11.0"
resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1"
@@ -12049,11 +12011,6 @@ utils-merge@1.0.1:
resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=
-uuid@3.3.2:
- version "3.3.2"
- resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131"
- integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==
-
uuid@8.1.0:
version "8.1.0"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.1.0.tgz#6f1536eb43249f473abc6bd58ff983da1ca30d8d"
@@ -12634,24 +12591,11 @@ xml-name-validator@^3.0.0:
resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a"
integrity sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==
-xml2js@0.4.19:
- version "0.4.19"
- resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.19.tgz#686c20f213209e94abf0d1bcf1efaa291c7827a7"
- integrity sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==
- dependencies:
- sax ">=0.6.0"
- xmlbuilder "~9.0.1"
-
xml@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/xml/-/xml-1.0.1.tgz#78ba72020029c5bc87b8a81a3cfcd74b4a2fc1e5"
integrity sha1-eLpyAgApxbyHuKgaPPzXS0ovweU=
-xmlbuilder@~9.0.1:
- version "9.0.7"
- resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d"
- integrity sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=
-
xmlchars@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb"