summaryrefslogtreecommitdiff
path: root/app/models/project.rb
diff options
context:
space:
mode:
Diffstat (limited to 'app/models/project.rb')
-rw-r--r--app/models/project.rb156
1 files changed, 105 insertions, 51 deletions
diff --git a/app/models/project.rb b/app/models/project.rb
index 0c4f76fb2b9..73dbb55a07b 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -89,68 +89,56 @@ class Project < ApplicationRecord
cache_markdown_field :description, pipeline: :description
- default_value_for :packages_enabled, true
- default_value_for :archived, false
- default_value_for :resolve_outdated_diff_discussions, false
- default_value_for(:repository_storage) do
- Repository.pick_storage_shard
- end
+ attribute :packages_enabled, default: true
+ attribute :archived, default: false
+ attribute :resolve_outdated_diff_discussions, default: false
+ attribute :repository_storage, default: -> { Repository.pick_storage_shard }
+ attribute :shared_runners_enabled, default: -> { Gitlab::CurrentSettings.shared_runners_enabled }
+ attribute :only_allow_merge_if_all_discussions_are_resolved, default: false
+ attribute :remove_source_branch_after_merge, default: true
+ attribute :autoclose_referenced_issues, default: true
+ attribute :ci_config_path, default: -> { Gitlab::CurrentSettings.default_ci_config_path }
- default_value_for(:shared_runners_enabled) { Gitlab::CurrentSettings.shared_runners_enabled }
default_value_for :issues_enabled, gitlab_config_features.issues
default_value_for :merge_requests_enabled, gitlab_config_features.merge_requests
default_value_for :builds_enabled, gitlab_config_features.builds
default_value_for :wiki_enabled, gitlab_config_features.wiki
default_value_for :snippets_enabled, gitlab_config_features.snippets
- default_value_for :only_allow_merge_if_all_discussions_are_resolved, false
- default_value_for :remove_source_branch_after_merge, true
- default_value_for :autoclose_referenced_issues, true
- default_value_for(:ci_config_path) { Gitlab::CurrentSettings.default_ci_config_path }
add_authentication_token_field :runners_token,
encrypted: -> { Feature.enabled?(:projects_tokens_optional_encryption) ? :optional : :required },
prefix: RunnersTokenPrefixable::RUNNERS_TOKEN_PREFIX
+ # Storage specific hooks
+ after_initialize :use_hashed_storage
before_validation :mark_remote_mirrors_for_removal, if: -> { RemoteMirror.table_exists? }
- before_save :ensure_runners_token
before_validation :ensure_project_namespace_in_sync
-
before_validation :set_package_registry_access_level, if: :packages_enabled_changed?
before_validation :remove_leading_spaces_on_name
-
- after_save :update_project_statistics, if: :saved_change_to_namespace_id?
-
- after_save :schedule_sync_event_worker, if: -> { saved_change_to_id? || saved_change_to_namespace_id? }
-
- after_save :create_import_state, if: ->(project) { project.import? && project.import_state.nil? }
-
- after_save :save_topics
-
- after_save :reload_project_namespace_details
+ after_validation :check_pending_delete
+ before_save :ensure_runners_token
after_create -> { create_or_load_association(:project_feature) }
-
after_create -> { create_or_load_association(:ci_cd_settings) }
-
after_create -> { create_or_load_association(:container_expiration_policy) }
-
after_create -> { create_or_load_association(:pages_metadatum) }
-
after_create :set_timestamps_for_create
+ after_create :check_repository_absence!
after_update :update_forks_visibility_level
-
before_destroy :remove_private_deploy_keys
+ after_destroy :remove_exports
+ after_save :update_project_statistics, if: :saved_change_to_namespace_id?
- use_fast_destroy :build_trace_chunks
+ after_save :schedule_sync_event_worker, if: -> { saved_change_to_id? || saved_change_to_namespace_id? }
- after_destroy :remove_exports
+ after_save :create_import_state, if: ->(project) { project.import? && project.import_state.nil? }
- after_validation :check_pending_delete
+ after_save :save_topics
- # Storage specific hooks
- after_initialize :use_hashed_storage
- after_create :check_repository_absence!
+ after_save :reload_project_namespace_details
+
+ use_fast_destroy :build_trace_chunks
has_many :project_topics, -> { order(:id) }, class_name: 'Projects::ProjectTopic'
has_many :topics, through: :project_topics, class_name: 'Projects::Topic'
@@ -196,7 +184,6 @@ class Project < ApplicationRecord
has_one :emails_on_push_integration, class_name: 'Integrations::EmailsOnPush'
has_one :ewm_integration, class_name: 'Integrations::Ewm'
has_one :external_wiki_integration, class_name: 'Integrations::ExternalWiki'
- has_one :flowdock_integration, class_name: 'Integrations::Flowdock'
has_one :hangouts_chat_integration, class_name: 'Integrations::HangoutsChat'
has_one :harbor_integration, class_name: 'Integrations::Harbor'
has_one :irker_integration, class_name: 'Integrations::Irker'
@@ -231,6 +218,17 @@ class Project < ApplicationRecord
has_one :fork_network_member
has_one :fork_network, through: :fork_network_member
has_one :forked_from_project, through: :fork_network_member
+
+ # Projects with a very large number of notes may time out destroying them
+ # through the foreign key. Additionally, the deprecated attachment uploader
+ # for notes requires us to use dependent: :destroy to avoid orphaning uploaded
+ # files.
+ #
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/207222
+ # Order of this association is important for project deletion.
+ # has_many :notes` should be the first association among all `has_many` associations.
+ has_many :notes, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
+
has_many :forked_to_members, class_name: 'ForkNetworkMember', foreign_key: 'forked_from_project_id'
has_many :forks, through: :forked_to_members, source: :project, inverse_of: :forked_from_project
has_many :fork_network_projects, through: :fork_network, source: :projects
@@ -259,25 +257,30 @@ class Project < ApplicationRecord
has_one :service_desk_setting, class_name: 'ServiceDeskSetting'
# Merge requests for target project should be removed with it
- has_many :merge_requests, foreign_key: 'target_project_id', inverse_of: :target_project
+ has_many :merge_requests, foreign_key: 'target_project_id', inverse_of: :target_project, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :merge_request_metrics, foreign_key: 'target_project', class_name: 'MergeRequest::Metrics', inverse_of: :target_project
has_many :source_of_merge_requests, foreign_key: 'source_project_id', class_name: 'MergeRequest'
- has_many :issues
+ has_many :issues, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :incident_management_issuable_escalation_statuses, through: :issues, inverse_of: :project, class_name: 'IncidentManagement::IssuableEscalationStatus'
has_many :incident_management_timeline_event_tags, inverse_of: :project, class_name: 'IncidentManagement::TimelineEventTag'
- has_many :labels, class_name: 'ProjectLabel'
- has_many :integrations
+ has_many :labels, class_name: 'ProjectLabel', dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :events
has_many :milestones
- # Projects with a very large number of notes may time out destroying them
- # through the foreign key. Additionally, the deprecated attachment uploader
- # for notes requires us to use dependent: :destroy to avoid orphaning uploaded
- # files.
- #
- # https://gitlab.com/gitlab-org/gitlab/-/issues/207222
- has_many :notes, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
-
+ has_many :integrations
+ has_many :alert_hooks_integrations, -> { alert_hooks }, class_name: 'Integration'
+ has_many :archive_trace_hooks_integrations, -> { archive_trace_hooks }, class_name: 'Integration'
+ has_many :confidential_issue_hooks_integrations, -> { confidential_issue_hooks }, class_name: 'Integration'
+ has_many :confidential_note_hooks_integrations, -> { confidential_note_hooks }, class_name: 'Integration'
+ has_many :deployment_hooks_integrations, -> { deployment_hooks }, class_name: 'Integration'
+ has_many :issue_hooks_integrations, -> { issue_hooks }, class_name: 'Integration'
+ has_many :job_hooks_integrations, -> { job_hooks }, class_name: 'Integration'
+ has_many :merge_request_hooks_integrations, -> { merge_request_hooks }, class_name: 'Integration'
+ has_many :note_hooks_integrations, -> { note_hooks }, class_name: 'Integration'
+ has_many :pipeline_hooks_integrations, -> { pipeline_hooks }, class_name: 'Integration'
+ has_many :push_hooks_integrations, -> { push_hooks }, class_name: 'Integration'
+ has_many :tag_push_hooks_integrations, -> { tag_push_hooks }, class_name: 'Integration'
+ has_many :wiki_page_hooks_integrations, -> { wiki_page_hooks }, class_name: 'Integration'
has_many :snippets, class_name: 'ProjectSnippet'
has_many :hooks, class_name: 'ProjectHook'
has_many :protected_branches
@@ -380,7 +383,7 @@ class Project < ApplicationRecord
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_many :project_badges, class_name: 'ProjectBadge', inverse_of: :project
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
@@ -650,6 +653,11 @@ class Project < ApplicationRecord
.where(repository_languages: { programming_language_id: lang_id_query })
end
+ scope :with_programming_language_id, ->(language_id) do
+ joins(:repository_languages)
+ .where(repository_languages: { programming_language_id: language_id })
+ end
+
scope :service_desk_enabled, -> { where(service_desk_enabled: true) }
scope :with_builds_enabled, -> { with_feature_enabled(:builds) }
scope :with_issues_enabled, -> { with_feature_enabled(:issues) }
@@ -742,6 +750,29 @@ class Project < ApplicationRecord
end
end
+ # Defines instance methods:
+ #
+ # - only_allow_merge_if_pipeline_succeeds?(inherit_group_setting: false)
+ # - allow_merge_on_skipped_pipeline?(inherit_group_setting: false)
+ # - only_allow_merge_if_all_discussions_are_resolved?(inherit_group_setting: false)
+ # - only_allow_merge_if_pipeline_succeeds_locked?
+ # - allow_merge_on_skipped_pipeline_locked?
+ # - only_allow_merge_if_all_discussions_are_resolved_locked?
+ def self.cascading_with_parent_namespace(attribute)
+ # method overriden in EE
+ define_method("#{attribute}?") do |inherit_group_setting: false|
+ self.public_send(attribute) # rubocop:disable GitlabSecurity/PublicSend
+ end
+
+ define_method("#{attribute}_locked?") do
+ false
+ end
+ end
+
+ cascading_with_parent_namespace :only_allow_merge_if_pipeline_succeeds
+ cascading_with_parent_namespace :allow_merge_on_skipped_pipeline
+ cascading_with_parent_namespace :only_allow_merge_if_all_discussions_are_resolved
+
def self.with_feature_available_for_user(feature, user)
with_project_feature.merge(ProjectFeature.with_feature_available_for_user(feature, user))
end
@@ -1691,8 +1722,14 @@ class Project < ApplicationRecord
def execute_integrations(data, hooks_scope = :push_hooks)
# Call only service hooks that are active for this scope
run_after_commit_or_now do
- integrations.public_send(hooks_scope).each do |integration| # rubocop:disable GitlabSecurity/PublicSend
- integration.async_execute(data)
+ if use_integration_relations?
+ association("#{hooks_scope}_integrations").reader.each do |integration|
+ integration.async_execute(data)
+ end
+ else
+ integrations.public_send(hooks_scope).each do |integration| # rubocop:disable GitlabSecurity/PublicSend
+ integration.async_execute(data)
+ end
end
end
end
@@ -2301,6 +2338,7 @@ class Project < ApplicationRecord
.append(key: 'CI_PROJECT_PATH', value: full_path)
.append(key: 'CI_PROJECT_PATH_SLUG', value: full_path_slug)
.append(key: 'CI_PROJECT_NAMESPACE', value: namespace.full_path)
+ .append(key: 'CI_PROJECT_NAMESPACE_ID', value: namespace.id.to_s)
.append(key: 'CI_PROJECT_ROOT_NAMESPACE', value: namespace.root_ancestor.path)
.append(key: 'CI_PROJECT_URL', value: web_url)
.append(key: 'CI_PROJECT_VISIBILITY', value: Gitlab::VisibilityLevel.string_level(visibility_level))
@@ -2700,7 +2738,13 @@ class Project < ApplicationRecord
def access_request_approvers_to_be_notified
access_request_approvers = members.owners_and_maintainers
- access_request_approvers.connected_to_user.order_recent_sign_in.limit(Member::ACCESS_REQUEST_APPROVERS_TO_BE_NOTIFIED_LIMIT)
+ recipients = access_request_approvers.connected_to_user.order_recent_sign_in.limit(Member::ACCESS_REQUEST_APPROVERS_TO_BE_NOTIFIED_LIMIT)
+
+ if recipients.blank?
+ recipients = group.access_request_approvers_to_be_notified
+ end
+
+ recipients
end
def pages_lookup_path(trim_prefix: nil, domain: nil)
@@ -2994,6 +3038,10 @@ class Project < ApplicationRecord
group&.work_items_feature_flag_enabled? || Feature.enabled?(:work_items, self)
end
+ def work_items_mvc_feature_flag_enabled?
+ group&.work_items_mvc_feature_flag_enabled? || Feature.enabled?(:work_items_mvc)
+ end
+
def work_items_mvc_2_feature_flag_enabled?
group&.work_items_mvc_2_feature_flag_enabled? || Feature.enabled?(:work_items_mvc_2)
end
@@ -3321,6 +3369,12 @@ class Project < ApplicationRecord
ProjectFeature::PRIVATE
end
end
+
+ def use_integration_relations?
+ strong_memoize(:use_integration_relations) do
+ Feature.enabled?(:cache_project_integrations, self)
+ end
+ end
end
Project.prepend_mod_with('Project')