diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 56ca3c3b586..344a880760e 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -475,7 +475,6 @@ Style/MixinUsage: Style/MultilineIfModifier: Exclude: - 'app/helpers/snippets_helper.rb' - - 'app/models/project_wiki.rb' - 'app/services/ci/process_pipeline_service.rb' - 'lib/api/commit_statuses.rb' diff --git a/app/assets/javascripts/diffs/store/getters_versions_dropdowns.js b/app/assets/javascripts/diffs/store/getters_versions_dropdowns.js index dd682060b4b..acc8874dad8 100644 --- a/app/assets/javascripts/diffs/store/getters_versions_dropdowns.js +++ b/app/assets/javascripts/diffs/store/getters_versions_dropdowns.js @@ -39,7 +39,11 @@ export const diffCompareDropdownTargetVersions = (state, getters) => { ...v, }; }; - return [...state.mergeRequestDiffs.slice(1).map(formatVersion), baseVersion, headVersion]; + + if (gon.features?.diffCompareWithHead) { + return [...state.mergeRequestDiffs.slice(1).map(formatVersion), baseVersion, headVersion]; + } + return [...state.mergeRequestDiffs.slice(1).map(formatVersion), baseVersion]; }; export const diffCompareDropdownSourceVersions = (state, getters) => { diff --git a/app/assets/javascripts/vue_shared/components/awards_list.vue b/app/assets/javascripts/vue_shared/components/awards_list.vue index 848295cc984..c0a42e08dee 100644 --- a/app/assets/javascripts/vue_shared/components/awards_list.vue +++ b/app/assets/javascripts/vue_shared/components/awards_list.vue @@ -34,10 +34,21 @@ export default { required: false, default: '', }, + defaultAwards: { + type: Array, + required: false, + default: () => [], + }, }, computed: { + groupedDefaultAwards() { + return this.defaultAwards.reduce((obj, key) => Object.assign(obj, { [key]: [] }), {}); + }, groupedAwards() { - const { thumbsup, thumbsdown, ...rest } = groupBy(this.awards, x => x.name); + const { thumbsup, thumbsdown, ...rest } = { + ...this.groupedDefaultAwards, + ...groupBy(this.awards, x => x.name), + }; return [ ...(thumbsup ? [this.createAwardList('thumbsup', thumbsup)] : []), @@ -73,6 +84,10 @@ export default { }; }, getAwardListTitle(awardsList) { + if (!awardsList.length) { + return ''; + } + const hasReactionByCurrentUser = this.hasReactionByCurrentUser(awardsList); const TOOLTIP_NAME_COUNT = hasReactionByCurrentUser ? 9 : 10; let awardList = awardsList; diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss index 7f6542261b8..b42f0195f69 100644 --- a/app/assets/stylesheets/pages/labels.scss +++ b/app/assets/stylesheets/pages/labels.scss @@ -307,7 +307,7 @@ } .label-name { - width: 150px; + width: 200px; flex-shrink: 0; .scoped-label-wrapper, diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index b2bb49993d1..471b12aa4c4 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -26,6 +26,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo push_frontend_feature_flag(:code_navigation, @project) push_frontend_feature_flag(:widget_visibility_polling, @project, default_enabled: true) push_frontend_feature_flag(:merge_ref_head_comments, @project) + push_frontend_feature_flag(:diff_compare_with_head, @project) end before_action do diff --git a/app/models/blob.rb b/app/models/blob.rb index cdc5838797b..56f6066b576 100644 --- a/app/models/blob.rb +++ b/app/models/blob.rb @@ -129,7 +129,7 @@ class Blob < SimpleDelegator def external_storage_error? if external_storage == :lfs - !project&.lfs_enabled? + !repository.lfs_enabled? else false end diff --git a/app/models/concerns/has_repository.rb b/app/models/concerns/has_repository.rb index af7afd6604a..29d31b8bb4f 100644 --- a/app/models/concerns/has_repository.rb +++ b/app/models/concerns/has_repository.rb @@ -9,7 +9,6 @@ # needs any special behavior. module HasRepository extend ActiveSupport::Concern - include AfterCommitQueue include Referable include Gitlab::ShellAdapter include Gitlab::Utils::StrongMemoize diff --git a/app/models/concerns/has_wiki.rb b/app/models/concerns/has_wiki.rb new file mode 100644 index 00000000000..4dd72216e77 --- /dev/null +++ b/app/models/concerns/has_wiki.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +module HasWiki + extend ActiveSupport::Concern + + included do + validate :check_wiki_path_conflict + end + + def create_wiki + wiki.wiki + true + rescue Wiki::CouldNotCreateWikiError + errors.add(:base, _('Failed to create wiki')) + false + end + + def wiki + strong_memoize(:wiki) do + Wiki.for_container(self, self.owner) + end + end + + def wiki_repository_exists? + wiki.repository_exists? + end + + def after_wiki_activity + true + end + + private + + def check_wiki_path_conflict + return if path.blank? + + path_to_check = path.ends_with?('.wiki') ? path.chomp('.wiki') : "#{path}.wiki" + + if Project.in_namespace(parent_id).where(path: path_to_check).exists? || + GroupsFinder.new(nil, parent: parent_id).execute.where(path: path_to_check).exists? + errors.add(:name, _('has already been taken')) + end + end +end diff --git a/app/models/concerns/redis_cacheable.rb b/app/models/concerns/redis_cacheable.rb index 4bb4ffe2a8e..2f23467dc78 100644 --- a/app/models/concerns/redis_cacheable.rb +++ b/app/models/concerns/redis_cacheable.rb @@ -26,7 +26,7 @@ module RedisCacheable end def cache_attributes(values) - Gitlab::Redis::SharedState.with do |redis| + Gitlab::Redis::Cache.with do |redis| redis.set(cache_attribute_key, values.to_json, ex: CACHED_ATTRIBUTES_EXPIRY_TIME) end @@ -41,7 +41,7 @@ module RedisCacheable def cached_attributes strong_memoize(:cached_attributes) do - Gitlab::Redis::SharedState.with do |redis| + Gitlab::Redis::Cache.with do |redis| data = redis.get(cache_attribute_key) JSON.parse(data, symbolize_names: true) if data end diff --git a/app/models/concerns/storage/legacy_project_wiki.rb b/app/models/concerns/storage/legacy_project_wiki.rb deleted file mode 100644 index a377fa1e5de..00000000000 --- a/app/models/concerns/storage/legacy_project_wiki.rb +++ /dev/null @@ -1,11 +0,0 @@ -# frozen_string_literal: true - -module Storage - module LegacyProjectWiki - extend ActiveSupport::Concern - - def disk_path - project.disk_path + '.wiki' - end - end -end diff --git a/app/models/group.rb b/app/models/group.rb index 55a2c4ba9a9..a1e3850d362 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -15,6 +15,7 @@ class Group < Namespace include WithUploads include Gitlab::Utils::StrongMemoize include GroupAPICompatibility + include HasWiki ACCESS_REQUEST_APPROVERS_TO_BE_NOTIFIED_LIMIT = 10 diff --git a/app/models/group_wiki.rb b/app/models/group_wiki.rb new file mode 100644 index 00000000000..703ef80c121 --- /dev/null +++ b/app/models/group_wiki.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +class GroupWiki < Wiki + alias_method :group, :container + + override :storage + def storage + @storage ||= Storage::Hashed.new(container, prefix: Storage::Hashed::GROUP_REPOSITORY_PATH_PREFIX) + end + + override :repository_storage + def repository_storage + # TODO: Add table to track storage + # https://gitlab.com/gitlab-org/gitlab/-/issues/207865 + 'default' + end + + override :hashed_storage? + def hashed_storage? + true + end + + override :disk_path + def disk_path(*args, &block) + storage.disk_path + '.wiki' + end +end diff --git a/app/models/personal_snippet.rb b/app/models/personal_snippet.rb index 1b5be8698b1..197795dccfe 100644 --- a/app/models/personal_snippet.rb +++ b/app/models/personal_snippet.rb @@ -2,4 +2,8 @@ class PersonalSnippet < Snippet include WithUploads + + def skip_project_check? + true + end end diff --git a/app/models/project.rb b/app/models/project.rb index 3ff782a9643..d5c5ab99611 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -3,6 +3,7 @@ require 'carrierwave/orm/activerecord' class Project < ApplicationRecord + extend ::Gitlab::Utils::Override include Gitlab::ConfigHelper include Gitlab::VisibilityLevel include AccessRequestable @@ -18,6 +19,7 @@ class Project < ApplicationRecord include SelectForProjectAuthorization include Presentable include HasRepository + include HasWiki include Routable include GroupDescendant include Gitlab::SQL::Pattern @@ -386,7 +388,6 @@ class Project < ApplicationRecord validate :check_repository_path_availability, on: :update, if: ->(project) { project.renamed? } validate :visibility_level_allowed_by_group, if: :should_validate_visibility_level? validate :visibility_level_allowed_as_fork, if: :should_validate_visibility_level? - validate :check_wiki_path_conflict validate :validate_pages_https_only, if: -> { changes.has_key?(:pages_https_only) } validates :repository_storage, presence: true, @@ -1056,16 +1057,6 @@ class Project < ApplicationRecord self.errors.add(:visibility_level, _("%{level_name} is not allowed since the fork source project has lower visibility.") % { level_name: level_name }) end - def check_wiki_path_conflict - return if path.blank? - - path_to_check = path.ends_with?('.wiki') ? path.chomp('.wiki') : "#{path}.wiki" - - if Project.where(namespace_id: namespace_id, path: path_to_check).exists? - errors.add(:name, _('has already been taken')) - end - end - def pages_https_only return false unless Gitlab.config.pages.external_https @@ -1557,10 +1548,6 @@ class Project < ApplicationRecord create_repository(force: true) unless repository_exists? end - def wiki_repository_exists? - wiki.repository_exists? - end - # update visibility_level of forks def update_forks_visibility_level return if unlink_forks_upon_visibility_decrease_enabled? @@ -1574,20 +1561,6 @@ class Project < ApplicationRecord end end - def create_wiki - ProjectWiki.new(self, self.owner).wiki - true - rescue ProjectWiki::CouldNotCreateWikiError - errors.add(:base, _('Failed create wiki')) - false - end - - def wiki - strong_memoize(:wiki) do - ProjectWiki.new(self, self.owner) - end - end - def allowed_to_share_with_group? !namespace.share_with_group_lock end @@ -2417,6 +2390,11 @@ class Project < ApplicationRecord jira_imports.last end + override :after_wiki_activity + def after_wiki_activity + touch(:last_activity_at, :last_repository_updated_at) + end + private def find_service(services, name) diff --git a/app/models/project_wiki.rb b/app/models/project_wiki.rb index 708b45cf5f0..5df0a33dc9a 100644 --- a/app/models/project_wiki.rb +++ b/app/models/project_wiki.rb @@ -1,219 +1,17 @@ # frozen_string_literal: true -class ProjectWiki - include Storage::LegacyProjectWiki - include Gitlab::Utils::StrongMemoize +class ProjectWiki < Wiki + alias_method :project, :container - MARKUPS = { - 'Markdown' => :markdown, - 'RDoc' => :rdoc, - 'AsciiDoc' => :asciidoc, - 'Org' => :org - }.freeze unless defined?(MARKUPS) + # Project wikis are tied to the main project storage + delegate :storage, :repository_storage, :hashed_storage?, to: :container - CouldNotCreateWikiError = Class.new(StandardError) - SIDEBAR = '_sidebar' - - TITLE_ORDER = 'title' - CREATED_AT_ORDER = 'created_at' - DIRECTION_DESC = 'desc' - DIRECTION_ASC = 'asc' - - attr_reader :project, :user - - # Returns a string describing what went wrong after - # an operation fails. - attr_reader :error_message - - def initialize(project, user = nil) - @project = project - @user = user - end - - delegate :repository_storage, :hashed_storage?, to: :project - - def path - @project.path + '.wiki' - end - - def full_path - @project.full_path + '.wiki' - end - alias_method :id, :full_path - - # @deprecated use full_path when you need it for an URL route or disk_path when you want to point to the filesystem - alias_method :path_with_namespace, :full_path - - def web_url(only_path: nil) - Gitlab::UrlBuilder.build(self, only_path: only_path) - end - - def url_to_repo - ssh_url_to_repo - end - - def ssh_url_to_repo - Gitlab::RepositoryUrlBuilder.build(repository.full_path, protocol: :ssh) - end - - def http_url_to_repo - Gitlab::RepositoryUrlBuilder.build(repository.full_path, protocol: :http) - end - - def wiki_base_path - [Gitlab.config.gitlab.relative_url_root, '/', @project.full_path, '/-', '/wikis'].join('') - end - - # Returns the Gitlab::Git::Wiki object. - def wiki - strong_memoize(:wiki) do - repository.create_if_not_exists - raise CouldNotCreateWikiError unless repository_exists? - - Gitlab::Git::Wiki.new(repository.raw) - end - rescue => err - Gitlab::ErrorTracking.track_exception(err, project_wiki: { project_id: project.id, full_path: full_path, disk_path: disk_path }) - raise CouldNotCreateWikiError - end - - def repository_exists? - !!repository.exists? - end - - def has_home_page? - !!find_page('home') - end - - def empty? - list_pages(limit: 1).empty? - end - - def exists? - !empty? - end - - # Lists wiki pages of the repository. - # - # limit - max number of pages returned by the method. - # sort - criterion by which the pages are sorted. - # direction - order of the sorted pages. - # load_content - option, which specifies whether the content inside the page - # will be loaded. - # - # Returns an Array of GitLab WikiPage instances or an - # empty Array if this Wiki has no pages. - def list_pages(limit: 0, sort: nil, direction: DIRECTION_ASC, load_content: false) - wiki.list_pages( - limit: limit, - sort: sort, - direction_desc: direction == DIRECTION_DESC, - load_content: load_content - ).map do |page| - WikiPage.new(self, page) - end - end - - # Finds a page within the repository based on a tile - # or slug. - # - # title - The human readable or parameterized title of - # the page. - # - # Returns an initialized WikiPage instance or nil - def find_page(title, version = nil) - page_title, page_dir = page_title_and_dir(title) - - if page = wiki.page(title: page_title, version: version, dir: page_dir) - WikiPage.new(self, page) - end - end - - def find_sidebar(version = nil) - find_page(SIDEBAR, version) - end - - def find_file(name, version = nil) - wiki.file(name, version) - end - - def create_page(title, content, format = :markdown, message = nil) - commit = commit_details(:created, message, title) - - wiki.write_page(title, format.to_sym, content, commit) - - update_project_activity - rescue Gitlab::Git::Wiki::DuplicatePageError => e - @error_message = "Duplicate page: #{e.message}" - false - end - - def update_page(page, content:, title: nil, format: :markdown, message: nil) - commit = commit_details(:updated, message, page.title) - - wiki.update_page(page.path, title || page.name, format.to_sym, content, commit) - - update_project_activity - end - - def delete_page(page, message = nil) - return unless page - - wiki.delete_page(page.path, commit_details(:deleted, message, page.title)) - - update_project_activity - end - - def page_title_and_dir(title) - return unless title - - title_array = title.split("/") - title = title_array.pop - [title, title_array.join("/")] - end - - def repository - @repository ||= Repository.new(full_path, @project, shard: repository_storage, disk_path: disk_path, repo_type: Gitlab::GlRepository::WIKI) - end - - def default_branch - wiki.class.default_ref - end - - def ensure_repository - raise CouldNotCreateWikiError unless wiki.repository_exists? - end - - def hook_attrs - { - web_url: web_url, - git_ssh_url: ssh_url_to_repo, - git_http_url: http_url_to_repo, - path_with_namespace: full_path, - default_branch: default_branch - } - end - - private - - def commit_details(action, message = nil, title = nil) - commit_message = message.presence || default_message(action, title) - git_user = Gitlab::Git::User.from_gitlab(user) - - Gitlab::Git::Wiki::CommitDetails.new(user.id, - git_user.username, - git_user.name, - git_user.email, - commit_message) - end - - def default_message(action, title) - "#{user.username} #{action} page: #{title}" - end - - def update_project_activity - @project.touch(:last_activity_at, :last_repository_updated_at) + override :disk_path + def disk_path(*args, &block) + container.disk_path + '.wiki' end end +# TODO: Remove this once we implement ES support for group wikis. +# https://gitlab.com/gitlab-org/gitlab/-/issues/207889 ProjectWiki.prepend_if_ee('EE::ProjectWiki') diff --git a/app/models/repository.rb b/app/models/repository.rb index a9ef0504a3d..2673033ff1f 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -1120,6 +1120,17 @@ class Repository end end + # TODO: pass this in directly to `Blob` rather than delegating it to here + # + # https://gitlab.com/gitlab-org/gitlab/-/issues/201886 + def lfs_enabled? + if container.is_a?(Project) + container.lfs_enabled? + else + false # LFS is not supported for snippet or group repositories + end + end + private # TODO Genericize finder, later split this on finders by Ref or Oid diff --git a/app/models/snippet.rb b/app/models/snippet.rb index 7bff6d02910..d9e167829b0 100644 --- a/app/models/snippet.rb +++ b/app/models/snippet.rb @@ -15,6 +15,7 @@ class Snippet < ApplicationRecord include FromUnion include IgnorableColumns include HasRepository + include AfterCommitQueue extend ::Gitlab::Utils::Override MAX_FILE_COUNT = 1 diff --git a/app/models/storage/hashed.rb b/app/models/storage/hashed.rb index 3dea50ab98b..c61cd3b6b30 100644 --- a/app/models/storage/hashed.rb +++ b/app/models/storage/hashed.rb @@ -6,6 +6,7 @@ module Storage delegate :gitlab_shell, :repository_storage, to: :container REPOSITORY_PATH_PREFIX = '@hashed' + GROUP_REPOSITORY_PATH_PREFIX = '@groups' SNIPPET_REPOSITORY_PATH_PREFIX = '@snippets' POOL_PATH_PREFIX = '@pools' diff --git a/app/models/wiki.rb b/app/models/wiki.rb new file mode 100644 index 00000000000..a1f779ca282 --- /dev/null +++ b/app/models/wiki.rb @@ -0,0 +1,221 @@ +# frozen_string_literal: true + +class Wiki + extend ::Gitlab::Utils::Override + include HasRepository + include Gitlab::Utils::StrongMemoize + + MARKUPS = { # rubocop:disable Style/MultilineIfModifier + 'Markdown' => :markdown, + 'RDoc' => :rdoc, + 'AsciiDoc' => :asciidoc, + 'Org' => :org + }.freeze unless defined?(MARKUPS) + + CouldNotCreateWikiError = Class.new(StandardError) + + HOMEPAGE = 'home' + SIDEBAR = '_sidebar' + + TITLE_ORDER = 'title' + CREATED_AT_ORDER = 'created_at' + DIRECTION_DESC = 'desc' + DIRECTION_ASC = 'asc' + + attr_reader :container, :user + + # Returns a string describing what went wrong after + # an operation fails. + attr_reader :error_message + + def self.for_container(container, user = nil) + "#{container.class.name}Wiki".constantize.new(container, user) + end + + def initialize(container, user = nil) + @container = container + @user = user + end + + def path + container.path + '.wiki' + end + + # Returns the Gitlab::Git::Wiki object. + def wiki + strong_memoize(:wiki) do + repository.create_if_not_exists + raise CouldNotCreateWikiError unless repository_exists? + + Gitlab::Git::Wiki.new(repository.raw) + end + rescue => err + Gitlab::ErrorTracking.track_exception(err, wiki: { + container_type: container.class.name, + container_id: container.id, + full_path: full_path, + disk_path: disk_path + }) + + raise CouldNotCreateWikiError + end + + def has_home_page? + !!find_page(HOMEPAGE) + end + + def empty? + list_pages(limit: 1).empty? + end + + def exists? + !empty? + end + + # Lists wiki pages of the repository. + # + # limit - max number of pages returned by the method. + # sort - criterion by which the pages are sorted. + # direction - order of the sorted pages. + # load_content - option, which specifies whether the content inside the page + # will be loaded. + # + # Returns an Array of GitLab WikiPage instances or an + # empty Array if this Wiki has no pages. + def list_pages(limit: 0, sort: nil, direction: DIRECTION_ASC, load_content: false) + wiki.list_pages( + limit: limit, + sort: sort, + direction_desc: direction == DIRECTION_DESC, + load_content: load_content + ).map do |page| + WikiPage.new(self, page) + end + end + + # Finds a page within the repository based on a tile + # or slug. + # + # title - The human readable or parameterized title of + # the page. + # + # Returns an initialized WikiPage instance or nil + def find_page(title, version = nil) + page_title, page_dir = page_title_and_dir(title) + + if page = wiki.page(title: page_title, version: version, dir: page_dir) + WikiPage.new(self, page) + end + end + + def find_sidebar(version = nil) + find_page(SIDEBAR, version) + end + + def find_file(name, version = nil) + wiki.file(name, version) + end + + def create_page(title, content, format = :markdown, message = nil) + commit = commit_details(:created, message, title) + + wiki.write_page(title, format.to_sym, content, commit) + + update_container_activity + rescue Gitlab::Git::Wiki::DuplicatePageError => e + @error_message = "Duplicate page: #{e.message}" + false + end + + def update_page(page, content:, title: nil, format: :markdown, message: nil) + commit = commit_details(:updated, message, page.title) + + wiki.update_page(page.path, title || page.name, format.to_sym, content, commit) + + update_container_activity + end + + def delete_page(page, message = nil) + return unless page + + wiki.delete_page(page.path, commit_details(:deleted, message, page.title)) + + update_container_activity + end + + def page_title_and_dir(title) + return unless title + + title_array = title.split("/") + title = title_array.pop + [title, title_array.join("/")] + end + + def ensure_repository + raise CouldNotCreateWikiError unless wiki.repository_exists? + end + + def hook_attrs + { + web_url: web_url, + git_ssh_url: ssh_url_to_repo, + git_http_url: http_url_to_repo, + path_with_namespace: full_path, + default_branch: default_branch + } + end + + override :repository + def repository + @repository ||= Repository.new(full_path, container, shard: repository_storage, disk_path: disk_path, repo_type: Gitlab::GlRepository::WIKI) + end + + def repository_storage + raise NotImplementedError + end + + def hashed_storage? + raise NotImplementedError + end + + override :full_path + def full_path + container.full_path + '.wiki' + end + alias_method :id, :full_path + + # @deprecated use full_path when you need it for an URL route or disk_path when you want to point to the filesystem + alias_method :path_with_namespace, :full_path + + override :default_branch + def default_branch + wiki.class.default_ref + end + + def wiki_base_path + Gitlab.config.gitlab.relative_url_root + web_url(only_path: true).sub(%r{/#{Wiki::HOMEPAGE}\z}, '') + end + + private + + def commit_details(action, message = nil, title = nil) + commit_message = message.presence || default_message(action, title) + git_user = Gitlab::Git::User.from_gitlab(user) + + Gitlab::Git::Wiki::CommitDetails.new(user.id, + git_user.username, + git_user.name, + git_user.email, + commit_message) + end + + def default_message(action, title) + "#{user.username} #{action} page: #{title}" + end + + def update_container_activity + container.after_wiki_activity + end +end + +Wiki.prepend_if_ee('EE::Wiki') diff --git a/app/models/wiki_page.rb b/app/models/wiki_page.rb index 9c887fc87f3..06443af9371 100644 --- a/app/models/wiki_page.rb +++ b/app/models/wiki_page.rb @@ -26,7 +26,7 @@ class WikiPage def eql?(other) return false unless other.present? && other.is_a?(self.class) - slug == other.slug && wiki.project == other.wiki.project + slug == other.slug && wiki.container == other.wiki.container end alias_method :==, :eql? @@ -66,9 +66,9 @@ class WikiPage validates :content, presence: true validate :validate_path_limits, if: :title_changed? - # The GitLab ProjectWiki instance. + # The GitLab Wiki instance. attr_reader :wiki - delegate :project, to: :wiki + delegate :container, to: :wiki # The raw Gitlab::Git::WikiPage instance. attr_reader :page @@ -83,7 +83,7 @@ class WikiPage # Construct a new WikiPage # - # @param [ProjectWiki] wiki + # @param [Wiki] wiki # @param [Gitlab::Git::WikiPage] page def initialize(wiki, page = nil) @wiki = wiki @@ -195,7 +195,7 @@ class WikiPage # :content - The raw markup content. # :format - Optional symbol representing the # content format. Can be any type - # listed in the ProjectWiki::MARKUPS + # listed in the Wiki::MARKUPS # Hash. # :message - Optional commit message to set on # the new page. @@ -215,7 +215,7 @@ class WikiPage # attrs - Hash of attributes to be updated on the page. # :content - The raw markup content to replace the existing. # :format - Optional symbol representing the content format. - # See ProjectWiki::MARKUPS Hash for available formats. + # See Wiki::MARKUPS Hash for available formats. # :message - Optional commit message to set on the new version. # :last_commit_sha - Optional last commit sha to validate the page unchanged. # :title - The Title (optionally including dir) to replace existing title @@ -261,6 +261,7 @@ class WikiPage # Relative path to the partial to be used when rendering collections # of this object. def to_partial_path + # TODO: Move into shared/ with https://gitlab.com/gitlab-org/gitlab/-/issues/196054 'projects/wikis/wiki_page' end @@ -303,7 +304,7 @@ class WikiPage end def update_front_matter(attrs) - return unless Gitlab::WikiPages::FrontMatterParser.enabled?(project) + return unless Gitlab::WikiPages::FrontMatterParser.enabled?(container) return unless attrs.has_key?(:front_matter) fm_yaml = serialize_front_matter(attrs[:front_matter]) @@ -314,7 +315,7 @@ class WikiPage def parsed_content strong_memoize(:parsed_content) do - Gitlab::WikiPages::FrontMatterParser.new(raw_content, project).parse + Gitlab::WikiPages::FrontMatterParser.new(raw_content, container).parse end end diff --git a/app/policies/wiki_page_policy.rb b/app/policies/wiki_page_policy.rb index 468632c9085..f284fd9f5df 100644 --- a/app/policies/wiki_page_policy.rb +++ b/app/policies/wiki_page_policy.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true class WikiPagePolicy < BasePolicy - delegate { @subject.wiki.project } + delegate { @subject.wiki.container } rule { can?(:read_wiki) }.enable :read_wiki_page end diff --git a/changelogs/unreleased/211460-annotations-clusters-endpoint.yml b/changelogs/unreleased/211460-annotations-clusters-endpoint.yml new file mode 100644 index 00000000000..56d5bae3f26 --- /dev/null +++ b/changelogs/unreleased/211460-annotations-clusters-endpoint.yml @@ -0,0 +1,5 @@ +--- +title: Create cluster annotations API endpoint +merge_request: 29502 +author: +type: added diff --git a/changelogs/unreleased/214065-increase-width-of-label-column-on-labels-page.yml b/changelogs/unreleased/214065-increase-width-of-label-column-on-labels-page.yml new file mode 100644 index 00000000000..0e4299a6749 --- /dev/null +++ b/changelogs/unreleased/214065-increase-width-of-label-column-on-labels-page.yml @@ -0,0 +1,5 @@ +--- +title: Increase label list label column width +merge_request: 29963 +author: +type: other diff --git a/changelogs/unreleased/ff-iid-routing.yml b/changelogs/unreleased/ff-iid-routing.yml new file mode 100644 index 00000000000..73117dc2127 --- /dev/null +++ b/changelogs/unreleased/ff-iid-routing.yml @@ -0,0 +1,5 @@ +--- +title: Route to feature flags based on internal id +merge_request: 29740 +author: +type: added diff --git a/changelogs/unreleased/fix-mentions-personal-snippet.yml b/changelogs/unreleased/fix-mentions-personal-snippet.yml new file mode 100644 index 00000000000..f6f17038b0f --- /dev/null +++ b/changelogs/unreleased/fix-mentions-personal-snippet.yml @@ -0,0 +1,5 @@ +--- +title: Fix bug in personal snippets when somebody is mentioned +merge_request: 29835 +author: Sashi Kumar +type: fixed diff --git a/doc/administration/instance_limits.md b/doc/administration/instance_limits.md index 57acdec4ea2..e9a7f01c0c4 100644 --- a/doc/administration/instance_limits.md +++ b/doc/administration/instance_limits.md @@ -180,7 +180,7 @@ Plan.default.limits.update!(ci_pipeline_schedules: 100) ### Incident Management inbound alert limits -> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/14932) in GitLab 12.5. +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/17859) in GitLab 12.5. Limiting inbound alerts for an incident reduces the number of alerts (issues) that can be created within a period of time, which can help prevent overloading @@ -192,7 +192,7 @@ alerts in the following ways: ### Prometheus Alert JSON payloads -> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/14929) in GitLab 12.6. +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/19940) in GitLab 12.6. Prometheus alert payloads sent to the `notify.json` endpoint are limited to 1 MB in size. diff --git a/doc/administration/troubleshooting/elasticsearch.md b/doc/administration/troubleshooting/elasticsearch.md index c049fdf617e..a39fe4ba8c3 100644 --- a/doc/administration/troubleshooting/elasticsearch.md +++ b/doc/administration/troubleshooting/elasticsearch.md @@ -1,5 +1,8 @@ # Troubleshooting Elasticsearch +To install and configure Elasticsearch, and for common and known issues, +visit the [administrator documentation](../../integration/elasticsearch.md). + Troubleshooting Elasticsearch requires: - Knowledge of common terms. diff --git a/doc/ci/examples/authenticating-with-hashicorp-vault/index.md b/doc/ci/examples/authenticating-with-hashicorp-vault/index.md index 2986afe8284..089babc8945 100644 --- a/doc/ci/examples/authenticating-with-hashicorp-vault/index.md +++ b/doc/ci/examples/authenticating-with-hashicorp-vault/index.md @@ -20,7 +20,7 @@ You will need to replace the `vault.example.com` URL below with the URL of your ## How it works -Each job has JSON Web Token (JWT) provided as environment variable named `CI_JOB_JWT`. This JWT can be used to authenticate with Vault using the [JWT Auth](https://www.vaultproject.io/docs/auth/jwt/#jwt-authentication) method. +Each job has JSON Web Token (JWT) provided as environment variable named `CI_JOB_JWT`. This JWT can be used to authenticate with Vault using the [JWT Auth](https://www.vaultproject.io/docs/auth/jwt#jwt-authentication) method. The JWT's payload looks like this: @@ -51,7 +51,7 @@ The JWT is encoded by using RS256 and signed with your GitLab instance's OpenID You can use this JWT and your instance's JWKS endpoint (`https://gitlab.example.com/-/jwks`) to authenticate with a Vault server that is configured to allow the JWT Authentication method for authentication. -When configuring roles in Vault, you can use [bound_claims](https://www.vaultproject.io/docs/auth/jwt/#bound-claims) to match against the JWT's claims and restrict which secrets each CI job has access to. +When configuring roles in Vault, you can use [bound_claims](https://www.vaultproject.io/docs/auth/jwt#bound-claims) to match against the JWT's claims and restrict which secrets each CI job has access to. To communicate with Vault, you can use either its CLI client or perform API requests (using `curl` or another client). @@ -70,7 +70,7 @@ $ vault kv get -field=password secret/myproject/production/db real-pa$$w0rd ``` -To configure your Vault server, start by enabling the [JWT Auth](https://www.vaultproject.io/docs/auth/jwt/) method: +To configure your Vault server, start by enabling the [JWT Auth](https://www.vaultproject.io/docs/auth/jwt) method: ```shell $ vault auth enable jwt diff --git a/doc/ci/jenkins/index.md b/doc/ci/jenkins/index.md index 551e32ac816..01bf4c605a1 100644 --- a/doc/ci/jenkins/index.md +++ b/doc/ci/jenkins/index.md @@ -113,7 +113,7 @@ There are some important differences in the way Runners work in comparison to ag If you are using `gitlab.com`, you can take advantage of our [shared Runner fleet](../../user/gitlab_com/index.md#shared-runners) to run jobs without provisioning your own Runners. We are investigating making them -[available for self-managed instances](https://gitlab.com/gitlab-org/customers-gitlab-com/issues/414) +[available for self-managed instances](https://gitlab.com/groups/gitlab-org/-/epics/835) as well. ## Groovy vs. YAML diff --git a/doc/development/api_graphql_styleguide.md b/doc/development/api_graphql_styleguide.md index 036eddd7c37..f19016968d4 100644 --- a/doc/development/api_graphql_styleguide.md +++ b/doc/development/api_graphql_styleguide.md @@ -12,7 +12,7 @@ which is exposed as an API endpoint at `/api/graphql`. ## Deep Dive -In March 2019, Nick Thomas hosted a [Deep Dive](https://gitlab.com/gitlab-org/create-stage/issues/1) +In March 2019, Nick Thomas hosted a Deep Dive (GitLab team members only: `https://gitlab.com/gitlab-org/create-stage/issues/1`) on GitLab's [GraphQL API](../api/graphql/index.md) to share his domain specific knowledge with anyone who may work in this part of the code base in the future. You can find the [recording on YouTube](https://www.youtube.com/watch?v=-9L_1MWrjkg), and the slides on @@ -102,7 +102,7 @@ be `id` fields. Further reading: - [GraphQL Best Practices Guide](https://graphql.org/learn/best-practices/#nullability) -- [Using nullability in GraphQL](https://blog.apollographql.com/using-nullability-in-graphql-2254f84c4ed7) +- [Using nullability in GraphQL](https://www.apollographql.com/blog/using-nullability-in-graphql-2254f84c4ed7) ### Exposing Global IDs diff --git a/doc/development/api_styleguide.md b/doc/development/api_styleguide.md index 77e44760ef2..948644fa786 100644 --- a/doc/development/api_styleguide.md +++ b/doc/development/api_styleguide.md @@ -142,7 +142,7 @@ data](https://gitlab.com/gitlab-org/gitlab/blob/19f74903240e209736c7668132e6a5a7 for `Todo` _targets_ when returned in the Todos API. For more context and discussion about preloading see -[this merge request](https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/25711) +[this merge request](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/25711) which introduced the scope. ### Verifying with tests diff --git a/doc/development/diffs.md b/doc/development/diffs.md index f5e0032ce30..e065e0acc6f 100644 --- a/doc/development/diffs.md +++ b/doc/development/diffs.md @@ -8,7 +8,7 @@ Currently we rely on different sources to present diffs, these include: ## Deep Dive -In January 2019, Oswaldo Ferreira hosted a [Deep Dive](https://gitlab.com/gitlab-org/create-stage/issues/1) on GitLab's Diffs and Commenting on Diffs functionality to share his domain specific knowledge with anyone who may work in this part of the code base in the future. You can find the [recording on YouTube](https://www.youtube.com/watch?v=K6G3gMcFyek), and the slides on [Google Slides](https://docs.google.com/presentation/d/1bGutFH2AT3bxOPZuLMGl1ANWHqFnrxwQwjiwAZkF-TU/edit) and in [PDF](https://gitlab.com/gitlab-org/create-stage/uploads/b5ad2f336e0afcfe0f99db0af0ccc71a/). Everything covered in this deep dive was accurate as of GitLab 11.7, and while specific details may have changed since then, it should still serve as a good introduction. +In January 2019, Oswaldo Ferreira hosted a Deep Dive (GitLab team members only: `https://gitlab.com/gitlab-org/create-stage/issues/1`) on GitLab's Diffs and Commenting on Diffs functionality to share his domain specific knowledge with anyone who may work in this part of the code base in the future. You can find the [recording on YouTube](https://www.youtube.com/watch?v=K6G3gMcFyek), and the slides on [Google Slides](https://docs.google.com/presentation/d/1bGutFH2AT3bxOPZuLMGl1ANWHqFnrxwQwjiwAZkF-TU/edit) and in [PDF](https://gitlab.com/gitlab-org/create-stage/uploads/b5ad2f336e0afcfe0f99db0af0ccc71a/). Everything covered in this deep dive was accurate as of GitLab 11.7, and while specific details may have changed since then, it should still serve as a good introduction. ## Architecture overview diff --git a/doc/development/elasticsearch.md b/doc/development/elasticsearch.md index 758cecce315..185f536fc01 100644 --- a/doc/development/elasticsearch.md +++ b/doc/development/elasticsearch.md @@ -7,7 +7,7 @@ the [Elasticsearch integration documentation](../integration/elasticsearch.md#en ## Deep Dive -In June 2019, Mario de la Ossa hosted a [Deep Dive](https://gitlab.com/gitlab-org/create-stage/issues/1) on GitLab's [Elasticsearch integration](../integration/elasticsearch.md) to share his domain specific knowledge with anyone who may work in this part of the code base in the future. You can find the [recording on YouTube](https://www.youtube.com/watch?v=vrvl-tN2EaA), and the slides on [Google Slides](https://docs.google.com/presentation/d/1H-pCzI_LNrgrL5pJAIQgvLX8Ji0-jIKOg1QeJQzChug/edit) and in [PDF](https://gitlab.com/gitlab-org/create-stage/uploads/c5aa32b6b07476fa8b597004899ec538/Elasticsearch_Deep_Dive.pdf). Everything covered in this deep dive was accurate as of GitLab 12.0, and while specific details may have changed since then, it should still serve as a good introduction. +In June 2019, Mario de la Ossa hosted a Deep Dive (GitLab team members only: `https://gitlab.com/gitlab-org/create-stage/issues/1`) on GitLab's [Elasticsearch integration](../integration/elasticsearch.md) to share his domain specific knowledge with anyone who may work in this part of the code base in the future. You can find the [recording on YouTube](https://www.youtube.com/watch?v=vrvl-tN2EaA), and the slides on [Google Slides](https://docs.google.com/presentation/d/1H-pCzI_LNrgrL5pJAIQgvLX8Ji0-jIKOg1QeJQzChug/edit) and in [PDF](https://gitlab.com/gitlab-org/create-stage/uploads/c5aa32b6b07476fa8b597004899ec538/Elasticsearch_Deep_Dive.pdf). Everything covered in this deep dive was accurate as of GitLab 12.0, and while specific details may have changed since then, it should still serve as a good introduction. ## Supported Versions diff --git a/doc/development/gitaly.md b/doc/development/gitaly.md index b9db21fed12..5e5cae7228b 100644 --- a/doc/development/gitaly.md +++ b/doc/development/gitaly.md @@ -5,7 +5,7 @@ Workhorse and GitLab-Shell. ## Deep Dive -In May 2019, Bob Van Landuyt hosted a [Deep Dive](https://gitlab.com/gitlab-org/create-stage/issues/1) +In May 2019, Bob Van Landuyt hosted a Deep Dive (GitLab team members only: `https://gitlab.com/gitlab-org/create-stage/issues/1`) on GitLab's [Gitaly project](https://gitlab.com/gitlab-org/gitaly) and how to contribute to it as a Ruby developer, to share his domain specific knowledge with anyone who may work in this part of the code base in the future. diff --git a/doc/development/i18n/proofreader.md b/doc/development/i18n/proofreader.md index 1170103490b..837da349f7e 100644 --- a/doc/development/i18n/proofreader.md +++ b/doc/development/i18n/proofreader.md @@ -87,7 +87,7 @@ are very appreciative of the work done by translators and proofreaders! - Mark Minakou - [GitLab](https://gitlab.com/sandzhaj), [Crowdin](https://crowdin.com/profile/sandzhaj) - NickVolynkin - [Crowdin](https://crowdin.com/profile/NickVolynkin) - Andrey Komarov - [GitLab](https://gitlab.com/elkamarado), [Crowdin](https://crowdin.com/profile/kamarado) - - Iaroslav Postovalov - [GitLab](https://gitlab/CMDR_Tvis), [Crowdin](https://crowdin.com/profile/CMDR_Tvis) + - Iaroslav Postovalov - [GitLab](https://gitlab.com/CMDR_Tvis), [Crowdin](https://crowdin.com/profile/CMDR_Tvis) - Serbian (Cyrillic) - Proofreaders needed. - Serbian (Latin) diff --git a/doc/development/lfs.md b/doc/development/lfs.md index e64bc0f7d3a..32e2e3d1bde 100644 --- a/doc/development/lfs.md +++ b/doc/development/lfs.md @@ -2,7 +2,7 @@ ## Deep Dive -In April 2019, Francisco Javier López hosted a [Deep Dive](https://gitlab.com/gitlab-org/create-stage/issues/1) +In April 2019, Francisco Javier López hosted a Deep Dive (GitLab team members only: `https://gitlab.com/gitlab-org/create-stage/issues/1`) on GitLab's [Git LFS](../topics/git/lfs/index.md) implementation to share his domain specific knowledge with anyone who may work in this part of the code base in the future. You can find the [recording on YouTube](https://www.youtube.com/watch?v=Yyxwcksr0Qc), diff --git a/doc/development/repository_mirroring.md b/doc/development/repository_mirroring.md index 16572129124..1d4dbe88399 100644 --- a/doc/development/repository_mirroring.md +++ b/doc/development/repository_mirroring.md @@ -2,7 +2,7 @@ ## Deep Dive -In December 2018, Tiago Botelho hosted [a Deep Dive](`https://gitlab.com/gitlab-org/create-stage/issues/1`) +In December 2018, Tiago Botelho hosted a Deep Dive (GitLab team members only: `https://gitlab.com/gitlab-org/create-stage/issues/1`) on GitLab's [Pull Repository Mirroring functionality](../user/project/repository/repository_mirroring.md#pulling-from-a-remote-repository-starter) to share his domain specific knowledge with anyone who may work in this part of the code base in the future. You can find the [recording on YouTube](https://www.youtube.com/watch?v=sSZq0fpdY-Y), diff --git a/doc/development/secure_coding_guidelines.md b/doc/development/secure_coding_guidelines.md index 411ac01e475..8e34f549050 100644 --- a/doc/development/secure_coding_guidelines.md +++ b/doc/development/secure_coding_guidelines.md @@ -26,7 +26,7 @@ Improper permission handling can have significant impacts on the security of an Some situations may reveal [sensitive data](https://gitlab.com/gitlab-com/gl-infra/production/issues/477) or allow a malicious actor to perform [harmful actions](https://gitlab.com/gitlab-org/gitlab/issues/8180). The overall impact depends heavily on what resources can be accessed or modified improperly. -A common vulnerability when permission checks are missing is called [IDOR](https://www.owasp.org/index.php/Testing_for_Insecure_Direct_Object_References_(OTG-AUTHZ-004)) for Insecure Direct Object References. +A common vulnerability when permission checks are missing is called [IDOR](https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/05-Authorization_Testing/04-Testing_for_Insecure_Direct_Object_References) for Insecure Direct Object References. ### When to Consider @@ -49,8 +49,8 @@ Be careful to **also test [visibility levels](https://gitlab.com/gitlab-org/gitl Some example of well implemented access controls and tests: 1. [example1](https://dev.gitlab.org/gitlab/gitlab-ee/merge_requests/710/diffs?diff_id=13750#af40ef0eaae3c1e018809e1d88086e32bccaca40_43_43) -1. [example2](https://dev.gitlab.org/gitlab/gitlabhq/merge_requests/2511/diffs#ed3aaab1510f43b032ce345909a887e5b167e196_142_155) -1. [example3](https://dev.gitlab.org/gitlab/gitlabhq/merge_requests/3170/diffs?diff_id=17494) +1. [example2](https://dev.gitlab.org/gitlab/gitlabhq/-/merge_requests/2511/diffs#ed3aaab1510f43b032ce345909a887e5b167e196_142_155) +1. [example3](https://dev.gitlab.org/gitlab/gitlabhq/-/merge_requests/3170/diffs?diff_id=17494) **NB:** any input from development team is welcome, e.g. about rubocop rules. @@ -209,7 +209,7 @@ In some cases, it has been possible to configure GitLab::HTTP as the HTTP connection library for 3rd-party gems. This is preferrable over re-implementing the mitigations for a new feature. -- [More details](https://dev.gitlab.org/gitlab/gitlabhq/merge_requests/2530/diffs) +- [More details](https://dev.gitlab.org/gitlab/gitlabhq/-/merge_requests/2530/diffs) #### Feature-specific Mitigations @@ -279,7 +279,7 @@ For any and all input fields, ensure to define expectations on the type/format o - Validate the input using a [whitelist approach](https://youtu.be/2VFavqfDS6w?t=7816) to only allow characters through which you are expecting to receive for the field. - Input which fails validation should be **rejected**, and not sanitized. -Note that blacklists should be avoided, as it is near impossible to block all [variations of XSS](https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet). +Note that blacklists should be avoided, as it is near impossible to block all [variations of XSS](https://owasp.org/www-community/xss-filter-evasion-cheatsheet). #### Output encoding diff --git a/doc/development/testing_guide/end_to_end/style_guide.md b/doc/development/testing_guide/end_to_end/style_guide.md index 7f4616f394b..9c02af12d5d 100644 --- a/doc/development/testing_guide/end_to_end/style_guide.md +++ b/doc/development/testing_guide/end_to_end/style_guide.md @@ -66,6 +66,7 @@ We follow a simple formula roughly based on hungarian notation. - `_placeholder`: a temporary element that appears while content is loading. For example, the elements that are displayed instead of discussions while the discussions are being fetched. - `_radio` - `_tab` + - `_menu_item` *Note: If none of the listed types are suitable, please open a merge request to add an appropriate type to the list.* diff --git a/doc/integration/elasticsearch.md b/doc/integration/elasticsearch.md index ddd5a97adbb..58df29e8cca 100644 --- a/doc/integration/elasticsearch.md +++ b/doc/integration/elasticsearch.md @@ -497,6 +497,8 @@ However, some larger installations may wish to tune the merge policy settings: ## Troubleshooting +### Common issues + Here are some common pitfalls and how to overcome them: - **How can I verify my GitLab instance is using Elasticsearch?** @@ -625,6 +627,10 @@ Here are some common pitfalls and how to overcome them: You probably have not used either `http://` or `https://` as part of your value in the **"URL"** field of the Elasticseach Integration Menu. Please make sure you are using either `http://` or `https://` in this field as the [Elasticsearch client for Go](https://github.com/olivere/elastic) that we are using [needs the prefix for the URL to be accepted as valid](https://github.com/olivere/elastic/commit/a80af35aa41856dc2c986204e2b64eab81ccac3a). Once you have corrected the formatting of the URL, delete the index (via the [dedicated Rake task](#gitlab-elasticsearch-rake-tasks)) and [reindex the content of your instance](#adding-gitlabs-data-to-the-elasticsearch-index). +### Low level troubleshooting + +There is more [low level troubleshooting documentation](../administration/troubleshooting/elasticsearch.md) for when you experience other issues, including poor performance. + ### Known Issues - **[Elasticsearch `code_analyzer` doesn't account for all code cases](https://gitlab.com/gitlab-org/gitlab/issues/10693)** diff --git a/doc/user/admin_area/settings/rate_limit_on_issues_creation.md b/doc/user/admin_area/settings/rate_limit_on_issues_creation.md index 96a20681b2f..dc23865e730 100644 --- a/doc/user/admin_area/settings/rate_limit_on_issues_creation.md +++ b/doc/user/admin_area/settings/rate_limit_on_issues_creation.md @@ -4,7 +4,7 @@ type: reference # Rate limits on issue creation -> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/55241) in GitLab 12.10. +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/28129) in GitLab 12.10. This setting allows you to rate limit the requests to the issue creation endpoint. It defaults to 300 requests per minute. diff --git a/doc/user/project/merge_requests/versions.md b/doc/user/project/merge_requests/versions.md index 87c10717671..2f51af24a95 100644 --- a/doc/user/project/merge_requests/versions.md +++ b/doc/user/project/merge_requests/versions.md @@ -67,6 +67,26 @@ current default comparison. ![Merge request versions compare HEAD](img/versions_compare_head_v12_10.png) +### Enable or disable `HEAD` comparison mode **(CORE ONLY)** + +`HEAD` comparison mode is under development and not ready for production use. It is +deployed behind a feature flag that is **disabled by default**. +[GitLab administrators with access to the GitLab Rails console](../../../administration/troubleshooting/navigating_gitlab_via_rails_console.md#starting-a-rails-console-session) +can enable it for your instance. You're welcome to test it, but use it at your +own risk. + +To enable it: + +```ruby +Feature.enable(:diff_compare_with_head) +``` + +To disable it: + +```ruby +Feature.disable(:diff_compare_with_head) +``` +