1# frozen_string_literal: true 2 3require 'carrierwave/orm/activerecord' 4 5class Project < ApplicationRecord 6 include Gitlab::ConfigHelper 7 include Gitlab::VisibilityLevel 8 include AccessRequestable 9 include Avatarable 10 include CacheMarkdownField 11 include Sortable 12 include AfterCommitQueue 13 include CaseSensitivity 14 include TokenAuthenticatable 15 include ValidAttribute 16 include ProjectAPICompatibility 17 include ProjectFeaturesCompatibility 18 include SelectForProjectAuthorization 19 include Presentable 20 include HasRepository 21 include HasWiki 22 include CanMoveRepositoryStorage 23 include Routable 24 include GroupDescendant 25 include Gitlab::SQL::Pattern 26 include DeploymentPlatform 27 include ::Gitlab::Utils::StrongMemoize 28 include ChronicDurationAttribute 29 include FastDestroyAll::Helpers 30 include WithUploads 31 include BatchDestroyDependentAssociations 32 include FeatureGate 33 include OptionallySearch 34 include FromUnion 35 include IgnorableColumns 36 include Repositories::CanHousekeepRepository 37 include EachBatch 38 include GitlabRoutingHelper 39 include BulkMemberAccessLoad 40 41 extend Gitlab::Cache::RequestCache 42 extend Gitlab::Utils::Override 43 44 extend Gitlab::ConfigHelper 45 46 ignore_columns :container_registry_enabled, remove_after: '2021-09-22', remove_with: '14.4' 47 48 BoardLimitExceeded = Class.new(StandardError) 49 50 ignore_columns :mirror_last_update_at, :mirror_last_successful_update_at, remove_after: '2021-09-22', remove_with: '14.4' 51 ignore_columns :pull_mirror_branch_prefix, remove_after: '2021-09-22', remove_with: '14.4' 52 53 STATISTICS_ATTRIBUTE = 'repositories_count' 54 UNKNOWN_IMPORT_URL = 'http://unknown.git' 55 # Hashed Storage versions handle rolling out new storage to project and dependents models: 56 # nil: legacy 57 # 1: repository 58 # 2: attachments 59 LATEST_STORAGE_VERSION = 2 60 HASHED_STORAGE_FEATURES = { 61 repository: 1, 62 attachments: 2 63 }.freeze 64 65 VALID_IMPORT_PORTS = [80, 443].freeze 66 VALID_IMPORT_PROTOCOLS = %w(http https git).freeze 67 68 VALID_MIRROR_PORTS = [22, 80, 443].freeze 69 VALID_MIRROR_PROTOCOLS = %w(http https ssh git).freeze 70 71 SORTING_PREFERENCE_FIELD = :projects_sort 72 MAX_BUILD_TIMEOUT = 1.month 73 74 GL_REPOSITORY_TYPES = [Gitlab::GlRepository::PROJECT, Gitlab::GlRepository::WIKI, Gitlab::GlRepository::DESIGN].freeze 75 76 cache_markdown_field :description, pipeline: :description 77 78 default_value_for :packages_enabled, true 79 default_value_for :archived, false 80 default_value_for :resolve_outdated_diff_discussions, false 81 default_value_for(:repository_storage) do 82 Repository.pick_storage_shard 83 end 84 85 default_value_for(:shared_runners_enabled) { Gitlab::CurrentSettings.shared_runners_enabled } 86 default_value_for :issues_enabled, gitlab_config_features.issues 87 default_value_for :merge_requests_enabled, gitlab_config_features.merge_requests 88 default_value_for :builds_enabled, gitlab_config_features.builds 89 default_value_for :wiki_enabled, gitlab_config_features.wiki 90 default_value_for :snippets_enabled, gitlab_config_features.snippets 91 default_value_for :only_allow_merge_if_all_discussions_are_resolved, false 92 default_value_for :remove_source_branch_after_merge, true 93 default_value_for :autoclose_referenced_issues, true 94 default_value_for(:ci_config_path) { Gitlab::CurrentSettings.default_ci_config_path } 95 96 add_authentication_token_field :runners_token, encrypted: -> { Feature.enabled?(:projects_tokens_optional_encryption, default_enabled: true) ? :optional : :required } 97 98 before_validation :mark_remote_mirrors_for_removal, if: -> { RemoteMirror.table_exists? } 99 100 before_save :ensure_runners_token 101 before_validation :ensure_project_namespace_in_sync 102 103 after_save :update_project_statistics, if: :saved_change_to_namespace_id? 104 105 after_save :schedule_sync_event_worker, if: -> { saved_change_to_id? || saved_change_to_namespace_id? } 106 107 after_save :create_import_state, if: ->(project) { project.import? && project.import_state.nil? } 108 109 after_save :save_topics 110 111 after_create -> { create_or_load_association(:project_feature) } 112 113 after_create -> { create_or_load_association(:ci_cd_settings) } 114 115 after_create -> { create_or_load_association(:container_expiration_policy) } 116 117 after_create -> { create_or_load_association(:pages_metadatum) } 118 119 after_create :set_timestamps_for_create 120 after_update :update_forks_visibility_level 121 122 before_destroy :remove_private_deploy_keys 123 124 use_fast_destroy :build_trace_chunks 125 126 after_destroy :remove_exports 127 128 after_validation :check_pending_delete 129 130 # Storage specific hooks 131 after_initialize :use_hashed_storage 132 after_create :check_repository_absence! 133 134 has_many :project_topics, -> { order(:id) }, class_name: 'Projects::ProjectTopic' 135 has_many :topics, through: :project_topics, class_name: 'Projects::Topic' 136 137 attr_accessor :old_path_with_namespace 138 attr_accessor :template_name 139 attr_writer :pipeline_status 140 attr_accessor :skip_disk_validation 141 attr_writer :topic_list 142 143 alias_attribute :title, :name 144 145 # Relations 146 belongs_to :pool_repository 147 belongs_to :creator, class_name: 'User' 148 belongs_to :group, -> { where(type: Group.sti_name) }, foreign_key: 'namespace_id' 149 belongs_to :namespace 150 # Sync deletion via DB Trigger to ensure we do not have 151 # a project without a project_namespace (or vice-versa) 152 belongs_to :project_namespace, autosave: true, class_name: 'Namespaces::ProjectNamespace', foreign_key: 'project_namespace_id' 153 alias_method :parent, :namespace 154 alias_attribute :parent_id, :namespace_id 155 156 has_one :last_event, -> {order 'events.created_at DESC'}, class_name: 'Event' 157 has_many :boards 158 159 def self.integration_association_name(name) 160 "#{name}_integration" 161 end 162 163 # Project integrations 164 has_one :asana_integration, class_name: 'Integrations::Asana' 165 has_one :assembla_integration, class_name: 'Integrations::Assembla' 166 has_one :bamboo_integration, class_name: 'Integrations::Bamboo' 167 has_one :bugzilla_integration, class_name: 'Integrations::Bugzilla' 168 has_one :buildkite_integration, class_name: 'Integrations::Buildkite' 169 has_one :campfire_integration, class_name: 'Integrations::Campfire' 170 has_one :confluence_integration, class_name: 'Integrations::Confluence' 171 has_one :custom_issue_tracker_integration, class_name: 'Integrations::CustomIssueTracker' 172 has_one :datadog_integration, class_name: 'Integrations::Datadog' 173 has_one :discord_integration, class_name: 'Integrations::Discord' 174 has_one :drone_ci_integration, class_name: 'Integrations::DroneCi' 175 has_one :emails_on_push_integration, class_name: 'Integrations::EmailsOnPush' 176 has_one :ewm_integration, class_name: 'Integrations::Ewm' 177 has_one :external_wiki_integration, class_name: 'Integrations::ExternalWiki' 178 has_one :flowdock_integration, class_name: 'Integrations::Flowdock' 179 has_one :hangouts_chat_integration, class_name: 'Integrations::HangoutsChat' 180 has_one :irker_integration, class_name: 'Integrations::Irker' 181 has_one :jenkins_integration, class_name: 'Integrations::Jenkins' 182 has_one :jira_integration, class_name: 'Integrations::Jira' 183 has_one :mattermost_integration, class_name: 'Integrations::Mattermost' 184 has_one :mattermost_slash_commands_integration, class_name: 'Integrations::MattermostSlashCommands' 185 has_one :microsoft_teams_integration, class_name: 'Integrations::MicrosoftTeams' 186 has_one :mock_ci_integration, class_name: 'Integrations::MockCi' 187 has_one :mock_monitoring_integration, class_name: 'Integrations::MockMonitoring' 188 has_one :packagist_integration, class_name: 'Integrations::Packagist' 189 has_one :pipelines_email_integration, class_name: 'Integrations::PipelinesEmail' 190 has_one :pivotaltracker_integration, class_name: 'Integrations::Pivotaltracker' 191 has_one :prometheus_integration, class_name: 'Integrations::Prometheus', inverse_of: :project 192 has_one :pushover_integration, class_name: 'Integrations::Pushover' 193 has_one :redmine_integration, class_name: 'Integrations::Redmine' 194 has_one :shimo_integration, class_name: 'Integrations::Shimo' 195 has_one :slack_integration, class_name: 'Integrations::Slack' 196 has_one :slack_slash_commands_integration, class_name: 'Integrations::SlackSlashCommands' 197 has_one :teamcity_integration, class_name: 'Integrations::Teamcity' 198 has_one :unify_circuit_integration, class_name: 'Integrations::UnifyCircuit' 199 has_one :webex_teams_integration, class_name: 'Integrations::WebexTeams' 200 has_one :youtrack_integration, class_name: 'Integrations::Youtrack' 201 has_one :zentao_integration, class_name: 'Integrations::Zentao' 202 203 has_one :root_of_fork_network, 204 foreign_key: 'root_project_id', 205 inverse_of: :root_project, 206 class_name: 'ForkNetwork' 207 has_one :fork_network_member 208 has_one :fork_network, through: :fork_network_member 209 has_one :forked_from_project, through: :fork_network_member 210 has_many :forked_to_members, class_name: 'ForkNetworkMember', foreign_key: 'forked_from_project_id' 211 has_many :forks, through: :forked_to_members, source: :project, inverse_of: :forked_from_project 212 has_many :fork_network_projects, through: :fork_network, source: :projects 213 214 # Packages 215 has_many :packages, class_name: 'Packages::Package' 216 has_many :package_files, through: :packages, class_name: 'Packages::PackageFile' 217 # debian_distributions and associated component_files must be destroyed by ruby code in order to properly remove carrierwave uploads 218 has_many :debian_distributions, class_name: 'Packages::Debian::ProjectDistribution', dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent 219 220 has_one :import_state, autosave: true, class_name: 'ProjectImportState', inverse_of: :project 221 has_one :import_export_upload, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent 222 has_many :export_jobs, class_name: 'ProjectExportJob' 223 has_many :bulk_import_exports, class_name: 'BulkImports::Export', inverse_of: :project 224 has_one :project_repository, inverse_of: :project 225 has_one :tracing_setting, class_name: 'ProjectTracingSetting' 226 has_one :incident_management_setting, inverse_of: :project, class_name: 'IncidentManagement::ProjectIncidentManagementSetting' 227 has_one :error_tracking_setting, inverse_of: :project, class_name: 'ErrorTracking::ProjectErrorTrackingSetting' 228 has_one :metrics_setting, inverse_of: :project, class_name: 'ProjectMetricsSetting' 229 has_one :grafana_integration, inverse_of: :project 230 has_one :project_setting, inverse_of: :project, autosave: true 231 has_one :alerting_setting, inverse_of: :project, class_name: 'Alerting::ProjectAlertingSetting' 232 has_one :service_desk_setting, class_name: 'ServiceDeskSetting' 233 234 # Merge requests for target project should be removed with it 235 has_many :merge_requests, foreign_key: 'target_project_id', inverse_of: :target_project 236 has_many :merge_request_metrics, foreign_key: 'target_project', class_name: 'MergeRequest::Metrics', inverse_of: :target_project 237 has_many :source_of_merge_requests, foreign_key: 'source_project_id', class_name: 'MergeRequest' 238 has_many :issues 239 has_many :labels, class_name: 'ProjectLabel' 240 has_many :integrations 241 has_many :events 242 has_many :milestones 243 has_many :iterations 244 245 # Projects with a very large number of notes may time out destroying them 246 # through the foreign key. Additionally, the deprecated attachment uploader 247 # for notes requires us to use dependent: :destroy to avoid orphaning uploaded 248 # files. 249 # 250 # https://gitlab.com/gitlab-org/gitlab/-/issues/207222 251 has_many :notes, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent 252 253 has_many :snippets, class_name: 'ProjectSnippet' 254 has_many :hooks, class_name: 'ProjectHook' 255 has_many :protected_branches 256 has_many :exported_protected_branches 257 has_many :protected_tags 258 has_many :repository_languages, -> { order "share DESC" } 259 has_many :designs, inverse_of: :project, class_name: 'DesignManagement::Design' 260 261 has_many :project_authorizations 262 has_many :authorized_users, through: :project_authorizations, source: :user, class_name: 'User' 263 has_many :project_members, -> { where(requested_at: nil) }, 264 as: :source, dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent 265 266 alias_method :members, :project_members 267 has_many :users, through: :project_members 268 269 has_many :requesters, -> { where.not(requested_at: nil) }, 270 as: :source, class_name: 'ProjectMember', dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent 271 has_many :members_and_requesters, as: :source, class_name: 'ProjectMember' 272 273 has_many :deploy_keys_projects, inverse_of: :project 274 has_many :deploy_keys, through: :deploy_keys_projects 275 has_many :users_star_projects 276 has_many :starrers, through: :users_star_projects, source: :user 277 has_many :releases 278 has_many :lfs_objects_projects 279 has_many :lfs_objects, -> { distinct }, through: :lfs_objects_projects 280 has_many :lfs_file_locks 281 has_many :project_group_links 282 has_many :invited_groups, through: :project_group_links, source: :group 283 has_many :todos 284 has_many :notification_settings, as: :source, dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent 285 286 has_many :internal_ids 287 288 has_one :import_data, class_name: 'ProjectImportData', inverse_of: :project, autosave: true 289 has_one :project_feature, inverse_of: :project 290 has_one :statistics, class_name: 'ProjectStatistics' 291 has_one :feature_usage, class_name: 'ProjectFeatureUsage' 292 293 has_one :cluster_project, class_name: 'Clusters::Project' 294 has_many :clusters, through: :cluster_project, class_name: 'Clusters::Cluster' 295 has_many :kubernetes_namespaces, class_name: 'Clusters::KubernetesNamespace' 296 has_many :management_clusters, class_name: 'Clusters::Cluster', foreign_key: :management_project_id, inverse_of: :management_project 297 has_many :cluster_agents, class_name: 'Clusters::Agent' 298 299 has_many :prometheus_metrics 300 has_many :prometheus_alerts, inverse_of: :project 301 has_many :prometheus_alert_events, inverse_of: :project 302 has_many :self_managed_prometheus_alert_events, inverse_of: :project 303 has_many :metrics_users_starred_dashboards, class_name: 'Metrics::UsersStarredDashboard', inverse_of: :project 304 305 has_many :alert_management_alerts, class_name: 'AlertManagement::Alert', inverse_of: :project 306 has_many :alert_management_http_integrations, class_name: 'AlertManagement::HttpIntegration', inverse_of: :project 307 308 # Container repositories need to remove data from the container registry, 309 # which is not managed by the DB. Hence we're still using dependent: :destroy 310 # here. 311 has_many :container_repositories, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent 312 has_one :container_expiration_policy, inverse_of: :project 313 314 has_many :commit_statuses 315 # The relation :all_pipelines is intended to be used when we want to get the 316 # whole list of pipelines associated to the project 317 has_many :all_pipelines, class_name: 'Ci::Pipeline', inverse_of: :project 318 # The relation :ci_pipelines includes all those that directly contribute to the 319 # latest status of a ref. This does not include dangling pipelines such as those 320 # from webide, child pipelines, etc. 321 has_many :ci_pipelines, 322 -> { ci_sources }, 323 class_name: 'Ci::Pipeline', 324 inverse_of: :project 325 has_many :stages, class_name: 'Ci::Stage', inverse_of: :project 326 has_many :ci_refs, class_name: 'Ci::Ref', inverse_of: :project 327 328 # Ci::Build objects store data on the file system such as artifact files and 329 # build traces. Currently there's no efficient way of removing this data in 330 # bulk that doesn't involve loading the rows into memory. As a result we're 331 # still using `dependent: :destroy` here. 332 has_many :pending_builds, class_name: 'Ci::PendingBuild' 333 has_many :builds, class_name: 'Ci::Build', inverse_of: :project, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent 334 has_many :processables, class_name: 'Ci::Processable', inverse_of: :project 335 has_many :build_trace_chunks, class_name: 'Ci::BuildTraceChunk', through: :builds, source: :trace_chunks 336 has_many :build_report_results, class_name: 'Ci::BuildReportResult', inverse_of: :project 337 has_many :job_artifacts, class_name: 'Ci::JobArtifact' 338 has_many :pipeline_artifacts, class_name: 'Ci::PipelineArtifact', inverse_of: :project 339 has_many :runner_projects, class_name: 'Ci::RunnerProject', inverse_of: :project 340 has_many :runners, through: :runner_projects, source: :runner, class_name: 'Ci::Runner' 341 has_many :variables, class_name: 'Ci::Variable' 342 has_many :triggers, class_name: 'Ci::Trigger' 343 has_many :environments 344 has_many :environments_for_dashboard, -> { from(with_rank.unfoldered.available, :environments).where('rank <= 3') }, class_name: 'Environment' 345 has_many :deployments 346 has_many :pipeline_schedules, class_name: 'Ci::PipelineSchedule' 347 has_many :project_deploy_tokens 348 has_many :deploy_tokens, through: :project_deploy_tokens 349 has_many :resource_groups, class_name: 'Ci::ResourceGroup', inverse_of: :project 350 has_many :freeze_periods, class_name: 'Ci::FreezePeriod', inverse_of: :project 351 352 has_one :auto_devops, class_name: 'ProjectAutoDevops', inverse_of: :project, autosave: true 353 has_many :custom_attributes, class_name: 'ProjectCustomAttribute' 354 355 has_many :project_badges, class_name: 'ProjectBadge' 356 has_one :ci_cd_settings, class_name: 'ProjectCiCdSetting', inverse_of: :project, autosave: true, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent 357 358 has_many :remote_mirrors, inverse_of: :project 359 has_many :cycle_analytics_stages, class_name: 'Analytics::CycleAnalytics::ProjectStage', inverse_of: :project 360 has_many :value_streams, class_name: 'Analytics::CycleAnalytics::ProjectValueStream', inverse_of: :project 361 362 has_many :external_pull_requests, inverse_of: :project 363 364 has_many :sourced_pipelines, class_name: 'Ci::Sources::Pipeline', foreign_key: :source_project_id 365 has_many :source_pipelines, class_name: 'Ci::Sources::Pipeline', foreign_key: :project_id 366 367 has_many :import_failures, inverse_of: :project 368 has_many :jira_imports, -> { order 'jira_imports.created_at' }, class_name: 'JiraImportState', inverse_of: :project 369 370 has_many :daily_build_group_report_results, class_name: 'Ci::DailyBuildGroupReportResult' 371 has_many :ci_feature_usages, class_name: 'Projects::CiFeatureUsage' 372 373 has_many :repository_storage_moves, class_name: 'Projects::RepositoryStorageMove', inverse_of: :container 374 375 has_many :webide_pipelines, -> { webide_source }, class_name: 'Ci::Pipeline', inverse_of: :project 376 has_many :reviews, inverse_of: :project 377 378 has_many :terraform_states, class_name: 'Terraform::State', inverse_of: :project 379 380 # GitLab Pages 381 has_many :pages_domains 382 has_one :pages_metadatum, class_name: 'ProjectPagesMetadatum', inverse_of: :project 383 # we need to clean up files, not only remove records 384 has_many :pages_deployments, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent 385 386 # Can be too many records. We need to implement delete_all in batches. 387 # Issue https://gitlab.com/gitlab-org/gitlab/-/issues/228637 388 has_many :product_analytics_events, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent 389 390 has_many :operations_feature_flags, class_name: 'Operations::FeatureFlag' 391 has_one :operations_feature_flags_client, class_name: 'Operations::FeatureFlagsClient' 392 has_many :operations_feature_flags_user_lists, class_name: 'Operations::FeatureFlags::UserList' 393 394 has_many :error_tracking_errors, inverse_of: :project, class_name: 'ErrorTracking::Error' 395 has_many :error_tracking_client_keys, inverse_of: :project, class_name: 'ErrorTracking::ClientKey' 396 397 has_many :timelogs 398 399 has_one :ci_project_mirror, class_name: 'Ci::ProjectMirror' 400 has_many :sync_events, class_name: 'Projects::SyncEvent' 401 402 accepts_nested_attributes_for :variables, allow_destroy: true 403 accepts_nested_attributes_for :project_feature, update_only: true 404 accepts_nested_attributes_for :project_setting, update_only: true 405 accepts_nested_attributes_for :import_data 406 accepts_nested_attributes_for :auto_devops, update_only: true 407 accepts_nested_attributes_for :ci_cd_settings, update_only: true 408 accepts_nested_attributes_for :container_expiration_policy, update_only: true 409 410 accepts_nested_attributes_for :remote_mirrors, 411 allow_destroy: true, 412 reject_if: ->(attrs) { attrs[:id].blank? && attrs[:url].blank? } 413 414 accepts_nested_attributes_for :tracing_setting, update_only: true, allow_destroy: true 415 accepts_nested_attributes_for :incident_management_setting, update_only: true 416 accepts_nested_attributes_for :error_tracking_setting, update_only: true 417 accepts_nested_attributes_for :metrics_setting, update_only: true, allow_destroy: true 418 accepts_nested_attributes_for :grafana_integration, update_only: true, allow_destroy: true 419 accepts_nested_attributes_for :prometheus_integration, update_only: true 420 accepts_nested_attributes_for :alerting_setting, update_only: true 421 422 delegate :feature_available?, :builds_enabled?, :wiki_enabled?, 423 :merge_requests_enabled?, :forking_enabled?, :issues_enabled?, 424 :pages_enabled?, :analytics_enabled?, :snippets_enabled?, :public_pages?, :private_pages?, 425 :merge_requests_access_level, :forking_access_level, :issues_access_level, 426 :wiki_access_level, :snippets_access_level, :builds_access_level, 427 :repository_access_level, :pages_access_level, :metrics_dashboard_access_level, :analytics_access_level, 428 :operations_enabled?, :operations_access_level, :security_and_compliance_access_level, 429 :container_registry_access_level, :container_registry_enabled?, 430 to: :project_feature, allow_nil: true 431 alias_method :container_registry_enabled, :container_registry_enabled? 432 delegate :show_default_award_emojis, :show_default_award_emojis=, :show_default_award_emojis?, 433 :warn_about_potentially_unwanted_characters, :warn_about_potentially_unwanted_characters=, :warn_about_potentially_unwanted_characters?, 434 to: :project_setting, allow_nil: true 435 delegate :scheduled?, :started?, :in_progress?, :failed?, :finished?, 436 prefix: :import, to: :import_state, allow_nil: true 437 delegate :squash_always?, :squash_never?, :squash_enabled_by_default?, :squash_readonly?, to: :project_setting 438 delegate :squash_option, :squash_option=, to: :project_setting 439 delegate :previous_default_branch, :previous_default_branch=, to: :project_setting 440 delegate :no_import?, to: :import_state, allow_nil: true 441 delegate :name, to: :owner, allow_nil: true, prefix: true 442 delegate :members, to: :team, prefix: true 443 delegate :add_user, :add_users, to: :team 444 delegate :add_guest, :add_reporter, :add_developer, :add_maintainer, :add_role, to: :team 445 delegate :group_runners_enabled, :group_runners_enabled=, to: :ci_cd_settings, allow_nil: true 446 delegate :root_ancestor, to: :namespace, allow_nil: true 447 delegate :last_pipeline, to: :commit, allow_nil: true 448 delegate :external_dashboard_url, to: :metrics_setting, allow_nil: true, prefix: true 449 delegate :dashboard_timezone, to: :metrics_setting, allow_nil: true, prefix: true 450 delegate :default_git_depth, :default_git_depth=, to: :ci_cd_settings, prefix: :ci, allow_nil: true 451 delegate :forward_deployment_enabled, :forward_deployment_enabled=, to: :ci_cd_settings, prefix: :ci, allow_nil: true 452 delegate :job_token_scope_enabled, :job_token_scope_enabled=, to: :ci_cd_settings, prefix: :ci, allow_nil: true 453 delegate :keep_latest_artifact, :keep_latest_artifact=, to: :ci_cd_settings, allow_nil: true 454 delegate :restrict_user_defined_variables, :restrict_user_defined_variables=, to: :ci_cd_settings, allow_nil: true 455 delegate :actual_limits, :actual_plan_name, to: :namespace, allow_nil: true 456 delegate :allow_merge_on_skipped_pipeline, :allow_merge_on_skipped_pipeline?, 457 :allow_merge_on_skipped_pipeline=, :has_confluence?, :has_shimo?, 458 to: :project_setting 459 delegate :active?, to: :prometheus_integration, allow_nil: true, prefix: true 460 delegate :merge_commit_template, :merge_commit_template=, to: :project_setting, allow_nil: true 461 delegate :squash_commit_template, :squash_commit_template=, to: :project_setting, allow_nil: true 462 463 delegate :log_jira_dvcs_integration_usage, :jira_dvcs_server_last_sync_at, :jira_dvcs_cloud_last_sync_at, to: :feature_usage 464 465 # Validations 466 validates :creator, presence: true, on: :create 467 validates :description, length: { maximum: 2000 }, allow_blank: true 468 validates :ci_config_path, 469 format: { without: %r{(\.{2}|\A/)}, 470 message: _('cannot include leading slash or directory traversal.') }, 471 length: { maximum: 255 }, 472 allow_blank: true 473 validates :name, 474 presence: true, 475 length: { maximum: 255 }, 476 format: { with: Gitlab::Regex.project_name_regex, 477 message: Gitlab::Regex.project_name_regex_message } 478 validates :path, 479 presence: true, 480 project_path: true, 481 length: { maximum: 255 } 482 483 validates :project_feature, presence: true 484 485 validates :namespace, presence: true 486 validates :project_namespace, presence: true, on: :create, if: -> { self.namespace && self.root_namespace.project_namespace_creation_enabled? } 487 validates :project_namespace, presence: true, on: :update, if: -> { self.project_namespace_id_changed?(to: nil) } 488 validates :name, uniqueness: { scope: :namespace_id } 489 validates :import_url, public_url: { schemes: ->(project) { project.persisted? ? VALID_MIRROR_PROTOCOLS : VALID_IMPORT_PROTOCOLS }, 490 ports: ->(project) { project.persisted? ? VALID_MIRROR_PORTS : VALID_IMPORT_PORTS }, 491 enforce_user: true }, if: [:external_import?, :import_url_changed?] 492 validates :star_count, numericality: { greater_than_or_equal_to: 0 } 493 validate :check_personal_projects_limit, on: :create 494 validate :check_repository_path_availability, on: :update, if: ->(project) { project.renamed? } 495 validate :visibility_level_allowed_by_group, if: :should_validate_visibility_level? 496 validate :visibility_level_allowed_as_fork, if: :should_validate_visibility_level? 497 validate :validate_pages_https_only, if: -> { changes.has_key?(:pages_https_only) } 498 validate :changing_shared_runners_enabled_is_allowed 499 validates :repository_storage, 500 presence: true, 501 inclusion: { in: ->(_object) { Gitlab.config.repositories.storages.keys } } 502 validates :variables, nested_attributes_duplicates: { scope: :environment_scope } 503 validates :bfg_object_map, file_size: { maximum: :max_attachment_size } 504 validates :max_artifacts_size, numericality: { only_integer: true, greater_than: 0, allow_nil: true } 505 validates :suggestion_commit_message, length: { maximum: 255 } 506 507 # Scopes 508 scope :pending_delete, -> { where(pending_delete: true) } 509 scope :without_deleted, -> { where(pending_delete: false) } 510 511 scope :with_storage_feature, ->(feature) do 512 where(arel_table[:storage_version].gteq(HASHED_STORAGE_FEATURES[feature])) 513 end 514 scope :without_storage_feature, ->(feature) do 515 where(arel_table[:storage_version].lt(HASHED_STORAGE_FEATURES[feature]) 516 .or(arel_table[:storage_version].eq(nil))) 517 end 518 scope :with_unmigrated_storage, -> do 519 where(arel_table[:storage_version].lt(LATEST_STORAGE_VERSION) 520 .or(arel_table[:storage_version].eq(nil))) 521 end 522 523 # last_activity_at is throttled every minute, but last_repository_updated_at is updated with every push 524 scope :sorted_by_activity, -> { reorder(Arel.sql("GREATEST(COALESCE(last_activity_at, '1970-01-01'), COALESCE(last_repository_updated_at, '1970-01-01')) DESC")) } 525 scope :sorted_by_stars_desc, -> { reorder(self.arel_table['star_count'].desc) } 526 scope :sorted_by_stars_asc, -> { reorder(self.arel_table['star_count'].asc) } 527 # Sometimes queries (e.g. using CTEs) require explicit disambiguation with table name 528 scope :projects_order_id_asc, -> { reorder(self.arel_table['id'].asc) } 529 scope :projects_order_id_desc, -> { reorder(self.arel_table['id'].desc) } 530 531 scope :sorted_by_similarity_desc, -> (search, include_in_select: false) do 532 order_expression = Gitlab::Database::SimilarityScore.build_expression(search: search, rules: [ 533 { column: arel_table["path"], multiplier: 1 }, 534 { column: arel_table["name"], multiplier: 0.7 }, 535 { column: arel_table["description"], multiplier: 0.2 } 536 ]) 537 538 order = Gitlab::Pagination::Keyset::Order.build([ 539 Gitlab::Pagination::Keyset::ColumnOrderDefinition.new( 540 attribute_name: 'similarity', 541 column_expression: order_expression, 542 order_expression: order_expression.desc, 543 order_direction: :desc, 544 distinct: false, 545 add_to_projections: true 546 ), 547 Gitlab::Pagination::Keyset::ColumnOrderDefinition.new( 548 attribute_name: 'id', 549 order_expression: Project.arel_table[:id].desc 550 ) 551 ]) 552 553 order.apply_cursor_conditions(reorder(order)) 554 end 555 556 scope :with_packages, -> { joins(:packages) } 557 scope :in_namespace, ->(namespace_ids) { where(namespace_id: namespace_ids) } 558 scope :personal, ->(user) { where(namespace_id: user.namespace_id) } 559 scope :joined, ->(user) { where.not(namespace_id: user.namespace_id) } 560 scope :starred_by, ->(user) { joins(:users_star_projects).where('users_star_projects.user_id': user.id) } 561 scope :visible_to_user, ->(user) { where(id: user.authorized_projects.select(:id).reorder(nil)) } 562 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)) } 563 scope :archived, -> { where(archived: true) } 564 scope :non_archived, -> { where(archived: false) } 565 scope :with_push, -> { joins(:events).merge(Event.pushed_action) } 566 scope :with_project_feature, -> { joins('LEFT JOIN project_features ON projects.id = project_features.project_id') } 567 scope :with_jira_dvcs_cloud, -> { joins(:feature_usage).merge(ProjectFeatureUsage.with_jira_dvcs_integration_enabled(cloud: true)) } 568 scope :with_jira_dvcs_server, -> { joins(:feature_usage).merge(ProjectFeatureUsage.with_jira_dvcs_integration_enabled(cloud: false)) } 569 scope :inc_routes, -> { includes(:route, namespace: :route) } 570 scope :with_statistics, -> { includes(:statistics) } 571 scope :with_namespace, -> { includes(:namespace) } 572 scope :with_import_state, -> { includes(:import_state) } 573 scope :include_project_feature, -> { includes(:project_feature) } 574 scope :include_integration, -> (integration_association_name) { includes(integration_association_name) } 575 scope :with_integration, -> (integration_class) { joins(:integrations).merge(integration_class.all) } 576 scope :with_active_integration, -> (integration_class) { with_integration(integration_class).merge(integration_class.active) } 577 scope :with_shared_runners, -> { where(shared_runners_enabled: true) } 578 scope :inside_path, ->(path) do 579 # We need routes alias rs for JOIN so it does not conflict with 580 # includes(:route) which we use in ProjectsFinder. 581 joins("INNER JOIN routes rs ON rs.source_id = projects.id AND rs.source_type = 'Project'") 582 .where('rs.path LIKE ?', "#{sanitize_sql_like(path)}/%") 583 end 584 585 scope :with_feature_enabled, ->(feature) { 586 with_project_feature.merge(ProjectFeature.with_feature_enabled(feature)) 587 } 588 589 scope :with_feature_access_level, ->(feature, level) { 590 with_project_feature.merge(ProjectFeature.with_feature_access_level(feature, level)) 591 } 592 593 # Picks projects which use the given programming language 594 scope :with_programming_language, ->(language_name) do 595 lang_id_query = ProgrammingLanguage 596 .with_name_case_insensitive(language_name) 597 .select(:id) 598 599 joins(:repository_languages) 600 .where(repository_languages: { programming_language_id: lang_id_query }) 601 end 602 603 scope :service_desk_enabled, -> { where(service_desk_enabled: true) } 604 scope :with_builds_enabled, -> { with_feature_enabled(:builds) } 605 scope :with_issues_enabled, -> { with_feature_enabled(:issues) } 606 scope :with_issues_available_for_user, ->(current_user) { with_feature_available_for_user(:issues, current_user) } 607 scope :with_merge_requests_available_for_user, ->(current_user) { with_feature_available_for_user(:merge_requests, current_user) } 608 scope :with_issues_or_mrs_available_for_user, -> (user) do 609 with_issues_available_for_user(user).or(with_merge_requests_available_for_user(user)) 610 end 611 scope :with_merge_requests_enabled, -> { with_feature_enabled(:merge_requests) } 612 scope :with_remote_mirrors, -> { joins(:remote_mirrors).where(remote_mirrors: { enabled: true }) } 613 scope :with_limit, -> (maximum) { limit(maximum) } 614 615 scope :with_group_runners_enabled, -> do 616 joins(:ci_cd_settings) 617 .where(project_ci_cd_settings: { group_runners_enabled: true }) 618 end 619 620 scope :with_pages_deployed, -> do 621 joins(:pages_metadatum).merge(ProjectPagesMetadatum.deployed) 622 end 623 624 scope :pages_metadata_not_migrated, -> do 625 left_outer_joins(:pages_metadatum) 626 .where(project_pages_metadata: { project_id: nil }) 627 end 628 629 scope :with_api_commit_entity_associations, -> { 630 preload(:project_feature, :route, namespace: [:route, :owner]) 631 } 632 633 scope :imported_from, -> (type) { where(import_type: type) } 634 scope :with_tracing_enabled, -> { joins(:tracing_setting) } 635 scope :with_enabled_error_tracking, -> { joins(:error_tracking_setting).where(project_error_tracking_settings: { enabled: true }) } 636 637 scope :with_service_desk_key, -> (key) do 638 # project_key is not indexed for now 639 # see https://gitlab.com/gitlab-org/gitlab/-/merge_requests/24063#note_282435524 for details 640 joins(:service_desk_setting).where('service_desk_settings.project_key' => key) 641 end 642 643 scope :with_topic, ->(topic_name) do 644 topic = Projects::Topic.find_by_name(topic_name) 645 646 topic ? where(id: topic.project_topics.select(:project_id)) : none 647 end 648 649 enum auto_cancel_pending_pipelines: { disabled: 0, enabled: 1 } 650 651 chronic_duration_attr :build_timeout_human_readable, :build_timeout, 652 default: 3600, error_message: _('Maximum job timeout has a value which could not be accepted') 653 654 validates :build_timeout, allow_nil: true, 655 numericality: { greater_than_or_equal_to: 10.minutes, 656 less_than: MAX_BUILD_TIMEOUT, 657 only_integer: true, 658 message: _('needs to be between 10 minutes and 1 month') } 659 660 # Used by Projects::CleanupService to hold a map of rewritten object IDs 661 mount_uploader :bfg_object_map, AttachmentUploader 662 663 def self.with_api_entity_associations 664 preload(:project_feature, :route, :topics, :group, :timelogs, namespace: [:route, :owner]) 665 end 666 667 def self.with_web_entity_associations 668 preload(:project_feature, :route, :creator, group: :parent, namespace: [:route, :owner]) 669 end 670 671 def self.eager_load_namespace_and_owner 672 includes(namespace: :owner) 673 end 674 675 # Returns a collection of projects that is either public or visible to the 676 # logged in user. 677 def self.public_or_visible_to_user(user = nil, min_access_level = nil) 678 min_access_level = nil if user&.can_read_all_resources? 679 680 return public_to_user unless user 681 682 if user.is_a?(DeployToken) 683 user.accessible_projects 684 else 685 where('EXISTS (?) OR projects.visibility_level IN (?)', 686 user.authorizations_for_projects(min_access_level: min_access_level), 687 Gitlab::VisibilityLevel.levels_for_user(user)) 688 end 689 end 690 691 def self.with_feature_available_for_user(feature, user) 692 with_project_feature.merge(ProjectFeature.with_feature_available_for_user(feature, user)) 693 end 694 695 def self.projects_user_can(projects, user, action) 696 projects = where(id: projects) 697 698 DeclarativePolicy.user_scope do 699 projects.select { |project| Ability.allowed?(user, action, project) } 700 end 701 end 702 703 # This scope returns projects where user has access to both the project and the feature. 704 def self.filter_by_feature_visibility(feature, user) 705 with_feature_available_for_user(feature, user) 706 .public_or_visible_to_user( 707 user, 708 ProjectFeature.required_minimum_access_level_for_private_project(feature) 709 ) 710 end 711 712 def self.wrap_with_cte(collection) 713 cte = Gitlab::SQL::CTE.new(:projects_cte, collection) 714 Project.with(cte.to_arel).from(cte.alias_to(Project.arel_table)) 715 end 716 717 scope :active, -> { joins(:issues, :notes, :merge_requests).order('issues.created_at, notes.created_at, merge_requests.created_at DESC') } 718 scope :abandoned, -> { where('projects.last_activity_at < ?', 6.months.ago) } 719 720 scope :excluding_project, ->(project) { where.not(id: project) } 721 722 # We require an alias to the project_mirror_data_table in order to use import_state in our queries 723 scope :joins_import_state, -> { joins("INNER JOIN project_mirror_data import_state ON import_state.project_id = projects.id") } 724 scope :for_group, -> (group) { where(group: group) } 725 scope :for_group_and_its_subgroups, ->(group) { where(namespace_id: group.self_and_descendants.select(:id)) } 726 727 class << self 728 # Searches for a list of projects based on the query given in `query`. 729 # 730 # On PostgreSQL this method uses "ILIKE" to perform a case-insensitive 731 # search. 732 # 733 # query - The search query as a String. 734 def search(query, include_namespace: false) 735 if include_namespace 736 joins(:route).fuzzy_search(query, [Route.arel_table[:path], Route.arel_table[:name], :description]) 737 else 738 fuzzy_search(query, [:path, :name, :description]) 739 end 740 end 741 742 def search_by_title(query) 743 non_archived.fuzzy_search(query, [:name]) 744 end 745 746 def visibility_levels 747 Gitlab::VisibilityLevel.options 748 end 749 750 def sort_by_attribute(method) 751 case method.to_s 752 when 'storage_size_desc' 753 # storage_size is a joined column so we need to 754 # pass a string to avoid AR adding the table name 755 reorder('project_statistics.storage_size DESC, projects.id DESC') 756 when 'latest_activity_desc' 757 reorder(self.arel_table['last_activity_at'].desc) 758 when 'latest_activity_asc' 759 reorder(self.arel_table['last_activity_at'].asc) 760 when 'stars_desc' 761 sorted_by_stars_desc 762 when 'stars_asc' 763 sorted_by_stars_asc 764 else 765 order_by(method) 766 end 767 end 768 769 def reference_pattern 770 %r{ 771 (?<!#{Gitlab::PathRegex::PATH_START_CHAR}) 772 ((?<namespace>#{Gitlab::PathRegex::FULL_NAMESPACE_FORMAT_REGEX})\/)? 773 (?<project>#{Gitlab::PathRegex::PROJECT_PATH_FORMAT_REGEX}) 774 }x 775 end 776 777 def reference_postfix 778 '>' 779 end 780 781 def reference_postfix_escaped 782 '>' 783 end 784 785 # Pattern used to extract `namespace/project>` project references from text. 786 # '>' or its escaped form ('>') are checked for because '>' is sometimes escaped 787 # when the reference comes from an external source. 788 def markdown_reference_pattern 789 @markdown_reference_pattern ||= 790 %r{ 791 #{reference_pattern} 792 (#{reference_postfix}|#{reference_postfix_escaped}) 793 }x 794 end 795 796 def trending 797 joins('INNER JOIN trending_projects ON projects.id = trending_projects.project_id') 798 .reorder('trending_projects.id ASC') 799 end 800 801 def cached_count 802 Rails.cache.fetch('total_project_count', expires_in: 5.minutes) do 803 Project.count 804 end 805 end 806 807 def group_ids 808 joins(:namespace).where(namespaces: { type: Group.sti_name }).select(:namespace_id) 809 end 810 811 # Returns ids of projects with issuables available for given user 812 # 813 # Used on queries to find milestones or labels which user can see 814 # For example: Milestone.where(project_id: ids_with_issuables_available_for(user)) 815 def ids_with_issuables_available_for(user) 816 with_issues_enabled = with_issues_available_for_user(user).select(:id) 817 with_merge_requests_enabled = with_merge_requests_available_for_user(user).select(:id) 818 819 from_union([with_issues_enabled, with_merge_requests_enabled]).select(:id) 820 end 821 822 def find_by_url(url) 823 uri = URI(url) 824 825 return unless uri.host == Gitlab.config.gitlab.host 826 827 match = Rails.application.routes.recognize_path(url) 828 829 return if match[:unmatched_route].present? 830 return if match[:namespace_id].blank? || match[:id].blank? 831 832 find_by_full_path(match.values_at(:namespace_id, :id).join("/")) 833 rescue ActionController::RoutingError, URI::InvalidURIError 834 nil 835 end 836 837 def without_integration(integration) 838 integrations = Integration 839 .select('1') 840 .where("#{Integration.table_name}.project_id = projects.id") 841 .where(type: integration.type) 842 843 Project 844 .where('NOT EXISTS (?)', integrations) 845 .where(pending_delete: false) 846 .where(archived: false) 847 end 848 end 849 850 def initialize(attributes = nil) 851 # We can't use default_value_for because the database has a default 852 # value of 0 for visibility_level. If someone attempts to create a 853 # private project, default_value_for will assume that the 854 # visibility_level hasn't changed and will use the application 855 # setting default, which could be internal or public. For projects 856 # inside a private group, those levels are invalid. 857 # 858 # To fix the problem, we assign the actual default in the application if 859 # no explicit visibility has been initialized. 860 attributes ||= {} 861 862 unless visibility_attribute_present?(attributes) 863 attributes[:visibility_level] = Gitlab::CurrentSettings.default_project_visibility 864 end 865 866 super 867 end 868 869 def parent_loaded? 870 association(:namespace).loaded? 871 end 872 873 def project_setting 874 super.presence || build_project_setting 875 end 876 877 def all_pipelines 878 if builds_enabled? 879 super 880 else 881 super.external 882 end 883 end 884 885 def ci_pipelines 886 if builds_enabled? 887 super 888 else 889 super.external 890 end 891 end 892 893 def active_webide_pipelines(user:) 894 webide_pipelines.running_or_pending.for_user(user) 895 end 896 897 def default_pipeline_lock 898 if keep_latest_artifacts_available? 899 return :artifacts_locked 900 end 901 902 :unlocked 903 end 904 905 def autoclose_referenced_issues 906 return true if super.nil? 907 908 super 909 end 910 911 def preload_protected_branches 912 preloader = ActiveRecord::Associations::Preloader.new 913 preloader.preload(self, protected_branches: [:push_access_levels, :merge_access_levels]) 914 end 915 916 # returns all ancestor-groups upto but excluding the given namespace 917 # when no namespace is given, all ancestors upto the top are returned 918 def ancestors_upto(top = nil, hierarchy_order: nil) 919 Gitlab::ObjectHierarchy.new(Group.where(id: namespace_id)) 920 .base_and_ancestors(upto: top, hierarchy_order: hierarchy_order) 921 end 922 923 def ancestors(hierarchy_order: nil) 924 if Feature.enabled?(:linear_project_ancestors, self, default_enabled: :yaml) 925 group&.self_and_ancestors(hierarchy_order: hierarchy_order) || Group.none 926 else 927 ancestors_upto(hierarchy_order: hierarchy_order) 928 end 929 end 930 931 def ancestors_upto_ids(...) 932 ancestors_upto(...).pluck(:id) 933 end 934 935 def emails_disabled? 936 strong_memoize(:emails_disabled) do 937 # disabling in the namespace overrides the project setting 938 super || namespace.emails_disabled? 939 end 940 end 941 942 override :lfs_enabled? 943 def lfs_enabled? 944 return namespace.lfs_enabled? if self[:lfs_enabled].nil? 945 946 self[:lfs_enabled] && Gitlab.config.lfs.enabled 947 end 948 949 alias_method :lfs_enabled, :lfs_enabled? 950 951 def auto_devops_enabled? 952 if auto_devops&.enabled.nil? 953 has_auto_devops_implicitly_enabled? 954 else 955 auto_devops.enabled? 956 end 957 end 958 959 def has_auto_devops_implicitly_enabled? 960 auto_devops_config = first_auto_devops_config 961 962 auto_devops_config[:scope] != :project && auto_devops_config[:status] 963 end 964 965 def has_auto_devops_implicitly_disabled? 966 auto_devops_config = first_auto_devops_config 967 968 auto_devops_config[:scope] != :project && !auto_devops_config[:status] 969 end 970 971 def has_packages?(package_type) 972 packages.where(package_type: package_type).exists? 973 end 974 975 def first_auto_devops_config 976 return namespace.first_auto_devops_config if auto_devops&.enabled.nil? 977 978 { scope: :project, status: auto_devops&.enabled || Feature.enabled?(:force_autodevops_on_by_default, self) } 979 end 980 981 def unlink_forks_upon_visibility_decrease_enabled? 982 Feature.enabled?(:unlink_fork_network_upon_visibility_decrease, self, default_enabled: true) 983 end 984 985 def context_commits_enabled? 986 Feature.enabled?(:context_commits, default_enabled: true) 987 end 988 989 # LFS and hashed repository storage are required for using Design Management. 990 def design_management_enabled? 991 lfs_enabled? && hashed_storage?(:repository) 992 end 993 994 def team 995 @team ||= ProjectTeam.new(self) 996 end 997 998 def repository 999 @repository ||= Gitlab::GlRepository::PROJECT.repository_for(self) 1000 end 1001 1002 def design_repository 1003 strong_memoize(:design_repository) do 1004 Gitlab::GlRepository::DESIGN.repository_for(self) 1005 end 1006 end 1007 1008 # Because we use default_value_for we need to be sure 1009 # packages_enabled= method does exist even if we rollback migration. 1010 # Otherwise many tests from spec/migrations will fail. 1011 def packages_enabled=(value) 1012 if has_attribute?(:packages_enabled) 1013 write_attribute(:packages_enabled, value) 1014 end 1015 end 1016 1017 def cleanup 1018 @repository = nil 1019 end 1020 1021 alias_method :reload_repository!, :cleanup 1022 1023 def container_registry_url 1024 if Gitlab.config.registry.enabled 1025 "#{Gitlab.config.registry.host_port}/#{full_path.downcase}" 1026 end 1027 end 1028 1029 def has_container_registry_tags? 1030 return @images if defined?(@images) 1031 1032 @images = container_repositories.to_a.any?(&:has_tags?) || 1033 has_root_container_repository_tags? 1034 end 1035 1036 # ref can't be HEAD, can only be branch/tag name 1037 def latest_successful_build_for_ref(job_name, ref = default_branch) 1038 return unless ref 1039 1040 latest_pipeline = ci_pipelines.latest_successful_for_ref(ref) 1041 return unless latest_pipeline 1042 1043 latest_pipeline.build_with_artifacts_in_self_and_descendants(job_name) 1044 end 1045 1046 def latest_successful_build_for_sha(job_name, sha) 1047 return unless sha 1048 1049 latest_pipeline = ci_pipelines.latest_successful_for_sha(sha) 1050 return unless latest_pipeline 1051 1052 latest_pipeline.build_with_artifacts_in_self_and_descendants(job_name) 1053 end 1054 1055 def latest_successful_build_for_ref!(job_name, ref = default_branch) 1056 latest_successful_build_for_ref(job_name, ref) || raise(ActiveRecord::RecordNotFound, "Couldn't find job #{job_name}") 1057 end 1058 1059 def latest_pipeline(ref = default_branch, sha = nil) 1060 ref = ref.presence || default_branch 1061 sha ||= commit(ref)&.sha 1062 return unless sha 1063 1064 ci_pipelines.newest_first(ref: ref, sha: sha).take 1065 end 1066 1067 def merge_base_commit(first_commit_id, second_commit_id) 1068 sha = repository.merge_base(first_commit_id, second_commit_id) 1069 commit_by(oid: sha) if sha 1070 end 1071 1072 def saved? 1073 id && persisted? 1074 end 1075 1076 def import_status 1077 import_state&.status || 'none' 1078 end 1079 1080 def jira_import_status 1081 latest_jira_import&.status || 'initial' 1082 end 1083 1084 def human_import_status_name 1085 import_state&.human_status_name || 'none' 1086 end 1087 1088 def add_import_job 1089 job_id = 1090 if forked? 1091 RepositoryForkWorker.perform_async(id) 1092 else 1093 RepositoryImportWorker.perform_async(self.id) 1094 end 1095 1096 log_import_activity(job_id) 1097 1098 job_id 1099 end 1100 1101 def log_import_activity(job_id, type: :import) 1102 job_type = type.to_s.capitalize 1103 1104 if job_id 1105 Gitlab::AppLogger.info("#{job_type} job scheduled for #{full_path} with job ID #{job_id}.") 1106 else 1107 Gitlab::AppLogger.error("#{job_type} job failed to create for #{full_path}.") 1108 end 1109 end 1110 1111 def reset_cache_and_import_attrs 1112 run_after_commit do 1113 ProjectCacheWorker.perform_async(self.id) 1114 end 1115 1116 import_state.update(last_error: nil) 1117 remove_import_data 1118 end 1119 1120 # This method is overridden in EE::Project model 1121 def remove_import_data 1122 import_data&.destroy 1123 end 1124 1125 def ci_config_path=(value) 1126 # Strip all leading slashes so that //foo -> foo 1127 super(value&.delete("\0")) 1128 end 1129 1130 def import_url=(value) 1131 if Gitlab::UrlSanitizer.valid?(value) 1132 import_url = Gitlab::UrlSanitizer.new(value) 1133 super(import_url.sanitized_url) 1134 1135 credentials = import_url.credentials.to_h.transform_values { |value| CGI.unescape(value.to_s) } 1136 create_or_update_import_data(credentials: credentials) 1137 else 1138 super(value) 1139 end 1140 end 1141 1142 def import_url 1143 if import_data && super.present? 1144 import_url = Gitlab::UrlSanitizer.new(super, credentials: import_data.credentials) 1145 import_url.full_url 1146 else 1147 super 1148 end 1149 rescue StandardError 1150 super 1151 end 1152 1153 def valid_import_url? 1154 valid?(:import_url) || errors.messages[:import_url].nil? 1155 end 1156 1157 def create_or_update_import_data(data: nil, credentials: nil) 1158 return if data.nil? && credentials.nil? 1159 1160 project_import_data = import_data || build_import_data 1161 1162 project_import_data.merge_data(data.to_h) 1163 project_import_data.merge_credentials(credentials.to_h) 1164 1165 project_import_data 1166 end 1167 1168 def import? 1169 external_import? || forked? || gitlab_project_import? || jira_import? || bare_repository_import? || gitlab_project_migration? 1170 end 1171 1172 def external_import? 1173 import_url.present? 1174 end 1175 1176 def safe_import_url 1177 Gitlab::UrlSanitizer.new(import_url).masked_url 1178 end 1179 1180 def bare_repository_import? 1181 import_type == 'bare_repository' 1182 end 1183 1184 def jira_import? 1185 import_type == 'jira' && latest_jira_import.present? 1186 end 1187 1188 def gitlab_project_import? 1189 import_type == 'gitlab_project' 1190 end 1191 1192 def gitlab_project_migration? 1193 import_type == 'gitlab_project_migration' 1194 end 1195 1196 def gitea_import? 1197 import_type == 'gitea' 1198 end 1199 1200 def github_import? 1201 import_type == 'github' 1202 end 1203 1204 def github_enterprise_import? 1205 github_import? && 1206 URI.parse(import_url).host != URI.parse(Octokit::Default::API_ENDPOINT).host 1207 end 1208 1209 def has_remote_mirror? 1210 remote_mirror_available? && remote_mirrors.enabled.exists? 1211 end 1212 1213 def updating_remote_mirror? 1214 remote_mirrors.enabled.started.exists? 1215 end 1216 1217 def update_remote_mirrors 1218 return unless remote_mirror_available? 1219 1220 remote_mirrors.enabled.each(&:sync) 1221 end 1222 1223 def mark_stuck_remote_mirrors_as_failed! 1224 remote_mirrors.stuck.update_all( 1225 update_status: :failed, 1226 last_error: _('The remote mirror took to long to complete.'), 1227 last_update_at: Time.current 1228 ) 1229 end 1230 1231 def mark_remote_mirrors_for_removal 1232 remote_mirrors.each(&:mark_for_delete_if_blank_url) 1233 end 1234 1235 def remote_mirror_available? 1236 remote_mirror_available_overridden || 1237 ::Gitlab::CurrentSettings.mirror_available 1238 end 1239 1240 def check_personal_projects_limit 1241 # Since this method is called as validation hook, `creator` might not be 1242 # present. Since the validation for that will fail, we can just return 1243 # early. 1244 return if !creator || creator.can_create_project? || 1245 namespace.kind == 'group' 1246 1247 limit = creator.projects_limit 1248 error = 1249 if limit == 0 1250 _('Personal project creation is not allowed. Please contact your administrator with questions') 1251 else 1252 _('Your project limit is %{limit} projects! Please contact your administrator to increase it') 1253 end 1254 1255 self.errors.add(:limit_reached, error % { limit: limit }) 1256 end 1257 1258 def should_validate_visibility_level? 1259 new_record? || changes.has_key?(:visibility_level) 1260 end 1261 1262 def visibility_level_allowed_by_group 1263 return if visibility_level_allowed_by_group? 1264 1265 level_name = Gitlab::VisibilityLevel.level_name(self.visibility_level).downcase 1266 group_level_name = Gitlab::VisibilityLevel.level_name(self.group.visibility_level).downcase 1267 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 }) 1268 end 1269 1270 def visibility_level_allowed_as_fork 1271 return if visibility_level_allowed_as_fork? 1272 1273 level_name = Gitlab::VisibilityLevel.level_name(self.visibility_level).downcase 1274 self.errors.add(:visibility_level, _("%{level_name} is not allowed since the fork source project has lower visibility.") % { level_name: level_name }) 1275 end 1276 1277 def pages_https_only 1278 return false unless Gitlab.config.pages.external_https 1279 1280 super 1281 end 1282 1283 def pages_https_only? 1284 return false unless Gitlab.config.pages.external_https 1285 1286 super 1287 end 1288 1289 def validate_pages_https_only 1290 return unless pages_https_only? 1291 1292 unless pages_domains.all?(&:https?) 1293 errors.add(:pages_https_only, _("cannot be enabled unless all domains have TLS certificates")) 1294 end 1295 end 1296 1297 def changing_shared_runners_enabled_is_allowed 1298 return unless new_record? || changes.has_key?(:shared_runners_enabled) 1299 1300 if shared_runners_setting_conflicting_with_group? 1301 errors.add(:shared_runners_enabled, _('cannot be enabled because parent group does not allow it')) 1302 end 1303 end 1304 1305 def shared_runners_setting_conflicting_with_group? 1306 shared_runners_enabled && group&.shared_runners_setting == Namespace::SR_DISABLED_AND_UNOVERRIDABLE 1307 end 1308 1309 def reconcile_shared_runners_setting! 1310 if shared_runners_setting_conflicting_with_group? 1311 self.shared_runners_enabled = false 1312 end 1313 end 1314 1315 def to_param 1316 if persisted? && errors.include?(:path) 1317 path_was 1318 else 1319 path 1320 end 1321 end 1322 1323 # Produce a valid reference (see Referable#to_reference) 1324 # 1325 # NB: For projects, all references are 'full' - i.e. they all include the 1326 # full_path, rather than just the project name. For this reason, we ignore 1327 # the value of `full:` passed to this method, which is part of the Referable 1328 # interface. 1329 def to_reference(from = nil, full: false) 1330 base = to_reference_base(from, full: true) 1331 "#{base}#{self.class.reference_postfix}" 1332 end 1333 1334 # `from` argument can be a Namespace or Project. 1335 def to_reference_base(from = nil, full: false) 1336 if full || cross_namespace_reference?(from) 1337 full_path 1338 elsif cross_project_reference?(from) 1339 path 1340 end 1341 end 1342 1343 def to_human_reference(from = nil) 1344 if cross_namespace_reference?(from) 1345 name_with_namespace 1346 elsif cross_project_reference?(from) 1347 name 1348 end 1349 end 1350 1351 def readme_url 1352 readme_path = repository.readme_path 1353 if readme_path 1354 Gitlab::Routing.url_helpers.project_blob_url(self, File.join(default_branch, readme_path)) 1355 end 1356 end 1357 1358 def new_issuable_address(author, address_type) 1359 return unless Gitlab::IncomingEmail.supports_issue_creation? && author 1360 1361 # check since this can come from a request parameter 1362 return unless %w(issue merge_request).include?(address_type) 1363 1364 author.ensure_incoming_email_token! 1365 1366 suffix = address_type.dasherize 1367 1368 # example: incoming+h5bp-html5-boilerplate-8-1234567890abcdef123456789-issue@localhost.com 1369 # example: incoming+h5bp-html5-boilerplate-8-1234567890abcdef123456789-merge-request@localhost.com 1370 Gitlab::IncomingEmail.reply_address("#{full_path_slug}-#{project_id}-#{author.incoming_email_token}-#{suffix}") 1371 end 1372 1373 def build_commit_note(commit) 1374 notes.new(commit_id: commit.id, noteable_type: 'Commit') 1375 end 1376 1377 def last_activity 1378 last_event 1379 end 1380 1381 def last_activity_date 1382 [last_activity_at, last_repository_updated_at, updated_at].compact.max 1383 end 1384 1385 def project_id 1386 self.id 1387 end 1388 1389 def get_issue(issue_id, current_user) 1390 issue = IssuesFinder.new(current_user, project_id: id).find_by(iid: issue_id) if issues_enabled? 1391 1392 if issue 1393 issue 1394 elsif external_issue_tracker 1395 ExternalIssue.new(issue_id, self) 1396 end 1397 end 1398 1399 def issue_exists?(issue_id) 1400 get_issue(issue_id) 1401 end 1402 1403 def external_issue_reference_pattern 1404 external_issue_tracker.class.reference_pattern(only_long: issues_enabled?) 1405 end 1406 1407 def default_issues_tracker? 1408 !external_issue_tracker 1409 end 1410 1411 def external_issue_tracker 1412 cache_has_external_issue_tracker if has_external_issue_tracker.nil? 1413 1414 return unless has_external_issue_tracker? 1415 1416 @external_issue_tracker ||= integrations.external_issue_trackers.first 1417 end 1418 1419 def external_references_supported? 1420 external_issue_tracker&.support_cross_reference? 1421 end 1422 1423 def has_wiki? 1424 wiki_enabled? || has_external_wiki? 1425 end 1426 1427 def external_wiki 1428 cache_has_external_wiki if has_external_wiki.nil? 1429 1430 return unless has_external_wiki? 1431 1432 @external_wiki ||= integrations.external_wikis.first 1433 end 1434 1435 def find_or_initialize_integrations 1436 Integration 1437 .available_integration_names 1438 .difference(disabled_integrations) 1439 .map { find_or_initialize_integration(_1) } 1440 .sort_by(&:title) 1441 end 1442 1443 def disabled_integrations 1444 disabled_integrations = [] 1445 disabled_integrations << 'shimo' unless Feature.enabled?(:shimo_integration, self) 1446 disabled_integrations 1447 end 1448 1449 def find_or_initialize_integration(name) 1450 return if disabled_integrations.include?(name) 1451 1452 find_integration(integrations, name) || build_from_instance(name) || build_integration(name) 1453 end 1454 1455 # rubocop: disable CodeReuse/ServiceClass 1456 def create_labels 1457 Label.templates.each do |label| 1458 params = label.attributes.except('id', 'template', 'created_at', 'updated_at', 'type') 1459 Labels::FindOrCreateService.new(nil, self, params).execute(skip_authorization: true) 1460 end 1461 end 1462 # rubocop: enable CodeReuse/ServiceClass 1463 1464 def ci_integrations 1465 integrations.where(category: :ci) 1466 end 1467 1468 def ci_integration 1469 @ci_integration ||= ci_integrations.reorder(nil).find_by(active: true) 1470 end 1471 1472 def avatar_in_git 1473 repository.avatar 1474 end 1475 1476 def avatar_url(**args) 1477 Gitlab::Routing.url_helpers.project_avatar_url(self) if avatar_in_git 1478 end 1479 1480 # For compatibility with old code 1481 def code 1482 path 1483 end 1484 1485 def all_clusters 1486 group_clusters = Clusters::Cluster.joins(:groups).where(cluster_groups: { group_id: ancestors_upto } ) 1487 instance_clusters = Clusters::Cluster.instance_type 1488 1489 Clusters::Cluster.from_union([clusters, group_clusters, instance_clusters]) 1490 end 1491 1492 def items_for(entity) 1493 case entity 1494 when 'issue' then 1495 issues 1496 when 'merge_request' then 1497 merge_requests 1498 end 1499 end 1500 1501 # rubocop: disable CodeReuse/ServiceClass 1502 def send_move_instructions(old_path_with_namespace) 1503 # New project path needs to be committed to the DB or notification will 1504 # retrieve stale information 1505 run_after_commit do 1506 NotificationService.new.project_was_moved(self, old_path_with_namespace) 1507 end 1508 end 1509 # rubocop: enable CodeReuse/ServiceClass 1510 1511 def owner 1512 group || namespace.try(:owner) 1513 end 1514 1515 def default_owner 1516 obj = owner 1517 1518 if obj.respond_to?(:default_owner) 1519 obj.default_owner 1520 else 1521 obj 1522 end 1523 end 1524 1525 # rubocop: disable CodeReuse/ServiceClass 1526 def execute_hooks(data, hooks_scope = :push_hooks) 1527 run_after_commit_or_now do 1528 hooks.hooks_for(hooks_scope).select_active(hooks_scope, data).each do |hook| 1529 hook.async_execute(data, hooks_scope.to_s) 1530 end 1531 SystemHooksService.new.execute_hooks(data, hooks_scope) 1532 end 1533 end 1534 # rubocop: enable CodeReuse/ServiceClass 1535 1536 def execute_integrations(data, hooks_scope = :push_hooks) 1537 # Call only service hooks that are active for this scope 1538 run_after_commit_or_now do 1539 integrations.public_send(hooks_scope).each do |integration| # rubocop:disable GitlabSecurity/PublicSend 1540 integration.async_execute(data) 1541 end 1542 end 1543 end 1544 1545 def has_active_hooks?(hooks_scope = :push_hooks) 1546 hooks.hooks_for(hooks_scope).any? || SystemHook.hooks_for(hooks_scope).any? || Gitlab::FileHook.any? 1547 end 1548 1549 def has_active_integrations?(hooks_scope = :push_hooks) 1550 integrations.public_send(hooks_scope).any? # rubocop:disable GitlabSecurity/PublicSend 1551 end 1552 1553 def feature_usage 1554 super.presence || build_feature_usage 1555 end 1556 1557 def forked? 1558 fork_network && fork_network.root_project != self 1559 end 1560 1561 def fork_source 1562 return unless forked? 1563 1564 forked_from_project || fork_network&.root_project 1565 end 1566 1567 def lfs_objects_for_repository_types(*types) 1568 LfsObject 1569 .joins(:lfs_objects_projects) 1570 .where(lfs_objects_projects: { project: self, repository_type: types }) 1571 end 1572 1573 def lfs_objects_oids(oids: []) 1574 oids(lfs_objects, oids: oids) 1575 end 1576 1577 def lfs_objects_oids_from_fork_source(oids: []) 1578 return [] unless forked? 1579 1580 oids(fork_source.lfs_objects, oids: oids) 1581 end 1582 1583 def personal? 1584 !group 1585 end 1586 1587 # Expires various caches before a project is renamed. 1588 def expire_caches_before_rename(old_path) 1589 project_repo = Repository.new(old_path, self, shard: repository_storage) 1590 wiki_repo = Repository.new("#{old_path}#{Gitlab::GlRepository::WIKI.path_suffix}", self, shard: repository_storage, repo_type: Gitlab::GlRepository::WIKI) 1591 design_repo = Repository.new("#{old_path}#{Gitlab::GlRepository::DESIGN.path_suffix}", self, shard: repository_storage, repo_type: Gitlab::GlRepository::DESIGN) 1592 1593 [project_repo, wiki_repo, design_repo].each do |repo| 1594 repo.before_delete if repo.exists? 1595 end 1596 end 1597 1598 # Check if repository already exists on disk 1599 def check_repository_path_availability 1600 return true if skip_disk_validation 1601 return false unless repository_storage 1602 1603 # Check if repository with same path already exists on disk we can 1604 # skip this for the hashed storage because the path does not change 1605 if legacy_storage? && repository_with_same_path_already_exists? 1606 errors.add(:base, _('There is already a repository with that name on disk')) 1607 return false 1608 end 1609 1610 true 1611 rescue GRPC::Internal # if the path is too long 1612 false 1613 end 1614 1615 def track_project_repository 1616 repository = project_repository || build_project_repository 1617 repository.update!(shard_name: repository_storage, disk_path: disk_path) 1618 end 1619 1620 def create_repository(force: false) 1621 # Forked import is handled asynchronously 1622 return if forked? && !force 1623 1624 repository.create_repository 1625 repository.after_create 1626 1627 true 1628 rescue StandardError => err 1629 Gitlab::ErrorTracking.track_exception(err, project: { id: id, full_path: full_path, disk_path: disk_path }) 1630 errors.add(:base, _('Failed to create repository')) 1631 false 1632 end 1633 1634 def hook_attrs(backward: true) 1635 attrs = { 1636 id: id, 1637 name: name, 1638 description: description, 1639 web_url: web_url, 1640 avatar_url: avatar_url(only_path: false), 1641 git_ssh_url: ssh_url_to_repo, 1642 git_http_url: http_url_to_repo, 1643 namespace: namespace.name, 1644 visibility_level: visibility_level, 1645 path_with_namespace: full_path, 1646 default_branch: default_branch, 1647 ci_config_path: ci_config_path 1648 } 1649 1650 # Backward compatibility 1651 if backward 1652 attrs.merge!({ 1653 homepage: web_url, 1654 url: url_to_repo, 1655 ssh_url: ssh_url_to_repo, 1656 http_url: http_url_to_repo 1657 }) 1658 end 1659 1660 attrs 1661 end 1662 1663 def project_member(user) 1664 if project_members.loaded? 1665 project_members.find { |member| member.user_id == user.id } 1666 else 1667 project_members.find_by(user_id: user) 1668 end 1669 end 1670 1671 def membership_locked? 1672 false 1673 end 1674 1675 def bots 1676 users.project_bot 1677 end 1678 1679 # Filters `users` to return only authorized users of the project 1680 def members_among(users) 1681 if users.is_a?(ActiveRecord::Relation) && !users.loaded? 1682 authorized_users.merge(users) 1683 else 1684 return [] if users.empty? 1685 1686 user_ids = authorized_users.where(users: { id: users.map(&:id) }).pluck(:id) 1687 users.select { |user| user_ids.include?(user.id) } 1688 end 1689 end 1690 1691 def visibility_level_field 1692 :visibility_level 1693 end 1694 1695 override :after_repository_change_head 1696 def after_repository_change_head 1697 ProjectCacheWorker.perform_async(self.id, [], [:commit_count]) 1698 1699 super 1700 end 1701 1702 def forked_from?(other_project) 1703 forked? && forked_from_project == other_project 1704 end 1705 1706 def in_fork_network_of?(other_project) 1707 return false if fork_network.nil? || other_project.fork_network.nil? 1708 1709 fork_network == other_project.fork_network 1710 end 1711 1712 def origin_merge_requests 1713 merge_requests.where(source_project_id: self.id) 1714 end 1715 1716 def ensure_repository 1717 create_repository(force: true) unless repository_exists? 1718 end 1719 1720 # update visibility_level of forks 1721 def update_forks_visibility_level 1722 return if unlink_forks_upon_visibility_decrease_enabled? 1723 return unless visibility_level < visibility_level_before_last_save 1724 1725 forks.each do |forked_project| 1726 if forked_project.visibility_level > visibility_level 1727 forked_project.visibility_level = visibility_level 1728 forked_project.save! 1729 end 1730 end 1731 end 1732 1733 def allowed_to_share_with_group? 1734 !namespace.share_with_group_lock 1735 end 1736 1737 def latest_successful_pipeline_for_default_branch 1738 if defined?(@latest_successful_pipeline_for_default_branch) 1739 return @latest_successful_pipeline_for_default_branch 1740 end 1741 1742 @latest_successful_pipeline_for_default_branch = 1743 ci_pipelines.latest_successful_for_ref(default_branch) 1744 end 1745 1746 def latest_successful_pipeline_for(ref = nil) 1747 if ref && ref != default_branch 1748 ci_pipelines.latest_successful_for_ref(ref) 1749 else 1750 latest_successful_pipeline_for_default_branch 1751 end 1752 end 1753 1754 def enable_ci 1755 project_feature.update_attribute(:builds_access_level, ProjectFeature::ENABLED) 1756 end 1757 1758 def shared_runners_available? 1759 shared_runners_enabled? 1760 end 1761 1762 def shared_runners 1763 @shared_runners ||= shared_runners_enabled? ? Ci::Runner.instance_type : Ci::Runner.none 1764 end 1765 1766 def available_shared_runners 1767 @available_shared_runners ||= shared_runners_available? ? shared_runners : Ci::Runner.none 1768 end 1769 1770 def group_runners 1771 @group_runners ||= group_runners_enabled? ? Ci::Runner.belonging_to_parent_group_of_project(self.id) : Ci::Runner.none 1772 end 1773 1774 def all_runners 1775 Ci::Runner.from_union([runners, group_runners, shared_runners]) 1776 .allow_cross_joins_across_databases(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/339937') 1777 end 1778 1779 def all_available_runners 1780 Ci::Runner.from_union([runners, group_runners, available_shared_runners]) 1781 .allow_cross_joins_across_databases(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/339937') 1782 end 1783 1784 # Once issue 339937 is fixed, please search for all mentioned of 1785 # https://gitlab.com/gitlab-org/gitlab/-/issues/339937, 1786 # and remove the allow_cross_joins_across_databases. 1787 def active_runners 1788 strong_memoize(:active_runners) do 1789 all_available_runners.active 1790 end 1791 end 1792 1793 def any_online_runners?(&block) 1794 ::Gitlab::Database.allow_cross_joins_across_databases(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/339937') do 1795 online_runners_with_tags.any?(&block) 1796 end 1797 end 1798 1799 def valid_runners_token?(token) 1800 self.runners_token && ActiveSupport::SecurityUtils.secure_compare(token, self.runners_token) 1801 end 1802 1803 # rubocop: disable CodeReuse/ServiceClass 1804 def open_issues_count(current_user = nil) 1805 return Projects::OpenIssuesCountService.new(self, current_user).count unless current_user.nil? 1806 1807 BatchLoader.for(self).batch do |projects, loader| 1808 issues_count_per_project = ::Projects::BatchOpenIssuesCountService.new(projects).refresh_cache_and_retrieve_data 1809 1810 issues_count_per_project.each do |project, count| 1811 loader.call(project, count) 1812 end 1813 end 1814 end 1815 # rubocop: enable CodeReuse/ServiceClass 1816 1817 # rubocop: disable CodeReuse/ServiceClass 1818 def open_merge_requests_count(_current_user = nil) 1819 Projects::OpenMergeRequestsCountService.new(self).count 1820 end 1821 # rubocop: enable CodeReuse/ServiceClass 1822 1823 def visibility_level_allowed_as_fork?(level = self.visibility_level) 1824 return true unless forked? 1825 1826 original_project = fork_source 1827 return true unless original_project 1828 1829 level <= original_project.visibility_level 1830 end 1831 1832 def visibility_level_allowed_by_group?(level = self.visibility_level) 1833 return true unless group 1834 1835 level <= group.visibility_level 1836 end 1837 1838 def visibility_level_allowed?(level = self.visibility_level) 1839 visibility_level_allowed_as_fork?(level) && visibility_level_allowed_by_group?(level) 1840 end 1841 1842 def runners_token 1843 ensure_runners_token! 1844 end 1845 1846 def pages_deployed? 1847 pages_metadatum&.deployed? 1848 end 1849 1850 def pages_group_url 1851 # The host in URL always needs to be downcased 1852 Gitlab.config.pages.url.sub(%r{^https?://}) do |prefix| 1853 "#{prefix}#{pages_subdomain}." 1854 end.downcase 1855 end 1856 1857 def pages_url 1858 url = pages_group_url 1859 url_path = full_path.partition('/').last 1860 1861 # If the project path is the same as host, we serve it as group page 1862 return url if url == "#{Settings.pages.protocol}://#{url_path}".downcase 1863 1864 "#{url}/#{url_path}" 1865 end 1866 1867 def pages_group_root? 1868 pages_group_url == pages_url 1869 end 1870 1871 def pages_subdomain 1872 full_path.partition('/').first 1873 end 1874 1875 def pages_path 1876 # TODO: when we migrate Pages to work with new storage types, change here to use disk_path 1877 File.join(Settings.pages.path, full_path) 1878 end 1879 1880 def pages_available? 1881 Gitlab.config.pages.enabled 1882 end 1883 1884 def remove_private_deploy_keys 1885 exclude_keys_linked_to_other_projects = <<-SQL 1886 NOT EXISTS ( 1887 SELECT 1 1888 FROM deploy_keys_projects dkp2 1889 WHERE dkp2.deploy_key_id = deploy_keys_projects.deploy_key_id 1890 AND dkp2.project_id != deploy_keys_projects.project_id 1891 ) 1892 SQL 1893 1894 deploy_keys.where(public: false) 1895 .where(exclude_keys_linked_to_other_projects) 1896 .delete_all 1897 end 1898 1899 def mark_pages_as_deployed(artifacts_archive: nil) 1900 ensure_pages_metadatum.update!(deployed: true, artifacts_archive: artifacts_archive) 1901 end 1902 1903 def mark_pages_as_not_deployed 1904 ensure_pages_metadatum.update!(deployed: false, artifacts_archive: nil, pages_deployment: nil) 1905 end 1906 1907 def update_pages_deployment!(deployment) 1908 ensure_pages_metadatum.update!(pages_deployment: deployment) 1909 end 1910 1911 def set_first_pages_deployment!(deployment) 1912 ensure_pages_metadatum 1913 1914 # where().update_all to perform update in the single transaction with check for null 1915 ProjectPagesMetadatum 1916 .where(project_id: id, pages_deployment_id: nil) 1917 .update_all(deployed: deployment.present?, pages_deployment_id: deployment&.id) 1918 end 1919 1920 def set_full_path(gl_full_path: full_path) 1921 # We'd need to keep track of project full path otherwise directory tree 1922 # created with hashed storage enabled cannot be usefully imported using 1923 # the import rake task. 1924 repository.raw_repository.set_full_path(full_path: gl_full_path) 1925 rescue Gitlab::Git::Repository::NoRepository => e 1926 Gitlab::AppLogger.error("Error writing to .git/config for project #{full_path} (#{id}): #{e.message}.") 1927 nil 1928 end 1929 1930 def after_import 1931 repository.expire_content_cache 1932 wiki.repository.expire_content_cache 1933 1934 DetectRepositoryLanguagesWorker.perform_async(id) 1935 ProjectCacheWorker.perform_async(self.id, [], [:repository_size]) 1936 AuthorizedProjectUpdate::ProjectRecalculateWorker.perform_async(id) 1937 1938 # The import assigns iid values on its own, e.g. by re-using GitHub ids. 1939 # Flush existing InternalId records for this project for consistency reasons. 1940 # Those records are going to be recreated with the next normal creation 1941 # of a model instance (e.g. an Issue). 1942 InternalId.flush_records!(project: self) 1943 1944 import_state.finish 1945 update_project_counter_caches 1946 after_create_default_branch 1947 join_pool_repository 1948 refresh_markdown_cache! 1949 set_full_path 1950 end 1951 1952 def update_project_counter_caches 1953 classes = [ 1954 Projects::OpenIssuesCountService, 1955 Projects::OpenMergeRequestsCountService 1956 ] 1957 1958 classes.each do |klass| 1959 klass.new(self).refresh_cache 1960 end 1961 end 1962 1963 # rubocop: disable CodeReuse/ServiceClass 1964 def after_create_default_branch 1965 Projects::ProtectDefaultBranchService.new(self).execute 1966 end 1967 # rubocop: enable CodeReuse/ServiceClass 1968 1969 # Lazy loading of the `pipeline_status` attribute 1970 def pipeline_status 1971 @pipeline_status ||= Gitlab::Cache::Ci::ProjectPipelineStatus.load_for_project(self) 1972 end 1973 1974 def add_export_job(current_user:, after_export_strategy: nil, params: {}) 1975 job_id = ProjectExportWorker.perform_async(current_user.id, self.id, after_export_strategy, params) 1976 1977 if job_id 1978 Gitlab::AppLogger.info "Export job started for project ID #{self.id} with job ID #{job_id}" 1979 else 1980 Gitlab::AppLogger.error "Export job failed to start for project ID #{self.id}" 1981 end 1982 end 1983 1984 def import_export_shared 1985 @import_export_shared ||= Gitlab::ImportExport::Shared.new(self) 1986 end 1987 1988 def export_path 1989 return unless namespace.present? || hashed_storage?(:repository) 1990 1991 import_export_shared.archive_path 1992 end 1993 1994 def export_status 1995 if regeneration_in_progress? 1996 :regeneration_in_progress 1997 elsif export_enqueued? 1998 :queued 1999 elsif export_in_progress? 2000 :started 2001 elsif export_file_exists? 2002 :finished 2003 else 2004 :none 2005 end 2006 end 2007 2008 def export_in_progress? 2009 strong_memoize(:export_in_progress) do 2010 ::Projects::ExportJobFinder.new(self, { status: :started }).execute.present? 2011 end 2012 end 2013 2014 def export_enqueued? 2015 strong_memoize(:export_enqueued) do 2016 ::Projects::ExportJobFinder.new(self, { status: :queued }).execute.present? 2017 end 2018 end 2019 2020 def regeneration_in_progress? 2021 (export_enqueued? || export_in_progress?) && export_file_exists? 2022 end 2023 2024 def remove_exports 2025 return unless export_file_exists? 2026 2027 import_export_upload.remove_export_file! 2028 import_export_upload.save unless import_export_upload.destroyed? 2029 end 2030 2031 def export_file_exists? 2032 import_export_upload&.export_file_exists? 2033 end 2034 2035 def export_archive_exists? 2036 import_export_upload&.export_archive_exists? 2037 end 2038 2039 def export_file 2040 import_export_upload&.export_file 2041 end 2042 2043 def full_path_slug 2044 Gitlab::Utils.slugify(full_path.to_s) 2045 end 2046 2047 def has_ci? 2048 repository.gitlab_ci_yml || auto_devops_enabled? 2049 end 2050 2051 def predefined_variables 2052 strong_memoize(:predefined_variables) do 2053 Gitlab::Ci::Variables::Collection.new 2054 .concat(predefined_ci_server_variables) 2055 .concat(predefined_project_variables) 2056 .concat(pages_variables) 2057 .concat(container_registry_variables) 2058 .concat(dependency_proxy_variables) 2059 .concat(auto_devops_variables) 2060 .concat(api_variables) 2061 end 2062 end 2063 2064 def predefined_project_variables 2065 Gitlab::Ci::Variables::Collection.new 2066 .append(key: 'GITLAB_FEATURES', value: licensed_features.join(',')) 2067 .append(key: 'CI_PROJECT_ID', value: id.to_s) 2068 .append(key: 'CI_PROJECT_NAME', value: path) 2069 .append(key: 'CI_PROJECT_TITLE', value: title) 2070 .append(key: 'CI_PROJECT_PATH', value: full_path) 2071 .append(key: 'CI_PROJECT_PATH_SLUG', value: full_path_slug) 2072 .append(key: 'CI_PROJECT_NAMESPACE', value: namespace.full_path) 2073 .append(key: 'CI_PROJECT_ROOT_NAMESPACE', value: namespace.root_ancestor.path) 2074 .append(key: 'CI_PROJECT_URL', value: web_url) 2075 .append(key: 'CI_PROJECT_VISIBILITY', value: Gitlab::VisibilityLevel.string_level(visibility_level)) 2076 .append(key: 'CI_PROJECT_REPOSITORY_LANGUAGES', value: repository_languages.map(&:name).join(',').downcase) 2077 .append(key: 'CI_PROJECT_CLASSIFICATION_LABEL', value: external_authorization_classification_label) 2078 .append(key: 'CI_DEFAULT_BRANCH', value: default_branch) 2079 .append(key: 'CI_CONFIG_PATH', value: ci_config_path_or_default) 2080 end 2081 2082 def predefined_ci_server_variables 2083 Gitlab::Ci::Variables::Collection.new 2084 .append(key: 'CI', value: 'true') 2085 .append(key: 'GITLAB_CI', value: 'true') 2086 .append(key: 'CI_SERVER_URL', value: Gitlab.config.gitlab.url) 2087 .append(key: 'CI_SERVER_HOST', value: Gitlab.config.gitlab.host) 2088 .append(key: 'CI_SERVER_PORT', value: Gitlab.config.gitlab.port.to_s) 2089 .append(key: 'CI_SERVER_PROTOCOL', value: Gitlab.config.gitlab.protocol) 2090 .append(key: 'CI_SERVER_NAME', value: 'GitLab') 2091 .append(key: 'CI_SERVER_VERSION', value: Gitlab::VERSION) 2092 .append(key: 'CI_SERVER_VERSION_MAJOR', value: Gitlab.version_info.major.to_s) 2093 .append(key: 'CI_SERVER_VERSION_MINOR', value: Gitlab.version_info.minor.to_s) 2094 .append(key: 'CI_SERVER_VERSION_PATCH', value: Gitlab.version_info.patch.to_s) 2095 .append(key: 'CI_SERVER_REVISION', value: Gitlab.revision) 2096 end 2097 2098 def pages_variables 2099 Gitlab::Ci::Variables::Collection.new.tap do |variables| 2100 break unless pages_enabled? 2101 2102 variables.append(key: 'CI_PAGES_DOMAIN', value: Gitlab.config.pages.host) 2103 variables.append(key: 'CI_PAGES_URL', value: pages_url) 2104 end 2105 end 2106 2107 def api_variables 2108 Gitlab::Ci::Variables::Collection.new.tap do |variables| 2109 variables.append(key: 'CI_API_V4_URL', value: API::Helpers::Version.new('v4').root_url) 2110 end 2111 end 2112 2113 def dependency_proxy_variables 2114 Gitlab::Ci::Variables::Collection.new.tap do |variables| 2115 break variables unless Gitlab.config.dependency_proxy.enabled 2116 2117 variables.append(key: 'CI_DEPENDENCY_PROXY_SERVER', value: Gitlab.host_with_port) 2118 variables.append( 2119 key: 'CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX', 2120 # The namespace path can include uppercase letters, which 2121 # Docker doesn't allow. The proxy expects it to be downcased. 2122 value: "#{Gitlab.host_with_port}/#{namespace.root_ancestor.path.downcase}#{DependencyProxy::URL_SUFFIX}" 2123 ) 2124 variables.append( 2125 key: 'CI_DEPENDENCY_PROXY_DIRECT_GROUP_IMAGE_PREFIX', 2126 value: "#{Gitlab.host_with_port}/#{namespace.full_path.downcase}#{DependencyProxy::URL_SUFFIX}" 2127 ) 2128 end 2129 end 2130 2131 def container_registry_variables 2132 Gitlab::Ci::Variables::Collection.new.tap do |variables| 2133 break variables unless Gitlab.config.registry.enabled 2134 2135 variables.append(key: 'CI_REGISTRY', value: Gitlab.config.registry.host_port) 2136 2137 if container_registry_enabled? 2138 variables.append(key: 'CI_REGISTRY_IMAGE', value: container_registry_url) 2139 end 2140 end 2141 end 2142 2143 def default_environment 2144 production_first = Arel.sql("(CASE WHEN name = 'production' THEN 0 ELSE 1 END), id ASC") 2145 2146 environments 2147 .with_state(:available) 2148 .reorder(production_first) 2149 .first 2150 end 2151 2152 def ci_variables_for(ref:, environment: nil) 2153 cache_key = "ci_variables_for:project:#{self&.id}:ref:#{ref}:environment:#{environment}" 2154 2155 ::Gitlab::SafeRequestStore.fetch(cache_key) do 2156 uncached_ci_variables_for(ref: ref, environment: environment) 2157 end 2158 end 2159 2160 def uncached_ci_variables_for(ref:, environment: nil) 2161 result = if protected_for?(ref) 2162 variables 2163 else 2164 variables.unprotected 2165 end 2166 2167 if environment 2168 result.on_environment(environment) 2169 else 2170 result.where(environment_scope: '*') 2171 end 2172 end 2173 2174 def ci_instance_variables_for(ref:) 2175 if protected_for?(ref) 2176 Ci::InstanceVariable.all_cached 2177 else 2178 Ci::InstanceVariable.unprotected_cached 2179 end 2180 end 2181 2182 def protected_for?(ref) 2183 raise Repository::AmbiguousRefError if repository.ambiguous_ref?(ref) 2184 2185 resolved_ref = repository.expand_ref(ref) || ref 2186 return false unless Gitlab::Git.tag_ref?(resolved_ref) || Gitlab::Git.branch_ref?(resolved_ref) 2187 2188 ref_name = if resolved_ref == ref 2189 Gitlab::Git.ref_name(resolved_ref) 2190 else 2191 ref 2192 end 2193 2194 if Gitlab::Git.branch_ref?(resolved_ref) 2195 ProtectedBranch.protected?(self, ref_name) 2196 elsif Gitlab::Git.tag_ref?(resolved_ref) 2197 ProtectedTag.protected?(self, ref_name) 2198 end 2199 end 2200 2201 def deployment_variables(environment:, kubernetes_namespace: nil) 2202 platform = deployment_platform(environment: environment) 2203 2204 return [] unless platform.present? 2205 2206 platform.predefined_variables( 2207 project: self, 2208 environment_name: environment, 2209 kubernetes_namespace: kubernetes_namespace 2210 ) 2211 end 2212 2213 def auto_devops_variables 2214 return [] unless auto_devops_enabled? 2215 2216 (auto_devops || build_auto_devops)&.predefined_variables 2217 end 2218 2219 def route_map_for(commit_sha) 2220 @route_maps_by_commit ||= Hash.new do |h, sha| 2221 h[sha] = begin 2222 data = repository.route_map_for(sha) 2223 2224 Gitlab::RouteMap.new(data) if data 2225 rescue Gitlab::RouteMap::FormatError 2226 nil 2227 end 2228 end 2229 2230 @route_maps_by_commit[commit_sha] 2231 end 2232 2233 def public_path_for_source_path(path, commit_sha) 2234 map = route_map_for(commit_sha) 2235 return unless map 2236 2237 map.public_path_for_source_path(path) 2238 end 2239 2240 def parent_changed? 2241 namespace_id_changed? 2242 end 2243 2244 def default_merge_request_target 2245 return self if project_setting.mr_default_target_self 2246 return self unless mr_can_target_upstream? 2247 2248 forked_from_project 2249 end 2250 2251 def mr_can_target_upstream? 2252 # When our current visibility is more restrictive than the upstream project, 2253 # (e.g., the fork is `private` but the parent is `public`), don't allow target upstream 2254 forked_from_project && 2255 forked_from_project.merge_requests_enabled? && 2256 forked_from_project.visibility_level_value <= visibility_level_value 2257 end 2258 2259 def multiple_issue_boards_available? 2260 true 2261 end 2262 2263 def full_path_before_last_save 2264 File.join(namespace.full_path, path_before_last_save) 2265 end 2266 2267 alias_method :name_with_namespace, :full_name 2268 alias_method :human_name, :full_name 2269 # @deprecated cannot remove yet because it has an index with its name in elasticsearch 2270 alias_method :path_with_namespace, :full_path 2271 2272 # rubocop: disable CodeReuse/ServiceClass 2273 def forks_count 2274 BatchLoader.for(self).batch do |projects, loader| 2275 fork_count_per_project = ::Projects::BatchForksCountService.new(projects).refresh_cache_and_retrieve_data 2276 2277 fork_count_per_project.each do |project, count| 2278 loader.call(project, count) 2279 end 2280 end 2281 end 2282 # rubocop: enable CodeReuse/ServiceClass 2283 2284 def legacy_storage? 2285 [nil, 0].include?(self.storage_version) 2286 end 2287 2288 # Check if Hashed Storage is enabled for the project with at least informed feature rolled out 2289 # 2290 # @param [Symbol] feature that needs to be rolled out for the project (:repository, :attachments) 2291 def hashed_storage?(feature) 2292 raise ArgumentError, _("Invalid feature") unless HASHED_STORAGE_FEATURES.include?(feature) 2293 2294 self.storage_version && self.storage_version >= HASHED_STORAGE_FEATURES[feature] 2295 end 2296 2297 def renamed? 2298 persisted? && path_changed? 2299 end 2300 2301 def human_merge_method 2302 if merge_method == :ff 2303 'Fast-forward' 2304 else 2305 merge_method.to_s.humanize 2306 end 2307 end 2308 2309 def merge_method 2310 if self.merge_requests_ff_only_enabled 2311 :ff 2312 elsif self.merge_requests_rebase_enabled 2313 :rebase_merge 2314 else 2315 :merge 2316 end 2317 end 2318 2319 def merge_method=(method) 2320 case method.to_s 2321 when "ff" 2322 self.merge_requests_ff_only_enabled = true 2323 self.merge_requests_rebase_enabled = true 2324 when "rebase_merge" 2325 self.merge_requests_ff_only_enabled = false 2326 self.merge_requests_rebase_enabled = true 2327 when "merge" 2328 self.merge_requests_ff_only_enabled = false 2329 self.merge_requests_rebase_enabled = false 2330 end 2331 end 2332 2333 def ff_merge_must_be_possible? 2334 self.merge_requests_ff_only_enabled || self.merge_requests_rebase_enabled 2335 end 2336 2337 def migrate_to_hashed_storage! 2338 return unless storage_upgradable? 2339 2340 if git_transfer_in_progress? 2341 HashedStorage::ProjectMigrateWorker.perform_in(Gitlab::ReferenceCounter::REFERENCE_EXPIRE_TIME, id) 2342 else 2343 HashedStorage::ProjectMigrateWorker.perform_async(id) 2344 end 2345 end 2346 2347 def rollback_to_legacy_storage! 2348 return if legacy_storage? 2349 2350 if git_transfer_in_progress? 2351 HashedStorage::ProjectRollbackWorker.perform_in(Gitlab::ReferenceCounter::REFERENCE_EXPIRE_TIME, id) 2352 else 2353 HashedStorage::ProjectRollbackWorker.perform_async(id) 2354 end 2355 end 2356 2357 override :git_transfer_in_progress? 2358 def git_transfer_in_progress? 2359 GL_REPOSITORY_TYPES.any? do |type| 2360 reference_counter(type: type).value > 0 2361 end 2362 end 2363 2364 def storage_version=(value) 2365 super 2366 2367 @storage = nil if storage_version_changed? 2368 end 2369 2370 def badges 2371 return project_badges unless group 2372 2373 Badge.from_union([ 2374 project_badges, 2375 GroupBadge.where(group: group.self_and_ancestors) 2376 ]) 2377 end 2378 2379 def merge_requests_allowing_push_to_user(user) 2380 return MergeRequest.none unless user 2381 2382 developer_access_exists = user.project_authorizations 2383 .where('access_level >= ? ', Gitlab::Access::DEVELOPER) 2384 .where('project_authorizations.project_id = merge_requests.target_project_id') 2385 .limit(1) 2386 .select(1) 2387 merge_requests_allowing_collaboration.where('EXISTS (?)', developer_access_exists) 2388 end 2389 2390 def any_branch_allows_collaboration?(user) 2391 fetch_branch_allows_collaboration(user) 2392 end 2393 2394 def branch_allows_collaboration?(user, branch_name) 2395 fetch_branch_allows_collaboration(user, branch_name) 2396 end 2397 2398 def external_authorization_classification_label 2399 super || ::Gitlab::CurrentSettings.current_application_settings 2400 .external_authorization_service_default_label 2401 end 2402 2403 # Overridden in EE::Project 2404 def licensed_feature_available?(_feature) 2405 false 2406 end 2407 2408 def licensed_features 2409 [] 2410 end 2411 2412 def mark_primary_write_location 2413 self.class.sticking.mark_primary_write_location(:project, self.id) 2414 end 2415 2416 def toggle_ci_cd_settings!(settings_attribute) 2417 ci_cd_settings.toggle!(settings_attribute) 2418 end 2419 2420 def gitlab_deploy_token 2421 @gitlab_deploy_token ||= deploy_tokens.gitlab_deploy_token 2422 end 2423 2424 def any_lfs_file_locks? 2425 lfs_file_locks.any? 2426 end 2427 request_cache(:any_lfs_file_locks?) { self.id } 2428 2429 def auto_cancel_pending_pipelines? 2430 auto_cancel_pending_pipelines == 'enabled' 2431 end 2432 2433 def storage 2434 @storage ||= 2435 if hashed_storage?(:repository) 2436 Storage::Hashed.new(self) 2437 else 2438 Storage::LegacyProject.new(self) 2439 end 2440 end 2441 2442 def storage_upgradable? 2443 storage_version != LATEST_STORAGE_VERSION 2444 end 2445 2446 def snippets_visible?(user = nil) 2447 Ability.allowed?(user, :read_snippet, self) 2448 end 2449 2450 def max_attachment_size 2451 Gitlab::CurrentSettings.max_attachment_size.megabytes.to_i 2452 end 2453 2454 def object_pool_params 2455 return {} unless !forked? && git_objects_poolable? 2456 2457 { 2458 repository_storage: repository_storage, 2459 pool_repository: pool_repository || create_new_pool_repository 2460 } 2461 end 2462 2463 # Git objects are only poolable when the project is or has: 2464 # - Hashed storage -> The object pool will have a remote to its members, using relative paths. 2465 # If the repository path changes we would have to update the remote. 2466 # - not private -> The visibility level or repository access level has to be greater than private 2467 # to prevent fetching objects that might not exist 2468 # - Repository -> Else the disk path will be empty, and there's nothing to pool 2469 def git_objects_poolable? 2470 hashed_storage?(:repository) && 2471 visibility_level > Gitlab::VisibilityLevel::PRIVATE && 2472 repository_access_level > ProjectFeature::PRIVATE && 2473 repository_exists? && 2474 Gitlab::CurrentSettings.hashed_storage_enabled 2475 end 2476 2477 def leave_pool_repository 2478 pool_repository&.mark_obsolete_if_last(repository) && update_column(:pool_repository_id, nil) 2479 end 2480 2481 def link_pool_repository 2482 pool_repository&.link_repository(repository) 2483 end 2484 2485 def has_pool_repository? 2486 pool_repository.present? 2487 end 2488 2489 def access_request_approvers_to_be_notified 2490 members.maintainers.connected_to_user.order_recent_sign_in.limit(Member::ACCESS_REQUEST_APPROVERS_TO_BE_NOTIFIED_LIMIT) 2491 end 2492 2493 def pages_lookup_path(trim_prefix: nil, domain: nil) 2494 Pages::LookupPath.new(self, trim_prefix: trim_prefix, domain: domain) 2495 end 2496 2497 def closest_setting(name) 2498 setting = read_attribute(name) 2499 setting = closest_namespace_setting(name) if setting.nil? 2500 setting = app_settings_for(name) if setting.nil? 2501 setting 2502 end 2503 2504 def drop_visibility_level! 2505 if group && group.visibility_level < visibility_level 2506 self.visibility_level = group.visibility_level 2507 end 2508 2509 if Gitlab::CurrentSettings.restricted_visibility_levels.include?(visibility_level) 2510 self.visibility_level = Gitlab::VisibilityLevel::PRIVATE 2511 end 2512 end 2513 2514 def template_source? 2515 false 2516 end 2517 2518 def jira_subscription_exists? 2519 JiraConnectSubscription.for_project(self).exists? 2520 end 2521 2522 def uses_default_ci_config? 2523 ci_config_path.blank? || ci_config_path == Gitlab::FileDetector::PATTERNS[:gitlab_ci] 2524 end 2525 2526 def uses_external_project_ci_config? 2527 !!(ci_config_path =~ %r{@.+/.+}) 2528 end 2529 2530 def limited_protected_branches(limit) 2531 protected_branches.limit(limit) 2532 end 2533 2534 def self_monitoring? 2535 Gitlab::CurrentSettings.self_monitoring_project_id == id 2536 end 2537 2538 def deploy_token_create_url(opts = {}) 2539 Gitlab::Routing.url_helpers.create_deploy_token_project_settings_repository_path(self, opts) 2540 end 2541 2542 def deploy_token_revoke_url_for(token) 2543 Gitlab::Routing.url_helpers.revoke_project_deploy_token_path(self, token) 2544 end 2545 2546 def default_branch_protected? 2547 branch_protection = Gitlab::Access::BranchProtection.new(self.namespace.default_branch_protection) 2548 2549 branch_protection.fully_protected? || branch_protection.developer_can_merge? 2550 end 2551 2552 def environments_for_scope(scope) 2553 quoted_scope = ::Gitlab::SQL::Glob.q(scope) 2554 2555 environments.where("name LIKE (#{::Gitlab::SQL::Glob.to_like(quoted_scope)})") # rubocop:disable GitlabSecurity/SqlInjection 2556 end 2557 2558 def latest_jira_import 2559 jira_imports.last 2560 end 2561 2562 def metrics_setting 2563 super || build_metrics_setting 2564 end 2565 2566 def service_desk_enabled 2567 Gitlab::ServiceDesk.enabled?(project: self) 2568 end 2569 2570 alias_method :service_desk_enabled?, :service_desk_enabled 2571 2572 def service_desk_address 2573 service_desk_custom_address || service_desk_incoming_address 2574 end 2575 2576 def service_desk_incoming_address 2577 return unless service_desk_enabled? 2578 2579 config = Gitlab.config.incoming_email 2580 wildcard = Gitlab::IncomingEmail::WILDCARD_PLACEHOLDER 2581 2582 config.address&.gsub(wildcard, "#{full_path_slug}-#{default_service_desk_suffix}") 2583 end 2584 2585 def service_desk_custom_address 2586 return unless Gitlab::ServiceDeskEmail.enabled? 2587 2588 key = service_desk_setting&.project_key || default_service_desk_suffix 2589 2590 Gitlab::ServiceDeskEmail.address_for_key("#{full_path_slug}-#{key}") 2591 end 2592 2593 def default_service_desk_suffix 2594 "#{id}-issue-" 2595 end 2596 2597 def root_namespace 2598 if namespace.has_parent? 2599 namespace.root_ancestor 2600 else 2601 namespace 2602 end 2603 end 2604 2605 # for projects that are part of user namespace, return project. 2606 def self_or_root_group_ids 2607 if group 2608 root_group = root_namespace 2609 else 2610 project = self 2611 end 2612 2613 [project&.id, root_group&.id] 2614 end 2615 2616 def package_already_taken?(package_name, package_version, package_type:) 2617 Packages::Package.with_name(package_name) 2618 .with_version(package_version) 2619 .with_package_type(package_type) 2620 .for_projects( 2621 root_ancestor.all_projects 2622 .id_not_in(id) 2623 .select(:id) 2624 ).exists? 2625 end 2626 2627 def default_branch_or_main 2628 return default_branch if default_branch 2629 2630 Gitlab::DefaultBranch.value(object: self) 2631 end 2632 2633 def ci_config_path_or_default 2634 ci_config_path.presence || Ci::Pipeline::DEFAULT_CONFIG_PATH 2635 end 2636 2637 def ci_config_for(sha) 2638 repository.gitlab_ci_yml_for(sha, ci_config_path_or_default) 2639 end 2640 2641 def ci_config_external_project 2642 Project.find_by_full_path(ci_config_path.split('@', 2).last) 2643 end 2644 2645 def enabled_group_deploy_keys 2646 return GroupDeployKey.none unless group 2647 2648 GroupDeployKey.for_groups(group.self_and_ancestors_ids) 2649 end 2650 2651 def feature_flags_client_token 2652 instance = operations_feature_flags_client || create_operations_feature_flags_client! 2653 instance.token 2654 end 2655 2656 def tracing_external_url 2657 tracing_setting&.external_url 2658 end 2659 2660 override :git_garbage_collect_worker_klass 2661 def git_garbage_collect_worker_klass 2662 Projects::GitGarbageCollectWorker 2663 end 2664 2665 def activity_path 2666 Gitlab::Routing.url_helpers.activity_project_path(self) 2667 end 2668 2669 def increment_statistic_value(statistic, delta) 2670 return if pending_delete? 2671 2672 ProjectStatistics.increment_statistic(self, statistic, delta) 2673 end 2674 2675 def ci_forward_deployment_enabled? 2676 return false unless ci_cd_settings 2677 2678 ci_cd_settings.forward_deployment_enabled? 2679 end 2680 2681 def ci_job_token_scope_enabled? 2682 return false unless ci_cd_settings 2683 2684 ci_cd_settings.job_token_scope_enabled? 2685 end 2686 2687 def restrict_user_defined_variables? 2688 return false unless ci_cd_settings 2689 2690 ci_cd_settings.restrict_user_defined_variables? 2691 end 2692 2693 def keep_latest_artifacts_available? 2694 return false unless ci_cd_settings 2695 2696 ci_cd_settings.keep_latest_artifacts_available? 2697 end 2698 2699 def keep_latest_artifact? 2700 return false unless ci_cd_settings 2701 2702 ci_cd_settings.keep_latest_artifact? 2703 end 2704 2705 def group_runners_enabled? 2706 return false unless ci_cd_settings 2707 2708 ci_cd_settings.group_runners_enabled? 2709 end 2710 2711 def topic_list 2712 self.topics.map(&:name) 2713 end 2714 2715 override :after_change_head_branch_does_not_exist 2716 def after_change_head_branch_does_not_exist(branch) 2717 self.errors.add(:base, _("Could not change HEAD: branch '%{branch}' does not exist") % { branch: branch }) 2718 end 2719 2720 def visible_group_links(for_user:) 2721 user = for_user 2722 links = project_group_links_with_preload 2723 user.max_member_access_for_group_ids(links.map(&:group_id)) if user && links.any? 2724 2725 DeclarativePolicy.user_scope do 2726 links.select { Ability.allowed?(user, :read_group, _1.group) } 2727 end 2728 end 2729 2730 def remove_project_authorizations(user_ids, per_batch = 1000) 2731 user_ids.each_slice(per_batch) do |user_ids_batch| 2732 project_authorizations.where(user_id: user_ids_batch).delete_all 2733 end 2734 end 2735 2736 private 2737 2738 # overridden in EE 2739 def project_group_links_with_preload 2740 project_group_links 2741 end 2742 2743 def save_topics 2744 return if @topic_list.nil? 2745 2746 @topic_list = @topic_list.split(',') if @topic_list.instance_of?(String) 2747 @topic_list = @topic_list.map(&:strip).uniq.reject(&:empty?) 2748 2749 if @topic_list != self.topic_list 2750 self.topics.delete_all 2751 self.topics = @topic_list.map { |topic| Projects::Topic.find_or_create_by(name: topic) } 2752 end 2753 2754 @topic_list = nil 2755 end 2756 2757 def find_integration(integrations, name) 2758 integrations.find { _1.to_param == name } 2759 end 2760 2761 def build_from_instance(name) 2762 instance = find_integration(integration_instances, name) 2763 2764 return unless instance 2765 2766 Integration.build_from_integration(instance, project_id: id) 2767 end 2768 2769 def build_integration(name) 2770 Integration.integration_name_to_model(name).new(project_id: id) 2771 end 2772 2773 def integration_instances 2774 @integration_instances ||= Integration.for_instance 2775 end 2776 2777 def closest_namespace_setting(name) 2778 namespace.closest_setting(name) 2779 end 2780 2781 def app_settings_for(name) 2782 Gitlab::CurrentSettings.send(name) # rubocop:disable GitlabSecurity/PublicSend 2783 end 2784 2785 def merge_requests_allowing_collaboration(source_branch = nil) 2786 relation = source_of_merge_requests.opened.where(allow_collaboration: true) 2787 relation = relation.where(source_branch: source_branch) if source_branch 2788 relation 2789 end 2790 2791 def create_new_pool_repository 2792 pool = PoolRepository.safe_find_or_create_by!(shard: Shard.by_name(repository_storage), source_project: self) 2793 update!(pool_repository: pool) 2794 2795 pool.schedule unless pool.scheduled? 2796 2797 pool 2798 end 2799 2800 def join_pool_repository 2801 return unless pool_repository 2802 2803 ObjectPool::JoinWorker.perform_async(pool_repository.id, self.id) 2804 end 2805 2806 def use_hashed_storage 2807 if self.new_record? && Gitlab::CurrentSettings.hashed_storage_enabled 2808 self.storage_version = LATEST_STORAGE_VERSION 2809 end 2810 end 2811 2812 def check_repository_absence! 2813 return if skip_disk_validation 2814 2815 if repository_storage.blank? || repository_with_same_path_already_exists? 2816 errors.add(:base, _('There is already a repository with that name on disk')) 2817 throw :abort # rubocop:disable Cop/BanCatchThrow 2818 end 2819 end 2820 2821 def repository_with_same_path_already_exists? 2822 gitlab_shell.repository_exists?(repository_storage, "#{disk_path}.git") 2823 end 2824 2825 def set_timestamps_for_create 2826 update_columns(last_activity_at: self.created_at, last_repository_updated_at: self.created_at) 2827 end 2828 2829 def cross_namespace_reference?(from) 2830 case from 2831 when Project 2832 namespace_id != from.namespace_id 2833 when Namespace 2834 namespace != from 2835 when User 2836 true 2837 end 2838 end 2839 2840 # Check if a reference is being done cross-project 2841 def cross_project_reference?(from) 2842 return true if from.is_a?(Namespace) 2843 2844 from && self != from 2845 end 2846 2847 def update_project_statistics 2848 stats = statistics || build_statistics 2849 stats.update(namespace_id: namespace_id) 2850 end 2851 2852 def check_pending_delete 2853 return if valid_attribute?(:name) && valid_attribute?(:path) 2854 return unless pending_delete_twin 2855 2856 %i[route route.path name path].each do |error| 2857 errors.delete(error) 2858 end 2859 2860 errors.add(:base, _("The project is still being deleted. Please try again later.")) 2861 end 2862 2863 def pending_delete_twin 2864 return false unless path 2865 2866 Project.pending_delete.find_by_full_path(full_path) 2867 end 2868 2869 ## 2870 # This method is here because of support for legacy container repository 2871 # which has exactly the same path like project does, but which might not be 2872 # persisted in `container_repositories` table. 2873 # 2874 def has_root_container_repository_tags? 2875 return false unless Gitlab.config.registry.enabled 2876 2877 ContainerRepository.build_root_repository(self).has_tags? 2878 end 2879 2880 def fetch_branch_allows_collaboration(user, branch_name = nil) 2881 return false unless user 2882 2883 Gitlab::SafeRequestStore.fetch("project-#{id}:branch-#{branch_name}:user-#{user.id}:branch_allows_collaboration") do 2884 next false if empty_repo? 2885 2886 # Issue for N+1: https://gitlab.com/gitlab-org/gitlab-foss/issues/49322 2887 Gitlab::GitalyClient.allow_n_plus_1_calls do 2888 merge_requests_allowing_collaboration(branch_name).any? do |merge_request| 2889 merge_request.can_be_merged_by?(user, skip_collaboration_check: true) 2890 end 2891 end 2892 end 2893 end 2894 2895 def ensure_pages_metadatum 2896 pages_metadatum || create_pages_metadatum! 2897 rescue ActiveRecord::RecordNotUnique 2898 reset 2899 retry 2900 end 2901 2902 def oids(objects, oids: []) 2903 objects = objects.where(oid: oids) if oids.any? 2904 2905 [].tap do |out| 2906 objects.each_batch { |relation| out.concat(relation.pluck(:oid)) } 2907 end 2908 end 2909 2910 def cache_has_external_wiki 2911 update_column(:has_external_wiki, integrations.external_wikis.any?) if Gitlab::Database.read_write? 2912 end 2913 2914 def cache_has_external_issue_tracker 2915 update_column(:has_external_issue_tracker, integrations.external_issue_trackers.any?) if Gitlab::Database.read_write? 2916 end 2917 2918 def online_runners_with_tags 2919 @online_runners_with_tags ||= active_runners.with_tags.online 2920 end 2921 2922 def ensure_project_namespace_in_sync 2923 # create project_namespace when project is created if create_project_namespace_on_project_create FF is enabled 2924 build_project_namespace if project_namespace_creation_enabled? 2925 2926 # regardless of create_project_namespace_on_project_create FF we need 2927 # to keep project and project namespace in sync if there is one 2928 sync_attributes(project_namespace) if sync_project_namespace? 2929 end 2930 2931 def project_namespace_creation_enabled? 2932 new_record? && !project_namespace && self.namespace && self.root_namespace.project_namespace_creation_enabled? 2933 end 2934 2935 def sync_project_namespace? 2936 (changes.keys & %w(name path namespace_id namespace visibility_level shared_runners_enabled)).any? && project_namespace.present? 2937 end 2938 2939 def sync_attributes(project_namespace) 2940 project_namespace.name = name 2941 project_namespace.path = path 2942 project_namespace.parent = namespace 2943 project_namespace.shared_runners_enabled = shared_runners_enabled 2944 project_namespace.visibility_level = visibility_level 2945 end 2946 2947 # SyncEvents are created by PG triggers (with the function `insert_projects_sync_event`) 2948 def schedule_sync_event_worker 2949 run_after_commit do 2950 Projects::SyncEvent.enqueue_worker 2951 end 2952 end 2953end 2954 2955Project.prepend_mod_with('Project') 2956