diff options
Diffstat (limited to 'app/models/project.rb')
-rw-r--r-- | app/models/project.rb | 137 |
1 files changed, 78 insertions, 59 deletions
diff --git a/app/models/project.rb b/app/models/project.rb index 2906aca75fc..10679fb1f85 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -55,6 +55,8 @@ class Project < ApplicationRecord VALID_MIRROR_PORTS = [22, 80, 443].freeze VALID_MIRROR_PROTOCOLS = %w(http https ssh git).freeze + SORTING_PREFERENCE_FIELD = :projects_sort + cache_markdown_field :description, pipeline: :description delegate :feature_available?, :builds_enabled?, :wiki_enabled?, @@ -162,7 +164,6 @@ class Project < ApplicationRecord has_one :bugzilla_service has_one :gitlab_issue_tracker_service, inverse_of: :project has_one :external_wiki_service - has_one :kubernetes_service, inverse_of: :project has_one :prometheus_service, inverse_of: :project has_one :mock_ci_service has_one :mock_deployment_service @@ -214,7 +215,7 @@ class Project < ApplicationRecord as: :source, class_name: 'ProjectMember', dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent has_many :members_and_requesters, as: :source, class_name: 'ProjectMember' - has_many :deploy_keys_projects + has_many :deploy_keys_projects, inverse_of: :project has_many :deploy_keys, through: :deploy_keys_projects has_many :users_star_projects has_many :starrers, through: :users_star_projects, source: :user @@ -277,13 +278,14 @@ class Project < ApplicationRecord has_many :project_deploy_tokens has_many :deploy_tokens, through: :project_deploy_tokens - has_one :auto_devops, class_name: 'ProjectAutoDevops' + has_one :auto_devops, class_name: 'ProjectAutoDevops', inverse_of: :project, autosave: true has_many :custom_attributes, class_name: 'ProjectCustomAttribute' has_many :project_badges, class_name: 'ProjectBadge' has_one :ci_cd_settings, class_name: 'ProjectCiCdSetting', inverse_of: :project, autosave: true, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_many :remote_mirrors, inverse_of: :project + has_many :cycle_analytics_stages, class_name: 'Analytics::CycleAnalytics::ProjectStage' accepts_nested_attributes_for :variables, allow_destroy: true accepts_nested_attributes_for :project_feature, update_only: true @@ -357,6 +359,7 @@ class Project < ApplicationRecord scope :sorted_by_activity, -> { reorder(Arel.sql("GREATEST(COALESCE(last_activity_at, '1970-01-01'), COALESCE(last_repository_updated_at, '1970-01-01')) DESC")) } scope :sorted_by_stars_desc, -> { reorder(star_count: :desc) } scope :sorted_by_stars_asc, -> { reorder(star_count: :asc) } + scope :sorted_by_name_asc_limited, ->(limit) { reorder(name: :asc).limit(limit) } scope :in_namespace, ->(namespace_ids) { where(namespace_id: namespace_ids) } scope :personal, ->(user) { where(namespace_id: user.namespace_id) } @@ -414,12 +417,6 @@ class Project < ApplicationRecord .where(project_ci_cd_settings: { group_runners_enabled: true }) end - scope :missing_kubernetes_namespace, -> (kubernetes_namespaces) do - subquery = kubernetes_namespaces.select('1').where('clusters_kubernetes_namespaces.project_id = projects.id') - - where('NOT EXISTS (?)', subquery) - end - enum auto_cancel_pending_pipelines: { disabled: 0, enabled: 1 } chronic_duration_attr :build_timeout_human_readable, :build_timeout, @@ -441,22 +438,6 @@ class Project < ApplicationRecord without_deleted.find_by_id(id) end - # Paginates a collection using a `WHERE id < ?` condition. - # - # before - A project ID to use for filtering out projects with an equal or - # greater ID. If no ID is given, all projects are included. - # - # limit - The maximum number of rows to include. - def self.paginate_in_descending_order_using_id( - before: nil, - limit: Kaminari.config.default_per_page - ) - relation = order_id_desc.limit(limit) - relation = relation.where('projects.id < ?', before) if before - - relation - end - def self.eager_load_namespace_and_owner includes(namespace: :owner) end @@ -653,6 +634,13 @@ class Project < ApplicationRecord alias_method :ancestors, :ancestors_upto + def emails_disabled? + strong_memoize(:emails_disabled) do + # disabling in the namespace overrides the project setting + Feature.enabled?(:emails_disabled, self, default_enabled: true) && (super || namespace.emails_disabled?) + end + end + def lfs_enabled? return namespace.lfs_enabled? if self[:lfs_enabled].nil? @@ -734,16 +722,27 @@ class Project < ApplicationRecord repository.commits_by(oids: oids) end - # ref can't be HEAD, can only be branch/tag name or SHA - def latest_successful_build_for(job_name, ref = default_branch) - latest_pipeline = ci_pipelines.latest_successful_for(ref) + # ref can't be HEAD, can only be branch/tag name + def latest_successful_build_for_ref(job_name, ref = default_branch) + return unless ref + + latest_pipeline = ci_pipelines.latest_successful_for_ref(ref) + return unless latest_pipeline + + latest_pipeline.builds.latest.with_artifacts_archive.find_by(name: job_name) + end + + def latest_successful_build_for_sha(job_name, sha) + return unless sha + + latest_pipeline = ci_pipelines.latest_successful_for_sha(sha) return unless latest_pipeline latest_pipeline.builds.latest.with_artifacts_archive.find_by(name: job_name) end - def latest_successful_build_for!(job_name, ref = default_branch) - latest_successful_build_for(job_name, ref) || raise(ActiveRecord::RecordNotFound.new("Couldn't find job #{job_name}")) + def latest_successful_build_for_ref!(job_name, ref = default_branch) + latest_successful_build_for_ref(job_name, ref) || raise(ActiveRecord::RecordNotFound.new("Couldn't find job #{job_name}")) end def merge_base_commit(first_commit_id, second_commit_id) @@ -1241,6 +1240,14 @@ class Project < ApplicationRecord end end + def has_active_hooks?(hooks_scope = :push_hooks) + hooks.hooks_for(hooks_scope).any? || SystemHook.hooks_for(hooks_scope).any? + end + + def has_active_services?(hooks_scope = :push_hooks) + services.public_send(hooks_scope).any? # rubocop:disable GitlabSecurity/PublicSend + end + def valid_repo? repository.exists? rescue @@ -1496,12 +1503,19 @@ class Project < ApplicationRecord !namespace.share_with_group_lock end - def pipeline_for(ref, sha = nil) + def pipeline_for(ref, sha = nil, id = nil) sha ||= commit(ref).try(:sha) - return unless sha - ci_pipelines.order(id: :desc).find_by(sha: sha, ref: ref) + if id.present? + pipelines_for(ref, sha).find_by(id: id) + else + pipelines_for(ref, sha).take + end + end + + def pipelines_for(ref, sha) + ci_pipelines.order(id: :desc).where(sha: sha, ref: ref) end def latest_successful_pipeline_for_default_branch @@ -1510,12 +1524,12 @@ class Project < ApplicationRecord end @latest_successful_pipeline_for_default_branch = - ci_pipelines.latest_successful_for(default_branch) + ci_pipelines.latest_successful_for_ref(default_branch) end def latest_successful_pipeline_for(ref = nil) if ref && ref != default_branch - ci_pipelines.latest_successful_for(ref) + ci_pipelines.latest_successful_for_ref(ref) else latest_successful_pipeline_for_default_branch end @@ -1831,11 +1845,16 @@ class Project < ApplicationRecord end def ci_variables_for(ref:, environment: nil) - # EE would use the environment - if protected_for?(ref) - variables + result = if protected_for?(ref) + variables + else + variables.unprotected + end + + if environment + result.on_environment(environment) else - variables.unprotected + result.where(environment_scope: '*') end end @@ -1858,8 +1877,12 @@ class Project < ApplicationRecord end end - def deployment_variables(environment: nil) - deployment_platform(environment: environment)&.predefined_variables(project: self) || [] + def deployment_variables(environment:) + platform = deployment_platform(environment: environment) + + return [] unless platform.present? + + platform.predefined_variables(project: self, environment_name: environment) end def auto_devops_variables @@ -1869,16 +1892,24 @@ class Project < ApplicationRecord end def append_or_update_attribute(name, value) - old_values = public_send(name.to_s) # rubocop:disable GitlabSecurity/PublicSend + if Project.reflect_on_association(name).try(:macro) == :has_many + # if this is 1-to-N relation, update the parent object + value.each do |item| + item.update!( + Project.reflect_on_association(name).foreign_key => id) + end + + # force to drop relation cache + public_send(name).reset # rubocop:disable GitlabSecurity/PublicSend - if Project.reflect_on_association(name).try(:macro) == :has_many && old_values.any? - update_attribute(name, old_values + value) + # succeeded + true else + # if this is another relation or attribute, update just object update_attribute(name, value) end - - rescue ActiveRecord::RecordNotSaved => e - handle_update_attribute_error(e, value) + rescue ActiveRecord::RecordInvalid => e + raise e, "Failed to set #{name}: #{e.message}" end # Tries to set repository as read_only, checking for existing Git transfers in progress beforehand @@ -2267,18 +2298,6 @@ class Project < ApplicationRecord ContainerRepository.build_root_repository(self).has_tags? end - def handle_update_attribute_error(ex, value) - if ex.message.start_with?('Failed to replace') - if value.respond_to?(:each) - invalid = value.detect(&:invalid?) - - raise ex, ([ex.message] + invalid.errors.full_messages).join(' ') if invalid - end - end - - raise ex - end - def fetch_branch_allows_collaboration(user, branch_name = nil) return false unless user |