debian-mirror-gitlab/app/models/project.rb

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

3491 lines
117 KiB
Ruby
Raw Normal View History

2018-11-18 11:00:15 +05:30
# frozen_string_literal: true
2015-04-26 12:48:37 +05:30
require 'carrierwave/orm/activerecord'
2019-07-07 11:18:12 +05:30
class Project < ApplicationRecord
2015-09-11 14:41:01 +05:30
include Gitlab::ConfigHelper
2014-09-02 18:07:02 +05:30
include Gitlab::VisibilityLevel
include AccessRequestable
2017-09-10 17:25:29 +05:30
include Avatarable
2016-11-03 12:29:30 +05:30
include CacheMarkdownField
2015-09-11 14:41:01 +05:30
include Sortable
2015-10-24 18:46:33 +05:30
include AfterCommitQueue
include CaseSensitivity
include TokenAuthenticatable
2017-08-17 22:00:37 +05:30
include ValidAttribute
2019-09-30 21:07:59 +05:30
include ProjectAPICompatibility
2016-09-29 09:46:39 +05:30
include ProjectFeaturesCompatibility
2017-08-17 22:00:37 +05:30
include SelectForProjectAuthorization
2018-03-27 19:54:05 +05:30
include Presentable
2020-03-13 15:44:24 +05:30
include HasRepository
2020-05-24 23:13:21 +05:30
include HasWiki
2023-05-27 22:25:52 +05:30
include WebHooks::HasWebHooks
2021-02-22 17:27:13 +05:30
include CanMoveRepositoryStorage
2017-08-17 22:00:37 +05:30
include Routable
2018-03-17 18:26:18 +05:30
include GroupDescendant
include Gitlab::SQL::Pattern
include DeploymentPlatform
include ::Gitlab::Utils::StrongMemoize
2018-05-09 12:01:36 +05:30
include ChronicDurationAttribute
2018-10-15 14:42:47 +05:30
include FastDestroyAll::Helpers
2018-11-08 19:23:39 +05:30
include WithUploads
include BatchDestroyDependentAssociations
2018-11-20 20:47:30 +05:30
include FeatureGate
include OptionallySearch
2018-12-05 23:21:45 +05:30
include FromUnion
2021-03-08 18:12:59 +05:30
include Repositories::CanHousekeepRepository
2021-01-03 14:25:43 +05:30
include EachBatch
2021-04-29 21:17:54 +05:30
include GitlabRoutingHelper
2021-12-07 22:27:20 +05:30
include BulkMemberAccessLoad
2022-06-21 17:19:12 +05:30
include BulkUsersByEmailLoad
2022-03-02 08:16:31 +05:30
include RunnerTokenExpirationInterval
2022-05-07 20:08:51 +05:30
include BlocksUnsafeSerialization
2023-01-13 00:05:48 +05:30
include Subquery
2023-04-23 21:23:45 +05:30
include IssueParent
2023-06-20 00:43:36 +05:30
include UpdatedAtFilterable
2021-04-29 21:17:54 +05:30
2018-11-08 19:23:39 +05:30
extend Gitlab::Cache::RequestCache
2021-01-03 14:25:43 +05:30
extend Gitlab::Utils::Override
2015-12-23 02:04:40 +05:30
2014-09-02 18:07:02 +05:30
extend Gitlab::ConfigHelper
2017-08-17 22:00:37 +05:30
BoardLimitExceeded = Class.new(StandardError)
2022-07-16 23:28:13 +05:30
ExportLimitExceeded = Class.new(StandardError)
2016-11-03 12:29:30 +05:30
2019-12-04 20:38:33 +05:30
STATISTICS_ATTRIBUTE = 'repositories_count'
UNKNOWN_IMPORT_URL = 'http://unknown.git'
2018-03-17 18:26:18 +05:30
# Hashed Storage versions handle rolling out new storage to project and dependents models:
# nil: legacy
# 1: repository
# 2: attachments
LATEST_STORAGE_VERSION = 2
HASHED_STORAGE_FEATURES = {
repository: 1,
attachments: 2
}.freeze
2018-03-26 14:24:53 +05:30
2018-12-13 13:39:08 +05:30
VALID_IMPORT_PORTS = [80, 443].freeze
VALID_IMPORT_PROTOCOLS = %w(http https git).freeze
VALID_MIRROR_PORTS = [22, 80, 443].freeze
VALID_MIRROR_PROTOCOLS = %w(http https ssh git).freeze
2015-09-25 12:07:36 +05:30
2019-12-04 20:38:33 +05:30
SORTING_PREFERENCE_FIELD = :projects_sort
MAX_BUILD_TIMEOUT = 1.month
2021-02-22 17:27:13 +05:30
GL_REPOSITORY_TYPES = [Gitlab::GlRepository::PROJECT, Gitlab::GlRepository::WIKI, Gitlab::GlRepository::DESIGN].freeze
2022-04-04 11:22:00 +05:30
MAX_SUGGESTIONS_TEMPLATE_LENGTH = 255
MAX_COMMIT_TEMPLATE_LENGTH = 500
DEFAULT_MERGE_COMMIT_TEMPLATE = <<~MSG.rstrip.freeze
Merge branch '%{source_branch}' into '%{target_branch}'
%{title}
%{issues}
See merge request %{reference}
MSG
DEFAULT_SQUASH_COMMIT_TEMPLATE = '%{title}'
2023-05-27 22:25:52 +05:30
PROJECT_FEATURES_DEFAULTS = {
issues: gitlab_config_features.issues,
merge_requests: gitlab_config_features.merge_requests,
builds: gitlab_config_features.builds,
wiki: gitlab_config_features.wiki,
snippets: gitlab_config_features.snippets
}.freeze
2016-11-03 12:29:30 +05:30
cache_markdown_field :description, pipeline: :description
2023-03-04 22:38:38 +05:30
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 }
2020-04-08 14:13:33 +05:30
2022-02-27 12:50:16 +05:30
add_authentication_token_field :runners_token,
2022-07-16 23:28:13 +05:30
encrypted: -> { Feature.enabled?(:projects_tokens_optional_encryption) ? :optional : :required },
2023-05-27 22:25:52 +05:30
format_with_prefix: :runners_token_prefix,
require_prefix_for_validation: true
2018-10-15 14:42:47 +05:30
2023-03-04 22:38:38 +05:30
# Storage specific hooks
after_initialize :use_hashed_storage
2023-05-27 22:25:52 +05:30
after_initialize :set_project_feature_defaults, if: :new_record?
2018-11-08 19:23:39 +05:30
before_validation :mark_remote_mirrors_for_removal, if: -> { RemoteMirror.table_exists? }
2018-10-15 14:42:47 +05:30
2021-12-11 22:18:48 +05:30
before_validation :ensure_project_namespace_in_sync
2022-07-23 23:45:48 +05:30
before_validation :set_package_registry_access_level, if: :packages_enabled_changed?
2022-10-11 01:57:18 +05:30
before_validation :remove_leading_spaces_on_name
2023-03-04 22:38:38 +05:30
after_validation :check_pending_delete
before_save :ensure_runners_token
2023-04-23 21:23:45 +05:30
before_save :update_new_emails_created_column, if: -> { emails_disabled_changed? }
2022-08-27 11:52:29 +05:30
2021-06-08 01:23:25 +05:30
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) }
2018-12-13 13:39:08 +05:30
after_create :set_timestamps_for_create
2023-03-04 22:38:38 +05:30
after_create :check_repository_absence!
2017-09-10 17:25:29 +05:30
before_destroy :remove_private_deploy_keys
2023-03-04 22:38:38 +05:30
after_destroy :remove_exports
after_save :update_project_statistics, if: :saved_change_to_namespace_id?
2018-10-15 14:42:47 +05:30
2023-03-04 22:38:38 +05:30
after_save :schedule_sync_event_worker, if: -> { saved_change_to_id? || saved_change_to_namespace_id? }
2018-10-15 14:42:47 +05:30
2023-03-04 22:38:38 +05:30
after_save :create_import_state, if: ->(project) { project.import? && project.import_state.nil? }
2023-03-04 22:38:38 +05:30
after_save :save_topics
2017-08-17 22:00:37 +05:30
2023-03-04 22:38:38 +05:30
after_save :reload_project_namespace_details
use_fast_destroy :build_trace_chunks
2017-09-10 17:25:29 +05:30
2021-11-11 11:23:49 +05:30
has_many :project_topics, -> { order(:id) }, class_name: 'Projects::ProjectTopic'
has_many :topics, through: :project_topics, class_name: 'Projects::Topic'
attr_accessor :old_path_with_namespace
2017-09-10 17:25:29 +05:30
attr_accessor :template_name
2017-08-17 22:00:37 +05:30
attr_writer :pipeline_status
2018-03-17 18:26:18 +05:30
attr_accessor :skip_disk_validation
2021-11-11 11:23:49 +05:30
attr_writer :topic_list
2014-09-02 18:07:02 +05:30
2016-06-02 11:05:42 +05:30
alias_attribute :title, :name
2014-09-02 18:07:02 +05:30
# Relations
2018-12-13 13:39:08 +05:30
belongs_to :pool_repository
2017-08-17 22:00:37 +05:30
belongs_to :creator, class_name: 'User'
2021-11-18 22:05:49 +05:30
belongs_to :group, -> { where(type: Group.sti_name) }, foreign_key: 'namespace_id'
2014-09-02 18:07:02 +05:30
belongs_to :namespace
2021-11-11 11:23:49 +05:30
# Sync deletion via DB Trigger to ensure we do not have
# a project without a project_namespace (or vice-versa)
2021-12-11 22:18:48 +05:30
belongs_to :project_namespace, autosave: true, class_name: 'Namespaces::ProjectNamespace', foreign_key: 'project_namespace_id'
2018-03-17 18:26:18 +05:30
alias_method :parent, :namespace
alias_attribute :parent_id, :namespace_id
2014-09-02 18:07:02 +05:30
2023-06-20 00:43:36 +05:30
has_one :catalog_resource, class_name: 'Ci::Catalog::Resource', inverse_of: :project
2022-08-27 11:52:29 +05:30
has_one :last_event, -> { order 'events.created_at DESC' }, class_name: 'Event'
2019-07-07 11:18:12 +05:30
has_many :boards
2016-09-29 09:46:39 +05:30
2021-09-04 01:27:46 +05:30
def self.integration_association_name(name)
2021-09-30 23:02:18 +05:30
"#{name}_integration"
2021-09-04 01:27:46 +05:30
end
2021-06-08 01:23:25 +05:30
# Project integrations
2023-03-17 16:20:25 +05:30
has_one :apple_app_store_integration, class_name: 'Integrations::AppleAppStore'
2021-09-04 01:27:46 +05:30
has_one :asana_integration, class_name: 'Integrations::Asana'
has_one :assembla_integration, class_name: 'Integrations::Assembla'
has_one :bamboo_integration, class_name: 'Integrations::Bamboo'
has_one :bugzilla_integration, class_name: 'Integrations::Bugzilla'
has_one :buildkite_integration, class_name: 'Integrations::Buildkite'
has_one :campfire_integration, class_name: 'Integrations::Campfire'
has_one :confluence_integration, class_name: 'Integrations::Confluence'
has_one :custom_issue_tracker_integration, class_name: 'Integrations::CustomIssueTracker'
has_one :datadog_integration, class_name: 'Integrations::Datadog'
2023-07-09 08:55:56 +05:30
has_one :container_registry_data_repair_detail, class_name: 'ContainerRegistry::DataRepairDetail'
2021-09-04 01:27:46 +05:30
has_one :discord_integration, class_name: 'Integrations::Discord'
has_one :drone_ci_integration, class_name: 'Integrations::DroneCi'
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'
2023-05-27 22:25:52 +05:30
has_one :google_play_integration, class_name: 'Integrations::GooglePlay'
2021-09-04 01:27:46 +05:30
has_one :hangouts_chat_integration, class_name: 'Integrations::HangoutsChat'
2022-05-07 20:08:51 +05:30
has_one :harbor_integration, class_name: 'Integrations::Harbor'
2021-09-04 01:27:46 +05:30
has_one :irker_integration, class_name: 'Integrations::Irker'
2021-09-30 23:02:18 +05:30
has_one :jenkins_integration, class_name: 'Integrations::Jenkins'
has_one :jira_integration, class_name: 'Integrations::Jira'
has_one :mattermost_integration, class_name: 'Integrations::Mattermost'
has_one :mattermost_slash_commands_integration, class_name: 'Integrations::MattermostSlashCommands'
has_one :microsoft_teams_integration, class_name: 'Integrations::MicrosoftTeams'
has_one :mock_ci_integration, class_name: 'Integrations::MockCi'
has_one :mock_monitoring_integration, class_name: 'Integrations::MockMonitoring'
has_one :packagist_integration, class_name: 'Integrations::Packagist'
has_one :pipelines_email_integration, class_name: 'Integrations::PipelinesEmail'
has_one :pivotaltracker_integration, class_name: 'Integrations::Pivotaltracker'
has_one :prometheus_integration, class_name: 'Integrations::Prometheus', inverse_of: :project
2022-08-27 11:52:29 +05:30
has_one :pumble_integration, class_name: 'Integrations::Pumble'
2021-09-30 23:02:18 +05:30
has_one :pushover_integration, class_name: 'Integrations::Pushover'
has_one :redmine_integration, class_name: 'Integrations::Redmine'
2021-12-11 22:18:48 +05:30
has_one :shimo_integration, class_name: 'Integrations::Shimo'
2021-09-30 23:02:18 +05:30
has_one :slack_integration, class_name: 'Integrations::Slack'
has_one :slack_slash_commands_integration, class_name: 'Integrations::SlackSlashCommands'
2023-05-27 22:25:52 +05:30
has_one :squash_tm_integration, class_name: 'Integrations::SquashTm'
2021-09-30 23:02:18 +05:30
has_one :teamcity_integration, class_name: 'Integrations::Teamcity'
has_one :unify_circuit_integration, class_name: 'Integrations::UnifyCircuit'
has_one :webex_teams_integration, class_name: 'Integrations::WebexTeams'
has_one :youtrack_integration, class_name: 'Integrations::Youtrack'
2021-11-11 11:23:49 +05:30
has_one :zentao_integration, class_name: 'Integrations::Zentao'
2017-09-10 17:25:29 +05:30
2023-01-13 00:05:48 +05:30
has_one :wiki_repository, class_name: 'Projects::WikiRepository', inverse_of: :project
2023-06-20 00:43:36 +05:30
has_one :design_management_repository, class_name: 'DesignManagement::Repository', inverse_of: :project
2018-03-17 18:26:18 +05:30
has_one :root_of_fork_network,
foreign_key: 'root_project_id',
inverse_of: :root_project,
class_name: 'ForkNetwork'
has_one :fork_network_member
has_one :fork_network, through: :fork_network_member
2018-12-13 13:39:08 +05:30
has_one :forked_from_project, through: :fork_network_member
2023-03-04 22:38:38 +05:30
# 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
2018-12-13 13:39:08 +05:30
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
2020-01-01 13:55:28 +05:30
has_many :fork_network_projects, through: :fork_network, source: :projects
2015-04-26 12:48:37 +05:30
2020-07-28 23:09:34 +05:30
# Packages
2023-05-27 22:25:52 +05:30
has_many :packages,
class_name: 'Packages::Package'
has_many :package_files,
through: :packages, class_name: 'Packages::PackageFile'
2022-11-25 23:54:43 +05:30
# repository_files must be destroyed by ruby code in order to properly remove carrierwave uploads
2023-05-27 22:25:52 +05:30
has_many :rpm_repository_files,
inverse_of: :project,
class_name: 'Packages::Rpm::RepositoryFile',
dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
2021-03-11 19:13:27 +05:30
# debian_distributions and associated component_files must be destroyed by ruby code in order to properly remove carrierwave uploads
2023-05-27 22:25:52 +05:30
has_many :debian_distributions,
class_name: 'Packages::Debian::ProjectDistribution',
dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
2023-06-20 00:43:36 +05:30
has_many :npm_metadata_caches,
class_name: 'Packages::Npm::MetadataCache'
2023-05-27 22:25:52 +05:30
has_one :packages_cleanup_policy,
class_name: 'Packages::Cleanup::Policy',
inverse_of: :project
2020-07-28 23:09:34 +05:30
2018-10-15 14:42:47 +05:30
has_one :import_state, autosave: true, class_name: 'ProjectImportState', inverse_of: :project
2018-11-08 19:23:39 +05:30
has_one :import_export_upload, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
2020-04-08 14:13:33 +05:30
has_many :export_jobs, class_name: 'ProjectExportJob'
2021-11-18 22:05:49 +05:30
has_many :bulk_import_exports, class_name: 'BulkImports::Export', inverse_of: :project
2019-02-15 15:39:39 +05:30
has_one :project_repository, inverse_of: :project
2020-03-13 15:44:24 +05:30
has_one :incident_management_setting, inverse_of: :project, class_name: 'IncidentManagement::ProjectIncidentManagementSetting'
2019-02-15 15:39:39 +05:30
has_one :error_tracking_setting, inverse_of: :project, class_name: 'ErrorTracking::ProjectErrorTrackingSetting'
2019-07-31 22:56:46 +05:30
has_one :metrics_setting, inverse_of: :project, class_name: 'ProjectMetricsSetting'
2019-12-21 20:55:43 +05:30
has_one :grafana_integration, inverse_of: :project
2020-06-23 00:09:42 +05:30
has_one :project_setting, inverse_of: :project, autosave: true
2020-04-22 19:07:51 +05:30
has_one :alerting_setting, inverse_of: :project, class_name: 'Alerting::ProjectAlertingSetting'
2020-07-28 23:09:34 +05:30
has_one :service_desk_setting, class_name: 'ServiceDeskSetting'
2023-05-27 22:25:52 +05:30
has_one :service_desk_custom_email_verification, class_name: 'ServiceDesk::CustomEmailVerification'
2023-06-20 00:43:36 +05:30
has_one :service_desk_custom_email_credential, class_name: 'ServiceDesk::CustomEmailCredential'
2018-10-15 14:42:47 +05:30
2021-04-29 21:17:54 +05:30
# Merge requests for target project should be removed with it
2023-03-04 22:38:38 +05:30
has_many :merge_requests, foreign_key: 'target_project_id', inverse_of: :target_project, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
2021-03-11 19:13:27 +05:30
has_many :merge_request_metrics, foreign_key: 'target_project', class_name: 'MergeRequest::Metrics', inverse_of: :target_project
2018-03-27 19:54:05 +05:30
has_many :source_of_merge_requests, foreign_key: 'source_project_id', class_name: 'MergeRequest'
2023-03-04 22:38:38 +05:30
has_many :issues, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
2023-07-09 08:55:56 +05:30
has_many :work_items # the issues relation will handle any destroys
2022-08-13 15:12:31 +05:30
has_many :incident_management_issuable_escalation_statuses, through: :issues, inverse_of: :project, class_name: 'IncidentManagement::IssuableEscalationStatus'
2022-11-25 23:54:43 +05:30
has_many :incident_management_timeline_event_tags, inverse_of: :project, class_name: 'IncidentManagement::TimelineEventTag'
2023-03-04 22:38:38 +05:30
has_many :labels, class_name: 'ProjectLabel', dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
2017-09-10 17:25:29 +05:30
has_many :events
has_many :milestones
2021-09-04 01:27:46 +05:30
2023-03-04 22:38:38 +05:30
has_many :integrations
has_many :alert_hooks_integrations, -> { alert_hooks }, class_name: 'Integration'
2023-03-17 16:20:25 +05:30
has_many :incident_hooks_integrations, -> { incident_hooks }, class_name: 'Integration'
2023-03-04 22:38:38 +05:30
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'
2017-09-10 17:25:29 +05:30
has_many :snippets, class_name: 'ProjectSnippet'
has_many :hooks, class_name: 'ProjectHook'
has_many :protected_branches
2021-02-22 17:27:13 +05:30
has_many :exported_protected_branches
2017-09-10 17:25:29 +05:30
has_many :protected_tags
2018-11-18 11:00:15 +05:30
has_many :repository_languages, -> { order "share DESC" }
2020-05-24 23:13:21 +05:30
has_many :designs, inverse_of: :project, class_name: 'DesignManagement::Design'
2016-08-24 12:49:21 +05:30
2017-08-17 22:00:37 +05:30
has_many :project_authorizations
has_many :authorized_users, through: :project_authorizations, source: :user, class_name: 'User'
2023-03-17 16:20:25 +05:30
2017-09-10 17:25:29 +05:30
has_many :project_members, -> { where(requested_at: nil) },
as: :source, dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent
alias_method :members, :project_members
2023-03-17 16:20:25 +05:30
has_many :namespace_members, ->(project) { where(requested_at: nil).unscope(where: %i[source_id source_type]) },
primary_key: :project_namespace_id, foreign_key: :member_namespace_id, inverse_of: :project, class_name: 'ProjectMember'
2016-08-24 12:49:21 +05:30
2017-09-10 17:25:29 +05:30
has_many :requesters, -> { where.not(requested_at: nil) },
as: :source, class_name: 'ProjectMember', dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent
2023-03-17 16:20:25 +05:30
has_many :namespace_requesters, ->(project) { where.not(requested_at: nil).unscope(where: %i[source_id source_type]) },
primary_key: :project_namespace_id, foreign_key: :member_namespace_id, inverse_of: :project, class_name: 'ProjectMember'
2018-03-17 18:26:18 +05:30
has_many :members_and_requesters, as: :source, class_name: 'ProjectMember'
2023-04-23 21:23:45 +05:30
has_many :namespace_members_and_requesters, -> { unscope(where: %i[source_id source_type]) },
primary_key: :project_namespace_id, foreign_key: :member_namespace_id, inverse_of: :project,
class_name: 'ProjectMember'
2016-08-24 12:49:21 +05:30
2023-03-17 16:20:25 +05:30
has_many :users, through: :project_members
has_many :project_callouts, class_name: 'Users::ProjectCallout', foreign_key: :project_id
2019-10-12 21:52:04 +05:30
has_many :deploy_keys_projects, inverse_of: :project
2014-09-02 18:07:02 +05:30
has_many :deploy_keys, through: :deploy_keys_projects
2017-09-10 17:25:29 +05:30
has_many :users_star_projects
2014-09-02 18:07:02 +05:30
has_many :starrers, through: :users_star_projects, source: :user
2017-09-10 17:25:29 +05:30
has_many :releases
2021-09-04 01:27:46 +05:30
has_many :lfs_objects_projects
2019-09-04 21:01:54 +05:30
has_many :lfs_objects, -> { distinct }, through: :lfs_objects_projects
2018-03-17 18:26:18 +05:30
has_many :lfs_file_locks
2017-09-10 17:25:29 +05:30
has_many :project_group_links
2016-06-02 11:05:42 +05:30
has_many :invited_groups, through: :project_group_links, source: :group
2017-09-10 17:25:29 +05:30
has_many :todos
has_many :notification_settings, as: :source, dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent
2018-05-09 12:01:36 +05:30
has_many :internal_ids
2017-09-10 17:25:29 +05:30
has_one :import_data, class_name: 'ProjectImportData', inverse_of: :project, autosave: true
2018-03-17 18:26:18 +05:30
has_one :project_feature, inverse_of: :project
2017-09-10 17:25:29 +05:30
has_one :statistics, class_name: 'ProjectStatistics'
2020-11-24 15:15:51 +05:30
has_one :feature_usage, class_name: 'ProjectFeatureUsage'
2017-09-10 17:25:29 +05:30
2018-03-17 18:26:18 +05:30
has_one :cluster_project, class_name: 'Clusters::Project'
has_many :clusters, through: :cluster_project, class_name: 'Clusters::Cluster'
2019-02-15 15:39:39 +05:30
has_many :kubernetes_namespaces, class_name: 'Clusters::KubernetesNamespace'
2019-12-21 20:55:43 +05:30
has_many :management_clusters, class_name: 'Clusters::Cluster', foreign_key: :management_project_id, inverse_of: :management_project
2020-10-24 23:57:45 +05:30
has_many :cluster_agents, class_name: 'Clusters::Agent'
2023-07-09 08:55:56 +05:30
has_many :ci_access_project_authorizations, class_name: 'Clusters::Agents::Authorizations::CiAccess::ProjectAuthorization'
2018-03-17 18:26:18 +05:30
2018-11-20 20:47:30 +05:30
has_many :prometheus_metrics
2020-03-13 15:44:24 +05:30
has_many :prometheus_alerts, inverse_of: :project
2020-04-22 19:07:51 +05:30
has_many :prometheus_alert_events, inverse_of: :project
has_many :self_managed_prometheus_alert_events, inverse_of: :project
2020-05-24 23:13:21 +05:30
has_many :metrics_users_starred_dashboards, class_name: 'Metrics::UsersStarredDashboard', inverse_of: :project
has_many :alert_management_alerts, class_name: 'AlertManagement::Alert', inverse_of: :project
2021-01-03 14:25:43 +05:30
has_many :alert_management_http_integrations, class_name: 'AlertManagement::HttpIntegration', inverse_of: :project
2018-11-20 20:47:30 +05:30
2017-09-10 17:25:29 +05:30
# Container repositories need to remove data from the container registry,
# which is not managed by the DB. Hence we're still using dependent: :destroy
# here.
has_many :container_repositories, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
2020-01-01 13:55:28 +05:30
has_one :container_expiration_policy, inverse_of: :project
2017-09-10 17:25:29 +05:30
has_many :commit_statuses
2019-07-07 11:18:12 +05:30
# The relation :all_pipelines is intended to be used when we want to get the
2019-02-15 15:39:39 +05:30
# whole list of pipelines associated to the project
has_many :all_pipelines, class_name: 'Ci::Pipeline', inverse_of: :project
2020-11-24 15:15:51 +05:30
# The relation :ci_pipelines includes all those that directly contribute to the
# latest status of a ref. This does not include dangling pipelines such as those
# from webide, child pipelines, etc.
2019-02-15 15:39:39 +05:30
has_many :ci_pipelines,
-> { ci_sources },
class_name: 'Ci::Pipeline',
inverse_of: :project
2018-11-08 19:23:39 +05:30
has_many :stages, class_name: 'Ci::Stage', inverse_of: :project
2020-06-23 00:09:42 +05:30
has_many :ci_refs, class_name: 'Ci::Ref', inverse_of: :project
2022-11-25 23:54:43 +05:30
has_many :pipeline_metadata, class_name: 'Ci::PipelineMetadata', inverse_of: :project
2021-11-11 11:23:49 +05:30
has_many :pending_builds, class_name: 'Ci::PendingBuild'
2022-05-07 20:08:51 +05:30
has_many :builds, class_name: 'Ci::Build', inverse_of: :project
2021-01-03 14:25:43 +05:30
has_many :processables, class_name: 'Ci::Processable', inverse_of: :project
2022-05-07 20:08:51 +05:30
has_many :build_trace_chunks, class_name: 'Ci::BuildTraceChunk', through: :builds, source: :trace_chunks, dependent: :restrict_with_error
2020-06-23 00:09:42 +05:30
has_many :build_report_results, class_name: 'Ci::BuildReportResult', inverse_of: :project
2022-05-07 20:08:51 +05:30
has_many :job_artifacts, class_name: 'Ci::JobArtifact', dependent: :restrict_with_error
has_many :pipeline_artifacts, class_name: 'Ci::PipelineArtifact', inverse_of: :project, dependent: :restrict_with_error
2018-11-08 19:23:39 +05:30
has_many :runner_projects, class_name: 'Ci::RunnerProject', inverse_of: :project
2015-12-23 02:04:40 +05:30
has_many :runners, through: :runner_projects, source: :runner, class_name: 'Ci::Runner'
2017-09-10 17:25:29 +05:30
has_many :variables, class_name: 'Ci::Variable'
has_many :triggers, class_name: 'Ci::Trigger'
2022-05-07 20:08:51 +05:30
has_many :secure_files, class_name: 'Ci::SecureFile', dependent: :restrict_with_error
2017-09-10 17:25:29 +05:30
has_many :environments
2019-12-26 22:10:19 +05:30
has_many :environments_for_dashboard, -> { from(with_rank.unfoldered.available, :environments).where('rank <= 3') }, class_name: 'Environment'
2019-12-21 20:55:43 +05:30
has_many :deployments
2017-09-10 17:25:29 +05:30
has_many :pipeline_schedules, class_name: 'Ci::PipelineSchedule'
2018-05-09 12:01:36 +05:30
has_many :project_deploy_tokens
has_many :deploy_tokens, through: :project_deploy_tokens
2020-03-13 15:44:24 +05:30
has_many :resource_groups, class_name: 'Ci::ResourceGroup', inverse_of: :project
2020-05-24 23:13:21 +05:30
has_many :freeze_periods, class_name: 'Ci::FreezePeriod', inverse_of: :project
2017-08-17 22:00:37 +05:30
2019-09-30 21:07:59 +05:30
has_one :auto_devops, class_name: 'ProjectAutoDevops', inverse_of: :project, autosave: true
2018-03-17 18:26:18 +05:30
has_many :custom_attributes, class_name: 'ProjectCustomAttribute'
2023-03-04 22:38:38 +05:30
has_many :project_badges, class_name: 'ProjectBadge', inverse_of: :project
2018-10-15 14:42:47 +05:30
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
2019-12-04 20:38:33 +05:30
has_many :external_pull_requests, inverse_of: :project
2019-12-21 20:55:43 +05:30
has_many :sourced_pipelines, class_name: 'Ci::Sources::Pipeline', foreign_key: :source_project_id
has_many :source_pipelines, class_name: 'Ci::Sources::Pipeline', foreign_key: :project_id
2020-01-01 13:55:28 +05:30
has_many :import_failures, inverse_of: :project
2022-06-21 17:19:12 +05:30
has_many :jira_imports, -> { order(JiraImportState.arel_table[:created_at].asc) }, class_name: 'JiraImportState', inverse_of: :project
2020-04-22 19:07:51 +05:30
2020-05-24 23:13:21 +05:30
has_many :daily_build_group_report_results, class_name: 'Ci::DailyBuildGroupReportResult'
2021-11-11 11:23:49 +05:30
has_many :ci_feature_usages, class_name: 'Projects::CiFeatureUsage'
2020-05-24 23:13:21 +05:30
2021-04-17 20:07:23 +05:30
has_many :repository_storage_moves, class_name: 'Projects::RepositoryStorageMove', inverse_of: :container
2020-01-01 13:55:28 +05:30
2020-06-23 00:09:42 +05:30
has_many :webide_pipelines, -> { webide_source }, class_name: 'Ci::Pipeline', inverse_of: :project
has_many :reviews, inverse_of: :project
2021-01-03 14:25:43 +05:30
has_many :terraform_states, class_name: 'Terraform::State', inverse_of: :project
2020-11-24 15:15:51 +05:30
# GitLab Pages
has_many :pages_domains
has_one :pages_metadatum, class_name: 'ProjectPagesMetadatum', inverse_of: :project
2021-01-29 00:20:46 +05:30
# we need to clean up files, not only remove records
has_many :pages_deployments, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
2020-11-24 15:15:51 +05:30
2020-10-24 23:57:45 +05:30
# Can be too many records. We need to implement delete_all in batches.
# Issue https://gitlab.com/gitlab-org/gitlab/-/issues/228637
has_many :product_analytics_events, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
2020-11-24 15:15:51 +05:30
has_many :operations_feature_flags, class_name: 'Operations::FeatureFlag'
has_one :operations_feature_flags_client, class_name: 'Operations::FeatureFlagsClient'
has_many :operations_feature_flags_user_lists, class_name: 'Operations::FeatureFlags::UserList'
2021-10-27 15:23:28 +05:30
has_many :error_tracking_client_keys, inverse_of: :project, class_name: 'ErrorTracking::ClientKey'
2021-09-30 23:02:18 +05:30
2021-06-08 01:23:25 +05:30
has_many :timelogs
2022-01-26 12:08:38 +05:30
has_one :ci_project_mirror, class_name: 'Ci::ProjectMirror'
has_many :sync_events, class_name: 'Projects::SyncEvent'
2022-07-23 23:45:48 +05:30
has_one :build_artifacts_size_refresh, class_name: 'Projects::BuildArtifactsSizeRefresh'
2015-12-23 02:04:40 +05:30
accepts_nested_attributes_for :variables, allow_destroy: true
2018-03-17 18:26:18 +05:30
accepts_nested_attributes_for :project_feature, update_only: true
2020-05-24 23:13:21 +05:30
accepts_nested_attributes_for :project_setting, update_only: true
2017-09-10 17:25:29 +05:30
accepts_nested_attributes_for :import_data
2018-03-17 18:26:18 +05:30
accepts_nested_attributes_for :auto_devops, update_only: true
2019-09-04 21:01:54 +05:30
accepts_nested_attributes_for :ci_cd_settings, update_only: true
2020-01-01 13:55:28 +05:30
accepts_nested_attributes_for :container_expiration_policy, update_only: true
2015-04-26 12:48:37 +05:30
2018-10-15 14:42:47 +05:30
accepts_nested_attributes_for :remote_mirrors,
allow_destroy: true,
reject_if: ->(attrs) { attrs[:id].blank? && attrs[:url].blank? }
2020-03-13 15:44:24 +05:30
accepts_nested_attributes_for :incident_management_setting, update_only: true
2019-02-15 15:39:39 +05:30
accepts_nested_attributes_for :error_tracking_setting, update_only: true
2019-07-31 22:56:46 +05:30
accepts_nested_attributes_for :metrics_setting, update_only: true, allow_destroy: true
2019-12-21 20:55:43 +05:30
accepts_nested_attributes_for :grafana_integration, update_only: true, allow_destroy: true
2021-09-30 23:02:18 +05:30
accepts_nested_attributes_for :prometheus_integration, update_only: true
2020-04-22 19:07:51 +05:30
accepts_nested_attributes_for :alerting_setting, update_only: true
2020-03-13 15:44:24 +05:30
2022-08-13 15:12:31 +05:30
delegate :merge_requests_access_level, :forking_access_level, :issues_access_level,
:wiki_access_level, :snippets_access_level, :builds_access_level,
:repository_access_level, :package_registry_access_level, :pages_access_level,
:metrics_dashboard_access_level, :analytics_access_level,
:operations_access_level, :security_and_compliance_access_level,
2022-08-27 11:52:29 +05:30
:container_registry_access_level, :environments_access_level, :feature_flags_access_level,
2023-01-13 00:05:48 +05:30
:monitor_access_level, :releases_access_level, :infrastructure_access_level,
2022-08-13 15:12:31 +05:30
to: :project_feature, allow_nil: true
delegate :show_default_award_emojis, :show_default_award_emojis=,
:enforce_auth_checks_on_uploads, :enforce_auth_checks_on_uploads=,
:warn_about_potentially_unwanted_characters, :warn_about_potentially_unwanted_characters=,
to: :project_setting, allow_nil: true
2022-10-11 01:57:18 +05:30
delegate :show_diff_preview_in_email, :show_diff_preview_in_email=, :show_diff_preview_in_email?,
2023-06-20 00:43:36 +05:30
:runner_registration_enabled, :runner_registration_enabled?, :runner_registration_enabled=,
2022-10-11 01:57:18 +05:30
to: :project_setting
2020-07-28 23:09:34 +05:30
delegate :squash_always?, :squash_never?, :squash_enabled_by_default?, :squash_readonly?, to: :project_setting
2021-09-30 23:02:18 +05:30
delegate :squash_option, :squash_option=, to: :project_setting
2022-03-02 08:16:31 +05:30
delegate :mr_default_target_self, :mr_default_target_self=, to: :project_setting
2021-09-30 23:02:18 +05:30
delegate :previous_default_branch, :previous_default_branch=, to: :project_setting
2014-09-02 18:07:02 +05:30
delegate :name, to: :owner, allow_nil: true, prefix: true
delegate :members, to: :team, prefix: true
2023-06-20 00:43:36 +05:30
delegate :add_member, :add_members, :member?, to: :team
2022-05-07 20:08:51 +05:30
delegate :add_guest, :add_reporter, :add_developer, :add_maintainer, :add_owner, :add_role, to: :team
2021-09-30 23:02:18 +05:30
delegate :group_runners_enabled, :group_runners_enabled=, to: :ci_cd_settings, allow_nil: true
2022-08-13 15:12:31 +05:30
delegate :root_ancestor, to: :namespace, allow_nil: true
2019-07-07 11:18:12 +05:30
delegate :last_pipeline, to: :commit, allow_nil: true
2019-09-04 21:01:54 +05:30
delegate :external_dashboard_url, to: :metrics_setting, allow_nil: true, prefix: true
2020-06-23 00:09:42 +05:30
delegate :dashboard_timezone, to: :metrics_setting, allow_nil: true, prefix: true
2021-09-04 01:27:46 +05:30
delegate :default_git_depth, :default_git_depth=, to: :ci_cd_settings, prefix: :ci, allow_nil: true
2021-09-30 23:02:18 +05:30
delegate :forward_deployment_enabled, :forward_deployment_enabled=, to: :ci_cd_settings, prefix: :ci, allow_nil: true
2022-11-25 23:54:43 +05:30
delegate :job_token_scope_enabled, :job_token_scope_enabled=, to: :ci_cd_settings, prefix: :ci_outbound, allow_nil: true
delegate :inbound_job_token_scope_enabled, :inbound_job_token_scope_enabled=, to: :ci_cd_settings, prefix: :ci, allow_nil: true
2021-09-30 23:02:18 +05:30
delegate :keep_latest_artifact, :keep_latest_artifact=, to: :ci_cd_settings, allow_nil: true
2022-08-27 11:52:29 +05:30
delegate :allow_fork_pipelines_to_run_in_parent_project, :allow_fork_pipelines_to_run_in_parent_project=, to: :ci_cd_settings, prefix: :ci, allow_nil: true
2021-09-30 23:02:18 +05:30
delegate :restrict_user_defined_variables, :restrict_user_defined_variables=, to: :ci_cd_settings, allow_nil: true
2022-06-21 17:19:12 +05:30
delegate :separated_caches, :separated_caches=, to: :ci_cd_settings, prefix: :ci, allow_nil: true
2022-03-02 08:16:31 +05:30
delegate :runner_token_expiration_interval, :runner_token_expiration_interval=, :runner_token_expiration_interval_human_readable, :runner_token_expiration_interval_human_readable=, to: :ci_cd_settings, allow_nil: true
delegate :actual_limits, :actual_plan_name, :actual_plan, to: :namespace, allow_nil: true
2020-07-28 23:09:34 +05:30
delegate :allow_merge_on_skipped_pipeline, :allow_merge_on_skipped_pipeline?,
2022-01-26 12:08:38 +05:30
:allow_merge_on_skipped_pipeline=, :has_confluence?, :has_shimo?,
2020-07-28 23:09:34 +05:30
to: :project_setting
2021-12-11 22:18:48 +05:30
delegate :merge_commit_template, :merge_commit_template=, to: :project_setting, allow_nil: true
2022-01-26 12:08:38 +05:30
delegate :squash_commit_template, :squash_commit_template=, to: :project_setting, allow_nil: true
2023-01-13 00:05:48 +05:30
delegate :issue_branch_template, :issue_branch_template=, to: :project_setting, allow_nil: true
2014-09-02 18:07:02 +05:30
2020-11-24 15:15:51 +05:30
delegate :log_jira_dvcs_integration_usage, :jira_dvcs_server_last_sync_at, :jira_dvcs_cloud_last_sync_at, to: :feature_usage
2022-11-25 23:54:43 +05:30
delegate :maven_package_requests_forwarding,
:pypi_package_requests_forwarding,
:npm_package_requests_forwarding,
to: :namespace
2014-09-02 18:07:02 +05:30
# Validations
validates :creator, presence: true, on: :create
validates :description, length: { maximum: 2000 }, allow_blank: true
2017-09-10 17:25:29 +05:30
validates :ci_config_path,
2018-03-17 18:26:18 +05:30
format: { without: %r{(\.{2}|\A/)},
2022-11-25 23:54:43 +05:30
message: N_('cannot include leading slash or directory traversal.') },
2017-09-10 17:25:29 +05:30
length: { maximum: 255 },
allow_blank: true
2015-04-26 12:48:37 +05:30
validates :name,
presence: true,
2017-08-17 22:00:37 +05:30
length: { maximum: 255 },
2015-04-26 12:48:37 +05:30
format: { with: Gitlab::Regex.project_name_regex,
message: Gitlab::Regex.project_name_regex_message }
validates :path,
presence: true,
2018-03-17 18:26:18 +05:30
project_path: true,
length: { maximum: 255 }
2022-05-07 20:08:51 +05:30
validates :path,
format: { with: Gitlab::Regex.oci_repository_path_regex,
message: Gitlab::Regex.oci_repository_path_regex_message },
if: :path_changed?
2017-08-17 22:00:37 +05:30
2020-03-13 15:44:24 +05:30
validates :project_feature, presence: true
2014-09-02 18:07:02 +05:30
validates :namespace, presence: true
2022-05-07 20:08:51 +05:30
validates :project_namespace, presence: true, on: :create, if: -> { self.namespace }
2022-01-26 12:08:38 +05:30
validates :project_namespace, presence: true, on: :update, if: -> { self.project_namespace_id_changed?(to: nil) }
2017-08-17 22:00:37 +05:30
validates :name, uniqueness: { scope: :namespace_id }
2019-07-31 22:56:46 +05:30
validates :import_url, public_url: { schemes: ->(project) { project.persisted? ? VALID_MIRROR_PROTOCOLS : VALID_IMPORT_PROTOCOLS },
2019-01-03 12:48:30 +05:30
ports: ->(project) { project.persisted? ? VALID_MIRROR_PORTS : VALID_IMPORT_PORTS },
enforce_user: true }, if: [:external_import?, :import_url_changed?]
2014-09-02 18:07:02 +05:30
validates :star_count, numericality: { greater_than_or_equal_to: 0 }
2019-03-02 22:35:43 +05:30
validate :check_personal_projects_limit, on: :create
2018-03-17 18:26:18 +05:30
validate :check_repository_path_availability, on: :update, if: ->(project) { project.renamed? }
2019-06-05 12:25:43 +05:30
validate :visibility_level_allowed_by_group, if: :should_validate_visibility_level?
validate :visibility_level_allowed_as_fork, if: :should_validate_visibility_level?
2018-05-09 12:01:36 +05:30
validate :validate_pages_https_only, if: -> { changes.has_key?(:pages_https_only) }
2021-01-03 14:25:43 +05:30
validate :changing_shared_runners_enabled_is_allowed
2016-08-24 12:49:21 +05:30
validates :repository_storage,
presence: true,
inclusion: { in: ->(_object) { Gitlab.config.repositories.storages.keys } }
2021-03-11 19:13:27 +05:30
validates :variables, nested_attributes_duplicates: { scope: :environment_scope }
2019-02-15 15:39:39 +05:30
validates :bfg_object_map, file_size: { maximum: :max_attachment_size }
2020-03-13 15:44:24 +05:30
validates :max_artifacts_size, numericality: { only_integer: true, greater_than: 0, allow_nil: true }
2022-04-04 11:22:00 +05:30
validates :suggestion_commit_message, length: { maximum: MAX_SUGGESTIONS_TEMPLATE_LENGTH }
2015-04-26 12:48:37 +05:30
2014-09-02 18:07:02 +05:30
# Scopes
2017-09-10 17:25:29 +05:30
scope :pending_delete, -> { where(pending_delete: true) }
scope :without_deleted, -> { where(pending_delete: false) }
2022-05-07 20:08:51 +05:30
scope :not_hidden, -> { where(hidden: false) }
2022-04-04 11:22:00 +05:30
scope :not_aimed_for_deletion, -> { where(marked_for_deletion_at: nil).without_deleted }
2017-08-17 22:00:37 +05:30
2020-01-01 13:55:28 +05:30
scope :with_storage_feature, ->(feature) do
where(arel_table[:storage_version].gteq(HASHED_STORAGE_FEATURES[feature]))
end
scope :without_storage_feature, ->(feature) do
where(arel_table[:storage_version].lt(HASHED_STORAGE_FEATURES[feature])
.or(arel_table[:storage_version].eq(nil)))
end
scope :with_unmigrated_storage, -> do
where(arel_table[:storage_version].lt(LATEST_STORAGE_VERSION)
.or(arel_table[:storage_version].eq(nil)))
end
2018-03-17 18:26:18 +05:30
2022-06-21 17:19:12 +05:30
scope :sorted_by_updated_asc, -> { reorder(self.arel_table['updated_at'].asc) }
scope :sorted_by_updated_desc, -> { reorder(self.arel_table['updated_at'].desc) }
2020-03-13 15:44:24 +05:30
scope :sorted_by_stars_desc, -> { reorder(self.arel_table['star_count'].desc) }
scope :sorted_by_stars_asc, -> { reorder(self.arel_table['star_count'].asc) }
# Sometimes queries (e.g. using CTEs) require explicit disambiguation with table name
2021-11-11 11:23:49 +05:30
scope :projects_order_id_asc, -> { reorder(self.arel_table['id'].asc) }
2020-03-13 15:44:24 +05:30
scope :projects_order_id_desc, -> { reorder(self.arel_table['id'].desc) }
2015-04-26 12:48:37 +05:30
2020-11-24 15:15:51 +05:30
scope :sorted_by_similarity_desc, -> (search, include_in_select: false) do
2022-10-11 01:57:18 +05:30
order_expression = Gitlab::Database::SimilarityScore.build_expression(
search: search,
rules: [
{ column: arel_table["path"], multiplier: 1 },
{ column: arel_table["name"], multiplier: 0.7 },
{ column: arel_table["description"], multiplier: 0.2 }
])
order = Gitlab::Pagination::Keyset::Order.build(
[
Gitlab::Pagination::Keyset::ColumnOrderDefinition.new(
attribute_name: 'similarity',
column_expression: order_expression,
order_expression: order_expression.desc,
order_direction: :desc,
distinct: false,
add_to_projections: true
),
Gitlab::Pagination::Keyset::ColumnOrderDefinition.new(
attribute_name: 'id',
order_expression: Project.arel_table[:id].desc
)
])
2020-11-24 15:15:51 +05:30
2021-04-17 20:07:23 +05:30
order.apply_cursor_conditions(reorder(order))
2020-10-24 23:57:45 +05:30
end
2020-07-28 23:09:34 +05:30
scope :with_packages, -> { joins(:packages) }
2015-09-11 14:41:01 +05:30
scope :in_namespace, ->(namespace_ids) { where(namespace_id: namespace_ids) }
2014-09-02 18:07:02 +05:30
scope :personal, ->(user) { where(namespace_id: user.namespace_id) }
2021-04-29 21:17:54 +05:30
scope :joined, ->(user) { where.not(namespace_id: user.namespace_id) }
2017-09-10 17:25:29 +05:30
scope :starred_by, ->(user) { joins(:users_star_projects).where('users_star_projects.user_id': user.id) }
2016-06-02 11:05:42 +05:30
scope :visible_to_user, ->(user) { where(id: user.authorized_projects.select(:id).reorder(nil)) }
2018-11-18 11:00:15 +05:30
scope :visible_to_user_and_access_level, ->(user, access_level) { where(id: user.authorized_projects.where('project_authorizations.access_level >= ?', access_level).select(:id).reorder(nil)) }
2018-03-17 18:26:18 +05:30
scope :archived, -> { where(archived: true) }
2014-09-02 18:07:02 +05:30
scope :non_archived, -> { where(archived: false) }
2020-06-23 00:09:42 +05:30
scope :with_push, -> { joins(:events).merge(Event.pushed_action) }
2016-11-24 13:41:30 +05:30
scope :with_project_feature, -> { joins('LEFT JOIN project_features ON projects.id = project_features.project_id') }
2020-11-24 15:15:51 +05:30
scope :with_jira_dvcs_cloud, -> { joins(:feature_usage).merge(ProjectFeatureUsage.with_jira_dvcs_integration_enabled(cloud: true)) }
scope :with_jira_dvcs_server, -> { joins(:feature_usage).merge(ProjectFeatureUsage.with_jira_dvcs_integration_enabled(cloud: false)) }
2020-01-01 13:55:28 +05:30
scope :inc_routes, -> { includes(:route, namespace: :route) }
2017-08-17 22:00:37 +05:30
scope :with_statistics, -> { includes(:statistics) }
2020-03-13 15:44:24 +05:30
scope :with_namespace, -> { includes(:namespace) }
2022-07-23 23:45:48 +05:30
scope :with_group, -> { includes(:group) }
2020-03-13 15:44:24 +05:30
scope :with_import_state, -> { includes(:import_state) }
2020-07-28 23:09:34 +05:30
scope :include_project_feature, -> { includes(:project_feature) }
2021-10-27 15:23:28 +05:30
scope :include_integration, -> (integration_association_name) { includes(integration_association_name) }
scope :with_integration, -> (integration_class) { joins(:integrations).merge(integration_class.all) }
scope :with_active_integration, -> (integration_class) { with_integration(integration_class).merge(integration_class.active) }
2022-10-11 01:57:18 +05:30
scope :with_shared_runners_enabled, -> { where(shared_runners_enabled: true) }
2017-08-17 22:00:37 +05:30
scope :inside_path, ->(path) do
# We need routes alias rs for JOIN so it does not conflict with
# includes(:route) which we use in ProjectsFinder.
2017-09-10 17:25:29 +05:30
joins("INNER JOIN routes rs ON rs.source_id = projects.id AND rs.source_type = 'Project'")
.where('rs.path LIKE ?', "#{sanitize_sql_like(path)}/%")
2017-08-17 22:00:37 +05:30
end
2016-11-24 13:41:30 +05:30
scope :with_feature_enabled, ->(feature) {
2022-01-26 12:08:38 +05:30
with_project_feature.merge(ProjectFeature.with_feature_enabled(feature))
2016-11-24 13:41:30 +05:30
}
scope :with_feature_access_level, ->(feature, level) {
2022-01-26 12:08:38 +05:30
with_project_feature.merge(ProjectFeature.with_feature_access_level(feature, level))
2016-11-24 13:41:30 +05:30
}
2019-03-02 22:35:43 +05:30
# Picks projects which use the given programming language
scope :with_programming_language, ->(language_name) do
lang_id_query = ProgrammingLanguage
.with_name_case_insensitive(language_name)
.select(:id)
joins(:repository_languages)
.where(repository_languages: { programming_language_id: lang_id_query })
end
2023-03-04 22:38:38 +05:30
scope :with_programming_language_id, ->(language_id) do
joins(:repository_languages)
.where(repository_languages: { programming_language_id: language_id })
end
2020-07-28 23:09:34 +05:30
scope :service_desk_enabled, -> { where(service_desk_enabled: true) }
2016-11-24 13:41:30 +05:30
scope :with_builds_enabled, -> { with_feature_enabled(:builds) }
scope :with_issues_enabled, -> { with_feature_enabled(:issues) }
2018-03-17 18:26:18 +05:30
scope :with_issues_available_for_user, ->(current_user) { with_feature_available_for_user(:issues, current_user) }
2019-06-05 12:25:43 +05:30
scope :with_merge_requests_available_for_user, ->(current_user) { with_feature_available_for_user(:merge_requests, current_user) }
2020-03-13 15:44:24 +05:30
scope :with_issues_or_mrs_available_for_user, -> (user) do
with_issues_available_for_user(user).or(with_merge_requests_available_for_user(user))
end
2017-09-10 17:25:29 +05:30
scope :with_merge_requests_enabled, -> { with_feature_enabled(:merge_requests) }
2021-04-29 21:17:54 +05:30
scope :with_remote_mirrors, -> { joins(:remote_mirrors).where(remote_mirrors: { enabled: true }) }
2020-01-01 13:55:28 +05:30
scope :with_limit, -> (maximum) { limit(maximum) }
2018-10-15 14:42:47 +05:30
scope :with_group_runners_enabled, -> do
joins(:ci_cd_settings)
.where(project_ci_cd_settings: { group_runners_enabled: true })
end
2016-11-24 13:41:30 +05:30
2019-12-21 20:55:43 +05:30
scope :with_pages_deployed, -> do
joins(:pages_metadatum).merge(ProjectPagesMetadatum.deployed)
end
scope :pages_metadata_not_migrated, -> do
left_outer_joins(:pages_metadatum)
.where(project_pages_metadata: { project_id: nil })
end
2020-07-28 23:09:34 +05:30
scope :with_api_commit_entity_associations, -> {
preload(:project_feature, :route, namespace: [:route, :owner])
2020-06-23 00:09:42 +05:30
}
2022-06-21 17:19:12 +05:30
scope :created_by, -> (user) { where(creator: user) }
2020-11-24 15:15:51 +05:30
scope :imported_from, -> (type) { where(import_type: type) }
2022-06-21 17:19:12 +05:30
scope :imported, -> { where.not(import_type: nil) }
2021-01-29 00:20:46 +05:30
scope :with_enabled_error_tracking, -> { joins(:error_tracking_setting).where(project_error_tracking_settings: { enabled: true }) }
2022-08-27 11:52:29 +05:30
scope :last_activity_before, -> (time) { where('projects.last_activity_at < ?', time) }
2020-11-24 15:15:51 +05:30
2021-09-04 01:27:46 +05:30
scope :with_service_desk_key, -> (key) do
# project_key is not indexed for now
# see https://gitlab.com/gitlab-org/gitlab/-/merge_requests/24063#note_282435524 for details
joins(:service_desk_setting).where('service_desk_settings.project_key' => key)
end
2022-08-13 15:12:31 +05:30
scope :with_topic, ->(topic) { where(id: topic.project_topics.select(:project_id)) }
scope :with_topic_by_name, ->(topic_name) do
2021-11-11 11:23:49 +05:30
topic = Projects::Topic.find_by_name(topic_name)
2022-08-13 15:12:31 +05:30
topic ? with_topic(topic) : none
2021-11-11 11:23:49 +05:30
end
2023-07-09 08:55:56 +05:30
scope :pending_data_repair_analysis, -> do
left_outer_joins(:container_registry_data_repair_detail)
.where(container_registry_data_repair_details: { project_id: nil })
end
2017-08-17 22:00:37 +05:30
enum auto_cancel_pending_pipelines: { disabled: 0, enabled: 1 }
2019-02-15 15:39:39 +05:30
chronic_duration_attr :build_timeout_human_readable, :build_timeout,
2022-11-25 23:54:43 +05:30
default: 3600, error_message: N_('Maximum job timeout has a value which could not be accepted')
2018-05-09 12:01:36 +05:30
validates :build_timeout, allow_nil: true,
2018-11-18 11:00:15 +05:30
numericality: { greater_than_or_equal_to: 10.minutes,
2019-12-04 20:38:33 +05:30
less_than: MAX_BUILD_TIMEOUT,
2018-11-18 11:00:15 +05:30
only_integer: true,
2022-11-25 23:54:43 +05:30
message: N_('needs to be between 10 minutes and 1 month') }
2018-05-09 12:01:36 +05:30
2019-02-15 15:39:39 +05:30
# Used by Projects::CleanupService to hold a map of rewritten object IDs
mount_uploader :bfg_object_map, AttachmentUploader
2020-07-28 23:09:34 +05:30
def self.with_api_entity_associations
2021-11-18 22:05:49 +05:30
preload(:project_feature, :route, :topics, :group, :timelogs, namespace: [:route, :owner])
2020-07-28 23:09:34 +05:30
end
2020-06-23 00:09:42 +05:30
def self.with_web_entity_associations
2021-04-29 21:17:54 +05:30
preload(:project_feature, :route, :creator, group: :parent, namespace: [:route, :owner])
2020-06-23 00:09:42 +05:30
end
2018-11-20 20:47:30 +05:30
def self.eager_load_namespace_and_owner
includes(namespace: :owner)
end
2017-09-10 17:25:29 +05:30
# Returns a collection of projects that is either public or visible to the
# logged in user.
2019-07-07 11:18:12 +05:30
def self.public_or_visible_to_user(user = nil, min_access_level = nil)
2021-01-29 00:20:46 +05:30
min_access_level = nil if user&.can_read_all_resources?
2019-07-07 11:18:12 +05:30
2020-05-24 23:13:21 +05:30
return public_to_user unless user
if user.is_a?(DeployToken)
2023-04-23 21:23:45 +05:30
where(id: user.accessible_projects)
2020-05-24 23:13:21 +05:30
else
2018-03-17 18:26:18 +05:30
where('EXISTS (?) OR projects.visibility_level IN (?)',
2019-07-07 11:18:12 +05:30
user.authorizations_for_projects(min_access_level: min_access_level),
2018-03-17 18:26:18 +05:30
Gitlab::VisibilityLevel.levels_for_user(user))
2017-09-10 17:25:29 +05:30
end
end
2023-03-17 16:20:25 +05:30
# Define two instance methods:
2023-03-04 22:38:38 +05:30
#
2023-03-17 16:20:25 +05:30
# - [attribute]?(inherit_group_setting) Returns the final value after inheriting the parent group
# - [attribute]_locked? Returns true if the value is inherited from the parent group
#
# These functions will be overridden in EE to make sense afterwards
2023-03-04 22:38:38 +05:30
def self.cascading_with_parent_namespace(attribute)
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
2016-11-24 13:41:30 +05:30
def self.with_feature_available_for_user(feature, user)
2022-01-26 12:08:38 +05:30
with_project_feature.merge(ProjectFeature.with_feature_available_for_user(feature, user))
2016-11-24 13:41:30 +05:30
end
2016-09-29 09:46:39 +05:30
2020-07-28 23:09:34 +05:30
def self.projects_user_can(projects, user, action)
projects = where(id: projects)
DeclarativePolicy.user_scope do
projects.select { |project| Ability.allowed?(user, action, project) }
end
end
2019-10-03 12:08:05 +05:30
# This scope returns projects where user has access to both the project and the feature.
def self.filter_by_feature_visibility(feature, user)
2019-12-04 20:38:33 +05:30
with_feature_available_for_user(feature, user)
.public_or_visible_to_user(
user,
ProjectFeature.required_minimum_access_level_for_private_project(feature)
)
2019-10-03 12:08:05 +05:30
end
2020-03-13 15:44:24 +05:30
def self.wrap_with_cte(collection)
cte = Gitlab::SQL::CTE.new(:projects_cte, collection)
Project.with(cte.to_arel).from(cte.alias_to(Project.arel_table))
end
2022-07-16 23:28:13 +05:30
def self.inactive
project_statistics = ::ProjectStatistics.arel_table
minimum_size_mb = ::Gitlab::CurrentSettings.inactive_projects_min_size_mb.megabytes
last_activity_cutoff = ::Gitlab::CurrentSettings.inactive_projects_send_warning_email_after_months.months.ago
joins(:statistics)
.where((project_statistics[:storage_size]).gt(minimum_size_mb))
.where('last_activity_at < ?', last_activity_cutoff)
end
2016-06-02 11:05:42 +05:30
scope :active, -> { joins(:issues, :notes, :merge_requests).order('issues.created_at, notes.created_at, merge_requests.created_at DESC') }
scope :abandoned, -> { where('projects.last_activity_at < ?', 6.months.ago) }
2014-09-02 18:07:02 +05:30
2016-09-13 17:45:13 +05:30
scope :excluding_project, ->(project) { where.not(id: project) }
2017-09-10 17:25:29 +05:30
2019-02-15 15:39:39 +05:30
# We require an alias to the project_mirror_data_table in order to use import_state in our queries
scope :joins_import_state, -> { joins("INNER JOIN project_mirror_data import_state ON import_state.project_id = projects.id") }
2018-12-13 13:39:08 +05:30
scope :for_group, -> (group) { where(group: group) }
2019-12-04 20:38:33 +05:30
scope :for_group_and_its_subgroups, ->(group) { where(namespace_id: group.self_and_descendants.select(:id)) }
2022-04-04 11:22:00 +05:30
scope :for_group_and_its_ancestor_groups, ->(group) { where(namespace_id: group.self_and_ancestors.select(:id)) }
2023-04-23 21:23:45 +05:30
scope :is_importing, -> { with_import_state.where(import_state: { status: %w[started scheduled] }) }
2014-09-02 18:07:02 +05:30
class << self
2016-06-02 11:05:42 +05:30
# Searches for a list of projects based on the query given in `query`.
#
# On PostgreSQL this method uses "ILIKE" to perform a case-insensitive
2020-06-23 00:09:42 +05:30
# search.
2016-06-02 11:05:42 +05:30
#
# query - The search query as a String.
2020-04-22 19:07:51 +05:30
def search(query, include_namespace: false)
if include_namespace
joins(:route).fuzzy_search(query, [Route.arel_table[:path], Route.arel_table[:name], :description])
2020-01-01 13:55:28 +05:30
else
fuzzy_search(query, [:path, :name, :description])
end
2014-09-02 18:07:02 +05:30
end
2015-04-26 12:48:37 +05:30
def search_by_title(query)
2018-03-17 18:26:18 +05:30
non_archived.fuzzy_search(query, [:name])
2014-09-02 18:07:02 +05:30
end
def visibility_levels
Gitlab::VisibilityLevel.options
end
2018-05-09 12:01:36 +05:30
def sort_by_attribute(method)
2017-08-17 22:00:37 +05:30
case method.to_s
when 'storage_size_desc'
# storage_size is a joined column so we need to
# pass a string to avoid AR adding the table name
reorder('project_statistics.storage_size DESC, projects.id DESC')
when 'latest_activity_desc'
2022-06-21 17:19:12 +05:30
sorted_by_updated_desc
2017-08-17 22:00:37 +05:30
when 'latest_activity_asc'
2022-06-21 17:19:12 +05:30
sorted_by_updated_asc
2018-12-05 23:21:45 +05:30
when 'stars_desc'
2019-07-31 22:56:46 +05:30
sorted_by_stars_desc
when 'stars_asc'
sorted_by_stars_asc
2015-04-26 12:48:37 +05:30
else
order_by(method)
2014-09-02 18:07:02 +05:30
end
end
2015-09-11 14:41:01 +05:30
def reference_pattern
2017-08-17 22:00:37 +05:30
%r{
2019-02-02 18:00:53 +05:30
(?<!#{Gitlab::PathRegex::PATH_START_CHAR})
2023-05-27 22:25:52 +05:30
((?<namespace>#{Gitlab::PathRegex::FULL_NAMESPACE_FORMAT_REGEX})/)?
2017-09-10 17:25:29 +05:30
(?<project>#{Gitlab::PathRegex::PROJECT_PATH_FORMAT_REGEX})
2022-08-27 11:52:29 +05:30
}xo
2015-09-11 14:41:01 +05:30
end
2015-10-24 18:46:33 +05:30
2018-11-20 20:47:30 +05:30
def reference_postfix
'>'
end
def reference_postfix_escaped
'&gt;'
end
# Pattern used to extract `namespace/project>` project references from text.
# '>' or its escaped form ('&gt;') are checked for because '>' is sometimes escaped
# when the reference comes from an external source.
def markdown_reference_pattern
2020-07-28 23:09:34 +05:30
@markdown_reference_pattern ||=
%r{
#{reference_pattern}
(#{reference_postfix}|#{reference_postfix_escaped})
}x
2018-11-20 20:47:30 +05:30
end
2016-11-03 12:29:30 +05:30
def trending
2017-09-10 17:25:29 +05:30
joins('INNER JOIN trending_projects ON projects.id = trending_projects.project_id')
.reorder('trending_projects.id ASC')
2015-10-24 18:46:33 +05:30
end
2016-06-22 15:30:34 +05:30
2016-08-24 12:49:21 +05:30
def cached_count
Rails.cache.fetch('total_project_count', expires_in: 5.minutes) do
Project.count
end
2016-06-22 15:30:34 +05:30
end
2016-11-03 12:29:30 +05:30
def group_ids
2021-11-18 22:05:49 +05:30
joins(:namespace).where(namespaces: { type: Group.sti_name }).select(:namespace_id)
2016-11-03 12:29:30 +05:30
end
2019-06-05 12:25:43 +05:30
2019-10-31 01:37:42 +05:30
# Returns ids of projects with issuables available for given user
2019-06-05 12:25:43 +05:30
#
2019-10-31 01:37:42 +05:30
# Used on queries to find milestones or labels which user can see
# For example: Milestone.where(project_id: ids_with_issuables_available_for(user))
def ids_with_issuables_available_for(user)
2019-06-05 12:25:43 +05:30
with_issues_enabled = with_issues_available_for_user(user).select(:id)
with_merge_requests_enabled = with_merge_requests_available_for_user(user).select(:id)
from_union([with_issues_enabled, with_merge_requests_enabled]).select(:id)
end
2020-07-28 23:09:34 +05:30
2021-09-04 01:27:46 +05:30
def find_by_url(url)
uri = URI(url)
return unless uri.host == Gitlab.config.gitlab.host
match = Rails.application.routes.recognize_path(url)
return if match[:unmatched_route].present?
return if match[:namespace_id].blank? || match[:id].blank?
find_by_full_path(match.values_at(:namespace_id, :id).join("/"))
rescue ActionController::RoutingError, URI::InvalidURIError
nil
2020-07-28 23:09:34 +05:30
end
2021-12-11 22:18:48 +05:30
def without_integration(integration)
integrations = Integration
.select('1')
.where("#{Integration.table_name}.project_id = projects.id")
.where(type: integration.type)
Project
.where('NOT EXISTS (?)', integrations)
.where(pending_delete: false)
.where(archived: false)
end
2023-05-27 22:25:52 +05:30
def project_features_defaults
PROJECT_FEATURES_DEFAULTS
end
def by_pages_enabled_unique_domain(domain)
without_deleted
.joins(:project_setting)
.find_by(project_setting: {
pages_unique_domain_enabled: true,
pages_unique_domain: domain
})
end
2014-09-02 18:07:02 +05:30
end
2019-09-30 21:07:59 +05:30
def initialize(attributes = nil)
2023-05-27 22:25:52 +05:30
# We assign the actual snippet default if no explicit visibility has been initialized.
2019-09-30 21:07:59 +05:30
attributes ||= {}
2019-09-04 21:01:54 +05:30
unless visibility_attribute_present?(attributes)
attributes[:visibility_level] = Gitlab::CurrentSettings.default_project_visibility
end
2023-05-27 22:25:52 +05:30
@init_attributes = attributes
2019-09-04 21:01:54 +05:30
super
end
2023-05-27 22:25:52 +05:30
# Remove along with ProjectFeaturesCompatibility module
def set_project_feature_defaults
self.class.project_features_defaults.each do |attr, value|
# If the deprecated _enabled or the accepted _access_level attribute is specified, we don't need to set the default
next unless @init_attributes[:"#{attr}_enabled"].nil? && @init_attributes[:"#{attr}_access_level"].nil?
public_send("#{attr}_enabled=", value) # rubocop:disable GitlabSecurity/PublicSend
end
end
2021-06-08 01:23:25 +05:30
def parent_loaded?
association(:namespace).loaded?
end
2022-08-13 15:12:31 +05:30
def certificate_based_clusters_enabled?
!!namespace&.certificate_based_clusters_enabled?
end
def prometheus_integration_active?
!!prometheus_integration&.active?
end
2022-06-21 17:19:12 +05:30
def personal_namespace_holder?(user)
return false unless personal?
return false unless user
# We do not want to use a check like `project.team.owner?(user)`
# here because that would depend upon the state of the `project_authorizations` cache,
# and also perform the check across multiple `owners` of the project, but our intention
# is to check if the user is the "holder" of the personal namespace, so need to make this
# check against only a single user (ie, namespace.owner).
namespace.owner == user
end
2023-04-23 21:23:45 +05:30
def invalidate_personal_projects_count_of_owner
return unless personal?
return unless namespace.owner
namespace.owner.invalidate_personal_projects_count
end
2020-06-23 00:09:42 +05:30
def project_setting
super.presence || build_project_setting
end
2022-08-13 15:12:31 +05:30
def show_default_award_emojis?
!!project_setting&.show_default_award_emojis?
end
def enforce_auth_checks_on_uploads?
!!project_setting&.enforce_auth_checks_on_uploads?
end
def warn_about_potentially_unwanted_characters?
!!project_setting&.warn_about_potentially_unwanted_characters?
end
def no_import?
!!import_state&.no_import?
end
def import_scheduled?
!!import_state&.scheduled?
end
def import_started?
!!import_state&.started?
end
def import_in_progress?
!!import_state&.in_progress?
end
def import_failed?
!!import_state&.failed?
end
def import_finished?
!!import_state&.finished?
end
2019-02-15 15:39:39 +05:30
def all_pipelines
2019-02-02 18:00:53 +05:30
if builds_enabled?
super
else
super.external
end
end
2019-07-07 11:18:12 +05:30
def ci_pipelines
if builds_enabled?
super
else
super.external
end
end
2020-06-23 00:09:42 +05:30
def active_webide_pipelines(user:)
webide_pipelines.running_or_pending.for_user(user)
end
2021-03-11 19:13:27 +05:30
def default_pipeline_lock
if keep_latest_artifacts_available?
return :artifacts_locked
end
:unlocked
2021-03-08 18:12:59 +05:30
end
2020-03-13 15:44:24 +05:30
def autoclose_referenced_issues
return true if super.nil?
super
end
2019-12-26 22:10:19 +05:30
def preload_protected_branches
2023-05-27 22:25:52 +05:30
ActiveRecord::Associations::Preloader.new(
records: [self],
associations: { protected_branches: [:push_access_levels, :merge_access_levels] }
).call
2019-12-26 22:10:19 +05:30
end
2018-03-17 18:26:18 +05:30
# returns all ancestor-groups upto but excluding the given namespace
# when no namespace is given, all ancestors upto the top are returned
2019-02-15 15:39:39 +05:30
def ancestors_upto(top = nil, hierarchy_order: nil)
Gitlab::ObjectHierarchy.new(Group.where(id: namespace_id))
.base_and_ancestors(upto: top, hierarchy_order: hierarchy_order)
2018-03-17 18:26:18 +05:30
end
2021-10-27 15:23:28 +05:30
def ancestors(hierarchy_order: nil)
2023-05-27 22:25:52 +05:30
group&.self_and_ancestors(hierarchy_order: hierarchy_order) || Group.none
2021-10-27 15:23:28 +05:30
end
2019-02-15 15:39:39 +05:30
2021-09-04 01:27:46 +05:30
def ancestors_upto_ids(...)
ancestors_upto(...).pluck(:id)
end
2019-10-12 21:52:04 +05:30
def emails_disabled?
strong_memoize(:emails_disabled) do
# disabling in the namespace overrides the project setting
2019-12-21 20:55:43 +05:30
super || namespace.emails_disabled?
2019-10-12 21:52:04 +05:30
end
end
2022-08-13 15:12:31 +05:30
def emails_enabled?
!emails_disabled?
end
2022-08-27 11:52:29 +05:30
2021-01-03 14:25:43 +05:30
override :lfs_enabled?
2016-09-29 09:46:39 +05:30
def lfs_enabled?
return namespace.lfs_enabled? if self[:lfs_enabled].nil?
self[:lfs_enabled] && Gitlab.config.lfs.enabled
end
2018-12-13 13:39:08 +05:30
alias_method :lfs_enabled, :lfs_enabled?
2018-03-17 18:26:18 +05:30
def auto_devops_enabled?
if auto_devops&.enabled.nil?
2018-11-20 20:47:30 +05:30
has_auto_devops_implicitly_enabled?
2018-03-17 18:26:18 +05:30
else
auto_devops.enabled?
end
end
2018-11-18 11:00:15 +05:30
def has_auto_devops_implicitly_enabled?
2019-07-07 11:18:12 +05:30
auto_devops_config = first_auto_devops_config
auto_devops_config[:scope] != :project && auto_devops_config[:status]
2018-11-18 11:00:15 +05:30
end
2018-03-17 18:26:18 +05:30
def has_auto_devops_implicitly_disabled?
2019-07-07 11:18:12 +05:30
auto_devops_config = first_auto_devops_config
auto_devops_config[:scope] != :project && !auto_devops_config[:status]
end
2022-07-16 23:28:13 +05:30
def packages_cleanup_policy
super || build_packages_cleanup_policy
end
2019-07-07 11:18:12 +05:30
def first_auto_devops_config
return namespace.first_auto_devops_config if auto_devops&.enabled.nil?
{ scope: :project, status: auto_devops&.enabled || Feature.enabled?(:force_autodevops_on_by_default, self) }
end
2020-05-24 23:13:21 +05:30
# LFS and hashed repository storage are required for using Design Management.
def design_management_enabled?
lfs_enabled? && hashed_storage?(:repository)
end
2014-09-02 18:07:02 +05:30
def team
@team ||= ProjectTeam.new(self)
end
def repository
2020-11-24 15:15:51 +05:30
@repository ||= Gitlab::GlRepository::PROJECT.repository_for(self)
2015-09-11 14:41:01 +05:30
end
2023-07-09 08:55:56 +05:30
def design_management_repository
super || create_design_management_repository
end
2020-05-24 23:13:21 +05:30
def design_repository
strong_memoize(:design_repository) do
2020-11-24 15:15:51 +05:30
Gitlab::GlRepository::DESIGN.repository_for(self)
2020-05-24 23:13:21 +05:30
end
end
2018-03-17 18:26:18 +05:30
def cleanup
@repository = nil
end
alias_method :reload_repository!, :cleanup
2017-08-17 22:00:37 +05:30
def container_registry_url
2016-06-02 11:05:42 +05:30
if Gitlab.config.registry.enabled
2017-09-10 17:25:29 +05:30
"#{Gitlab.config.registry.host_port}/#{full_path.downcase}"
2016-06-02 11:05:42 +05:30
end
end
2022-06-21 17:19:12 +05:30
def container_repositories_size
strong_memoize(:container_repositories_size) do
next unless Gitlab.com?
next 0 if container_repositories.empty?
next unless container_repositories.all_migrated?
next unless ContainerRegistry::GitlabApiClient.supports_gitlab_api?
ContainerRegistry::GitlabApiClient.deduplicated_size(full_path)
end
end
2016-06-02 11:05:42 +05:30
def has_container_registry_tags?
2017-09-10 17:25:29 +05:30
return @images if defined?(@images)
@images = container_repositories.to_a.any?(&:has_tags?) ||
2017-08-17 22:00:37 +05:30
has_root_container_repository_tags?
2016-06-02 11:05:42 +05:30
end
2019-10-12 21:52:04 +05:30
# 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)
2019-03-02 22:35:43 +05:30
return unless latest_pipeline
2016-08-24 12:49:21 +05:30
2022-10-11 01:57:18 +05:30
latest_pipeline.build_with_artifacts_in_self_and_project_descendants(job_name)
2014-09-02 18:07:02 +05:30
end
2019-10-12 21:52:04 +05:30
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
2022-10-11 01:57:18 +05:30
latest_pipeline.build_with_artifacts_in_self_and_project_descendants(job_name)
2019-10-12 21:52:04 +05:30
end
def latest_successful_build_for_ref!(job_name, ref = default_branch)
2021-06-08 01:23:25 +05:30
latest_successful_build_for_ref(job_name, ref) || raise(ActiveRecord::RecordNotFound, "Couldn't find job #{job_name}")
2019-02-15 15:39:39 +05:30
end
2023-07-09 08:55:56 +05:30
def latest_pipelines(ref = default_branch, sha = nil)
2019-12-04 20:38:33 +05:30
ref = ref.presence || default_branch
2020-11-24 15:15:51 +05:30
sha ||= commit(ref)&.sha
2023-07-09 08:55:56 +05:30
return ci_pipelines.none unless sha
2019-12-04 20:38:33 +05:30
2023-07-09 08:55:56 +05:30
ci_pipelines.newest_first(ref: ref, sha: sha)
end
def latest_pipeline(ref = default_branch, sha = nil)
latest_pipelines(ref, sha).take
2019-12-04 20:38:33 +05:30
end
2016-02-05 20:25:01 +05:30
def merge_base_commit(first_commit_id, second_commit_id)
sha = repository.merge_base(first_commit_id, second_commit_id)
2018-03-17 18:26:18 +05:30
commit_by(oid: sha) if sha
2016-02-05 20:25:01 +05:30
end
2014-09-02 18:07:02 +05:30
def saved?
id && persisted?
end
2019-02-15 15:39:39 +05:30
def import_status
import_state&.status || 'none'
end
2023-04-23 21:23:45 +05:30
def import_checksums
import_state&.checksums || {}
end
2020-04-22 19:07:51 +05:30
def jira_import_status
latest_jira_import&.status || 'initial'
end
2019-02-15 15:39:39 +05:30
def human_import_status_name
import_state&.human_status_name || 'none'
end
2023-05-27 22:25:52 +05:30
def beautified_import_status_name
if import_finished?
return 'completed' unless import_checksums.present?
fetched = import_checksums['fetched']
imported = import_checksums['imported']
fetched.keys.any? { |key| fetched[key] != imported[key] } ? 'partially completed' : 'completed'
else
import_status
end
end
2014-09-02 18:07:02 +05:30
def add_import_job
2017-08-17 22:00:37 +05:30
job_id =
if forked?
2018-05-09 12:01:36 +05:30
RepositoryForkWorker.perform_async(id)
2017-08-17 22:00:37 +05:30
else
RepositoryImportWorker.perform_async(self.id)
end
2016-06-02 11:05:42 +05:30
2018-03-17 18:26:18 +05:30
log_import_activity(job_id)
job_id
end
def log_import_activity(job_id, type: :import)
job_type = type.to_s.capitalize
2016-06-02 11:05:42 +05:30
if job_id
2022-07-23 23:45:48 +05:30
Gitlab::AppLogger.info("#{job_type} job scheduled for #{full_path} with job ID #{job_id} (primary: #{::Gitlab::Database::LoadBalancing::Session.current.use_primary?}).")
2015-09-25 12:07:36 +05:30
else
2020-06-23 00:09:42 +05:30
Gitlab::AppLogger.error("#{job_type} job failed to create for #{full_path}.")
2015-09-25 12:07:36 +05:30
end
2014-09-02 18:07:02 +05:30
end
2016-06-02 11:05:42 +05:30
def reset_cache_and_import_attrs
2017-09-10 17:25:29 +05:30
run_after_commit do
ProjectCacheWorker.perform_async(self.id)
end
2019-02-15 15:39:39 +05:30
import_state.update(last_error: nil)
2017-09-10 17:25:29 +05:30
remove_import_data
end
2015-11-26 14:37:03 +05:30
2018-12-13 13:39:08 +05:30
# This method is overridden in EE::Project model
2017-09-10 17:25:29 +05:30
def remove_import_data
import_data&.destroy
end
def ci_config_path=(value)
# Strip all leading slashes so that //foo -> foo
2018-03-17 18:26:18 +05:30
super(value&.delete("\0"))
2015-04-26 12:48:37 +05:30
end
2023-05-27 22:25:52 +05:30
# Used by Import/Export to export commit notes
def commit_notes
notes.where(noteable_type: "Commit")
end
2016-06-02 11:05:42 +05:30
def import_url=(value)
2019-03-02 22:35:43 +05:30
if Gitlab::UrlSanitizer.valid?(value)
import_url = Gitlab::UrlSanitizer.new(value)
super(import_url.sanitized_url)
2020-03-13 15:44:24 +05:30
credentials = import_url.credentials.to_h.transform_values { |value| CGI.unescape(value.to_s) }
create_or_update_import_data(credentials: credentials)
2019-03-02 22:35:43 +05:30
else
super(value)
end
2016-06-02 11:05:42 +05:30
end
def import_url
2016-11-03 12:29:30 +05:30
if import_data && super.present?
2016-06-02 11:05:42 +05:30
import_url = Gitlab::UrlSanitizer.new(super, credentials: import_data.credentials)
import_url.full_url
else
super
end
2021-06-08 01:23:25 +05:30
rescue StandardError
2018-12-13 13:39:08 +05:30
super
2016-06-02 11:05:42 +05:30
end
2016-08-24 12:49:21 +05:30
def valid_import_url?
2017-09-10 17:25:29 +05:30
valid?(:import_url) || errors.messages[:import_url].nil?
2016-08-24 12:49:21 +05:30
end
2022-11-25 23:54:43 +05:30
# TODO: rename to build_or_assign_import_data as it doesn't save record
# https://gitlab.com/gitlab-org/gitlab/-/issues/377319
2016-06-02 11:05:42 +05:30
def create_or_update_import_data(data: nil, credentials: nil)
2018-05-09 12:01:36 +05:30
return if data.nil? && credentials.nil?
2016-08-24 12:49:21 +05:30
2016-06-02 11:05:42 +05:30
project_import_data = import_data || build_import_data
2018-03-17 18:26:18 +05:30
2019-02-15 15:39:39 +05:30
project_import_data.merge_data(data.to_h)
project_import_data.merge_credentials(credentials.to_h)
2018-11-18 11:00:15 +05:30
project_import_data
2016-06-02 11:05:42 +05:30
end
2014-09-02 18:07:02 +05:30
def import?
2023-07-09 08:55:56 +05:30
external_import? || forked? || gitlab_project_import? || jira_import? || gitlab_project_migration?
2015-09-25 12:07:36 +05:30
end
def external_import?
2014-09-02 18:07:02 +05:30
import_url.present?
end
2015-11-26 14:37:03 +05:30
def safe_import_url
2016-06-02 11:05:42 +05:30
Gitlab::UrlSanitizer.new(import_url).masked_url
2015-11-26 14:37:03 +05:30
end
2020-04-22 19:07:51 +05:30
def jira_import?
2020-06-23 00:09:42 +05:30
import_type == 'jira' && latest_jira_import.present?
2020-04-22 19:07:51 +05:30
end
2016-06-22 15:30:34 +05:30
def gitlab_project_import?
import_type == 'gitlab_project'
end
2021-11-18 22:05:49 +05:30
def gitlab_project_migration?
import_type == 'gitlab_project_migration'
end
2017-08-17 22:00:37 +05:30
def gitea_import?
import_type == 'gitea'
end
2021-10-27 15:23:28 +05:30
def github_import?
import_type == 'github'
end
def github_enterprise_import?
github_import? &&
URI.parse(import_url).host != URI.parse(Octokit::Default::API_ENDPOINT).host
end
2018-10-15 14:42:47 +05:30
def has_remote_mirror?
remote_mirror_available? && remote_mirrors.enabled.exists?
end
def updating_remote_mirror?
remote_mirrors.enabled.started.exists?
end
def update_remote_mirrors
return unless remote_mirror_available?
remote_mirrors.enabled.each(&:sync)
end
def mark_stuck_remote_mirrors_as_failed!
remote_mirrors.stuck.update_all(
update_status: :failed,
2019-07-31 22:56:46 +05:30
last_error: _('The remote mirror took to long to complete.'),
2020-06-23 00:09:42 +05:30
last_update_at: Time.current
2018-10-15 14:42:47 +05:30
)
end
def mark_remote_mirrors_for_removal
remote_mirrors.each(&:mark_for_delete_if_blank_url)
end
def remote_mirror_available?
remote_mirror_available_overridden ||
::Gitlab::CurrentSettings.mirror_available
end
2019-03-02 22:35:43 +05:30
def check_personal_projects_limit
# Since this method is called as validation hook, `creator` might not be
# present. Since the validation for that will fail, we can just return
# early.
return if !creator || creator.can_create_project? ||
namespace.kind == 'group'
2019-03-02 22:35:43 +05:30
limit = creator.projects_limit
error =
2020-10-24 23:57:45 +05:30
if limit == 0
2019-03-02 22:35:43 +05:30
_('Personal project creation is not allowed. Please contact your administrator with questions')
else
2019-03-02 22:35:43 +05:30
_('Your project limit is %{limit} projects! Please contact your administrator to increase it')
end
2019-03-02 22:35:43 +05:30
self.errors.add(:limit_reached, error % { limit: limit })
2016-06-02 11:05:42 +05:30
end
2019-06-05 12:25:43 +05:30
def should_validate_visibility_level?
new_record? || changes.has_key?(:visibility_level)
end
2016-06-02 11:05:42 +05:30
def visibility_level_allowed_by_group
return if visibility_level_allowed_by_group?
level_name = Gitlab::VisibilityLevel.level_name(self.visibility_level).downcase
group_level_name = Gitlab::VisibilityLevel.level_name(self.group.visibility_level).downcase
2019-07-31 22:56:46 +05:30
self.errors.add(:visibility_level, _("%{level_name} is not allowed in a %{group_level_name} group.") % { level_name: level_name, group_level_name: group_level_name })
2016-06-02 11:05:42 +05:30
end
def visibility_level_allowed_as_fork
return if visibility_level_allowed_as_fork?
level_name = Gitlab::VisibilityLevel.level_name(self.visibility_level).downcase
2019-07-31 22:56:46 +05:30
self.errors.add(:visibility_level, _("%{level_name} is not allowed since the fork source project has lower visibility.") % { level_name: level_name })
2014-09-02 18:07:02 +05:30
end
2018-05-09 12:01:36 +05:30
def pages_https_only
return false unless Gitlab.config.pages.external_https
super
end
def pages_https_only?
return false unless Gitlab.config.pages.external_https
super
end
def validate_pages_https_only
return unless pages_https_only?
unless pages_domains.all?(&:https?)
2019-07-31 22:56:46 +05:30
errors.add(:pages_https_only, _("cannot be enabled unless all domains have TLS certificates"))
2018-05-09 12:01:36 +05:30
end
end
2021-01-03 14:25:43 +05:30
def changing_shared_runners_enabled_is_allowed
return unless new_record? || changes.has_key?(:shared_runners_enabled)
2021-11-18 22:05:49 +05:30
if shared_runners_setting_conflicting_with_group?
2021-01-03 14:25:43 +05:30
errors.add(:shared_runners_enabled, _('cannot be enabled because parent group does not allow it'))
end
end
2021-11-18 22:05:49 +05:30
def shared_runners_setting_conflicting_with_group?
shared_runners_enabled && group&.shared_runners_setting == Namespace::SR_DISABLED_AND_UNOVERRIDABLE
end
def reconcile_shared_runners_setting!
if shared_runners_setting_conflicting_with_group?
self.shared_runners_enabled = false
end
end
2014-09-02 18:07:02 +05:30
def to_param
2016-09-13 17:45:13 +05:30
if persisted? && errors.include?(:path)
path_was
else
path
end
2014-09-02 18:07:02 +05:30
end
2020-03-13 15:44:24 +05:30
# Produce a valid reference (see Referable#to_reference)
#
# NB: For projects, all references are 'full' - i.e. they all include the
# full_path, rather than just the project name. For this reason, we ignore
# the value of `full:` passed to this method, which is part of the Referable
# interface.
def to_reference(from = nil, full: false)
base = to_reference_base(from, full: true)
"#{base}#{self.class.reference_postfix}"
2018-11-20 20:47:30 +05:30
end
2017-08-17 22:00:37 +05:30
# `from` argument can be a Namespace or Project.
2020-03-13 15:44:24 +05:30
def to_reference_base(from = nil, full: false)
2017-08-17 22:00:37 +05:30
if full || cross_namespace_reference?(from)
2017-09-10 17:25:29 +05:30
full_path
2017-08-17 22:00:37 +05:30
elsif cross_project_reference?(from)
path
end
2015-09-11 14:41:01 +05:30
end
2018-03-17 18:26:18 +05:30
def to_human_reference(from = nil)
if cross_namespace_reference?(from)
2017-08-17 22:00:37 +05:30
name_with_namespace
2018-03-17 18:26:18 +05:30
elsif cross_project_reference?(from)
2017-08-17 22:00:37 +05:30
name
end
2014-09-02 18:07:02 +05:30
end
2018-11-08 19:23:39 +05:30
def readme_url
2019-02-15 15:39:39 +05:30
readme_path = repository.readme_path
if readme_path
Gitlab::Routing.url_helpers.project_blob_url(self, File.join(default_branch, readme_path))
2018-11-08 19:23:39 +05:30
end
end
2018-03-17 18:26:18 +05:30
def new_issuable_address(author, address_type)
2023-06-20 00:43:36 +05:30
return unless Gitlab::Email::IncomingEmail.supports_issue_creation? && author
2016-09-13 17:45:13 +05:30
2019-02-15 15:39:39 +05:30
# check since this can come from a request parameter
return unless %w(issue merge_request).include?(address_type)
2017-08-17 22:00:37 +05:30
author.ensure_incoming_email_token!
2019-02-15 15:39:39 +05:30
suffix = address_type.dasherize
# example: incoming+h5bp-html5-boilerplate-8-1234567890abcdef123456789-issue@localhost.com
# example: incoming+h5bp-html5-boilerplate-8-1234567890abcdef123456789-merge-request@localhost.com
2023-06-20 00:43:36 +05:30
Gitlab::Email::IncomingEmail.reply_address("#{full_path_slug}-#{project_id}-#{author.incoming_email_token}-#{suffix}")
2016-09-13 17:45:13 +05:30
end
2014-09-02 18:07:02 +05:30
def build_commit_note(commit)
2015-04-26 12:48:37 +05:30
notes.new(commit_id: commit.id, noteable_type: 'Commit')
2014-09-02 18:07:02 +05:30
end
def last_activity
last_event
end
def last_activity_date
2022-06-21 17:19:12 +05:30
updated_at
2014-09-02 18:07:02 +05:30
end
def project_id
self.id
end
2017-01-15 13:20:01 +05:30
def get_issue(issue_id, current_user)
2017-09-10 17:25:29 +05:30
issue = IssuesFinder.new(current_user, project_id: id).find_by(iid: issue_id) if issues_enabled?
if issue
issue
elsif external_issue_tracker
2015-09-11 14:41:01 +05:30
ExternalIssue.new(issue_id, self)
2014-09-02 18:07:02 +05:30
end
end
2015-09-11 14:41:01 +05:30
def issue_exists?(issue_id)
get_issue(issue_id)
end
2017-08-17 22:00:37 +05:30
def external_issue_reference_pattern
2023-06-20 00:43:36 +05:30
external_issue_tracker.reference_pattern(only_long: issues_enabled?)
2016-11-03 12:29:30 +05:30
end
2015-04-26 12:48:37 +05:30
def default_issues_tracker?
2015-09-11 14:41:01 +05:30
!external_issue_tracker
2015-04-26 12:48:37 +05:30
end
def external_issue_tracker
2021-03-11 19:13:27 +05:30
cache_has_external_issue_tracker if has_external_issue_tracker.nil?
2021-03-11 19:13:27 +05:30
return unless has_external_issue_tracker?
2014-09-02 18:07:02 +05:30
2021-06-08 01:23:25 +05:30
@external_issue_tracker ||= integrations.external_issue_trackers.first
2014-09-02 18:07:02 +05:30
end
2020-04-08 14:13:33 +05:30
def external_references_supported?
external_issue_tracker&.support_cross_reference?
end
2016-09-29 09:46:39 +05:30
def has_wiki?
wiki_enabled? || has_external_wiki?
end
2016-08-24 12:49:21 +05:30
def external_wiki
2021-03-08 18:12:59 +05:30
cache_has_external_wiki if has_external_wiki.nil?
2016-08-24 12:49:21 +05:30
2021-03-08 18:12:59 +05:30
return unless has_external_wiki?
2016-08-24 12:49:21 +05:30
2021-06-08 01:23:25 +05:30
@external_wiki ||= integrations.external_wikis.first
2016-08-24 12:49:21 +05:30
end
2021-09-30 23:02:18 +05:30
def find_or_initialize_integrations
Integration
.available_integration_names
.difference(disabled_integrations)
.map { find_or_initialize_integration(_1) }
.sort_by(&:title)
2018-11-08 19:23:39 +05:30
end
2021-09-30 23:02:18 +05:30
def disabled_integrations
2023-07-09 08:55:56 +05:30
%w[shimo zentao]
2014-09-02 18:07:02 +05:30
end
2021-09-30 23:02:18 +05:30
def find_or_initialize_integration(name)
2022-06-21 17:19:12 +05:30
return if disabled_integrations.include?(name) || Integration.available_integration_names.exclude?(name)
2018-12-05 23:21:45 +05:30
2021-10-27 15:23:28 +05:30
find_integration(integrations, name) || build_from_instance(name) || build_integration(name)
2017-08-17 22:00:37 +05:30
end
2018-12-05 23:21:45 +05:30
# rubocop: disable CodeReuse/ServiceClass
2015-09-25 12:07:36 +05:30
def create_labels
Label.templates.each do |label|
2021-10-27 15:23:28 +05:30
params = label.attributes.except('id', 'template', 'created_at', 'updated_at', 'type')
2016-11-03 12:29:30 +05:30
Labels::FindOrCreateService.new(nil, self, params).execute(skip_authorization: true)
2015-09-25 12:07:36 +05:30
end
end
2018-12-05 23:21:45 +05:30
# rubocop: enable CodeReuse/ServiceClass
2015-09-25 12:07:36 +05:30
2021-09-30 23:02:18 +05:30
def ci_integrations
2021-06-08 01:23:25 +05:30
integrations.where(category: :ci)
2014-09-02 18:07:02 +05:30
end
2021-09-30 23:02:18 +05:30
def ci_integration
@ci_integration ||= ci_integrations.reorder(nil).find_by(active: true)
2017-08-17 22:00:37 +05:30
end
2015-04-26 12:48:37 +05:30
def avatar_in_git
2016-06-02 11:05:42 +05:30
repository.avatar
2015-04-26 12:48:37 +05:30
end
2017-09-10 17:25:29 +05:30
def avatar_url(**args)
2018-03-17 18:26:18 +05:30
Gitlab::Routing.url_helpers.project_avatar_url(self) if avatar_in_git
2015-04-26 12:48:37 +05:30
end
2014-09-02 18:07:02 +05:30
# For compatibility with old code
def code
path
end
2019-02-15 15:39:39 +05:30
def all_clusters
2023-01-13 00:05:48 +05:30
group_clusters = Clusters::Cluster.joins(:groups).where(cluster_groups: { group_id: ancestors_upto })
2020-01-01 13:55:28 +05:30
instance_clusters = Clusters::Cluster.instance_type
2019-02-15 15:39:39 +05:30
2020-01-01 13:55:28 +05:30
Clusters::Cluster.from_union([clusters, group_clusters, instance_clusters])
2019-02-15 15:39:39 +05:30
end
2015-04-26 12:48:37 +05:30
def items_for(entity)
2014-09-02 18:07:02 +05:30
case entity
when 'issue' then
issues
when 'merge_request' then
merge_requests
end
end
2018-12-05 23:21:45 +05:30
# rubocop: disable CodeReuse/ServiceClass
2015-10-24 18:46:33 +05:30
def send_move_instructions(old_path_with_namespace)
# New project path needs to be committed to the DB or notification will
# retrieve stale information
2018-03-17 18:26:18 +05:30
run_after_commit do
NotificationService.new.project_was_moved(self, old_path_with_namespace)
end
2014-09-02 18:07:02 +05:30
end
2018-12-05 23:21:45 +05:30
# rubocop: enable CodeReuse/ServiceClass
2014-09-02 18:07:02 +05:30
def owner
2022-04-04 11:22:00 +05:30
# This will be phased out and replaced with `owners` relationship
# backed by memberships with direct/inherited Owner access roles
# See https://gitlab.com/groups/gitlab-org/-/epics/7405
2020-05-24 23:13:21 +05:30
group || namespace.try(:owner)
2014-09-02 18:07:02 +05:30
end
2022-04-04 11:22:00 +05:30
def deprecated_owner
# Kept in order to maintain webhook structures until we remove owner_name and owner_email
# See https://gitlab.com/gitlab-org/gitlab/-/issues/350603
group || namespace.try(:owner)
end
def owners
# This will be phased out and replaced with `owners` relationship
# backed by memberships with direct/inherited Owner access roles
# See https://gitlab.com/groups/gitlab-org/-/epics/7405
team.owners
end
2022-03-02 08:16:31 +05:30
def first_owner
2020-10-24 23:57:45 +05:30
obj = owner
2022-03-02 08:16:31 +05:30
if obj.respond_to?(:first_owner)
obj.first_owner
2020-10-24 23:57:45 +05:30
else
obj
end
end
2018-12-05 23:21:45 +05:30
# rubocop: disable CodeReuse/ServiceClass
2014-09-02 18:07:02 +05:30
def execute_hooks(data, hooks_scope = :push_hooks)
2018-03-17 18:26:18 +05:30
run_after_commit_or_now do
2022-05-07 20:08:51 +05:30
triggered_hooks(hooks_scope, data).execute
2018-03-17 18:26:18 +05:30
SystemHooksService.new.execute_hooks(data, hooks_scope)
2014-09-02 18:07:02 +05:30
end
end
2018-12-05 23:21:45 +05:30
# rubocop: enable CodeReuse/ServiceClass
2014-09-02 18:07:02 +05:30
2022-05-07 20:08:51 +05:30
def triggered_hooks(hooks_scope, data)
triggered = ::Projects::TriggeredHooks.new(hooks_scope, data)
triggered.add_hooks(hooks)
end
2021-09-30 23:02:18 +05:30
def execute_integrations(data, hooks_scope = :push_hooks)
2015-04-26 12:48:37 +05:30
# Call only service hooks that are active for this scope
2018-03-17 18:26:18 +05:30
run_after_commit_or_now do
2023-03-17 16:20:25 +05:30
association("#{hooks_scope}_integrations").reader.each do |integration|
integration.async_execute(data)
2018-03-17 18:26:18 +05:30
end
2014-09-02 18:07:02 +05:30
end
end
2019-10-12 21:52:04 +05:30
def has_active_hooks?(hooks_scope = :push_hooks)
2022-08-27 11:52:29 +05:30
@has_active_hooks ||= {} # rubocop: disable Gitlab/PredicateMemoization
return @has_active_hooks[hooks_scope] if @has_active_hooks.key?(hooks_scope)
@has_active_hooks[hooks_scope] = hooks.hooks_for(hooks_scope).any? ||
SystemHook.hooks_for(hooks_scope).any? ||
Gitlab::FileHook.any?
2019-10-12 21:52:04 +05:30
end
2021-09-30 23:02:18 +05:30
def has_active_integrations?(hooks_scope = :push_hooks)
2023-01-13 00:05:48 +05:30
@has_active_integrations ||= {} # rubocop: disable Gitlab/PredicateMemoization
return @has_active_integrations[hooks_scope] if @has_active_integrations.key?(hooks_scope)
@has_active_integrations[hooks_scope] = integrations.public_send(hooks_scope).any? # rubocop:disable GitlabSecurity/PublicSend
2019-10-12 21:52:04 +05:30
end
2020-11-24 15:15:51 +05:30
def feature_usage
super.presence || build_feature_usage
end
2014-09-02 18:07:02 +05:30
def forked?
2018-12-13 13:39:08 +05:30
fork_network && fork_network.root_project != self
2014-09-02 18:07:02 +05:30
end
2018-03-17 18:26:18 +05:30
def fork_source
2019-07-07 11:18:12 +05:30
return unless forked?
2018-03-17 18:26:18 +05:30
forked_from_project || fork_network&.root_project
end
2020-11-24 15:15:51 +05:30
def lfs_objects_for_repository_types(*types)
2020-03-13 15:44:24 +05:30
LfsObject
.joins(:lfs_objects_projects)
2020-11-24 15:15:51 +05:30
.where(lfs_objects_projects: { project: self, repository_type: types })
2020-04-08 14:13:33 +05:30
end
def lfs_objects_oids(oids: [])
oids(lfs_objects, oids: oids)
2018-05-09 12:01:36 +05:30
end
2022-01-26 12:08:38 +05:30
def lfs_objects_oids_from_fork_source(oids: [])
return [] unless forked?
oids(fork_source.lfs_objects, oids: oids)
end
2014-09-02 18:07:02 +05:30
def personal?
!group
end
2016-04-02 18:10:28 +05:30
# Expires various caches before a project is renamed.
def expire_caches_before_rename(old_path)
2020-05-24 23:13:21 +05:30
project_repo = Repository.new(old_path, self, shard: repository_storage)
wiki_repo = Repository.new("#{old_path}#{Gitlab::GlRepository::WIKI.path_suffix}", self, shard: repository_storage, repo_type: Gitlab::GlRepository::WIKI)
design_repo = Repository.new("#{old_path}#{Gitlab::GlRepository::DESIGN.path_suffix}", self, shard: repository_storage, repo_type: Gitlab::GlRepository::DESIGN)
2016-04-02 18:10:28 +05:30
2020-05-24 23:13:21 +05:30
[project_repo, wiki_repo, design_repo].each do |repo|
repo.before_delete if repo.exists?
2016-04-02 18:10:28 +05:30
end
end
2017-09-10 17:25:29 +05:30
# Check if repository already exists on disk
2018-03-17 18:26:18 +05:30
def check_repository_path_availability
return true if skip_disk_validation
2018-10-15 14:42:47 +05:30
return false unless repository_storage
2017-09-10 17:25:29 +05:30
2018-03-17 18:26:18 +05:30
# Check if repository with same path already exists on disk we can
# skip this for the hashed storage because the path does not change
if legacy_storage? && repository_with_same_path_already_exists?
2019-07-31 22:56:46 +05:30
errors.add(:base, _('There is already a repository with that name on disk'))
2017-09-10 17:25:29 +05:30
return false
end
true
2018-03-17 18:26:18 +05:30
rescue GRPC::Internal # if the path is too long
false
2017-09-10 17:25:29 +05:30
end
2019-02-15 15:39:39 +05:30
def track_project_repository
repository = project_repository || build_project_repository
repository.update!(shard_name: repository_storage, disk_path: disk_path)
end
2023-07-09 08:55:56 +05:30
def create_repository(force: false, default_branch: nil)
2018-03-17 18:26:18 +05:30
# Forked import is handled asynchronously
return if forked? && !force
2023-07-09 08:55:56 +05:30
repository.create_repository(default_branch)
2020-04-08 14:13:33 +05:30
repository.after_create
true
2022-08-27 11:52:29 +05:30
rescue StandardError => e
Gitlab::ErrorTracking.track_exception(e, project: { id: id, full_path: full_path, disk_path: disk_path })
2020-04-08 14:13:33 +05:30
errors.add(:base, _('Failed to create repository'))
false
2017-09-10 17:25:29 +05:30
end
2016-06-02 11:05:42 +05:30
def hook_attrs(backward: true)
attrs = {
2018-03-17 18:26:18 +05:30
id: id,
2015-04-26 12:48:37 +05:30
name: name,
2016-04-02 18:10:28 +05:30
description: description,
2015-09-25 12:07:36 +05:30
web_url: web_url,
2018-03-17 18:26:18 +05:30
avatar_url: avatar_url(only_path: false),
2016-04-02 18:10:28 +05:30
git_ssh_url: ssh_url_to_repo,
git_http_url: http_url_to_repo,
2015-04-26 12:48:37 +05:30
namespace: namespace.name,
2016-04-02 18:10:28 +05:30
visibility_level: visibility_level,
2017-09-10 17:25:29 +05:30
path_with_namespace: full_path,
2016-04-02 18:10:28 +05:30
default_branch: default_branch,
2017-09-10 17:25:29 +05:30
ci_config_path: ci_config_path
2015-04-26 12:48:37 +05:30
}
2016-06-02 11:05:42 +05:30
# Backward compatibility
if backward
attrs.merge!({
homepage: web_url,
url: url_to_repo,
ssh_url: ssh_url_to_repo,
http_url: http_url_to_repo
})
end
attrs
2015-04-26 12:48:37 +05:30
end
2022-03-02 08:16:31 +05:30
def member(user)
2018-03-17 18:26:18 +05:30
if project_members.loaded?
project_members.find { |member| member.user_id == user.id }
else
project_members.find_by(user_id: user)
end
2014-09-02 18:07:02 +05:30
end
2021-11-11 11:23:49 +05:30
def membership_locked?
false
end
2020-05-24 23:13:21 +05:30
def bots
users.project_bot
end
2018-12-05 23:21:45 +05:30
# Filters `users` to return only authorized users of the project
def members_among(users)
if users.is_a?(ActiveRecord::Relation) && !users.loaded?
authorized_users.merge(users)
else
return [] if users.empty?
user_ids = authorized_users.where(users: { id: users.map(&:id) }).pluck(:id)
users.select { |user| user_ids.include?(user.id) }
end
end
2014-09-02 18:07:02 +05:30
def visibility_level_field
2017-08-17 22:00:37 +05:30
:visibility_level
2014-09-02 18:07:02 +05:30
end
2021-09-04 01:27:46 +05:30
override :after_repository_change_head
def after_repository_change_head
ProjectCacheWorker.perform_async(self.id, [], [:commit_count])
super
2014-09-02 18:07:02 +05:30
end
2018-03-17 18:26:18 +05:30
def forked_from?(other_project)
forked? && forked_from_project == other_project
end
def in_fork_network_of?(other_project)
return false if fork_network.nil? || other_project.fork_network.nil?
fork_network == other_project.fork_network
2014-09-02 18:07:02 +05:30
end
2015-04-26 12:48:37 +05:30
def origin_merge_requests
merge_requests.where(source_project_id: self.id)
end
2017-09-10 17:25:29 +05:30
def ensure_repository
create_repository(force: true) unless repository_exists?
2015-04-26 12:48:37 +05:30
end
2016-06-02 11:05:42 +05:30
def allowed_to_share_with_group?
!namespace.share_with_group_lock
2015-10-24 18:46:33 +05:30
end
2018-03-17 18:26:18 +05:30
def latest_successful_pipeline_for_default_branch
if defined?(@latest_successful_pipeline_for_default_branch)
return @latest_successful_pipeline_for_default_branch
end
@latest_successful_pipeline_for_default_branch =
2019-10-12 21:52:04 +05:30
ci_pipelines.latest_successful_for_ref(default_branch)
2018-03-17 18:26:18 +05:30
end
def latest_successful_pipeline_for(ref = nil)
if ref && ref != default_branch
2019-10-12 21:52:04 +05:30
ci_pipelines.latest_successful_for_ref(ref)
2018-03-17 18:26:18 +05:30
else
latest_successful_pipeline_for_default_branch
end
end
2022-08-13 15:12:31 +05:30
def feature_available?(feature, user = nil)
!!project_feature&.feature_available?(feature, user)
end
def builds_enabled?
!!project_feature&.builds_enabled?
end
def wiki_enabled?
!!project_feature&.wiki_enabled?
end
def merge_requests_enabled?
!!project_feature&.merge_requests_enabled?
end
def forking_enabled?
!!project_feature&.forking_enabled?
end
def issues_enabled?
!!project_feature&.issues_enabled?
end
def pages_enabled?
!!project_feature&.pages_enabled?
end
def analytics_enabled?
!!project_feature&.analytics_enabled?
end
def snippets_enabled?
!!project_feature&.snippets_enabled?
end
def public_pages?
!!project_feature&.public_pages?
end
def private_pages?
!!project_feature&.private_pages?
end
def operations_enabled?
!!project_feature&.operations_enabled?
end
def container_registry_enabled?
!!project_feature&.container_registry_enabled?
end
alias_method :container_registry_enabled, :container_registry_enabled?
2016-06-02 11:05:42 +05:30
def enable_ci
2016-09-29 09:46:39 +05:30
project_feature.update_attribute(:builds_access_level, ProjectFeature::ENABLED)
2015-11-26 14:37:03 +05:30
end
2015-12-23 02:04:40 +05:30
2017-08-17 22:00:37 +05:30
def shared_runners_available?
shared_runners_enabled?
end
2015-12-23 02:04:40 +05:30
2017-08-17 22:00:37 +05:30
def shared_runners
2021-09-04 01:27:46 +05:30
@shared_runners ||= shared_runners_enabled? ? Ci::Runner.instance_type : Ci::Runner.none
end
def available_shared_runners
@available_shared_runners ||= shared_runners_available? ? shared_runners : Ci::Runner.none
2015-12-23 02:04:40 +05:30
end
2018-10-15 14:42:47 +05:30
def group_runners
2023-06-20 00:43:36 +05:30
@group_runners ||= group_runners_enabled? ? Ci::Runner.belonging_to_parent_groups_of_project(self.id) : Ci::Runner.none
2018-10-15 14:42:47 +05:30
end
def all_runners
2018-12-05 23:21:45 +05:30
Ci::Runner.from_union([runners, group_runners, shared_runners])
2015-12-23 02:04:40 +05:30
end
2021-09-04 01:27:46 +05:30
def all_available_runners
Ci::Runner.from_union([runners, group_runners, available_shared_runners])
end
2018-11-08 19:23:39 +05:30
def active_runners
strong_memoize(:active_runners) do
2021-09-04 01:27:46 +05:30
all_available_runners.active
2018-11-08 19:23:39 +05:30
end
end
2021-04-29 21:17:54 +05:30
def any_online_runners?(&block)
2022-03-02 08:16:31 +05:30
online_runners_with_tags.any?(&block)
2021-04-29 21:17:54 +05:30
end
2017-08-17 22:00:37 +05:30
def valid_runners_token?(token)
2019-09-30 21:07:59 +05:30
self.runners_token && ActiveSupport::SecurityUtils.secure_compare(token, self.runners_token)
2015-12-23 02:04:40 +05:30
end
2018-12-05 23:21:45 +05:30
# rubocop: disable CodeReuse/ServiceClass
2018-11-08 19:23:39 +05:30
def open_issues_count(current_user = nil)
2021-11-11 11:23:49 +05:30
return Projects::OpenIssuesCountService.new(self, current_user).count unless current_user.nil?
2021-11-18 22:05:49 +05:30
BatchLoader.for(self).batch do |projects, loader|
2021-11-11 11:23:49 +05:30
issues_count_per_project = ::Projects::BatchOpenIssuesCountService.new(projects).refresh_cache_and_retrieve_data
issues_count_per_project.each do |project, count|
loader.call(project, count)
end
end
2018-03-17 18:26:18 +05:30
end
2018-12-05 23:21:45 +05:30
# rubocop: enable CodeReuse/ServiceClass
2018-03-17 18:26:18 +05:30
2018-12-05 23:21:45 +05:30
# rubocop: disable CodeReuse/ServiceClass
2021-09-04 01:27:46 +05:30
def open_merge_requests_count(_current_user = nil)
2023-05-27 22:25:52 +05:30
BatchLoader.for(self).batch do |projects, loader|
::Projects::BatchOpenMergeRequestsCountService.new(projects)
.refresh_cache_and_retrieve_data
.each { |project, count| loader.call(project, count) }
end
2015-12-23 02:04:40 +05:30
end
2018-12-05 23:21:45 +05:30
# rubocop: enable CodeReuse/ServiceClass
2016-06-02 11:05:42 +05:30
def visibility_level_allowed_as_fork?(level = self.visibility_level)
return true unless forked?
2016-06-02 11:05:42 +05:30
2018-12-13 13:39:08 +05:30
original_project = fork_source
2016-06-02 11:05:42 +05:30
return true unless original_project
level <= original_project.visibility_level
end
def visibility_level_allowed_by_group?(level = self.visibility_level)
return true unless group
level <= group.visibility_level
end
def visibility_level_allowed?(level = self.visibility_level)
visibility_level_allowed_as_fork?(level) && visibility_level_allowed_by_group?(level)
end
def runners_token
ensure_runners_token!
end
2016-04-02 18:10:28 +05:30
2017-08-17 22:00:37 +05:30
def pages_deployed?
2020-10-24 23:57:45 +05:30
pages_metadatum&.deployed?
2017-08-17 22:00:37 +05:30
end
2023-07-09 08:55:56 +05:30
def pages_url(with_unique_domain: false)
return pages_unique_url if with_unique_domain && pages_unique_domain_enabled?
2023-05-27 22:25:52 +05:30
2023-03-17 16:20:25 +05:30
url = pages_namespace_url
2018-05-09 12:01:36 +05:30
url_path = full_path.partition('/').last
2023-03-17 16:20:25 +05:30
namespace_url = "#{Settings.pages.protocol}://#{url_path}".downcase
if Rails.env.development?
url_without_port = URI.parse(url)
url_without_port.port = nil
return url if url_without_port.to_s == namespace_url
end
2017-08-17 22:00:37 +05:30
# If the project path is the same as host, we serve it as group page
2023-03-17 16:20:25 +05:30
return url if url == namespace_url
2017-08-17 22:00:37 +05:30
"#{url}/#{url_path}"
end
2023-05-27 22:25:52 +05:30
def pages_unique_url
pages_url_for(project_setting.pages_unique_domain)
end
2023-06-20 00:43:36 +05:30
def pages_unique_host
URI(pages_unique_url).host
end
2023-05-27 22:25:52 +05:30
def pages_namespace_url
pages_url_for(pages_subdomain)
end
2017-08-17 22:00:37 +05:30
def pages_subdomain
full_path.partition('/').first
end
def pages_path
2018-03-17 18:26:18 +05:30
# TODO: when we migrate Pages to work with new storage types, change here to use disk_path
File.join(Settings.pages.path, full_path)
2017-08-17 22:00:37 +05:30
end
2018-03-17 18:26:18 +05:30
def pages_available?
2019-03-02 22:35:43 +05:30
Gitlab.config.pages.enabled
2018-03-17 18:26:18 +05:30
end
2022-06-21 17:19:12 +05:30
def pages_show_onboarding?
!(pages_metadatum&.onboarding_complete || pages_metadatum&.deployed)
end
2017-09-10 17:25:29 +05:30
def remove_private_deploy_keys
exclude_keys_linked_to_other_projects = <<-SQL
NOT EXISTS (
SELECT 1
FROM deploy_keys_projects dkp2
WHERE dkp2.deploy_key_id = deploy_keys_projects.deploy_key_id
AND dkp2.project_id != deploy_keys_projects.project_id
)
SQL
deploy_keys.where(public: false)
.where(exclude_keys_linked_to_other_projects)
.delete_all
end
2022-06-21 17:19:12 +05:30
def mark_pages_onboarding_complete
ensure_pages_metadatum.update!(onboarding_complete: true)
end
2022-05-07 20:08:51 +05:30
def mark_pages_as_deployed
ensure_pages_metadatum.update!(deployed: true)
2019-12-21 20:55:43 +05:30
end
def mark_pages_as_not_deployed
2022-05-07 20:08:51 +05:30
ensure_pages_metadatum.update!(deployed: false)
2021-01-29 00:20:46 +05:30
end
def update_pages_deployment!(deployment)
ensure_pages_metadatum.update!(pages_deployment: deployment)
2019-12-21 20:55:43 +05:30
end
2021-03-08 18:12:59 +05:30
def set_first_pages_deployment!(deployment)
ensure_pages_metadatum
# where().update_all to perform update in the single transaction with check for null
ProjectPagesMetadatum
.where(project_id: id, pages_deployment_id: nil)
2021-04-29 21:17:54 +05:30
.update_all(deployed: deployment.present?, pages_deployment_id: deployment&.id)
2021-03-08 18:12:59 +05:30
end
2021-10-27 15:23:28 +05:30
def set_full_path(gl_full_path: full_path)
2018-03-17 18:26:18 +05:30
# We'd need to keep track of project full path otherwise directory tree
# created with hashed storage enabled cannot be usefully imported using
# the import rake task.
2021-10-27 15:23:28 +05:30
repository.raw_repository.set_full_path(full_path: gl_full_path)
2018-03-17 18:26:18 +05:30
rescue Gitlab::Git::Repository::NoRepository => e
2020-06-23 00:09:42 +05:30
Gitlab::AppLogger.error("Error writing to .git/config for project #{full_path} (#{id}): #{e.message}.")
2018-03-17 18:26:18 +05:30
nil
end
def after_import
2020-04-08 14:13:33 +05:30
repository.expire_content_cache
2023-01-10 11:22:00 +05:30
repository.remove_prohibited_branches
2020-04-08 14:13:33 +05:30
wiki.repository.expire_content_cache
DetectRepositoryLanguagesWorker.perform_async(id)
2023-06-20 00:43:36 +05:30
ProjectCacheWorker.perform_async(self.id, [], [:repository_size, :wiki_size])
2021-10-27 15:23:28 +05:30
AuthorizedProjectUpdate::ProjectRecalculateWorker.perform_async(id)
2019-03-02 22:35:43 +05:30
2022-06-21 17:19:12 +05:30
enqueue_record_project_target_platforms
2019-03-02 22:35:43 +05:30
# The import assigns iid values on its own, e.g. by re-using GitHub ids.
# Flush existing InternalId records for this project for consistency reasons.
# Those records are going to be recreated with the next normal creation
# of a model instance (e.g. an Issue).
InternalId.flush_records!(project: self)
2022-06-21 17:19:12 +05:30
import_state&.finish
2018-03-17 18:26:18 +05:30
update_project_counter_caches
after_create_default_branch
2019-02-15 15:39:39 +05:30
join_pool_repository
2018-05-09 12:01:36 +05:30
refresh_markdown_cache!
2021-10-27 15:23:28 +05:30
set_full_path
2018-03-17 18:26:18 +05:30
end
def update_project_counter_caches
classes = [
Projects::OpenIssuesCountService,
Projects::OpenMergeRequestsCountService
]
classes.each do |klass|
klass.new(self).refresh_cache
end
end
2018-12-05 23:21:45 +05:30
# rubocop: disable CodeReuse/ServiceClass
2018-03-17 18:26:18 +05:30
def after_create_default_branch
2019-03-02 22:35:43 +05:30
Projects::ProtectDefaultBranchService.new(self).execute
2018-03-17 18:26:18 +05:30
end
2018-12-05 23:21:45 +05:30
# rubocop: enable CodeReuse/ServiceClass
2018-03-17 18:26:18 +05:30
2017-08-17 22:00:37 +05:30
# Lazy loading of the `pipeline_status` attribute
def pipeline_status
@pipeline_status ||= Gitlab::Cache::Ci::ProjectPipelineStatus.load_for_project(self)
end
2018-05-09 12:01:36 +05:30
def add_export_job(current_user:, after_export_strategy: nil, params: {})
2022-07-16 23:28:13 +05:30
check_project_export_limit!
2018-05-09 12:01:36 +05:30
job_id = ProjectExportWorker.perform_async(current_user.id, self.id, after_export_strategy, params)
2016-06-22 15:30:34 +05:30
if job_id
2020-06-23 00:09:42 +05:30
Gitlab::AppLogger.info "Export job started for project ID #{self.id} with job ID #{job_id}"
2016-06-22 15:30:34 +05:30
else
2020-06-23 00:09:42 +05:30
Gitlab::AppLogger.error "Export job failed to start for project ID #{self.id}"
2016-06-22 15:30:34 +05:30
end
end
2018-03-27 19:54:05 +05:30
def import_export_shared
@import_export_shared ||= Gitlab::ImportExport::Shared.new(self)
end
2016-06-22 15:30:34 +05:30
def export_path
2019-07-07 11:18:12 +05:30
return unless namespace.present? || hashed_storage?(:repository)
2018-03-17 18:26:18 +05:30
2018-03-27 19:54:05 +05:30
import_export_shared.archive_path
2016-06-22 15:30:34 +05:30
end
2018-03-27 19:54:05 +05:30
def export_status
2020-04-08 14:13:33 +05:30
if regeneration_in_progress?
:regeneration_in_progress
elsif export_enqueued?
:queued
elsif export_in_progress?
2018-03-27 19:54:05 +05:30
:started
2018-11-20 20:47:30 +05:30
elsif export_file_exists?
2018-03-27 19:54:05 +05:30
:finished
else
:none
end
end
def export_in_progress?
2020-04-08 14:13:33 +05:30
strong_memoize(:export_in_progress) do
::Projects::ExportJobFinder.new(self, { status: :started }).execute.present?
end
end
def export_enqueued?
strong_memoize(:export_enqueued) do
::Projects::ExportJobFinder.new(self, { status: :queued }).execute.present?
end
2018-03-27 19:54:05 +05:30
end
2020-04-08 14:13:33 +05:30
def regeneration_in_progress?
(export_enqueued? || export_in_progress?) && export_file_exists?
2018-05-09 12:01:36 +05:30
end
2018-11-20 20:47:30 +05:30
def remove_exports
return unless export_file_exists?
import_export_upload.remove_export_file!
import_export_upload.save unless import_export_upload.destroyed?
2018-03-17 18:26:18 +05:30
end
2018-11-20 20:47:30 +05:30
def export_file_exists?
2021-09-04 01:27:46 +05:30
import_export_upload&.export_file_exists?
end
def export_archive_exists?
import_export_upload&.export_archive_exists?
2018-11-08 19:23:39 +05:30
end
2018-05-09 12:01:36 +05:30
2018-11-20 20:47:30 +05:30
def export_file
import_export_upload&.export_file
2018-05-09 12:01:36 +05:30
end
2018-03-17 18:26:18 +05:30
def full_path_slug
Gitlab::Utils.slugify(full_path.to_s)
end
def has_ci?
repository.gitlab_ci_yml || auto_devops_enabled?
2016-06-22 15:30:34 +05:30
end
2016-08-24 12:49:21 +05:30
def predefined_variables
2021-12-11 22:18:48 +05:30
strong_memoize(:predefined_variables) do
Gitlab::Ci::Variables::Collection.new
.concat(predefined_ci_server_variables)
.concat(predefined_project_variables)
.concat(pages_variables)
.concat(container_registry_variables)
.concat(dependency_proxy_variables)
.concat(auto_devops_variables)
.concat(api_variables)
2022-08-27 11:52:29 +05:30
.concat(ci_template_variables)
2021-12-11 22:18:48 +05:30
end
2020-01-01 13:55:28 +05:30
end
2018-05-09 12:01:36 +05:30
2020-01-01 13:55:28 +05:30
def predefined_project_variables
2018-05-09 12:01:36 +05:30
Gitlab::Ci::Variables::Collection.new
2020-01-01 13:55:28 +05:30
.append(key: 'GITLAB_FEATURES', value: licensed_features.join(','))
2018-05-09 12:01:36 +05:30
.append(key: 'CI_PROJECT_ID', value: id.to_s)
.append(key: 'CI_PROJECT_NAME', value: path)
2019-12-21 20:55:43 +05:30
.append(key: 'CI_PROJECT_TITLE', value: title)
2022-07-23 23:45:48 +05:30
.append(key: 'CI_PROJECT_DESCRIPTION', value: description)
2018-05-09 12:01:36 +05:30
.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)
2023-03-04 22:38:38 +05:30
.append(key: 'CI_PROJECT_NAMESPACE_ID', value: namespace.id.to_s)
2020-07-28 23:09:34 +05:30
.append(key: 'CI_PROJECT_ROOT_NAMESPACE', value: namespace.root_ancestor.path)
2018-05-09 12:01:36 +05:30
.append(key: 'CI_PROJECT_URL', value: web_url)
2020-01-01 13:55:28 +05:30
.append(key: 'CI_PROJECT_VISIBILITY', value: Gitlab::VisibilityLevel.string_level(visibility_level))
2019-12-04 20:38:33 +05:30
.append(key: 'CI_PROJECT_REPOSITORY_LANGUAGES', value: repository_languages.map(&:name).join(',').downcase)
2021-10-27 15:23:28 +05:30
.append(key: 'CI_PROJECT_CLASSIFICATION_LABEL', value: external_authorization_classification_label)
2020-01-01 13:55:28 +05:30
.append(key: 'CI_DEFAULT_BRANCH', value: default_branch)
2021-04-17 20:07:23 +05:30
.append(key: 'CI_CONFIG_PATH', value: ci_config_path_or_default)
2020-01-01 13:55:28 +05:30
end
def predefined_ci_server_variables
Gitlab::Ci::Variables::Collection.new
.append(key: 'CI', value: 'true')
.append(key: 'GITLAB_CI', value: 'true')
2020-03-13 15:44:24 +05:30
.append(key: 'CI_SERVER_URL', value: Gitlab.config.gitlab.url)
2020-01-01 13:55:28 +05:30
.append(key: 'CI_SERVER_HOST', value: Gitlab.config.gitlab.host)
2020-03-13 15:44:24 +05:30
.append(key: 'CI_SERVER_PORT', value: Gitlab.config.gitlab.port.to_s)
.append(key: 'CI_SERVER_PROTOCOL', value: Gitlab.config.gitlab.protocol)
2023-06-20 00:43:36 +05:30
.append(key: 'CI_SERVER_SHELL_SSH_HOST', value: Gitlab.config.gitlab_shell.ssh_host.to_s)
.append(key: 'CI_SERVER_SHELL_SSH_PORT', value: Gitlab.config.gitlab_shell.ssh_port.to_s)
2020-01-01 13:55:28 +05:30
.append(key: 'CI_SERVER_NAME', value: 'GitLab')
.append(key: 'CI_SERVER_VERSION', value: Gitlab::VERSION)
.append(key: 'CI_SERVER_VERSION_MAJOR', value: Gitlab.version_info.major.to_s)
.append(key: 'CI_SERVER_VERSION_MINOR', value: Gitlab.version_info.minor.to_s)
.append(key: 'CI_SERVER_VERSION_PATCH', value: Gitlab.version_info.patch.to_s)
.append(key: 'CI_SERVER_REVISION', value: Gitlab.revision)
2019-02-15 15:39:39 +05:30
end
2019-03-02 22:35:43 +05:30
def pages_variables
Gitlab::Ci::Variables::Collection.new.tap do |variables|
2020-01-01 13:55:28 +05:30
break unless pages_enabled?
2019-03-02 22:35:43 +05:30
variables.append(key: 'CI_PAGES_DOMAIN', value: Gitlab.config.pages.host)
variables.append(key: 'CI_PAGES_URL', value: pages_url)
end
end
2019-02-15 15:39:39 +05:30
def api_variables
Gitlab::Ci::Variables::Collection.new.tap do |variables|
variables.append(key: 'CI_API_V4_URL', value: API::Helpers::Version.new('v4').root_url)
2023-06-20 00:43:36 +05:30
variables.append(key: 'CI_API_GRAPHQL_URL', value: Gitlab::Routing.url_helpers.api_graphql_url)
2019-02-15 15:39:39 +05:30
end
2016-08-24 12:49:21 +05:30
end
2022-08-27 11:52:29 +05:30
def ci_template_variables
Gitlab::Ci::Variables::Collection.new.tap do |variables|
variables.append(key: 'CI_TEMPLATE_REGISTRY_HOST', value: 'registry.gitlab.com')
end
end
2021-02-22 17:27:13 +05:30
def dependency_proxy_variables
Gitlab::Ci::Variables::Collection.new.tap do |variables|
break variables unless Gitlab.config.dependency_proxy.enabled
2021-04-17 20:07:23 +05:30
variables.append(key: 'CI_DEPENDENCY_PROXY_SERVER', value: Gitlab.host_with_port)
2021-02-22 17:27:13 +05:30
variables.append(
key: 'CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX',
2021-04-17 20:07:23 +05:30
# The namespace path can include uppercase letters, which
# Docker doesn't allow. The proxy expects it to be downcased.
value: "#{Gitlab.host_with_port}/#{namespace.root_ancestor.path.downcase}#{DependencyProxy::URL_SUFFIX}"
2021-02-22 17:27:13 +05:30
)
2021-11-11 11:23:49 +05:30
variables.append(
key: 'CI_DEPENDENCY_PROXY_DIRECT_GROUP_IMAGE_PREFIX',
value: "#{Gitlab.host_with_port}/#{namespace.full_path.downcase}#{DependencyProxy::URL_SUFFIX}"
)
2021-02-22 17:27:13 +05:30
end
end
2016-08-24 12:49:21 +05:30
def container_registry_variables
2018-05-09 12:01:36 +05:30
Gitlab::Ci::Variables::Collection.new.tap do |variables|
2018-10-15 14:42:47 +05:30
break variables unless Gitlab.config.registry.enabled
2016-08-24 12:49:21 +05:30
2018-05-09 12:01:36 +05:30
variables.append(key: 'CI_REGISTRY', value: Gitlab.config.registry.host_port)
2016-08-24 12:49:21 +05:30
2018-05-09 12:01:36 +05:30
if container_registry_enabled?
variables.append(key: 'CI_REGISTRY_IMAGE', value: container_registry_url)
end
2016-08-24 12:49:21 +05:30
end
end
2018-11-08 19:23:39 +05:30
def default_environment
2019-12-26 22:10:19 +05:30
production_first = Arel.sql("(CASE WHEN name = 'production' THEN 0 ELSE 1 END), id ASC")
2018-11-08 19:23:39 +05:30
environments
.with_state(:available)
.reorder(production_first)
.first
end
2017-09-10 17:25:29 +05:30
def protected_for?(ref)
2019-01-03 12:48:30 +05:30
raise Repository::AmbiguousRefError if repository.ambiguous_ref?(ref)
resolved_ref = repository.expand_ref(ref) || ref
return false unless Gitlab::Git.tag_ref?(resolved_ref) || Gitlab::Git.branch_ref?(resolved_ref)
ref_name = if resolved_ref == ref
Gitlab::Git.ref_name(resolved_ref)
else
ref
end
if Gitlab::Git.branch_ref?(resolved_ref)
ProtectedBranch.protected?(self, ref_name)
elsif Gitlab::Git.tag_ref?(resolved_ref)
ProtectedTag.protected?(self, ref_name)
2018-03-17 18:26:18 +05:30
end
2017-09-10 17:25:29 +05:30
end
2020-01-01 13:55:28 +05:30
def deployment_variables(environment:, kubernetes_namespace: nil)
2019-10-12 21:52:04 +05:30
platform = deployment_platform(environment: environment)
return [] unless platform.present?
2020-01-01 13:55:28 +05:30
platform.predefined_variables(
project: self,
environment_name: environment,
kubernetes_namespace: kubernetes_namespace
)
2018-03-17 18:26:18 +05:30
end
2016-08-24 12:49:21 +05:30
2018-03-17 18:26:18 +05:30
def auto_devops_variables
return [] unless auto_devops_enabled?
2018-05-09 12:01:36 +05:30
(auto_devops || build_auto_devops)&.predefined_variables
2016-08-24 12:49:21 +05:30
end
2017-08-17 22:00:37 +05:30
def route_map_for(commit_sha)
@route_maps_by_commit ||= Hash.new do |h, sha|
h[sha] = begin
data = repository.route_map_for(sha)
2016-09-29 09:46:39 +05:30
2019-09-30 21:07:59 +05:30
Gitlab::RouteMap.new(data) if data
2021-04-29 21:17:54 +05:30
rescue Gitlab::RouteMap::FormatError
nil
2016-09-29 09:46:39 +05:30
end
2017-08-17 22:00:37 +05:30
end
@route_maps_by_commit[commit_sha]
end
2016-09-29 09:46:39 +05:30
2017-08-17 22:00:37 +05:30
def public_path_for_source_path(path, commit_sha)
map = route_map_for(commit_sha)
return unless map
map.public_path_for_source_path(path)
end
def parent_changed?
namespace_id_changed?
end
def default_merge_request_target
2021-04-29 21:17:54 +05:30
return self if project_setting.mr_default_target_self
return self unless mr_can_target_upstream?
forked_from_project
end
def mr_can_target_upstream?
# When our current visibility is more restrictive than the upstream project,
# (e.g., the fork is `private` but the parent is `public`), don't allow target upstream
forked_from_project &&
forked_from_project.merge_requests_enabled? &&
forked_from_project.visibility_level_value <= visibility_level_value
2016-09-29 09:46:39 +05:30
end
2018-03-27 19:54:05 +05:30
def multiple_issue_boards_available?
2019-09-30 21:07:59 +05:30
true
2018-03-17 18:26:18 +05:30
end
2019-07-31 22:56:46 +05:30
def full_path_before_last_save
File.join(namespace.full_path, path_before_last_save)
2018-03-17 18:26:18 +05:30
end
2017-08-17 22:00:37 +05:30
alias_method :name_with_namespace, :full_name
alias_method :human_name, :full_name
2017-09-10 17:25:29 +05:30
# @deprecated cannot remove yet because it has an index with its name in elasticsearch
2017-08-17 22:00:37 +05:30
alias_method :path_with_namespace, :full_path
2018-12-05 23:21:45 +05:30
# rubocop: disable CodeReuse/ServiceClass
2017-09-10 17:25:29 +05:30
def forks_count
2021-11-18 22:05:49 +05:30
BatchLoader.for(self).batch do |projects, loader|
2020-07-28 23:09:34 +05:30
fork_count_per_project = ::Projects::BatchForksCountService.new(projects).refresh_cache_and_retrieve_data
fork_count_per_project.each do |project, count|
loader.call(project, count)
end
end
2017-09-10 17:25:29 +05:30
end
2018-12-05 23:21:45 +05:30
# rubocop: enable CodeReuse/ServiceClass
2017-09-10 17:25:29 +05:30
def legacy_storage?
2018-03-17 18:26:18 +05:30
[nil, 0].include?(self.storage_version)
end
# Check if Hashed Storage is enabled for the project with at least informed feature rolled out
#
# @param [Symbol] feature that needs to be rolled out for the project (:repository, :attachments)
def hashed_storage?(feature)
2019-07-31 22:56:46 +05:30
raise ArgumentError, _("Invalid feature") unless HASHED_STORAGE_FEATURES.include?(feature)
2018-03-17 18:26:18 +05:30
self.storage_version && self.storage_version >= HASHED_STORAGE_FEATURES[feature]
end
def renamed?
persisted? && path_changed?
end
2019-07-07 11:18:12 +05:30
def human_merge_method
if merge_method == :ff
'Fast-forward'
else
merge_method.to_s.humanize
end
end
2018-03-17 18:26:18 +05:30
def merge_method
if self.merge_requests_ff_only_enabled
:ff
elsif self.merge_requests_rebase_enabled
:rebase_merge
else
:merge
end
end
def merge_method=(method)
case method.to_s
when "ff"
self.merge_requests_ff_only_enabled = true
self.merge_requests_rebase_enabled = true
when "rebase_merge"
self.merge_requests_ff_only_enabled = false
self.merge_requests_rebase_enabled = true
when "merge"
self.merge_requests_ff_only_enabled = false
self.merge_requests_rebase_enabled = false
end
end
def ff_merge_must_be_possible?
self.merge_requests_ff_only_enabled || self.merge_requests_rebase_enabled
end
def migrate_to_hashed_storage!
2019-02-15 15:39:39 +05:30
return unless storage_upgradable?
2018-03-17 18:26:18 +05:30
2019-02-15 15:39:39 +05:30
if git_transfer_in_progress?
2019-07-07 11:18:12 +05:30
HashedStorage::ProjectMigrateWorker.perform_in(Gitlab::ReferenceCounter::REFERENCE_EXPIRE_TIME, id)
else
HashedStorage::ProjectMigrateWorker.perform_async(id)
end
end
def rollback_to_legacy_storage!
return if legacy_storage?
if git_transfer_in_progress?
HashedStorage::ProjectRollbackWorker.perform_in(Gitlab::ReferenceCounter::REFERENCE_EXPIRE_TIME, id)
2018-03-17 18:26:18 +05:30
else
2019-07-07 11:18:12 +05:30
HashedStorage::ProjectRollbackWorker.perform_async(id)
2018-03-17 18:26:18 +05:30
end
end
2021-02-22 17:27:13 +05:30
override :git_transfer_in_progress?
2019-02-15 15:39:39 +05:30
def git_transfer_in_progress?
2021-02-22 17:27:13 +05:30
GL_REPOSITORY_TYPES.any? do |type|
reference_counter(type: type).value > 0
end
2019-02-15 15:39:39 +05:30
end
2018-03-17 18:26:18 +05:30
def storage_version=(value)
super
@storage = nil if storage_version_changed?
end
2018-03-27 19:54:05 +05:30
def badges
return project_badges unless group
2022-10-11 01:57:18 +05:30
Badge.from_union([project_badges, GroupBadge.where(group: group.self_and_ancestors)])
2018-03-27 19:54:05 +05:30
end
def merge_requests_allowing_push_to_user(user)
return MergeRequest.none unless user
developer_access_exists = user.project_authorizations
.where('access_level >= ? ', Gitlab::Access::DEVELOPER)
.where('project_authorizations.project_id = merge_requests.target_project_id')
.limit(1)
.select(1)
2019-02-15 15:39:39 +05:30
merge_requests_allowing_collaboration.where('EXISTS (?)', developer_access_exists)
2018-03-27 19:54:05 +05:30
end
2019-02-15 15:39:39 +05:30
def any_branch_allows_collaboration?(user)
fetch_branch_allows_collaboration(user)
end
2018-03-27 19:54:05 +05:30
2019-02-15 15:39:39 +05:30
def branch_allows_collaboration?(user, branch_name)
fetch_branch_allows_collaboration(user, branch_name)
2018-03-27 19:54:05 +05:30
end
2019-07-07 11:18:12 +05:30
def external_authorization_classification_label
super || ::Gitlab::CurrentSettings.current_application_settings
.external_authorization_service_default_label
end
2021-04-29 21:17:54 +05:30
# Overridden in EE::Project
def licensed_feature_available?(_feature)
false
end
2018-10-15 14:42:47 +05:30
def licensed_features
[]
end
2021-01-03 14:25:43 +05:30
def mark_primary_write_location
2021-11-18 22:05:49 +05:30
self.class.sticking.mark_primary_write_location(:project, self.id)
2021-01-03 14:25:43 +05:30
end
2018-10-15 14:42:47 +05:30
def toggle_ci_cd_settings!(settings_attribute)
ci_cd_settings.toggle!(settings_attribute)
end
def gitlab_deploy_token
2022-07-23 23:45:48 +05:30
strong_memoize(:gitlab_deploy_token) do
2022-10-11 01:57:18 +05:30
deploy_tokens.gitlab_deploy_token || group&.gitlab_deploy_token
2022-07-23 23:45:48 +05:30
end
2018-10-15 14:42:47 +05:30
end
2018-11-08 19:23:39 +05:30
def any_lfs_file_locks?
lfs_file_locks.any?
end
request_cache(:any_lfs_file_locks?) { self.id }
def auto_cancel_pending_pipelines?
auto_cancel_pending_pipelines == 'enabled'
end
2018-03-17 18:26:18 +05:30
def storage
@storage ||=
if hashed_storage?(:repository)
2020-03-13 15:44:24 +05:30
Storage::Hashed.new(self)
2018-03-17 18:26:18 +05:30
else
Storage::LegacyProject.new(self)
end
end
2018-12-13 13:39:08 +05:30
def storage_upgradable?
storage_version != LATEST_STORAGE_VERSION
end
def snippets_visible?(user = nil)
2020-03-13 15:44:24 +05:30
Ability.allowed?(user, :read_snippet, self)
2018-12-13 13:39:08 +05:30
end
2019-02-15 15:39:39 +05:30
def max_attachment_size
Gitlab::CurrentSettings.max_attachment_size.megabytes.to_i
end
def object_pool_params
return {} unless !forked? && git_objects_poolable?
{
repository_storage: repository_storage,
2022-08-27 11:52:29 +05:30
pool_repository: pool_repository || create_new_pool_repository
2019-02-15 15:39:39 +05:30
}
end
# Git objects are only poolable when the project is or has:
# - Hashed storage -> The object pool will have a remote to its members, using relative paths.
# If the repository path changes we would have to update the remote.
2020-01-01 13:55:28 +05:30
# - not private -> The visibility level or repository access level has to be greater than private
# to prevent fetching objects that might not exist
2019-02-15 15:39:39 +05:30
# - Repository -> Else the disk path will be empty, and there's nothing to pool
def git_objects_poolable?
hashed_storage?(:repository) &&
2020-01-01 13:55:28 +05:30
visibility_level > Gitlab::VisibilityLevel::PRIVATE &&
repository_access_level > ProjectFeature::PRIVATE &&
2019-02-15 15:39:39 +05:30
repository_exists? &&
2019-12-04 20:38:33 +05:30
Gitlab::CurrentSettings.hashed_storage_enabled
2019-02-15 15:39:39 +05:30
end
def leave_pool_repository
2022-10-11 01:57:18 +05:30
return if pool_repository.blank?
# Disconnecting the repository can be expensive, so let's skip it if
# this repository is being deleted anyway.
pool_repository.unlink_repository(repository, disconnect: !pending_delete?)
update_column(:pool_repository_id, nil)
2019-03-02 22:35:43 +05:30
end
def link_pool_repository
pool_repository&.link_repository(repository)
2019-02-15 15:39:39 +05:30
end
2019-07-07 11:18:12 +05:30
def has_pool_repository?
pool_repository.present?
end
2019-12-04 20:38:33 +05:30
def access_request_approvers_to_be_notified
2022-07-23 23:45:48 +05:30
access_request_approvers = members.owners_and_maintainers
2022-05-07 20:08:51 +05:30
2023-03-04 22:38:38 +05:30
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
2019-12-04 20:38:33 +05:30
end
2019-12-21 20:55:43 +05:30
def pages_lookup_path(trim_prefix: nil, domain: nil)
Pages::LookupPath.new(self, trim_prefix: trim_prefix, domain: domain)
end
def closest_setting(name)
setting = read_attribute(name)
setting = closest_namespace_setting(name) if setting.nil?
setting = app_settings_for(name) if setting.nil?
setting
end
def drop_visibility_level!
if group && group.visibility_level < visibility_level
self.visibility_level = group.visibility_level
end
if Gitlab::CurrentSettings.restricted_visibility_levels.include?(visibility_level)
self.visibility_level = Gitlab::VisibilityLevel::PRIVATE
end
2019-12-04 20:38:33 +05:30
end
2020-02-01 01:16:34 +05:30
def template_source?
false
end
2020-11-24 15:15:51 +05:30
def jira_subscription_exists?
JiraConnectSubscription.for_project(self).exists?
end
2020-03-13 15:44:24 +05:30
def uses_default_ci_config?
ci_config_path.blank? || ci_config_path == Gitlab::FileDetector::PATTERNS[:gitlab_ci]
end
def limited_protected_branches(limit)
protected_branches.limit(limit)
end
2023-04-23 21:23:45 +05:30
def group_protected_branches
root_namespace.is_a?(Group) ? root_namespace.protected_branches : ProtectedBranch.none
end
def all_protected_branches
2023-06-20 00:43:36 +05:30
if allow_protected_branches_for_group?
2023-04-23 21:23:45 +05:30
@all_protected_branches ||= ProtectedBranch.from_union([protected_branches, group_protected_branches])
else
protected_branches
end
end
2023-06-20 00:43:36 +05:30
def allow_protected_branches_for_group?
Feature.enabled?(:group_protected_branches, group) || Feature.enabled?(:allow_protected_branches_for_group, group)
end
2020-04-08 14:13:33 +05:30
def deploy_token_create_url(opts = {})
2020-04-25 10:58:03 +05:30
Gitlab::Routing.url_helpers.create_deploy_token_project_settings_repository_path(self, opts)
2020-04-08 14:13:33 +05:30
end
def deploy_token_revoke_url_for(token)
Gitlab::Routing.url_helpers.revoke_project_deploy_token_path(self, token)
end
def default_branch_protected?
branch_protection = Gitlab::Access::BranchProtection.new(self.namespace.default_branch_protection)
branch_protection.fully_protected? || branch_protection.developer_can_merge?
end
2020-04-22 19:07:51 +05:30
def environments_for_scope(scope)
quoted_scope = ::Gitlab::SQL::Glob.q(scope)
environments.where("name LIKE (#{::Gitlab::SQL::Glob.to_like(quoted_scope)})") # rubocop:disable GitlabSecurity/SqlInjection
end
def latest_jira_import
jira_imports.last
end
2020-06-23 00:09:42 +05:30
def metrics_setting
super || build_metrics_setting
end
2020-07-28 23:09:34 +05:30
def service_desk_enabled
Gitlab::ServiceDesk.enabled?(project: self)
end
alias_method :service_desk_enabled?, :service_desk_enabled
def service_desk_address
2020-10-24 23:57:45 +05:30
service_desk_custom_address || service_desk_incoming_address
end
def service_desk_incoming_address
2020-07-28 23:09:34 +05:30
return unless service_desk_enabled?
config = Gitlab.config.incoming_email
2023-01-13 00:05:48 +05:30
wildcard = Gitlab::Email::Common::WILDCARD_PLACEHOLDER
2020-07-28 23:09:34 +05:30
2021-12-11 22:18:48 +05:30
config.address&.gsub(wildcard, "#{full_path_slug}-#{default_service_desk_suffix}")
2020-07-28 23:09:34 +05:30
end
2020-10-24 23:57:45 +05:30
def service_desk_custom_address
2023-06-20 00:43:36 +05:30
return unless Gitlab::Email::ServiceDeskEmail.enabled?
2020-10-24 23:57:45 +05:30
2021-12-11 22:18:48 +05:30
key = service_desk_setting&.project_key || default_service_desk_suffix
2020-10-24 23:57:45 +05:30
2023-06-20 00:43:36 +05:30
Gitlab::Email::ServiceDeskEmail.address_for_key("#{full_path_slug}-#{key}")
2021-02-22 17:27:13 +05:30
end
2021-12-11 22:18:48 +05:30
def default_service_desk_suffix
"#{id}-issue-"
end
2020-07-28 23:09:34 +05:30
def root_namespace
if namespace.has_parent?
namespace.root_ancestor
else
namespace
end
end
2021-09-04 01:27:46 +05:30
# for projects that are part of user namespace, return project.
def self_or_root_group_ids
if group
root_group = root_namespace
else
project = self
end
[project&.id, root_group&.id]
end
2022-04-04 11:22:00 +05:30
def related_group_ids
ids = invited_group_ids
ids += group.self_and_ancestors_ids if group
ids
end
2021-10-27 15:23:28 +05:30
def package_already_taken?(package_name, package_version, package_type:)
Packages::Package.with_name(package_name)
.with_version(package_version)
.with_package_type(package_type)
2022-03-02 08:16:31 +05:30
.not_pending_destruction
2021-10-27 15:23:28 +05:30
.for_projects(
root_ancestor.all_projects
.id_not_in(id)
.select(:id)
).exists?
2020-07-28 23:09:34 +05:30
end
2023-07-09 08:55:56 +05:30
def has_namespaced_npm_packages?
packages.with_npm_scope(root_namespace.path)
.not_pending_destruction
.exists?
end
2021-06-08 01:23:25 +05:30
def default_branch_or_main
return default_branch if default_branch
Gitlab::DefaultBranch.value(object: self)
2020-11-24 15:15:51 +05:30
end
def ci_config_path_or_default
ci_config_path.presence || Ci::Pipeline::DEFAULT_CONFIG_PATH
end
2021-01-03 14:25:43 +05:30
def ci_config_for(sha)
repository.gitlab_ci_yml_for(sha, ci_config_path_or_default)
end
2020-11-24 15:15:51 +05:30
def enabled_group_deploy_keys
return GroupDeployKey.none unless group
GroupDeployKey.for_groups(group.self_and_ancestors_ids)
end
2021-01-03 14:25:43 +05:30
def feature_flags_client_token
instance = operations_feature_flags_client || create_operations_feature_flags_client!
instance.token
end
2021-03-11 19:13:27 +05:30
override :git_garbage_collect_worker_klass
def git_garbage_collect_worker_klass
Projects::GitGarbageCollectWorker
end
2021-06-08 01:23:25 +05:30
def activity_path
Gitlab::Routing.url_helpers.activity_project_path(self)
end
2021-09-30 23:02:18 +05:30
def ci_forward_deployment_enabled?
return false unless ci_cd_settings
ci_cd_settings.forward_deployment_enabled?
end
2022-08-27 11:52:29 +05:30
def ci_allow_fork_pipelines_to_run_in_parent_project?
return false unless ci_cd_settings
ci_cd_settings.allow_fork_pipelines_to_run_in_parent_project?
end
2022-11-25 23:54:43 +05:30
def ci_outbound_job_token_scope_enabled?
2021-09-30 23:02:18 +05:30
return false unless ci_cd_settings
ci_cd_settings.job_token_scope_enabled?
end
2022-11-25 23:54:43 +05:30
def ci_inbound_job_token_scope_enabled?
2023-05-27 22:25:52 +05:30
return true unless ci_cd_settings
2022-11-25 23:54:43 +05:30
ci_cd_settings.inbound_job_token_scope_enabled?
end
2021-09-30 23:02:18 +05:30
def restrict_user_defined_variables?
return false unless ci_cd_settings
ci_cd_settings.restrict_user_defined_variables?
end
def keep_latest_artifacts_available?
return false unless ci_cd_settings
ci_cd_settings.keep_latest_artifacts_available?
end
def keep_latest_artifact?
return false unless ci_cd_settings
ci_cd_settings.keep_latest_artifact?
end
def group_runners_enabled?
return false unless ci_cd_settings
ci_cd_settings.group_runners_enabled?
2021-09-04 01:27:46 +05:30
end
2021-11-11 11:23:49 +05:30
def topic_list
self.topics.map(&:name)
end
override :after_change_head_branch_does_not_exist
def after_change_head_branch_does_not_exist(branch)
self.errors.add(:base, _("Could not change HEAD: branch '%{branch}' does not exist") % { branch: branch })
end
2021-10-29 20:43:33 +05:30
def visible_group_links(for_user:)
user = for_user
links = project_group_links_with_preload
user.max_member_access_for_group_ids(links.map(&:group_id)) if user && links.any?
DeclarativePolicy.user_scope do
links.select { Ability.allowed?(user, :read_group, _1.group) }
end
end
2022-03-02 08:16:31 +05:30
def enforced_runner_token_expiration_interval
all_parent_groups = Gitlab::ObjectHierarchy.new(Group.where(id: group)).base_and_ancestors
all_group_settings = NamespaceSetting.where(namespace_id: all_parent_groups)
group_interval = all_group_settings.where.not(project_runner_token_expiration_interval: nil).minimum(:project_runner_token_expiration_interval)&.seconds
[
Gitlab::CurrentSettings.project_runner_token_expiration_interval&.seconds,
group_interval
].compact.min
end
2022-04-04 11:22:00 +05:30
def merge_commit_template_or_default
merge_commit_template.presence || DEFAULT_MERGE_COMMIT_TEMPLATE
end
def merge_commit_template_or_default=(value)
project_setting.merge_commit_template =
if value.blank? || value.delete("\r") == DEFAULT_MERGE_COMMIT_TEMPLATE
nil
else
value
end
end
def squash_commit_template_or_default
squash_commit_template.presence || DEFAULT_SQUASH_COMMIT_TEMPLATE
end
def squash_commit_template_or_default=(value)
project_setting.squash_commit_template =
if value.blank? || value.delete("\r") == DEFAULT_SQUASH_COMMIT_TEMPLATE
nil
else
value
end
end
2022-05-07 20:08:51 +05:30
def pending_delete_or_hidden?
pending_delete? || hidden?
end
2023-06-20 00:43:36 +05:30
def content_editor_on_issues_feature_flag_enabled?
group&.content_editor_on_issues_feature_flag_enabled? || Feature.enabled?(:content_editor_on_issues, self)
end
2022-06-21 17:19:12 +05:30
def work_items_feature_flag_enabled?
2022-07-16 23:28:13 +05:30
group&.work_items_feature_flag_enabled? || Feature.enabled?(:work_items, self)
2022-06-21 17:19:12 +05:30
end
2023-03-04 22:38:38 +05:30
def work_items_mvc_feature_flag_enabled?
group&.work_items_mvc_feature_flag_enabled? || Feature.enabled?(:work_items_mvc)
end
2022-08-27 11:52:29 +05:30
def work_items_mvc_2_feature_flag_enabled?
group&.work_items_mvc_2_feature_flag_enabled? || Feature.enabled?(:work_items_mvc_2)
end
2022-06-21 17:19:12 +05:30
def enqueue_record_project_target_platforms
return unless Gitlab.com?
Projects::RecordTargetPlatformsWorker.perform_async(id)
end
def inactive?
(statistics || build_statistics).storage_size > ::Gitlab::CurrentSettings.inactive_projects_min_size_mb.megabytes &&
last_activity_at < ::Gitlab::CurrentSettings.inactive_projects_send_warning_email_after_months.months.ago
end
2022-07-23 23:45:48 +05:30
def refreshing_build_artifacts_size?
build_artifacts_size_refresh&.started?
end
2022-08-13 15:12:31 +05:30
def group_group_links
group&.shared_with_group_links&.of_ancestors_and_self || GroupGroupLink.none
end
2022-07-23 23:45:48 +05:30
def security_training_available?
licensed_feature_available?(:security_training)
end
2022-10-11 01:57:18 +05:30
def packages_policy_subject
2022-11-25 23:54:43 +05:30
::Packages::Policies::Project.new(self)
2022-10-11 01:57:18 +05:30
end
2022-08-27 11:52:29 +05:30
def destroy_deployment_by_id(deployment_id)
deployments.where(id: deployment_id).fast_destroy_all
end
2022-10-11 01:57:18 +05:30
def can_create_custom_domains?
return true if Gitlab::CurrentSettings.max_pages_custom_domains_per_project == 0
pages_domains.count < Gitlab::CurrentSettings.max_pages_custom_domains_per_project
end
2022-11-25 23:54:43 +05:30
# overridden in EE
def can_suggest_reviewers?
false
end
# overridden in EE
def suggested_reviewers_available?
false
end
2023-06-20 00:43:36 +05:30
def crm_enabled?
return false unless group
group.crm_enabled?
end
2023-07-09 08:55:56 +05:30
def frozen_outbound_job_token_scopes?
Feature.enabled?(:frozen_outbound_job_token_scopes, self) && Feature.disabled?(:frozen_outbound_job_token_scopes_override, self)
end
strong_memoize_attr :frozen_outbound_job_token_scopes?
2018-12-13 13:39:08 +05:30
private
2023-05-27 22:25:52 +05:30
def pages_unique_domain_enabled?
2023-06-20 00:43:36 +05:30
Feature.enabled?(:pages_unique_domain, self) &&
2023-05-27 22:25:52 +05:30
project_setting.pages_unique_domain_enabled?
end
def pages_url_for(domain)
# The host in URL always needs to be downcased
Gitlab.config.pages.url.sub(%r{^https?://}) do |prefix|
"#{prefix}#{domain}."
end.downcase
end
2021-10-29 20:43:33 +05:30
# overridden in EE
def project_group_links_with_preload
project_group_links
end
2021-11-11 11:23:49 +05:30
def save_topics
2022-04-04 11:22:00 +05:30
topic_ids_before = self.topic_ids
update_topics
Projects::Topic.update_non_private_projects_counter(topic_ids_before, self.topic_ids, visibility_level_previously_was, visibility_level)
end
def update_topics
2021-11-11 11:23:49 +05:30
return if @topic_list.nil?
@topic_list = @topic_list.split(',') if @topic_list.instance_of?(String)
@topic_list = @topic_list.map(&:strip).uniq.reject(&:empty?)
2021-11-18 22:05:49 +05:30
if @topic_list != self.topic_list
self.topics.delete_all
2022-05-07 20:08:51 +05:30
self.topics = @topic_list.map do |topic|
2022-07-16 23:28:13 +05:30
Projects::Topic.where('lower(name) = ?', topic.downcase).order(total_projects_count: :desc).first_or_create(name: topic, title: topic)
2022-05-07 20:08:51 +05:30
end
2021-11-11 11:23:49 +05:30
end
@topic_list = nil
end
2021-09-30 23:02:18 +05:30
def find_integration(integrations, name)
integrations.find { _1.to_param == name }
2020-04-22 19:07:51 +05:30
end
2021-10-27 15:23:28 +05:30
def build_from_instance(name)
2021-09-30 23:02:18 +05:30
instance = find_integration(integration_instances, name)
2020-06-23 00:09:42 +05:30
2021-10-27 15:23:28 +05:30
return unless instance
Integration.build_from_integration(instance, project_id: id)
2020-06-23 00:09:42 +05:30
end
2021-09-30 23:02:18 +05:30
def build_integration(name)
2021-09-04 01:27:46 +05:30
Integration.integration_name_to_model(name).new(project_id: id)
2021-04-29 21:17:54 +05:30
end
2021-09-30 23:02:18 +05:30
def integration_instances
@integration_instances ||= Integration.for_instance
2020-06-23 00:09:42 +05:30
end
2019-12-21 20:55:43 +05:30
def closest_namespace_setting(name)
namespace.closest_setting(name)
end
def app_settings_for(name)
Gitlab::CurrentSettings.send(name) # rubocop:disable GitlabSecurity/PublicSend
end
2019-02-15 15:39:39 +05:30
def merge_requests_allowing_collaboration(source_branch = nil)
relation = source_of_merge_requests.opened.where(allow_collaboration: true)
relation = relation.where(source_branch: source_branch) if source_branch
relation
end
def create_new_pool_repository
2019-07-31 22:56:46 +05:30
pool = PoolRepository.safe_find_or_create_by!(shard: Shard.by_name(repository_storage), source_project: self)
update!(pool_repository: pool)
2019-02-15 15:39:39 +05:30
pool.schedule unless pool.scheduled?
2019-07-31 22:56:46 +05:30
2019-02-15 15:39:39 +05:30
pool
end
def join_pool_repository
return unless pool_repository
ObjectPool::JoinWorker.perform_async(pool_repository.id, self.id)
end
2018-03-17 18:26:18 +05:30
def use_hashed_storage
if self.new_record? && Gitlab::CurrentSettings.hashed_storage_enabled
self.storage_version = LATEST_STORAGE_VERSION
end
end
def check_repository_absence!
return if skip_disk_validation
2018-10-15 14:42:47 +05:30
if repository_storage.blank? || repository_with_same_path_already_exists?
2019-07-31 22:56:46 +05:30
errors.add(:base, _('There is already a repository with that name on disk'))
2020-04-08 14:13:33 +05:30
throw :abort # rubocop:disable Cop/BanCatchThrow
2018-03-17 18:26:18 +05:30
end
end
def repository_with_same_path_already_exists?
2019-12-21 20:55:43 +05:30
gitlab_shell.repository_exists?(repository_storage, "#{disk_path}.git")
2018-03-17 18:26:18 +05:30
end
2018-12-13 13:39:08 +05:30
def set_timestamps_for_create
update_columns(last_activity_at: self.created_at, last_repository_updated_at: self.created_at)
2018-03-17 18:26:18 +05:30
end
2017-08-17 22:00:37 +05:30
def cross_namespace_reference?(from)
case from
when Project
2021-06-08 01:23:25 +05:30
namespace_id != from.namespace_id
2023-05-27 22:25:52 +05:30
when Namespaces::ProjectNamespace
namespace_id != from.parent_id
2017-08-17 22:00:37 +05:30
when Namespace
namespace != from
2020-10-24 23:57:45 +05:30
when User
true
2017-08-17 22:00:37 +05:30
end
end
# Check if a reference is being done cross-project
def cross_project_reference?(from)
2023-05-27 22:25:52 +05:30
case from
when Namespaces::ProjectNamespace
project_namespace_id != from.id
when Namespace
true
else
from && self != from
end
2017-08-17 22:00:37 +05:30
end
def update_project_statistics
stats = statistics || build_statistics
stats.update(namespace_id: namespace_id)
2016-08-24 12:49:21 +05:30
end
2017-08-17 22:00:37 +05:30
def check_pending_delete
return if valid_attribute?(:name) && valid_attribute?(:path)
return unless pending_delete_twin
%i[route route.path name path].each do |error|
errors.delete(error)
end
2016-08-24 12:49:21 +05:30
2019-07-31 22:56:46 +05:30
errors.add(:base, _("The project is still being deleted. Please try again later."))
2016-08-24 12:49:21 +05:30
end
2017-08-17 22:00:37 +05:30
def pending_delete_twin
return false unless path
2016-08-24 12:49:21 +05:30
2017-09-10 17:25:29 +05:30
Project.pending_delete.find_by_full_path(full_path)
2017-08-17 22:00:37 +05:30
end
2016-08-24 12:49:21 +05:30
2017-08-17 22:00:37 +05:30
##
# This method is here because of support for legacy container repository
# which has exactly the same path like project does, but which might not be
# persisted in `container_repositories` table.
#
def has_root_container_repository_tags?
return false unless Gitlab.config.registry.enabled
ContainerRepository.build_root_repository(self).has_tags?
2016-08-24 12:49:21 +05:30
end
2016-11-03 12:29:30 +05:30
2019-02-15 15:39:39 +05:30
def fetch_branch_allows_collaboration(user, branch_name = nil)
return false unless user
2018-05-09 12:01:36 +05:30
2019-02-15 15:39:39 +05:30
Gitlab::SafeRequestStore.fetch("project-#{id}:branch-#{branch_name}:user-#{user.id}:branch_allows_collaboration") do
next false if empty_repo?
2018-11-08 19:23:39 +05:30
2019-12-04 20:38:33 +05:30
# Issue for N+1: https://gitlab.com/gitlab-org/gitlab-foss/issues/49322
2018-11-18 11:00:15 +05:30
Gitlab::GitalyClient.allow_n_plus_1_calls do
2019-02-15 15:39:39 +05:30
merge_requests_allowing_collaboration(branch_name).any? do |merge_request|
2021-04-01 16:36:13 +05:30
merge_request.can_be_merged_by?(user, skip_collaboration_check: true)
2018-11-18 11:00:15 +05:30
end
2018-11-08 19:23:39 +05:30
end
2018-03-27 19:54:05 +05:30
end
end
2018-12-05 23:21:45 +05:30
2019-12-21 20:55:43 +05:30
def ensure_pages_metadatum
pages_metadatum || create_pages_metadatum!
rescue ActiveRecord::RecordNotUnique
reset
retry
end
2020-04-08 14:13:33 +05:30
def oids(objects, oids: [])
2020-11-24 15:15:51 +05:30
objects = objects.where(oid: oids) if oids.any?
2020-04-08 14:13:33 +05:30
2020-11-24 15:15:51 +05:30
[].tap do |out|
objects.each_batch { |relation| out.concat(relation.pluck(:oid)) }
end
2020-04-08 14:13:33 +05:30
end
2021-03-08 18:12:59 +05:30
def cache_has_external_wiki
2021-06-08 01:23:25 +05:30
update_column(:has_external_wiki, integrations.external_wikis.any?) if Gitlab::Database.read_write?
2021-03-08 18:12:59 +05:30
end
2021-03-11 19:13:27 +05:30
def cache_has_external_issue_tracker
2021-06-08 01:23:25 +05:30
update_column(:has_external_issue_tracker, integrations.external_issue_trackers.any?) if Gitlab::Database.read_write?
2021-03-11 19:13:27 +05:30
end
2021-04-17 20:07:23 +05:30
2021-04-29 21:17:54 +05:30
def online_runners_with_tags
2021-11-11 11:23:49 +05:30
@online_runners_with_tags ||= active_runners.with_tags.online
2021-04-17 20:07:23 +05:30
end
2021-11-18 22:05:49 +05:30
def ensure_project_namespace_in_sync
2022-05-07 20:08:51 +05:30
# create project_namespace when project is created
2021-12-11 22:18:48 +05:30
build_project_namespace if project_namespace_creation_enabled?
sync_attributes(project_namespace) if sync_project_namespace?
end
def project_namespace_creation_enabled?
2022-05-07 20:08:51 +05:30
new_record? && !project_namespace && self.namespace
2021-12-11 22:18:48 +05:30
end
def sync_project_namespace?
(changes.keys & %w(name path namespace_id namespace visibility_level shared_runners_enabled)).any? && project_namespace.present?
end
def sync_attributes(project_namespace)
2022-07-23 23:45:48 +05:30
attributes_to_sync = changes.slice(*%w(name path namespace_id namespace visibility_level shared_runners_enabled))
.transform_values { |val| val[1] }
# if visibility_level is not set explicitly for project, it defaults to 0,
# but for namespace visibility_level defaults to 20,
# so it gets out of sync right away if we do not set it explicitly when creating the project namespace
attributes_to_sync['visibility_level'] ||= visibility_level if new_record?
# when a project is associated with a group while the group is created we need to ensure we associate the new
# group with the project namespace as well.
# E.g.
# project = create(:project) <- project is saved
# create(:group, projects: [project]) <- associate project with a group that is not yet created.
if attributes_to_sync.has_key?('namespace_id') && attributes_to_sync['namespace_id'].blank? && namespace.present?
attributes_to_sync['parent'] = namespace
end
project_namespace.assign_attributes(attributes_to_sync)
2021-11-18 22:05:49 +05:30
end
2022-01-26 12:08:38 +05:30
2022-08-27 11:52:29 +05:30
def reload_project_namespace_details
return unless (previous_changes.keys & %w(description description_html cached_markdown_version)).any? && project_namespace.namespace_details.present?
project_namespace.namespace_details.reset
end
2022-01-26 12:08:38 +05:30
# SyncEvents are created by PG triggers (with the function `insert_projects_sync_event`)
def schedule_sync_event_worker
run_after_commit do
Projects::SyncEvent.enqueue_worker
end
end
2022-07-16 23:28:13 +05:30
def check_project_export_limit!
return if Gitlab::CurrentSettings.current_application_settings.max_export_size == 0
if self.statistics.storage_size > Gitlab::CurrentSettings.current_application_settings.max_export_size.megabytes
raise ExportLimitExceeded, _('The project size exceeds the export limit.')
end
end
2022-07-23 23:45:48 +05:30
2022-10-11 01:57:18 +05:30
def remove_leading_spaces_on_name
name&.lstrip!
end
2022-07-23 23:45:48 +05:30
def set_package_registry_access_level
return if !project_feature || project_feature.package_registry_access_level_changed?
self.project_feature.package_registry_access_level = packages_enabled ? enabled_package_registry_access_level_by_project_visibility : ProjectFeature::DISABLED
end
def enabled_package_registry_access_level_by_project_visibility
case visibility_level
when PUBLIC
ProjectFeature::PUBLIC
when INTERNAL
ProjectFeature::ENABLED
else
ProjectFeature::PRIVATE
end
end
2023-04-23 21:23:45 +05:30
def update_new_emails_created_column
return if project_setting.nil?
return if project_setting.emails_enabled == !emails_disabled
if project_setting.persisted?
project_setting.update!(emails_enabled: !emails_disabled)
elsif project_setting
project_setting.emails_enabled = !emails_disabled
end
end
2023-05-27 22:25:52 +05:30
def runners_token_prefix
RunnersTokenPrefixable::RUNNERS_TOKEN_PREFIX
end
2014-09-02 18:07:02 +05:30
end
2019-12-04 20:38:33 +05:30
2021-06-08 01:23:25 +05:30
Project.prepend_mod_with('Project')