diff options
Diffstat (limited to 'app/models')
42 files changed, 410 insertions, 156 deletions
diff --git a/app/models/appearance.rb b/app/models/appearance.rb index bffba3e13fa..e114c435b67 100644 --- a/app/models/appearance.rb +++ b/app/models/appearance.rb @@ -28,4 +28,32 @@ class Appearance < ActiveRecord::Base errors.add(:single_appearance_row, 'Only 1 appearances row can exist') end end + + def logo_path + logo_system_path(logo, 'logo') + end + + def header_logo_path + logo_system_path(header_logo, 'header_logo') + end + + def favicon_path + logo_system_path(favicon, 'favicon') + end + + private + + def logo_system_path(logo, mount_type) + return unless logo&.upload + + # If we're using a CDN, we need to use the full URL + asset_host = ActionController::Base.asset_host + local_path = Gitlab::Routing.url_helpers.appearance_upload_path( + filename: logo.filename, + id: logo.upload.model_id, + model: 'appearance', + mounted_as: mount_type) + + Gitlab::Utils.append_path(asset_host, local_path) + end end diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index 4319db42019..88746375c67 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -302,7 +302,8 @@ class ApplicationSetting < ActiveRecord::Base user_show_add_ssh_key_message: true, usage_stats_set_by_user_id: nil, diff_max_patch_bytes: Gitlab::Git::Diff::DEFAULT_MAX_PATCH_BYTES, - commit_email_hostname: default_commit_email_hostname + commit_email_hostname: default_commit_email_hostname, + protected_ci_variables: false } end @@ -311,7 +312,7 @@ class ApplicationSetting < ActiveRecord::Base end def self.create_from_defaults - create(defaults) + build_from_defaults.tap(&:save) end def self.human_attribute_name(attr, _options = {}) @@ -382,7 +383,7 @@ class ApplicationSetting < ActiveRecord::Base end def restricted_visibility_levels=(levels) - super(levels.map { |level| Gitlab::VisibilityLevel.level_value(level) }) + super(levels&.map { |level| Gitlab::VisibilityLevel.level_value(level) }) end def strip_sentry_values diff --git a/app/models/blob.rb b/app/models/blob.rb index 66a0925c495..c5766eb0327 100644 --- a/app/models/blob.rb +++ b/app/models/blob.rb @@ -102,7 +102,7 @@ class Blob < SimpleDelegator # If the blob is a text based blob the content is converted to UTF-8 and any # invalid byte sequences are replaced. def data - if binary? + if binary_in_repo? super else @data ||= super.encode(Encoding::UTF_8, invalid: :replace, undef: :replace) @@ -149,7 +149,7 @@ class Blob < SimpleDelegator # an LFS pointer, we assume the file stored in LFS is binary, unless a # text-based rich blob viewer matched on the file's extension. Otherwise, this # depends on the type of the blob itself. - def raw_binary? + def binary? if stored_externally? if rich_viewer rich_viewer.binary? @@ -161,7 +161,7 @@ class Blob < SimpleDelegator true end else - binary? + binary_in_repo? end end @@ -180,7 +180,7 @@ class Blob < SimpleDelegator end def readable_text? - text? && !stored_externally? && !truncated? + text_in_repo? && !stored_externally? && !truncated? end def simple_viewer @@ -220,7 +220,7 @@ class Blob < SimpleDelegator def simple_viewer_class if empty? BlobViewer::Empty - elsif raw_binary? + elsif binary? BlobViewer::Download else # text BlobViewer::Text diff --git a/app/models/blob_viewer/base.rb b/app/models/blob_viewer/base.rb index eaaf9af1330..df6b9bb2f0b 100644 --- a/app/models/blob_viewer/base.rb +++ b/app/models/blob_viewer/base.rb @@ -16,7 +16,7 @@ module BlobViewer def initialize(blob) @blob = blob - @initially_binary = blob.binary? + @initially_binary = blob.binary_in_repo? end def self.partial_path @@ -52,7 +52,7 @@ module BlobViewer end def self.can_render?(blob, verify_binary: true) - return false if verify_binary && binary? != blob.binary? + return false if verify_binary && binary? != blob.binary_in_repo? return true if extensions&.include?(blob.extension) return true if file_types&.include?(blob.file_type) @@ -72,7 +72,7 @@ module BlobViewer end def binary_detected_after_load? - !@initially_binary && blob.binary? + !@initially_binary && blob.binary_in_repo? end # This method is used on the server side to check whether we can attempt to diff --git a/app/models/broadcast_message.rb b/app/models/broadcast_message.rb index 277f7c2717c..2d237383e60 100644 --- a/app/models/broadcast_message.rb +++ b/app/models/broadcast_message.rb @@ -22,49 +22,30 @@ class BroadcastMessage < ActiveRecord::Base after_commit :flush_redis_cache def self.current - raw_messages = Rails.cache.fetch(CACHE_KEY, expires_in: cache_expires_in) do + messages = cache.fetch(CACHE_KEY, as: BroadcastMessage, expires_in: cache_expires_in) do remove_legacy_cache_key - current_and_future_messages.to_json + current_and_future_messages end - messages = decode_messages(raw_messages) - return [] unless messages&.present? now_or_future = messages.select(&:now_or_future?) # If there are cached entries but none are to be displayed we'll purge the # cache so we don't keep running this code all the time. - Rails.cache.delete(CACHE_KEY) if now_or_future.empty? + cache.expire(CACHE_KEY) if now_or_future.empty? now_or_future.select(&:now?) end - def self.decode_messages(raw_messages) - return unless raw_messages&.present? - - message_list = ActiveSupport::JSON.decode(raw_messages) - - return unless message_list.is_a?(Array) - - valid_attr = BroadcastMessage.attribute_names - - message_list.map do |raw| - BroadcastMessage.new(raw) if valid_cache_entry?(raw, valid_attr) - end.compact - rescue ActiveSupport::JSON.parse_error - end - - def self.valid_cache_entry?(raw, valid_attr) - return false unless raw.is_a?(Hash) - - (raw.keys - valid_attr).empty? - end - def self.current_and_future_messages where('ends_at > :now', now: Time.zone.now).order_id_asc end + def self.cache + Gitlab::JsonCache.new(cache_key_with_version: false) + end + def self.cache_expires_in nil end @@ -74,7 +55,7 @@ class BroadcastMessage < ActiveRecord::Base # environment a one-shot migration would not work because the cache # would be repopulated by a node that has not been upgraded. def self.remove_legacy_cache_key - Rails.cache.delete(LEGACY_CACHE_KEY) + cache.expire(LEGACY_CACHE_KEY) end def active? @@ -102,7 +83,7 @@ class BroadcastMessage < ActiveRecord::Base end def flush_redis_cache - Rails.cache.delete(CACHE_KEY) + self.class.cache.expire(CACHE_KEY) self.class.remove_legacy_cache_key end end diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index e2917049902..16a72c680fa 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -840,6 +840,7 @@ module Ci variables.append(key: 'CI_JOB_NAME', value: name) variables.append(key: 'CI_JOB_STAGE', value: stage) variables.append(key: 'CI_COMMIT_SHA', value: sha) + variables.append(key: 'CI_COMMIT_SHORT_SHA', value: short_sha) variables.append(key: 'CI_COMMIT_BEFORE_SHA', value: before_sha) variables.append(key: 'CI_COMMIT_REF_NAME', value: ref) variables.append(key: 'CI_COMMIT_REF_SLUG', value: ref_slug) diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index d06022a0fb7..25937065011 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -56,11 +56,7 @@ module Ci validates :tag, inclusion: { in: [false], if: :merge_request? } validates :status, presence: { unless: :importing? } validate :valid_commit_sha, unless: :importing? - - # Replace validator below with - # `validates :source, presence: { unless: :importing? }, on: :create` - # when removing Gitlab.rails5? code. - validate :valid_source, unless: :importing?, on: :create + validates :source, exclusion: { in: %w(unknown), unless: :importing? }, on: :create after_create :keep_around_commits, unless: :importing? @@ -68,11 +64,7 @@ module Ci # this `Hash` with new values. enum_with_nil source: ::Ci::PipelineEnums.sources - enum_with_nil config_source: { - unknown_source: nil, - repository_source: 1, - auto_devops_source: 2 - } + enum_with_nil config_source: ::Ci::PipelineEnums.config_sources # We use `Ci::PipelineEnums.failure_reasons` here so that EE can more easily # extend this `Hash` with new values. @@ -742,11 +734,5 @@ module Ci project.repository.keep_around(self.sha, self.before_sha) end - - def valid_source - if source.nil? || source == "unknown" - errors.add(:source, "invalid source") - end - end end end diff --git a/app/models/ci/pipeline_enums.rb b/app/models/ci/pipeline_enums.rb index c0f16066e0b..2994aaae4aa 100644 --- a/app/models/ci/pipeline_enums.rb +++ b/app/models/ci/pipeline_enums.rb @@ -25,5 +25,15 @@ module Ci merge_request: 10 } end + + # Returns the `Hash` to use for creating the `config_sources` enum for + # `Ci::Pipeline`. + def self.config_sources + { + unknown_source: nil, + repository_source: 1, + auto_devops_source: 2 + } + end end end diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb index 2693386443a..8249199e76f 100644 --- a/app/models/ci/runner.rb +++ b/app/models/ci/runner.rb @@ -58,8 +58,7 @@ module Ci # BACKWARD COMPATIBILITY: There are needed to maintain compatibility with `AVAILABLE_SCOPES` used by `lib/api/runners.rb` scope :deprecated_shared, -> { instance_type } - # this should get replaced with `project_type.or(group_type)` once using Rails5 - scope :deprecated_specific, -> { where(runner_type: [runner_types[:project_type], runner_types[:group_type]]) } + scope :deprecated_specific, -> { project_type.or(group_type) } scope :belonging_to_project, -> (project_id) { joins(:runner_projects).where(ci_runner_projects: { project_id: project_id }) @@ -67,7 +66,7 @@ module Ci scope :belonging_to_parent_group_of_project, -> (project_id) { project_groups = ::Group.joins(:projects).where(projects: { id: project_id }) - hierarchy_groups = Gitlab::GroupHierarchy.new(project_groups).base_and_ancestors + hierarchy_groups = Gitlab::ObjectHierarchy.new(project_groups).base_and_ancestors joins(:groups).where(namespaces: { id: hierarchy_groups }) } diff --git a/app/models/clusters/applications/knative.rb b/app/models/clusters/applications/knative.rb index 168a24da738..0c72d7d8340 100644 --- a/app/models/clusters/applications/knative.rb +++ b/app/models/clusters/applications/knative.rb @@ -3,7 +3,7 @@ module Clusters module Applications class Knative < ActiveRecord::Base - VERSION = '0.1.3'.freeze + VERSION = '0.2.2'.freeze REPOSITORY = 'https://storage.googleapis.com/triggermesh-charts'.freeze FETCH_IP_ADDRESS_DELAY = 30.seconds diff --git a/app/models/clusters/platforms/kubernetes.rb b/app/models/clusters/platforms/kubernetes.rb index 867f0edcb07..0dc0c4f80d6 100644 --- a/app/models/clusters/platforms/kubernetes.rb +++ b/app/models/clusters/platforms/kubernetes.rb @@ -106,7 +106,7 @@ module Clusters def terminals(environment) with_reactive_cache do |data| pods = filter_by_label(data[:pods], app: environment.slug) - terminals = pods.flat_map { |pod| terminals_for_pod(api_url, actual_namespace, pod) } + terminals = pods.flat_map { |pod| terminals_for_pod(api_url, actual_namespace, pod) }.compact terminals.each { |terminal| add_terminal_auth(terminal, terminal_auth) } end end @@ -228,7 +228,7 @@ module Clusters return unless namespace_changed? run_after_commit do - ClusterPlatformConfigureWorker.perform_async(cluster_id) + ClusterConfigureWorker.perform_async(cluster_id) end end end diff --git a/app/models/commit.rb b/app/models/commit.rb index a422a0995ff..01f4c58daa1 100644 --- a/app/models/commit.rb +++ b/app/models/commit.rb @@ -469,6 +469,10 @@ class Commit !!merged_merge_request(user) end + def cache_key + "commit:#{sha}" + end + private def commit_reference(from, referable_commit_id, full: false) diff --git a/app/models/concerns/avatarable.rb b/app/models/concerns/avatarable.rb index b42236c1fa2..4687ec7d166 100644 --- a/app/models/concerns/avatarable.rb +++ b/app/models/concerns/avatarable.rb @@ -43,7 +43,18 @@ module Avatarable end def avatar_path(only_path: true, size: nil) - return unless self[:avatar].present? + unless self.try(:id) + return uncached_avatar_path(only_path: only_path, size: size) + end + + # Cache this avatar path only within the request because avatars in + # object storage may be generated with time-limited, signed URLs. + key = "#{self.class.name}:#{self.id}:#{only_path}:#{size}" + Gitlab::SafeRequestStore[key] ||= uncached_avatar_path(only_path: only_path, size: size) + end + + def uncached_avatar_path(only_path: true, size: nil) + return unless self.try(:avatar).present? asset_host = ActionController::Base.asset_host use_asset_host = asset_host.present? diff --git a/app/models/concerns/blob_like.rb b/app/models/concerns/blob_like.rb index f20f01486a5..dc80f8d62f4 100644 --- a/app/models/concerns/blob_like.rb +++ b/app/models/concerns/blob_like.rb @@ -28,7 +28,7 @@ module BlobLike nil end - def binary? + def binary_in_repo? false end diff --git a/app/models/concerns/cacheable_attributes.rb b/app/models/concerns/cacheable_attributes.rb index 75592bb63e2..3d60f6924c1 100644 --- a/app/models/concerns/cacheable_attributes.rb +++ b/app/models/concerns/cacheable_attributes.rb @@ -23,7 +23,12 @@ module CacheableAttributes end def build_from_defaults(attributes = {}) - new(defaults.merge(attributes)) + final_attributes = defaults + .merge(attributes) + .stringify_keys + .slice(*column_names) + + new(final_attributes) end def cached diff --git a/app/models/concerns/descendant.rb b/app/models/concerns/descendant.rb new file mode 100644 index 00000000000..4c436522122 --- /dev/null +++ b/app/models/concerns/descendant.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module Descendant + extend ActiveSupport::Concern + + class_methods do + def supports_nested_objects? + Gitlab::Database.postgresql? + end + end +end diff --git a/app/models/concerns/discussion_on_diff.rb b/app/models/concerns/discussion_on_diff.rb index 266c37fa3a1..e4e5928f5cf 100644 --- a/app/models/concerns/discussion_on_diff.rb +++ b/app/models/concerns/discussion_on_diff.rb @@ -9,7 +9,7 @@ module DiscussionOnDiff included do delegate :line_code, :original_line_code, - :diff_file, + :note_diff_file, :diff_line, :active?, :created_at_diff?, @@ -39,6 +39,7 @@ module DiscussionOnDiff # Returns an array of at most 16 highlighted lines above a diff note def truncated_diff_lines(highlight: true, diff_limit: nil) + return [] unless on_text? return [] if diff_line.nil? && first_note.is_a?(LegacyDiffNote) diff_limit = [diff_limit, NUMBER_OF_TRUNCATED_DIFF_LINES].compact.min @@ -59,6 +60,13 @@ module DiscussionOnDiff prev_lines end + def diff_file + strong_memoize(:diff_file) do + # Falling back here is important as `note_diff_files` are created async. + fetch_preloaded_diff_file || first_note.diff_file + end + end + def line_code_in_diffs(diff_refs) if active?(diff_refs) line_code @@ -66,4 +74,15 @@ module DiscussionOnDiff original_line_code end end + + private + + def fetch_preloaded_diff_file + fetch_preloaded_diff = + context_noteable && + context_noteable.preloads_discussion_diff_highlighting? && + note_diff_file + + context_noteable.discussions_diffs.find_by_id(note_diff_file.id) if fetch_preloaded_diff + end end diff --git a/app/models/concerns/enum_with_nil.rb b/app/models/concerns/enum_with_nil.rb index 23acfe9a55f..6d0a21cf070 100644 --- a/app/models/concerns/enum_with_nil.rb +++ b/app/models/concerns/enum_with_nil.rb @@ -16,7 +16,7 @@ module EnumWithNil # E.g. for enum_with_nil failure_reason: { unknown_failure: nil } # this overrides auto-generated method `unknown_failure?` define_method("#{key_with_nil}?") do - Gitlab.rails5? ? self[name].nil? : super() + self[name].nil? end # E.g. for enum_with_nil failure_reason: { unknown_failure: nil } @@ -24,7 +24,6 @@ module EnumWithNil define_method(name) do orig = super() - return orig unless Gitlab.rails5? return orig unless orig.nil? self.class.public_send(name.to_s.pluralize).key(nil) # rubocop:disable GitlabSecurity/PublicSend diff --git a/app/models/concerns/noteable.rb b/app/models/concerns/noteable.rb index eb315058c3a..29476654bf7 100644 --- a/app/models/concerns/noteable.rb +++ b/app/models/concerns/noteable.rb @@ -26,10 +26,18 @@ module Noteable DiscussionNote.noteable_types.include?(base_class_name) end + def supports_suggestion? + false + end + def discussions_rendered_on_frontend? false end + def preloads_discussion_diff_highlighting? + false + end + def discussion_notes notes end diff --git a/app/models/concerns/redis_cacheable.rb b/app/models/concerns/redis_cacheable.rb index 69554f18ea2..4bb4ffe2a8e 100644 --- a/app/models/concerns/redis_cacheable.rb +++ b/app/models/concerns/redis_cacheable.rb @@ -49,10 +49,6 @@ module RedisCacheable end def cast_value_from_cache(attribute, value) - if Gitlab.rails5? - self.class.type_for_attribute(attribute.to_s).cast(value) - else - self.class.column_for_attribute(attribute).type_cast_from_database(value) - end + self.class.type_for_attribute(attribute.to_s).cast(value) end end diff --git a/app/models/dashboard_group_milestone.rb b/app/models/dashboard_group_milestone.rb index 32e8104125c..ad0bb55f0a7 100644 --- a/app/models/dashboard_group_milestone.rb +++ b/app/models/dashboard_group_milestone.rb @@ -5,7 +5,6 @@ class DashboardGroupMilestone < GlobalMilestone attr_reader :group_name - override :initialize def initialize(milestone) super(milestone.title, Array(milestone)) diff --git a/app/models/diff_note.rb b/app/models/diff_note.rb index c32008aa9c7..279603496b0 100644 --- a/app/models/diff_note.rb +++ b/app/models/diff_note.rb @@ -66,10 +66,23 @@ class DiffNote < Note self.original_position.diff_refs == diff_refs end + def supports_suggestion? + return false unless noteable.supports_suggestion? && on_text? + # We don't want to trigger side-effects of `diff_file` call. + return false unless file = fetch_diff_file + return false unless line = file.line_for_position(self.original_position) + + line&.suggestible? + end + def discussion_first_note? self == discussion.first_note end + def banzai_render_context(field) + super.merge(suggestions_filter_enabled: supports_suggestion?) + end + private def enqueue_diff_file_creation_job diff --git a/app/models/diff_viewer/base.rb b/app/models/diff_viewer/base.rb index 1176861a827..527ee33b83b 100644 --- a/app/models/diff_viewer/base.rb +++ b/app/models/diff_viewer/base.rb @@ -18,7 +18,7 @@ module DiffViewer def initialize(diff_file) @diff_file = diff_file - @initially_binary = diff_file.binary? + @initially_binary = diff_file.binary_in_repo? end def self.partial_path @@ -48,7 +48,7 @@ module DiffViewer def self.can_render_blob?(blob, verify_binary: true) return true if blob.nil? - return false if verify_binary && binary? != blob.binary? + return false if verify_binary && binary? != blob.binary_in_repo? return true if extensions&.include?(blob.extension) return true if file_types&.include?(blob.file_type) @@ -70,20 +70,49 @@ module DiffViewer end def binary_detected_after_load? - !@initially_binary && diff_file.binary? + !@initially_binary && diff_file.binary_in_repo? end # This method is used on the server side to check whether we can attempt to - # render the diff_file at all. Human-readable error messages are found in the - # `BlobHelper#diff_render_error_reason` helper. + # render the diff_file at all. The human-readable error message can be + # retrieved by #render_error_message. def render_error if too_large? :too_large end end + def render_error_message + return unless render_error + + _("This %{viewer} could not be displayed because %{reason}. You can %{options} instead.") % + { + viewer: switcher_title, + reason: render_error_reason, + options: render_error_options.to_sentence(two_words_connector: _(' or '), last_word_connector: _(', or ')) + } + end + def prepare! # To be overridden by subclasses end + + private + + def render_error_options + options = [] + + blob_url = Gitlab::Routing.url_helpers.project_blob_path(diff_file.repository.project, + File.join(diff_file.content_sha, diff_file.file_path)) + options << ActionController::Base.helpers.link_to(_('view the blob'), blob_url) + + options + end + + def render_error_reason + if render_error == :too_large + _("it is too large") + end + end end end diff --git a/app/models/diff_viewer/image.rb b/app/models/diff_viewer/image.rb index c356c2ca50e..350bef1d42a 100644 --- a/app/models/diff_viewer/image.rb +++ b/app/models/diff_viewer/image.rb @@ -9,6 +9,6 @@ module DiffViewer self.extensions = UploaderHelper::IMAGE_EXT self.binary = true self.switcher_icon = 'picture-o' - self.switcher_title = 'image diff' + self.switcher_title = _('image diff') end end diff --git a/app/models/diff_viewer/rich.rb b/app/models/diff_viewer/rich.rb index 2faa1be6567..5caefa2031c 100644 --- a/app/models/diff_viewer/rich.rb +++ b/app/models/diff_viewer/rich.rb @@ -7,7 +7,7 @@ module DiffViewer included do self.type = :rich self.switcher_icon = 'file-text-o' - self.switcher_title = 'rendered diff' + self.switcher_title = _('rendered diff') end end end diff --git a/app/models/diff_viewer/server_side.rb b/app/models/diff_viewer/server_side.rb index 977204e6c97..0877c9dddec 100644 --- a/app/models/diff_viewer/server_side.rb +++ b/app/models/diff_viewer/server_side.rb @@ -24,5 +24,17 @@ module DiffViewer super end + + private + + def render_error_reason + return super unless render_error == :server_side_but_stored_externally + + if diff_file.external_storage == :lfs + _('it is stored in LFS') + else + _('it is stored externally') + end + end end end diff --git a/app/models/diff_viewer/simple.rb b/app/models/diff_viewer/simple.rb index 8d28ca5239a..929d8ad5a7e 100644 --- a/app/models/diff_viewer/simple.rb +++ b/app/models/diff_viewer/simple.rb @@ -7,7 +7,7 @@ module DiffViewer included do self.type = :simple self.switcher_icon = 'code' - self.switcher_title = 'source diff' + self.switcher_title = _('source diff') end end end diff --git a/app/models/environment.rb b/app/models/environment.rb index 934828946b9..cdfe3b7c023 100644 --- a/app/models/environment.rb +++ b/app/models/environment.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true class Environment < ActiveRecord::Base + include Gitlab::Utils::StrongMemoize # Used to generate random suffixes for the slug LETTERS = 'a'..'z' NUMBERS = '0'..'9' @@ -231,7 +232,9 @@ class Environment < ActiveRecord::Base end def deployment_platform - project.deployment_platform(environment: self.name) + strong_memoize(:deployment_platform) do + project.deployment_platform(environment: self.name) + end end private diff --git a/app/models/event.rb b/app/models/event.rb index 2ceef412af5..6a35bca72c5 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -114,19 +114,6 @@ class Event < ActiveRecord::Base end end - # Remove this method when removing Gitlab.rails5? code. - def subclass_from_attributes(attrs) - return super if Gitlab.rails5? - - # Without this Rails will keep calling this method on the returned class, - # resulting in an infinite loop. - return unless self == Event - - action = attrs.with_indifferent_access[inheritance_column].to_i - - PushEvent if action == PUSHED - end - # Update Gitlab::ContributionsCalendar#activity_dates if this changes def contributions where("action = ? OR (target_type IN (?) AND action IN (?)) OR (target_type = ? AND action = ?)", diff --git a/app/models/group.rb b/app/models/group.rb index 233747cc2c2..edac2444c4d 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -10,6 +10,7 @@ class Group < Namespace include Referable include SelectForProjectAuthorization include LoadedInGroupList + include Descendant include GroupDescendant include TokenAuthenticatable include WithUploads @@ -63,10 +64,6 @@ class Group < Namespace after_update :path_changed_hook, if: :path_changed? class << self - def supports_nested_groups? - Gitlab::Database.postgresql? - end - def sort_by_attribute(method) if method == 'storage_size_desc' # storage_size is a virtual column so we need to diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index a13cac73d04..b937bef100b 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -48,8 +48,8 @@ class MergeRequest < ActiveRecord::Base # is the inverse of MergeRequest#merge_request_diff, which means it may not be # the latest diff, because we could have loaded any diff from this particular # MR. If we haven't already loaded a diff, then it's fine to load the latest. - def merge_request_diff(*args) - fallback = latest_merge_request_diff if args.empty? && !association(:merge_request_diff).loaded? + def merge_request_diff + fallback = latest_merge_request_diff unless association(:merge_request_diff).loaded? fallback || super end @@ -363,6 +363,10 @@ class MergeRequest < ActiveRecord::Base end end + def supports_suggestion? + true + end + # Calls `MergeWorker` to proceed with the merge process and # updates `merge_jid` with the MergeWorker#jid. # This helps tracking enqueued and ongoing merge jobs. @@ -404,6 +408,28 @@ class MergeRequest < ActiveRecord::Base merge_request_diffs.where.not(id: merge_request_diff.id) end + def preloads_discussion_diff_highlighting? + true + end + + def preload_discussions_diff_highlight + preloadable_files = note_diff_files.for_commit_or_unresolved + + discussions_diffs.load_highlight(preloadable_files.pluck(:id)) + end + + def discussions_diffs + strong_memoize(:discussions_diffs) do + Gitlab::DiscussionsDiff::FileCollection.new(note_diff_files.to_a) + end + end + + def note_diff_files + NoteDiffFile + .where(diff_note: discussion_notes) + .includes(diff_note: :project) + end + def diff_size # Calling `merge_request_diff.diffs.real_size` will also perform # highlighting, which we don't need here. @@ -615,10 +641,6 @@ class MergeRequest < ActiveRecord::Base end end - def reload_merge_request_diff - merge_request_diff(true) - end - def viewable_diffs @viewable_diffs ||= merge_request_diffs.viewable.to_a end diff --git a/app/models/milestone.rb b/app/models/milestone.rb index 3cc8e2c44bb..6dc0fca68e6 100644 --- a/app/models/milestone.rb +++ b/app/models/milestone.rb @@ -212,7 +212,7 @@ class Milestone < ActiveRecord::Base end def reference_link_text(from = nil) - self.title + self.class.reference_prefix + self.title end def milestoneish_ids diff --git a/app/models/namespace.rb b/app/models/namespace.rb index 3c9b1d32a53..a0bebc5e9a2 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -175,16 +175,16 @@ class Namespace < ActiveRecord::Base # Returns all ancestors, self, and descendants of the current namespace. def self_and_hierarchy - Gitlab::GroupHierarchy + Gitlab::ObjectHierarchy .new(self.class.where(id: id)) - .all_groups + .all_objects end # Returns all the ancestors of the current namespaces. def ancestors return self.class.none unless parent_id - Gitlab::GroupHierarchy + Gitlab::ObjectHierarchy .new(self.class.where(id: parent_id)) .base_and_ancestors end @@ -192,27 +192,27 @@ class Namespace < ActiveRecord::Base # returns all ancestors upto but excluding the given namespace # when no namespace is given, all ancestors upto the top are returned def ancestors_upto(top = nil, hierarchy_order: nil) - Gitlab::GroupHierarchy.new(self.class.where(id: id)) + Gitlab::ObjectHierarchy.new(self.class.where(id: id)) .ancestors(upto: top, hierarchy_order: hierarchy_order) end def self_and_ancestors return self.class.where(id: id) unless parent_id - Gitlab::GroupHierarchy + Gitlab::ObjectHierarchy .new(self.class.where(id: id)) .base_and_ancestors end # Returns all the descendants of the current namespace. def descendants - Gitlab::GroupHierarchy + Gitlab::ObjectHierarchy .new(self.class.where(parent_id: id)) .base_and_descendants end def self_and_descendants - Gitlab::GroupHierarchy + Gitlab::ObjectHierarchy .new(self.class.where(id: id)) .base_and_descendants end @@ -293,7 +293,7 @@ class Namespace < ActiveRecord::Base end def force_share_with_group_lock_on_descendants - return unless Group.supports_nested_groups? + return unless Group.supports_nested_objects? # We can't use `descendants.update_all` since Rails will throw away the WITH # RECURSIVE statement. We also can't use WHERE EXISTS since we can't use @@ -306,6 +306,7 @@ class Namespace < ActiveRecord::Base def write_projects_repository_config all_projects.find_each do |project| project.write_repository_config + project.track_project_repository end end end diff --git a/app/models/note.rb b/app/models/note.rb index 17c7d97fa0a..becf14e9785 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -69,6 +69,12 @@ class Note < ActiveRecord::Base belongs_to :last_edited_by, class_name: 'User' has_many :todos + + # The delete_all definition is required here in order + # to generate the correct DELETE sql for + # suggestions.delete_all calls + has_many :suggestions, -> { order(:relative_order) }, + inverse_of: :note, dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent has_many :events, as: :target, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_one :system_note_metadata has_one :note_diff_file, inverse_of: :diff_note, foreign_key: :diff_note_id @@ -110,7 +116,7 @@ class Note < ActiveRecord::Base scope :inc_author, -> { includes(:author) } scope :inc_relations_for_view, -> do includes(:project, { author: :status }, :updated_by, :resolved_by, :award_emoji, - :system_note_metadata, :note_diff_file) + :system_note_metadata, :note_diff_file, :suggestions) end scope :with_notes_filter, -> (notes_filter) do @@ -226,6 +232,10 @@ class Note < ActiveRecord::Base Gitlab::HookData::NoteBuilder.new(self).build end + def supports_suggestion? + false + end + def for_commit? noteable_type == "Commit" end diff --git a/app/models/note_diff_file.rb b/app/models/note_diff_file.rb index 27aef7adc48..e369122003e 100644 --- a/app/models/note_diff_file.rb +++ b/app/models/note_diff_file.rb @@ -3,7 +3,22 @@ class NoteDiffFile < ActiveRecord::Base include DiffFile + scope :for_commit_or_unresolved, -> do + joins(:diff_note).where("resolved_at IS NULL OR noteable_type = 'Commit'") + end + + delegate :original_position, :project, to: :diff_note + belongs_to :diff_note, inverse_of: :note_diff_file validates :diff_note, presence: true + + def raw_diff_file + raw_diff = Gitlab::Git::Diff.new(to_hash) + + Gitlab::Diff::File.new(raw_diff, + repository: project.repository, + diff_refs: original_position.diff_refs, + unique_identifier: id) + end end diff --git a/app/models/pool_repository.rb b/app/models/pool_repository.rb index 47da0209c2f..ad6a008dee8 100644 --- a/app/models/pool_repository.rb +++ b/app/models/pool_repository.rb @@ -18,6 +18,7 @@ class PoolRepository < ActiveRecord::Base state :scheduled state :ready state :failed + state :obsolete event :schedule do transition none: :scheduled @@ -31,6 +32,10 @@ class PoolRepository < ActiveRecord::Base transition all => :failed end + event :mark_obsolete do + transition all => :obsolete + end + state all - [:ready] do def joinable? false @@ -54,6 +59,12 @@ class PoolRepository < ActiveRecord::Base ::ObjectPool::ScheduleJoinWorker.perform_async(pool.id) end end + + after_transition any => :obsolete do |pool, _| + pool.run_after_commit do + ::ObjectPool::DestroyWorker.perform_async(pool.id) + end + end end def create_object_pool @@ -71,10 +82,10 @@ class PoolRepository < ActiveRecord::Base end # This RPC can cause data loss, as not all objects are present the local repository - # No execution path yet, will be added through: - # https://gitlab.com/gitlab-org/gitaly/issues/1415 - def delete_repository_alternate(repository) + def unlink_repository(repository) object_pool.unlink_repository(repository.raw) + + mark_obsolete unless member_projects.where.not(id: repository.project.id).exists? end def object_pool diff --git a/app/models/project.rb b/app/models/project.rb index 67262ecce85..09e2a6114fe 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -256,7 +256,7 @@ class Project < ActiveRecord::Base # other pipelines, like webide ones, that we won't retrieve # if we use this relation. has_many :ci_pipelines, - -> { Feature.enabled?(:pipeline_ci_sources_only, default_enabled: true) ? ci_sources : all }, + -> { ci_sources }, class_name: 'Ci::Pipeline', inverse_of: :project has_many :stages, class_name: 'Ci::Stage', inverse_of: :project @@ -570,7 +570,7 @@ class Project < ActiveRecord::Base # returns all ancestor-groups upto but excluding the given namespace # when no namespace is given, all ancestors upto the top are returned def ancestors_upto(top = nil, hierarchy_order: nil) - Gitlab::GroupHierarchy.new(Group.where(id: namespace_id)) + Gitlab::ObjectHierarchy.new(Group.where(id: namespace_id)) .base_and_ancestors(upto: top, hierarchy_order: hierarchy_order) end @@ -1244,10 +1244,8 @@ class Project < ActiveRecord::Base end def track_project_repository - return unless hashed_storage?(:repository) - - project_repo = project_repository || build_project_repository - project_repo.update!(shard_name: repository_storage, disk_path: disk_path) + repository = project_repository || build_project_repository + repository.update!(shard_name: repository_storage, disk_path: disk_path) end def create_repository(force: false) @@ -2004,6 +2002,10 @@ class Project < ActiveRecord::Base Feature.enabled?(:object_pools, self) end + def leave_pool_repository + pool_repository&.unlink_repository(repository) + end + private def create_new_pool_repository diff --git a/app/models/prometheus_metric.rb b/app/models/prometheus_metric.rb index ce2db9cb44c..5594594a48d 100644 --- a/app/models/prometheus_metric.rb +++ b/app/models/prometheus_metric.rb @@ -5,11 +5,12 @@ class PrometheusMetric < ActiveRecord::Base enum group: { # built-in groups - nginx_ingress: -1, + nginx_ingress_vts: -1, ha_proxy: -2, aws_elb: -3, nginx: -4, kubernetes: -5, + nginx_ingress: -6, # custom/user groups business: 0, @@ -17,6 +18,54 @@ class PrometheusMetric < ActiveRecord::Base system: 2 } + GROUP_DETAILS = { + # built-in groups + nginx_ingress_vts: { + group_title: _('Response metrics (NGINX Ingress VTS)'), + required_metrics: %w(nginx_upstream_responses_total nginx_upstream_response_msecs_avg), + priority: 10 + }.freeze, + nginx_ingress: { + group_title: _('Response metrics (NGINX Ingress)'), + required_metrics: %w(nginx_ingress_controller_requests nginx_ingress_controller_ingress_upstream_latency_seconds_sum), + priority: 10 + }.freeze, + ha_proxy: { + group_title: _('Response metrics (HA Proxy)'), + required_metrics: %w(haproxy_frontend_http_requests_total haproxy_frontend_http_responses_total), + priority: 10 + }.freeze, + aws_elb: { + group_title: _('Response metrics (AWS ELB)'), + required_metrics: %w(aws_elb_request_count_sum aws_elb_latency_average aws_elb_httpcode_backend_5_xx_sum), + priority: 10 + }.freeze, + nginx: { + group_title: _('Response metrics (NGINX)'), + required_metrics: %w(nginx_server_requests nginx_server_requestMsec), + priority: 10 + }.freeze, + kubernetes: { + group_title: _('System metrics (Kubernetes)'), + required_metrics: %w(container_memory_usage_bytes container_cpu_usage_seconds_total), + priority: 5 + }.freeze, + + # custom/user groups + business: { + group_title: _('Business metrics (Custom)'), + priority: 0 + }.freeze, + response: { + group_title: _('Response metrics (Custom)'), + priority: -5 + }.freeze, + system: { + group_title: _('System metrics (Custom)'), + priority: -10 + }.freeze + }.freeze + validates :title, presence: true validates :query, presence: true validates :group, presence: true @@ -28,34 +77,16 @@ class PrometheusMetric < ActiveRecord::Base scope :common, -> { where(common: true) } - GROUP_TITLES = { - # built-in groups - nginx_ingress: _('Response metrics (NGINX Ingress)'), - ha_proxy: _('Response metrics (HA Proxy)'), - aws_elb: _('Response metrics (AWS ELB)'), - nginx: _('Response metrics (NGINX)'), - kubernetes: _('System metrics (Kubernetes)'), - - # custom/user groups - business: _('Business metrics (Custom)'), - response: _('Response metrics (Custom)'), - system: _('System metrics (Custom)') - }.freeze - - REQUIRED_METRICS = { - nginx_ingress: %w(nginx_upstream_responses_total nginx_upstream_response_msecs_avg), - ha_proxy: %w(haproxy_frontend_http_requests_total haproxy_frontend_http_responses_total), - aws_elb: %w(aws_elb_request_count_sum aws_elb_latency_average aws_elb_httpcode_backend_5_xx_sum), - nginx: %w(nginx_server_requests nginx_server_requestMsec), - kubernetes: %w(container_memory_usage_bytes container_cpu_usage_seconds_total) - }.freeze + def priority + group_details(group).fetch(:priority) + end def group_title - GROUP_TITLES[group.to_sym] + group_details(group).fetch(:group_title) end def required_metrics - REQUIRED_METRICS[group.to_sym].to_a.map(&:to_s) + group_details(group).fetch(:required_metrics, []).map(&:to_s) end def to_query_metric @@ -86,4 +117,10 @@ class PrometheusMetric < ActiveRecord::Base }] end end + + private + + def group_details(group) + GROUP_DETAILS.fetch(group.to_sym) + end end diff --git a/app/models/release.rb b/app/models/release.rb index cba80ad30ca..7a09ee459a6 100644 --- a/app/models/release.rb +++ b/app/models/release.rb @@ -6,6 +6,7 @@ class Release < ActiveRecord::Base cache_markdown_field :description belongs_to :project + belongs_to :author, class_name: 'User' validates :description, :project, :tag, presence: true end diff --git a/app/models/service.rb b/app/models/service.rb index 5b8bf6e7cf0..9dcb0aab0a3 100644 --- a/app/models/service.rb +++ b/app/models/service.rb @@ -210,11 +210,7 @@ class Service < ActiveRecord::Base class_eval %{ def #{arg}? # '!!' is used because nil or empty string is converted to nil - if Gitlab.rails5? - !!ActiveRecord::Type::Boolean.new.cast(#{arg}) - else - !!ActiveRecord::Type::Boolean.new.type_cast_from_database(#{arg}) - end + !!ActiveRecord::Type::Boolean.new.cast(#{arg}) end } end diff --git a/app/models/suggestion.rb b/app/models/suggestion.rb new file mode 100644 index 00000000000..c76b8e71507 --- /dev/null +++ b/app/models/suggestion.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +class Suggestion < ApplicationRecord + belongs_to :note, inverse_of: :suggestions + validates :note, presence: true + validates :commit_id, presence: true, if: :applied? + + delegate :original_position, :position, :diff_file, + :noteable, to: :note + + def project + noteable.source_project + end + + def branch + noteable.source_branch + end + + # For now, suggestions only serve as a way to send patches that + # will change a single line (being able to apply multiple in the same place), + # which explains `from_line` and `to_line` being the same line. + # We'll iterate on that in https://gitlab.com/gitlab-org/gitlab-ce/issues/53310 + # when allowing multi-line suggestions. + def from_line + position.new_line + end + alias_method :to_line, :from_line + + def from_original_line + original_position.new_line + end + alias_method :to_original_line, :from_original_line + + # `from_line_index` and `to_line_index` represents diff/blob line numbers in + # index-like way (N-1). + def from_line_index + from_line - 1 + end + alias_method :to_line_index, :from_line_index + + def appliable? + return false unless note.supports_suggestion? + + !applied? && + noteable.opened? && + different_content? && + note.active? + end + + private + + def different_content? + from_content != to_content + end +end diff --git a/app/models/user.rb b/app/models/user.rb index dbd754dd25a..26fd2d903a1 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -130,6 +130,7 @@ class User < ActiveRecord::Base has_many :issues, dependent: :destroy, foreign_key: :author_id # rubocop:disable Cop/ActiveRecordDependent has_many :merge_requests, dependent: :destroy, foreign_key: :author_id # rubocop:disable Cop/ActiveRecordDependent has_many :events, dependent: :destroy, foreign_key: :author_id # rubocop:disable Cop/ActiveRecordDependent + has_many :releases, dependent: :nullify, foreign_key: :author_id # rubocop:disable Cop/ActiveRecordDependent has_many :subscriptions, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_many :oauth_applications, class_name: 'Doorkeeper::Application', as: :owner, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_one :abuse_report, dependent: :destroy, foreign_key: :user_id # rubocop:disable Cop/ActiveRecordDependent @@ -708,13 +709,13 @@ class User < ActiveRecord::Base # Returns the groups a user is a member of, either directly or through a parent group def membership_groups - Gitlab::GroupHierarchy.new(groups).base_and_descendants + Gitlab::ObjectHierarchy.new(groups).base_and_descendants end # Returns a relation of groups the user has access to, including their parent # and child groups (recursively). def all_expanded_groups - Gitlab::GroupHierarchy.new(groups).all_groups + Gitlab::ObjectHierarchy.new(groups).all_objects end def expanded_groups_requiring_two_factor_authentication @@ -1152,7 +1153,7 @@ class User < ActiveRecord::Base end def manageable_groups - Gitlab::GroupHierarchy.new(owned_or_maintainers_groups).base_and_descendants + Gitlab::ObjectHierarchy.new(owned_or_maintainers_groups).base_and_descendants end def namespaces @@ -1421,6 +1422,10 @@ class User < ActiveRecord::Base todos.where(id: ids) end + def pending_todo_for(target) + todos.find_by(target: target, state: :pending) + end + # @deprecated alias_method :owned_or_masters_groups, :owned_or_maintainers_groups |