diff options
Diffstat (limited to 'app/models')
25 files changed, 155 insertions, 213 deletions
diff --git a/app/models/ci/artifact_blob.rb b/app/models/ci/artifact_blob.rb index ef00ad75683..d87d6a5cb2f 100644 --- a/app/models/ci/artifact_blob.rb +++ b/app/models/ci/artifact_blob.rb @@ -4,7 +4,7 @@ module Ci class ArtifactBlob include BlobLike - EXTENSIONS_SERVED_BY_PAGES = %w[.html .htm .txt .json .xml .log].freeze + EXTENSIONS_SERVED_BY_PAGES = %w[.html .htm .txt .json .log].freeze attr_reader :entry diff --git a/app/models/ci/build_metadata.rb b/app/models/ci/build_metadata.rb index 3097e40dd3b..4046048be1c 100644 --- a/app/models/ci/build_metadata.rb +++ b/app/models/ci/build_metadata.rb @@ -4,12 +4,9 @@ module Ci # The purpose of this class is to store Build related data that can be disposed. # Data that should be persisted forever, should be stored with Ci::Build model. class BuildMetadata < ApplicationRecord - BuildTimeout = Struct.new(:value, :source) - extend Gitlab::Ci::Model include Presentable include ChronicDurationAttribute - include Gitlab::Utils::StrongMemoize self.table_name = 'ci_builds_metadata' @@ -31,16 +28,17 @@ module Ci enum timeout_source: { unknown_timeout_source: 1, project_timeout_source: 2, - runner_timeout_source: 3, - job_timeout_source: 4 + runner_timeout_source: 3 } def update_timeout_state - timeout = timeout_with_highest_precedence + return unless build.runner.present? - return unless timeout + project_timeout = project&.build_timeout + timeout = [project_timeout, build.runner.maximum_timeout].compact.min + timeout_source = timeout < project_timeout ? :runner_timeout_source : :project_timeout_source - update(timeout: timeout.value, timeout_source: timeout.source) + update(timeout: timeout, timeout_source: timeout_source) end private @@ -48,37 +46,5 @@ module Ci def set_build_project self.project_id ||= self.build.project_id end - - def timeout_with_highest_precedence - [(job_timeout || project_timeout), runner_timeout].compact.min_by { |timeout| timeout.value } - end - - def project_timeout - strong_memoize(:project_timeout) do - BuildTimeout.new(project&.build_timeout, :project_timeout_source) - end - end - - def job_timeout - return unless build.options - - strong_memoize(:job_timeout) do - if timeout_from_options = build.options[:job_timeout] - BuildTimeout.new(timeout_from_options, :job_timeout_source) - end - end - end - - def runner_timeout - return unless runner_timeout_set? - - strong_memoize(:runner_timeout) do - BuildTimeout.new(build.runner.maximum_timeout, :runner_timeout_source) - end - end - - def runner_timeout_set? - build.runner&.maximum_timeout.to_i > 0 - end end end diff --git a/app/models/clusters/applications/ingress.rb b/app/models/clusters/applications/ingress.rb index 50def3ba38c..44c66f06059 100644 --- a/app/models/clusters/applications/ingress.rb +++ b/app/models/clusters/applications/ingress.rb @@ -35,6 +35,10 @@ module Clusters 'stable/nginx-ingress' end + def values + content_values.to_yaml + end + def allowed_to_uninstall? external_ip_or_hostname? && application_jupyter_nil_or_installable? end @@ -67,6 +71,23 @@ module Clusters private + def specification + return {} unless Feature.enabled?(:ingress_modsecurity) + + { + "controller" => { + "config" => { + "enable-modsecurity" => "true", + "enable-owasp-modsecurity-crs" => "true" + } + } + } + end + + def content_values + YAML.load_file(chart_values_file).deep_merge!(specification) + end + def application_jupyter_nil_or_installable? cluster.application_jupyter.nil? || cluster.application_jupyter&.installable? end diff --git a/app/models/clusters/cluster.rb b/app/models/clusters/cluster.rb index 7a61622b139..7855fb69bd6 100644 --- a/app/models/clusters/cluster.rb +++ b/app/models/clusters/cluster.rb @@ -37,18 +37,13 @@ module Clusters has_one :platform_kubernetes, class_name: 'Clusters::Platforms::Kubernetes', inverse_of: :cluster, autosave: true - def self.has_one_cluster_application(name) # rubocop:disable Naming/PredicateName - application = APPLICATIONS[name.to_s] - has_one application.association_name, class_name: application.to_s # rubocop:disable Rails/ReflectionClassName - end - - has_one_cluster_application :helm - has_one_cluster_application :ingress - has_one_cluster_application :cert_manager - has_one_cluster_application :prometheus - has_one_cluster_application :runner - has_one_cluster_application :jupyter - has_one_cluster_application :knative + has_one :application_helm, class_name: 'Clusters::Applications::Helm' + has_one :application_ingress, class_name: 'Clusters::Applications::Ingress' + has_one :application_cert_manager, class_name: 'Clusters::Applications::CertManager' + has_one :application_prometheus, class_name: 'Clusters::Applications::Prometheus' + has_one :application_runner, class_name: 'Clusters::Applications::Runner' + has_one :application_jupyter, class_name: 'Clusters::Applications::Jupyter' + has_one :application_knative, class_name: 'Clusters::Applications::Knative' has_many :kubernetes_namespaces @@ -132,9 +127,15 @@ module Clusters end def applications - APPLICATIONS.values.map do |application_class| - public_send(application_class.association_name) || public_send("build_#{application_class.association_name}") # rubocop:disable GitlabSecurity/PublicSend - end + [ + application_helm || build_application_helm, + application_ingress || build_application_ingress, + application_cert_manager || build_application_cert_manager, + application_prometheus || build_application_prometheus, + application_runner || build_application_runner, + application_jupyter || build_application_jupyter, + application_knative || build_application_knative + ] end def provider diff --git a/app/models/clusters/concerns/application_core.rb b/app/models/clusters/concerns/application_core.rb index d1b57a21a7d..803a65726d3 100644 --- a/app/models/clusters/concerns/application_core.rb +++ b/app/models/clusters/concerns/application_core.rb @@ -32,10 +32,6 @@ module Clusters self.to_s.demodulize.underscore end - def self.association_name - :"application_#{application_name}" - end - def name self.class.application_name end diff --git a/app/models/commit.rb b/app/models/commit.rb index a442f607fbf..1470b50f396 100644 --- a/app/models/commit.rb +++ b/app/models/commit.rb @@ -1,3 +1,4 @@ +# coding: utf-8 # frozen_string_literal: true class Commit diff --git a/app/models/concerns/routable.rb b/app/models/concerns/routable.rb index 57118bf7a6b..8b011bca72c 100644 --- a/app/models/concerns/routable.rb +++ b/app/models/concerns/routable.rb @@ -33,9 +33,16 @@ module Routable # # Returns a single object, or nil. def find_by_full_path(path, follow_redirects: false) - # Case sensitive match first (it's cheaper and the usual case) - # If we didn't have an exact match, we perform a case insensitive search - found = includes(:route).find_by(routes: { path: path }) || where_full_path_in([path]).take + routable_calls_counter.increment(method: 'find_by_full_path') + + if Feature.enabled?(:routable_two_step_lookup) + # Case sensitive match first (it's cheaper and the usual case) + # If we didn't have an exact match, we perform a case insensitive search + found = includes(:route).find_by(routes: { path: path }) || where_full_path_in([path]).take + else + order_sql = Arel.sql("(CASE WHEN routes.path = #{connection.quote(path)} THEN 0 ELSE 1 END)") + found = where_full_path_in([path]).reorder(order_sql).take + end return found if found @@ -54,12 +61,19 @@ module Routable def where_full_path_in(paths) return none if paths.empty? + routable_calls_counter.increment(method: 'where_full_path_in') + wheres = paths.map do |path| "(LOWER(routes.path) = LOWER(#{connection.quote(path)}))" end includes(:route).where(wheres.join(' OR ')).references(:routes) end + + # Temporary instrumentation of method calls + def routable_calls_counter + @routable_calls_counter ||= Gitlab::Metrics.counter(:gitlab_routable_calls_total, 'Number of calls to Routable by method') + end end def full_name diff --git a/app/models/diff_note.rb b/app/models/diff_note.rb index aa7286a9971..0b00cf10714 100644 --- a/app/models/diff_note.rb +++ b/app/models/diff_note.rb @@ -108,10 +108,13 @@ class DiffNote < Note end def fetch_diff_file - return note_diff_file.raw_diff_file if note_diff_file - file = - if created_at_diff?(noteable.diff_refs) + if note_diff_file + diff = Gitlab::Git::Diff.new(note_diff_file.to_hash) + Gitlab::Diff::File.new(diff, + repository: repository, + diff_refs: original_position.diff_refs) + elsif created_at_diff?(noteable.diff_refs) # We're able to use the already persisted diffs (Postgres) if we're # presenting a "current version" of the MR discussion diff. # So no need to make an extra Gitaly diff request for it. @@ -123,7 +126,9 @@ class DiffNote < Note original_position.diff_file(repository) end - file&.unfold_diff_lines(position) + # Since persisted diff files already have its content "unfolded" + # there's no need to make it pass through the unfolding process. + file&.unfold_diff_lines(position) unless note_diff_file file end diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index ac26d29ad19..90061fe181e 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -454,17 +454,24 @@ class MergeRequest < ApplicationRecord 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 - note_diff_files = NoteDiffFile - .joins(:diff_note) - .merge(notes.or(commit_notes)) - .includes(diff_note: :project) - 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. diff --git a/app/models/milestone.rb b/app/models/milestone.rb index 800c492e8e2..4b9fee2bbdf 100644 --- a/app/models/milestone.rb +++ b/app/models/milestone.rb @@ -27,8 +27,11 @@ class Milestone < ApplicationRecord belongs_to :project belongs_to :group - has_many :milestone_releases - has_many :releases, through: :milestone_releases + # A one-to-one relationship is set up here as part of a MVC: https://gitlab.com/gitlab-org/gitlab-ce/issues/62402 + # However, on the long term, we will want a many-to-many relationship between Release and Milestone. + # The "has_one through" allows us today to set up this one-to-one relationship while setting up the architecture for the long-term (ie intermediate table). + has_one :milestone_release + has_one :release, through: :milestone_release has_internal_id :iid, scope: :project, init: ->(s) { s&.project&.milestones&.maximum(:iid) } has_internal_id :iid, scope: :group, init: ->(s) { s&.group&.milestones&.maximum(:iid) } @@ -65,7 +68,7 @@ class Milestone < ApplicationRecord validate :milestone_type_check validate :start_date_should_be_less_than_due_date, if: proc { |m| m.start_date.present? && m.due_date.present? } validate :dates_within_4_digits - validates_associated :milestone_releases, message: -> (_, obj) { obj[:value].map(&:errors).map(&:full_messages).join(",") } + validates_associated :milestone_release, message: -> (_, obj) { obj[:value].errors.full_messages.join(",") } strip_attributes :title diff --git a/app/models/milestone_release.rb b/app/models/milestone_release.rb index f7127df339d..c8743a8cad8 100644 --- a/app/models/milestone_release.rb +++ b/app/models/milestone_release.rb @@ -4,11 +4,9 @@ class MilestoneRelease < ApplicationRecord belongs_to :milestone belongs_to :release + validates :milestone_id, uniqueness: { scope: [:release_id] } validate :same_project_between_milestone_and_release - # Keep until 2019-11-29 - self.ignored_columns += %i[id] - private def same_project_between_milestone_and_release diff --git a/app/models/note_diff_file.rb b/app/models/note_diff_file.rb index 67a6d5d6d6b..fcc9e2b3fd8 100644 --- a/app/models/note_diff_file.rb +++ b/app/models/note_diff_file.rb @@ -3,11 +3,15 @@ class NoteDiffFile < ApplicationRecord include DiffFile + scope :for_commit_or_unresolved, -> do + joins(:diff_note).where("resolved_at IS NULL OR noteable_type = 'Commit'") + end + scope :referencing_sha, -> (oids, project_id:) do joins(:diff_note).where(notes: { project_id: project_id, commit_id: oids }) end - delegate :original_position, :project, :resolved_at, to: :diff_note + delegate :original_position, :project, to: :diff_note belongs_to :diff_note, inverse_of: :note_diff_file diff --git a/app/models/project.rb b/app/models/project.rb index 57f1ca98ee2..96c715095f6 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -58,7 +58,6 @@ class Project < ApplicationRecord ACCESS_REQUEST_APPROVERS_TO_BE_NOTIFIED_LIMIT = 10 SORTING_PREFERENCE_FIELD = :projects_sort - MAX_BUILD_TIMEOUT = 1.month cache_markdown_field :description, pipeline: :description @@ -431,7 +430,7 @@ class Project < ApplicationRecord validates :build_timeout, allow_nil: true, numericality: { greater_than_or_equal_to: 10.minutes, - less_than: MAX_BUILD_TIMEOUT, + less_than: 1.month, only_integer: true, message: _('needs to be between 10 minutes and 1 month') } @@ -753,15 +752,6 @@ class Project < ApplicationRecord latest_successful_build_for_ref(job_name, ref) || raise(ActiveRecord::RecordNotFound.new("Couldn't find job #{job_name}")) end - def latest_pipeline_for_ref(ref = default_branch) - ref = ref.presence || default_branch - sha = commit(ref)&.sha - - return unless sha - - ci_pipelines.newest_first(ref: ref, sha: sha).first - end - def merge_base_commit(first_commit_id, second_commit_id) sha = repository.merge_base(first_commit_id, second_commit_id) commit_by(oid: sha) if sha diff --git a/app/models/project_services/bugzilla_service.rb b/app/models/project_services/bugzilla_service.rb index 0a498fde95a..8b79b5e9f0c 100644 --- a/app/models/project_services/bugzilla_service.rb +++ b/app/models/project_services/bugzilla_service.rb @@ -3,6 +3,8 @@ class BugzillaService < IssueTrackerService validates :project_url, :issues_url, :new_issue_url, presence: true, public_url: true, if: :activated? + prop_accessor :project_url, :issues_url, :new_issue_url + def default_title 'Bugzilla' end diff --git a/app/models/project_services/custom_issue_tracker_service.rb b/app/models/project_services/custom_issue_tracker_service.rb index dbc42b1b86d..535fcf6b94e 100644 --- a/app/models/project_services/custom_issue_tracker_service.rb +++ b/app/models/project_services/custom_issue_tracker_service.rb @@ -3,6 +3,8 @@ class CustomIssueTrackerService < IssueTrackerService validates :project_url, :issues_url, :new_issue_url, presence: true, public_url: true, if: :activated? + prop_accessor :title, :description, :project_url, :issues_url, :new_issue_url + def default_title 'Custom Issue Tracker' end diff --git a/app/models/project_services/data_fields.rb b/app/models/project_services/data_fields.rb index 0f5385f8ce2..438d85098c8 100644 --- a/app/models/project_services/data_fields.rb +++ b/app/models/project_services/data_fields.rb @@ -3,56 +3,8 @@ module DataFields extend ActiveSupport::Concern - class_methods do - # Provide convenient accessor methods for data fields. - # TODO: Simplify as part of https://gitlab.com/gitlab-org/gitlab-ce/issues/63084 - def data_field(*args) - args.each do |arg| - self.class_eval <<-RUBY, __FILE__, __LINE__ + 1 - unless method_defined?(arg) - def #{arg} - data_fields.send('#{arg}') || (properties && properties['#{arg}']) - end - end - - def #{arg}=(value) - @old_data_fields ||= {} - @old_data_fields['#{arg}'] ||= #{arg} # set only on the first assignment, IOW we remember the original value only - data_fields.send('#{arg}=', value) - end - - def #{arg}_touched? - @old_data_fields ||= {} - @old_data_fields.has_key?('#{arg}') - end - - def #{arg}_changed? - #{arg}_touched? && @old_data_fields['#{arg}'] != #{arg} - end - - def #{arg}_was - return unless #{arg}_touched? - return if data_fields.persisted? # arg_was does not work for attr_encrypted - - legacy_properties_data['#{arg}'] - end - RUBY - end - end - end - included do - has_one :issue_tracker_data, autosave: true - has_one :jira_tracker_data, autosave: true - - def data_fields - raise NotImplementedError - end - - def data_fields_present? - data_fields.persisted? - rescue NotImplementedError - false - end + has_one :issue_tracker_data + has_one :jira_tracker_data end end diff --git a/app/models/project_services/gitlab_issue_tracker_service.rb b/app/models/project_services/gitlab_issue_tracker_service.rb index ec28602b5e6..51032932eab 100644 --- a/app/models/project_services/gitlab_issue_tracker_service.rb +++ b/app/models/project_services/gitlab_issue_tracker_service.rb @@ -5,6 +5,8 @@ class GitlabIssueTrackerService < IssueTrackerService validates :project_url, :issues_url, :new_issue_url, presence: true, public_url: true, if: :activated? + prop_accessor :project_url, :issues_url, :new_issue_url + default_value_for :default, true def default_title diff --git a/app/models/project_services/issue_tracker_data.rb b/app/models/project_services/issue_tracker_data.rb index b1d67657fe6..2c1d28ed421 100644 --- a/app/models/project_services/issue_tracker_data.rb +++ b/app/models/project_services/issue_tracker_data.rb @@ -6,6 +6,9 @@ class IssueTrackerData < ApplicationRecord delegate :activated?, to: :service, allow_nil: true validates :service, presence: true + validates :project_url, presence: true, public_url: { enforce_sanitization: true }, if: :activated? + validates :issues_url, presence: true, public_url: { enforce_sanitization: true }, if: :activated? + validates :new_issue_url, public_url: { enforce_sanitization: true }, if: :activated? def self.encryption_options { diff --git a/app/models/project_services/issue_tracker_service.rb b/app/models/project_services/issue_tracker_service.rb index c201bd2ea18..b6ad46513db 100644 --- a/app/models/project_services/issue_tracker_service.rb +++ b/app/models/project_services/issue_tracker_service.rb @@ -3,14 +3,9 @@ class IssueTrackerService < Service validate :one_issue_tracker, if: :activated?, on: :manual_change - # TODO: we can probably just delegate as part of - # https://gitlab.com/gitlab-org/gitlab-ce/issues/63084 - data_field :project_url, :issues_url, :new_issue_url - default_value_for :category, 'issue_tracker' - before_validation :handle_properties - before_validation :set_default_data, on: :create + before_save :handle_properties # Pattern used to extract links from comments # Override this method on services that uses different patterns @@ -48,31 +43,12 @@ class IssueTrackerService < Service end def handle_properties - # this has been moved from initialize_properties and should be improved - # as part of https://gitlab.com/gitlab-org/gitlab-ce/issues/63084 - return unless properties - - @legacy_properties_data = properties.dup - data_values = properties.slice!('title', 'description') - properties.each do |key, _| + properties.slice('title', 'description').each do |key, _| current_value = self.properties.delete(key) value = attribute_changed?(key) ? attribute_change(key).last : current_value write_attribute(key, value) end - - data_values.reject! { |key| data_fields.changed.include?(key) } - data_fields.assign_attributes(data_values) if data_values.present? - - self.properties = {} - end - - def legacy_properties_data - @legacy_properties_data ||= {} - end - - def data_fields - issue_tracker_data || self.build_issue_tracker_data end def default? @@ -80,7 +56,7 @@ class IssueTrackerService < Service end def issue_url(iid) - issues_url.gsub(':id', iid.to_s) + self.issues_url.gsub(':id', iid.to_s) end def issue_tracker_path @@ -104,22 +80,25 @@ class IssueTrackerService < Service ] end - def initialize_properties - {} - end - # Initialize with default properties values - def set_default_data - return unless issues_tracker.present? - - self.title ||= issues_tracker['title'] - - # we don't want to override if we have set something - return if project_url || issues_url || new_issue_url - - data_fields.project_url = issues_tracker['project_url'] - data_fields.issues_url = issues_tracker['issues_url'] - data_fields.new_issue_url = issues_tracker['new_issue_url'] + # or receive a block with custom properties + def initialize_properties(&block) + return unless properties.nil? + + if enabled_in_gitlab_config + if block_given? + yield + else + self.properties = { + title: issues_tracker['title'], + project_url: issues_tracker['project_url'], + issues_url: issues_tracker['issues_url'], + new_issue_url: issues_tracker['new_issue_url'] + } + end + else + self.properties = {} + end end def self.supported_events diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb index 61ae78a0b95..0728c83005e 100644 --- a/app/models/project_services/jira_service.rb +++ b/app/models/project_services/jira_service.rb @@ -17,10 +17,7 @@ class JiraService < IssueTrackerService # Jira Cloud version is deprecating authentication via username and password. # We should use username/password for Jira Server and email/api_token for Jira Cloud, # for more information check: https://gitlab.com/gitlab-org/gitlab-ce/issues/49936. - - # TODO: we can probably just delegate as part of - # https://gitlab.com/gitlab-org/gitlab-ce/issues/63084 - data_field :username, :password, :url, :api_url, :jira_issue_transition_id + prop_accessor :username, :password, :url, :api_url, :jira_issue_transition_id before_update :reset_password @@ -38,34 +35,24 @@ class JiraService < IssueTrackerService end def initialize_properties - {} - end - - def data_fields - jira_tracker_data || self.build_jira_tracker_data + super do + self.properties = { + url: issues_tracker['url'], + api_url: issues_tracker['api_url'] + } + end end def reset_password - data_fields.password = nil if reset_password? - end - - def set_default_data - return unless issues_tracker.present? - - self.title ||= issues_tracker['title'] - - return if url - - data_fields.url ||= issues_tracker['url'] - data_fields.api_url ||= issues_tracker['api_url'] + self.password = nil if reset_password? end def options url = URI.parse(client_url) { - username: username, - password: password, + username: self.username, + password: self.password, site: URI.join(url, '/').to_s, # Intended to find the root context_path: url.path, auth_type: :basic, diff --git a/app/models/project_services/jira_tracker_data.rb b/app/models/project_services/jira_tracker_data.rb index e4e0f64150a..4f528e3d81b 100644 --- a/app/models/project_services/jira_tracker_data.rb +++ b/app/models/project_services/jira_tracker_data.rb @@ -6,6 +6,13 @@ class JiraTrackerData < ApplicationRecord delegate :activated?, to: :service, allow_nil: true validates :service, presence: true + validates :url, public_url: { enforce_sanitization: true }, presence: true, if: :activated? + validates :api_url, public_url: { enforce_sanitization: true }, allow_blank: true + validates :username, presence: true, if: :activated? + validates :password, presence: true, if: :activated? + validates :jira_issue_transition_id, + format: { with: Gitlab::Regex.jira_transition_id_regex, message: s_("JiraService|transition ids can have only numbers which can be split with , or ;") }, + allow_blank: true def self.encryption_options { diff --git a/app/models/project_services/redmine_service.rb b/app/models/project_services/redmine_service.rb index a4ca0d20669..5ca057ca833 100644 --- a/app/models/project_services/redmine_service.rb +++ b/app/models/project_services/redmine_service.rb @@ -3,6 +3,8 @@ class RedmineService < IssueTrackerService validates :project_url, :issues_url, :new_issue_url, presence: true, public_url: true, if: :activated? + prop_accessor :project_url, :issues_url, :new_issue_url + def default_title 'Redmine' end diff --git a/app/models/project_services/youtrack_service.rb b/app/models/project_services/youtrack_service.rb index 0416eaa5be0..f9de1f7dc49 100644 --- a/app/models/project_services/youtrack_service.rb +++ b/app/models/project_services/youtrack_service.rb @@ -3,6 +3,8 @@ class YoutrackService < IssueTrackerService validates :project_url, :issues_url, presence: true, public_url: true, if: :activated? + prop_accessor :project_url, :issues_url + # {PROJECT-KEY}-{NUMBER} Examples: YT-1, PRJ-1, gl-030 def self.reference_pattern(only_long: false) if only_long diff --git a/app/models/protected_branch.rb b/app/models/protected_branch.rb index 1857a59e01c..8769d3eb916 100644 --- a/app/models/protected_branch.rb +++ b/app/models/protected_branch.rb @@ -40,11 +40,6 @@ class ProtectedBranch < ApplicationRecord def self.protected_refs(project) project.protected_branches.select(:name) end - - def self.branch_requires_code_owner_approval?(project, branch_name) - # NOOP - # - end end ProtectedBranch.prepend_if_ee('EE::ProtectedBranch') diff --git a/app/models/release.rb b/app/models/release.rb index cd63b4d5fef..b2e65974aa0 100644 --- a/app/models/release.rb +++ b/app/models/release.rb @@ -12,8 +12,11 @@ class Release < ApplicationRecord has_many :links, class_name: 'Releases::Link' - has_many :milestone_releases - has_many :milestones, through: :milestone_releases + # A one-to-one relationship is set up here as part of a MVC: https://gitlab.com/gitlab-org/gitlab-ce/issues/62402 + # However, on the long term, we will want a many-to-many relationship between Release and Milestone. + # The "has_one through" allows us today to set up this one-to-one relationship while setting up the architecture for the long-term (ie intermediate table). + has_one :milestone_release + has_one :milestone, through: :milestone_release default_value_for :released_at, allows_nil: false do Time.zone.now @@ -23,7 +26,7 @@ class Release < ApplicationRecord validates :description, :project, :tag, presence: true validates :name, presence: true, on: :create - validates_associated :milestone_releases, message: -> (_, obj) { obj[:value].map(&:errors).map(&:full_messages).join(",") } + validates_associated :milestone_release, message: -> (_, obj) { obj[:value].errors.full_messages.join(",") } scope :sorted, -> { order(released_at: :desc) } |