project.rb 27.7 KB
Newer Older
gfyoung's avatar
gfyoung committed
1
2
# frozen_string_literal: true

3
4
5
6
module EE
  # Project EE mixin
  #
  # This module is intended to encapsulate EE-specific model logic
7
  # and be prepended in the `Project` model
8
  module Project
9
    extend ActiveSupport::Concern
10
    extend ::Gitlab::Utils::Override
11
    extend ::Gitlab::Cache::RequestCache
Lin Jen-Shin's avatar
Lin Jen-Shin committed
12
    include ::Gitlab::Utils::StrongMemoize
13
    include ::EE::GitlabRoutingHelper # rubocop: disable Cop/InjectEnterpriseEditionModule
Andreas Brandl's avatar
Andreas Brandl committed
14
    include IgnorableColumns
15
16

    GIT_LFS_DOWNLOAD_OPERATION = 'download'.freeze
17
18

    prepended do
19
      include Elastic::ProjectsSearch
20
      include EE::DeploymentPlatform # rubocop: disable Cop/InjectEnterpriseEditionModule
21
      include EachBatch
22
      include InsightsFeature
23
      include Vulnerable
24
      include DeprecatedApprovalsBeforeMerge
Ash McKenzie's avatar
Ash McKenzie committed
25
      include UsageStatistics
26

Andreas Brandl's avatar
Andreas Brandl committed
27
      ignore_columns :mirror_last_update_at, :mirror_last_successful_update_at, remove_after: '2019-12-15', remove_with: '12.6'
28

29
      before_save :set_override_pull_mirror_available, unless: -> { ::Gitlab::CurrentSettings.mirror_available }
Tiago Botelho's avatar
Tiago Botelho committed
30
      before_save :set_next_execution_timestamp_to_now, if: ->(project) { project.mirror? && project.mirror_changed? && project.import_state }
31

32
33
34
35
      after_update :remove_mirror_repository_reference,
        if: ->(project) { project.mirror? && project.import_url_updated? }

      belongs_to :mirror_user, foreign_key: 'mirror_user_id', class_name: 'User'
36
      belongs_to :deleting_user, foreign_key: 'marked_for_deletion_by_user_id', class_name: 'User'
37

38
      has_one :repository_state, class_name: 'ProjectRepositoryState', inverse_of: :project
39
      has_one :project_registry, class_name: 'Geo::ProjectRegistry', inverse_of: :project
40
41
      has_one :push_rule, ->(project) { project&.feature_available?(:push_rules) ? all : none }
      has_one :index_status
Simon Knox's avatar
Simon Knox committed
42

43
44
      has_one :jenkins_service
      has_one :jenkins_deprecated_service
45
      has_one :github_service
46
      has_one :gitlab_slack_application_service
Simon Knox's avatar
Simon Knox committed
47
48
      has_one :alerts_service

49
      has_one :service_desk_setting, class_name: 'ServiceDeskSetting'
Simon Knox's avatar
Simon Knox committed
50
      has_one :tracing_setting, class_name: 'ProjectTracingSetting'
51
      has_one :alerting_setting, inverse_of: :project, class_name: 'Alerting::ProjectAlertingSetting'
52
      has_one :incident_management_setting, inverse_of: :project, class_name: 'IncidentManagement::ProjectIncidentManagementSetting'
53
      has_one :feature_usage, class_name: 'ProjectFeatureUsage'
54

55
      has_many :reviews, inverse_of: :project
56
      has_many :approvers, as: :target, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
57
      has_many :approver_users, through: :approvers, source: :user
58
      has_many :approver_groups, as: :target, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
59
      has_many :approval_rules, class_name: 'ApprovalProjectRule'
Mo Khan's avatar
Mo Khan committed
60
      has_many :approval_merge_request_rules, through: :merge_requests, source: :approval_rules
61
      has_many :audit_events, as: :entity
Luke Duncalfe's avatar
Luke Duncalfe committed
62
      has_many :designs, inverse_of: :project, class_name: 'DesignManagement::Design'
63
      has_many :path_locks
64
65
66
67

      # the rationale behind vulnerabilities and vulnerability_findings can be found here:
      # https://gitlab.com/gitlab-org/gitlab/issues/10252#terminology
      has_many :vulnerabilities
68
      has_many :vulnerability_feedback, class_name: 'Vulnerabilities::Feedback'
69
70
71
72
73
      has_many :vulnerability_findings, class_name: 'Vulnerabilities::Occurrence' do
        def lock_for_confirmation!(id)
          where(vulnerability_id: nil).lock.find(id)
        end
      end
74
75
      has_many :vulnerability_identifiers, class_name: 'Vulnerabilities::Identifier'
      has_many :vulnerability_scanners, class_name: 'Vulnerabilities::Scanner'
76

77
      has_many :protected_environments
78
79
      has_many :software_license_policies, inverse_of: :project, class_name: 'SoftwareLicensePolicy'
      accepts_nested_attributes_for :software_license_policies, allow_destroy: true
80
      has_many :packages, class_name: 'Packages::Package'
81
      has_many :package_files, through: :packages, class_name: 'Packages::PackageFile'
82
      has_many :merge_trains, foreign_key: 'target_project_id', inverse_of: :target_project
83

84
85
      has_many :webide_pipelines, -> { webide_source }, class_name: 'Ci::Pipeline', inverse_of: :project

86
      has_many :prometheus_alerts, inverse_of: :project
87
      has_many :prometheus_alert_events, inverse_of: :project
88
      has_many :self_managed_prometheus_alert_events, inverse_of: :project
89

Kamil Trzciński's avatar
Kamil Trzciński committed
90
      has_many :operations_feature_flags, class_name: 'Operations::FeatureFlag'
91
      has_one :operations_feature_flags_client, class_name: 'Operations::FeatureFlagsClient'
Kamil Trzciński's avatar
Kamil Trzciński committed
92

93
94
      has_many :project_aliases

95
96
97
98
99
      has_many :upstream_project_subscriptions, class_name: 'Ci::Subscriptions::Project', foreign_key: :downstream_project_id, inverse_of: :downstream_project
      has_many :upstream_projects, class_name: 'Project', through: :upstream_project_subscriptions, source: :upstream_project
      has_many :downstream_project_subscriptions, class_name: 'Ci::Subscriptions::Project', foreign_key: :upstream_project_id, inverse_of: :upstream_project
      has_many :downstream_projects, class_name: 'Project', through: :downstream_project_subscriptions, source: :downstream_project

100
101
      scope :with_shared_runners_limit_enabled, -> { with_shared_runners.non_public_only }

102
      scope :mirror, -> { where(mirror: true) }
103

Andreas Brandl's avatar
Andreas Brandl committed
104
      scope :mirrors_to_sync, ->(freeze_at, limit: nil) do
105
        mirror
106
          .joins_import_state
107
108
109
          .where.not(import_state: { status: [:scheduled, :started] })
          .where("import_state.next_execution_timestamp <= ?", freeze_at)
          .where("import_state.retry_count <= ?", ::Gitlab::Mirror::MAX_RETRY)
Andreas Brandl's avatar
Andreas Brandl committed
110
          .limit(limit)
111
112
      end

Michael Kozono's avatar
Michael Kozono committed
113
      scope :with_wiki_enabled, -> { with_feature_enabled(:wiki) }
114
      scope :within_shards, -> (shard_names) { where(repository_storage: Array(shard_names)) }
115
      scope :outside_shards, -> (shard_names) { where.not(repository_storage: Array(shard_names)) }
116
117
      scope :verification_failed_repos, -> { joins(:repository_state).merge(ProjectRepositoryState.verification_failed_repos) }
      scope :verification_failed_wikis, -> { joins(:repository_state).merge(ProjectRepositoryState.verification_failed_wikis) }
118
      scope :for_plan_name, -> (name) { joins(namespace: :plan).where(plans: { name: name }) }
119
      scope :requiring_code_owner_approval,
120
            -> { joins(:protected_branches).where(protected_branches: { code_owner_approval_required: true }) }
Ash McKenzie's avatar
Ash McKenzie committed
121
122
123
124
125
      scope :with_active_services, -> { joins(:services).merge(::Service.active) }
      scope :with_active_jira_services, -> { joins(:services).merge(::JiraService.active) }
      scope :with_jira_dvcs_cloud, -> { joins(:feature_usage).merge(ProjectFeatureUsage.with_jira_dvcs_integration_enabled(cloud: true)) }
      scope :with_jira_dvcs_server, -> { joins(:feature_usage).merge(ProjectFeatureUsage.with_jira_dvcs_integration_enabled(cloud: false)) }
      scope :service_desk_enabled, -> { where(service_desk_enabled: true) }
126
127
128
      scope :github_imported, -> { where(import_type: 'github') }
      scope :with_protected_branches, -> { joins(:protected_branches) }
      scope :with_repositories_enabled, -> { joins(:project_feature).where(project_features: { repository_access_level: ::ProjectFeature::ENABLED }) }
129

130
131
      scope :with_security_reports_stored, -> { where('EXISTS (?)', ::Vulnerabilities::Occurrence.scoped_project.select(1)) }
      scope :with_security_reports, -> { where('EXISTS (?)', ::Ci::JobArtifact.security_reports.scoped_project.select(1)) }
132
      scope :with_github_service_pipeline_events, -> { joins(:github_service).merge(GithubService.pipeline_hooks) }
133
134
135
      scope :with_active_prometheus_service, -> { joins(:prometheus_service).merge(PrometheusService.active) }
      scope :with_enabled_error_tracking, -> { joins(:error_tracking_setting).where(project_error_tracking_settings: { enabled: true }) }
      scope :with_tracing_enabled, -> { joins(:tracing_setting) }
136
      scope :with_packages, -> { joins(:packages) }
137
138
139
140
      scope :mirrored_with_enabled_pipelines, -> do
        joins(:project_feature).mirror.where(mirror_trigger_builds: true,
                                             project_features: { builds_access_level: ::ProjectFeature::ENABLED })
      end
141
142
143
      scope :with_slack_service, -> { joins(:slack_service) }
      scope :with_slack_slash_commands_service, -> { joins(:slack_slash_commands_service) }
      scope :with_prometheus_service, -> { joins(:prometheus_service) }
144
      scope :aimed_for_deletion, -> (date) { where('marked_for_deletion_at <= ?', date).without_deleted }
145
146
      scope :with_repos_templates, -> { where(namespace_id: ::Gitlab::CurrentSettings.current_application_settings.custom_project_templates_group_id) }
      scope :with_groups_level_repos_templates, -> { joins("INNER JOIN namespaces ON projects.namespace_id = namespaces.custom_project_templates_group_id") }
147

148
      delegate :shared_runners_minutes, :shared_runners_seconds, :shared_runners_seconds_last_reset,
149
150
151
        to: :statistics, allow_nil: true

      delegate :actual_shared_runners_minutes_limit,
152
        :shared_runners_minutes_used?, to: :shared_runners_limit_namespace
153

154
155
156
157
      delegate :last_update_succeeded?, :last_update_failed?,
        :ever_updated_successfully?, :hard_failed?,
        to: :import_state, prefix: :mirror, allow_nil: true

158
159
      delegate :log_jira_dvcs_integration_usage, :jira_dvcs_server_last_sync_at, :jira_dvcs_cloud_last_sync_at, to: :feature_usage

160
      delegate :merge_pipelines_enabled, :merge_pipelines_enabled=, :merge_pipelines_enabled?, :merge_pipelines_were_disabled?, to: :ci_cd_settings
161
      delegate :merge_trains_enabled, :merge_trains_enabled=, :merge_trains_enabled?, to: :ci_cd_settings
162

163
164
165
166
167
      validates :repository_size_limit,
        numericality: { only_integer: true, greater_than_or_equal_to: 0, allow_nil: true }

      validates :approvals_before_merge, numericality: true, allow_blank: true

168
169
170
      validates :pull_mirror_branch_prefix, length: { maximum: 50 }
      validate :check_pull_mirror_branch_prefix

Rémy Coutable's avatar
Rémy Coutable committed
171
172
173
      with_options if: :mirror? do
        validates :import_url, presence: true
        validates :mirror_user, presence: true
174
      end
175
176

      default_value_for :packages_enabled, true
177

178
      accepts_nested_attributes_for :tracing_setting, update_only: true, allow_destroy: true
179
      accepts_nested_attributes_for :alerting_setting, update_only: true
180
      accepts_nested_attributes_for :incident_management_setting, update_only: true
181
182

      alias_attribute :fallback_approvals_required, :approvals_before_merge
183
184
    end

185
    class_methods do
186
187
188
      def search_by_visibility(level)
        where(visibility_level: ::Gitlab::VisibilityLevel.string_options[level])
      end
Valery Sizov's avatar
Valery Sizov committed
189
190
191
192
193

      def with_slack_application_disabled
        joins('LEFT JOIN services ON services.project_id = projects.id AND services.type = \'GitlabSlackApplicationService\' AND services.active IS true')
          .where('services.id IS NULL')
      end
194
195
196
197
198
199
200
201
202
203
204
205
206

      def inner_join_design_management
        join_statement =
          arel_table
            .join(DesignManagement::Design.arel_table, Arel::Nodes::InnerJoin)
            .on(arel_table[:id].eq(DesignManagement::Design.arel_table[:project_id]))

        joins(join_statement.join_sources)
      end

      def count_designs
        inner_join_design_management.distinct.count
      end
207
208
    end

209
210
211
212
    def can_store_security_reports?
      namespace.store_security_reports_available? || public?
    end

Simon Knox's avatar
Simon Knox committed
213
214
215
216
    def tracing_external_url
      self.tracing_setting.try(:external_url)
    end

217
    def latest_pipeline_with_security_reports
218
219
      all_pipelines.newest_first(ref: default_branch).with_reports(::Ci::JobArtifact.security_reports).first ||
        all_pipelines.newest_first(ref: default_branch).with_legacy_security_reports.first
220
221
    end

222
223
224
225
    def latest_pipeline_with_reports(reports)
      all_pipelines.newest_first(ref: default_branch).with_reports(reports).take
    end

226
227
228
229
230
231
    def environments_for_scope(scope)
      quoted_scope = ::Gitlab::SQL::Glob.q(scope)

      environments.where("name LIKE (#{::Gitlab::SQL::Glob.to_like(quoted_scope)})") # rubocop:disable GitlabSecurity/SqlInjection
    end

232
233
234
235
236
237
    def ensure_external_webhook_token
      return if external_webhook_token.present?

      self.external_webhook_token = Devise.friendly_token
    end

238
    def shared_runners_limit_namespace
239
      root_namespace
240
241
    end

242
    def mirror
243
      super && feature_available?(:repository_mirrors) && pull_mirror_available?
244
245
246
    end
    alias_method :mirror?, :mirror

247
    def mirror_with_content?
248
      mirror? && !empty_repo?
249
250
    end

251
    def fetch_mirror(forced: false)
252
253
      return unless mirror?

254
255
256
257
258
259
260
261
      # Only send the password if it's needed
      url =
        if import_data&.password_auth?
          import_url
        else
          username_only_import_url
        end

262
      repository.fetch_upstream(url, forced: forced)
263
264
    end

Simon Knox's avatar
Simon Knox committed
265
    def can_override_approvers?
Simon Knox's avatar
Simon Knox committed
266
      !disable_overriding_approvers_per_merge_request?
Simon Knox's avatar
Simon Knox committed
267
268
    end

269
    def shared_runners_available?
270
      super && !shared_runners_limit_namespace.shared_runners_minutes_used?
271
272
    end

273
274
275
276
277
278
279
280
281
    def link_pool_repository
      super
      repository.log_geo_updated_event
    end

    def object_pool_missing?
      has_pool_repository? && !pool_repository.object_pool.exists?
    end

282
    def shared_runners_minutes_limit_enabled?
Kamil Trzcinski's avatar
Kamil Trzcinski committed
283
      !public? && shared_runners_enabled? &&
284
        shared_runners_limit_namespace.shared_runners_minutes_limit_enabled?
285
    end
286

287
288
289
290
291
292
293
294
295
296
297
298
299
    # This makes the feature disabled by default, in contrary to how
    # `#feature_available?` makes a feature enabled by default.
    #
    # This allows to:
    # - Enable the feature flag for a given project, regardless of the license.
    #   This is useful for early testing a feature in production on a given project.
    # - Enable the feature flag globally and still check that the license allows
    #   it. This is the case when we're ready to enable a feature for anyone
    #   with the correct license.
    def beta_feature_available?(feature)
      ::Feature.enabled?(feature, self) ||
        (::Feature.enabled?(feature) && feature_available?(feature))
    end
300
    alias_method :alpha_feature_available?, :beta_feature_available?
301

302
303
304
305
    def push_audit_events_enabled?
      ::Feature.enabled?(:repository_push_audit_event, self)
    end

306
    def feature_available?(feature, user = nil)
307
      if ::ProjectFeature::FEATURES.include?(feature)
308
        super
309
      else
310
        licensed_feature_available?(feature, user)
311
312
313
      end
    end

314
315
316
317
    def multiple_approval_rules_available?
      feature_available?(:multiple_approval_rules)
    end

Bob Van Landuyt's avatar
Bob Van Landuyt committed
318
319
320
321
    def code_owner_approval_required_available?
      feature_available?(:code_owner_approval_required)
    end

322
323
324
325
326
    def service_desk_enabled
      ::EE::Gitlab::ServiceDesk.enabled?(project: self) && super
    end
    alias_method :service_desk_enabled?, :service_desk_enabled

327
    def service_desk_address
328
      return unless service_desk_enabled?
329

330
331
      config = ::Gitlab.config.incoming_email
      wildcard = ::Gitlab::IncomingEmail::WILDCARD_PLACEHOLDER
332

333
      config.address&.gsub(wildcard, "#{full_path_slug}-#{id}-issue-")
334
335
    end

336
    override :add_import_job
337
    def add_import_job
338
339
      return if gitlab_custom_project_template_import?

340
341
342
      if import? && !repository_exists?
        super
      elsif mirror?
343
        ::Gitlab::Metrics.add_event(:mirrors_scheduled)
344
345
        job_id = RepositoryUpdateMirrorWorker.perform_async(self.id)

346
        log_import_activity(job_id, type: :mirror)
347

348
        job_id
349
350
351
      end
    end

352
353
354
355
356
357
358
359
360
361
362
    override :has_active_hooks?
    def has_active_hooks?(hooks_scope = :push_hooks)
      super || has_group_hooks?(hooks_scope)
    end

    def has_group_hooks?(hooks_scope = :push_hooks)
      return unless group && feature_available?(:group_webhooks)

      group.hooks.hooks_for(hooks_scope).any?
    end

363
364
365
    def execute_hooks(data, hooks_scope = :push_hooks)
      super

366
      if group && feature_available?(:group_webhooks)
367
368
369
370
        run_after_commit_or_now do
          group.hooks.hooks_for(hooks_scope).each do |hook|
            hook.async_execute(data, hooks_scope.to_s)
          end
371
372
373
374
375
376
377
        end
      end
    end

    # No need to have a Kerberos Web url. Kerberos URL will be used only to
    # clone
    def kerberos_url_to_repo
378
      "#{::Gitlab.config.build_gitlab_kerberos_url + ::Gitlab::Routing.url_helpers.project_path(self)}.git"
379
380
381
    end

    def group_ldap_synced?
382
383
384
385
386
387
      group&.ldap_synced?
    end

    override :allowed_to_share_with_group?
    def allowed_to_share_with_group?
      super && !(group && ::Gitlab::CurrentSettings.lock_memberships_to_ldap?)
388
389
390
391
392
393
    end

    def reference_issue_tracker?
      default_issues_tracker? || jira_tracker_active?
    end

394
    # TODO: Clean up this method in the https://gitlab.com/gitlab-org/gitlab/issues/33329
395
396
397
398
399
400
    def approvals_before_merge
      return 0 unless feature_available?(:merge_request_approvers)

      super
    end

401
402
    def visible_approval_rules
      strong_memoize(:visible_approval_rules) do
403
        visible_user_defined_rules + approval_rules.report_approver
404
405
406
      end
    end

407
408
409
    def visible_user_defined_rules
      strong_memoize(:visible_user_defined_rules) do
        rules = approval_rules.regular_or_any_approver.order(rule_type: :desc, id: :asc)
410

411
        next rules.take(1) unless multiple_approval_rules_available?
412

413
        rules
414
415
416
      end
    end

417
    # TODO: Clean up this method in the https://gitlab.com/gitlab-org/gitlab/issues/33329
418
419
    def min_fallback_approvals
      strong_memoize(:min_fallback_approvals) do
420
        visible_user_defined_rules.map(&:approvals_required).max.to_i
421
422
423
      end
    end

424
425
426
427
428
    def reset_approvals_on_push
      super && feature_available?(:merge_request_approvers)
    end
    alias_method :reset_approvals_on_push?, :reset_approvals_on_push

429
    def approver_ids=(value)
430
      ::Gitlab::Utils.ensure_array_from_string(value).each do |user_id|
431
432
433
434
435
        approvers.find_or_create_by(user_id: user_id, target_id: id)
      end
    end

    def approver_group_ids=(value)
436
      ::Gitlab::Utils.ensure_array_from_string(value).each do |group_id|
437
438
439
440
        approver_groups.find_or_initialize_by(group_id: group_id, target_id: id)
      end
    end

Bob Van Landuyt's avatar
Bob Van Landuyt committed
441
    def merge_requests_require_code_owner_approval?
442
443
      code_owner_approval_required_available? &&
        protected_branches.requiring_code_owner_approval.any?
Bob Van Landuyt's avatar
Bob Van Landuyt committed
444
445
    end

446
447
448
    def branch_requires_code_owner_approval?(branch_name)
      return false unless code_owner_approval_required_available?

449
      ::ProtectedBranch.branch_requires_code_owner_approval?(self, branch_name)
450
451
    end

452
453
454
455
456
    def require_password_to_approve
      super && password_authentication_enabled_for_web?
    end
    alias_method :require_password_to_approve?, :require_password_to_approve

457
    def find_path_lock(path, exact_match: false, downstream: false)
Lin Jen-Shin's avatar
Lin Jen-Shin committed
458
459
460
461
462
      path_lock_finder = strong_memoize(:path_lock_finder) do
        ::Gitlab::PathLocksFinder.new(self)
      end

      path_lock_finder.find(path, exact_match: exact_match, downstream: downstream)
463
464
465
466
    end

    def import_url_updated?
      # check if import_url has been updated and it's not just the first assignment
467
      saved_change_to_import_url? && saved_changes['import_url'].first
468
469
470
    end

    def remove_mirror_repository_reference
471
472
473
      run_after_commit do
        repository.async_remove_remote(::Repository::MIRROR_REMOTE)
      end
474
475
    end

476
477
478
    def username_only_import_url
      bare_url = read_attribute(:import_url)
      return bare_url unless ::Gitlab::UrlSanitizer.valid?(bare_url)
479

480
481
482
483
484
485
      ::Gitlab::UrlSanitizer.new(bare_url, credentials: { user: import_data&.user }).full_url
    end

    def username_only_import_url=(value)
      unless ::Gitlab::UrlSanitizer.valid?(value)
        self.import_url = value
486
        self.import_data&.user = nil
Rémy Coutable's avatar
Rémy Coutable committed
487
        value
488
489
490
      end

      url = ::Gitlab::UrlSanitizer.new(value)
491
      creds = url.credentials.slice(:user)
492
493
494
495
496
497
498

      write_attribute(:import_url, url.sanitized_url)
      create_or_update_import_data(credentials: creds)

      username_only_import_url
    end

499
500
501
502
    def change_repository_storage(new_repository_storage_key)
      return if repository_read_only?
      return if repository_storage == new_repository_storage_key

503
      raise ArgumentError unless ::Gitlab.config.repositories.storages.key?(new_repository_storage_key)
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529

      run_after_commit { ProjectUpdateRepositoryStorageWorker.perform_async(id, new_repository_storage_key) }
      self.repository_read_only = true
    end

    def repository_and_lfs_size
      statistics.total_repository_size
    end

    def above_size_limit?
      return false unless size_limit_enabled?

      repository_and_lfs_size > actual_size_limit
    end

    def size_to_remove
      repository_and_lfs_size - actual_size_limit
    end

    def actual_size_limit
      return namespace.actual_size_limit if repository_size_limit.nil?

      repository_size_limit
    end

    def size_limit_enabled?
530
531
      return false unless License.feature_available?(:repository_size_limit)

532
533
534
535
536
537
538
539
540
541
542
543
544
      actual_size_limit != 0
    end

    def changes_will_exceed_size_limit?(size_in_bytes)
      size_limit_enabled? &&
        (size_in_bytes > actual_size_limit ||
         size_in_bytes + repository_and_lfs_size > actual_size_limit)
    end

    def remove_import_data
      super unless mirror?
    end

545
    def merge_requests_ff_only_enabled
546
      super
547
548
549
    end
    alias_method :merge_requests_ff_only_enabled?, :merge_requests_ff_only_enabled

550
    override :disabled_services
551
    def disabled_services
Lin Jen-Shin's avatar
Lin Jen-Shin committed
552
      strong_memoize(:disabled_services) do
Simon Knox's avatar
Simon Knox committed
553
554
555
556
        [].tap do |services|
          services.push('jenkins', 'jenkins_deprecated') unless feature_available?(:jenkins_integration)
          services.push('github') unless feature_available?(:github_project_service_integration)
          services.push('alerts') unless alerts_service_available?
557
        end
558
559
560
      end
    end

561
562
    def pull_mirror_available?
      pull_mirror_available_overridden ||
563
        ::Gitlab::CurrentSettings.mirror_available
564
565
    end

566
567
568
569
570
571
572
573
574
    override :licensed_features
    def licensed_features
      return super unless License.current

      License.current.features.select do |feature|
        License.global_feature?(feature) || licensed_feature_available?(feature)
      end
    end

575
576
577
578
579
    def any_path_locks?
      path_locks.any?
    end
    request_cache(:any_path_locks?) { self.id }

580
581
582
583
584
585
586
    def protected_environment_accessible_to?(environment_name, user)
      protected_environment = protected_environment_by_name(environment_name)

      !protected_environment || protected_environment.accessible_to?(user)
    end

    def protected_environment_by_name(environment_name)
587
      return unless protected_environments_feature_available?
588
589
590
591

      protected_environments.find_by(name: environment_name)
    end

592
593
594
    override :after_import
    def after_import
      super
595
596
      repository.log_geo_updated_event
      wiki.repository.log_geo_updated_event
597
      design_repository.log_geo_updated_event
598
599
    end

600
601
602
603
604
605
606
607
608
609
    override :import?
    def import?
      super || gitlab_custom_project_template_import?
    end

    def gitlab_custom_project_template_import?
      import_type == 'gitlab_custom_project_template' &&
        ::Gitlab::CurrentSettings.custom_project_templates_enabled?
    end

610
    def protected_environments_feature_available?
611
      feature_available?(:protected_environments)
612
613
    end

614
615
616
617
618
619
620
621
622
    # Because we use default_value_for we need to be sure
    # packages_enabled= method does exist even if we rollback migration.
    # Otherwise many tests from spec/migrations will fail.
    def packages_enabled=(value)
      if has_attribute?(:packages_enabled)
        write_attribute(:packages_enabled, value)
      end
    end

623
624
625
    # Update the default branch querying the remote to determine its HEAD
    def update_root_ref(remote_name)
      root_ref = repository.find_remote_root_ref(remote_name)
626
      change_head(root_ref) if root_ref.present?
627
628
    end

629
630
    def feature_flags_client_token
      instance = operations_feature_flags_client || create_operations_feature_flags_client!
631
      instance.token
Kamil Trzciński's avatar
Kamil Trzciński committed
632
633
    end

634
635
636
637
638
639
640
641
    def root_namespace
      if namespace.has_parent?
        namespace.root_ancestor
      else
        namespace
      end
    end

642
643
644
645
    def active_webide_pipelines(user:)
      webide_pipelines.running_or_pending.for_user(user)
    end

646
647
648
649
650
651
652
653
    override :lfs_http_url_to_repo
    def lfs_http_url_to_repo(operation)
      return super unless ::Gitlab::Geo.secondary_with_primary?
      return super if operation == GIT_LFS_DOWNLOAD_OPERATION # download always comes from secondary

      geo_primary_http_url_to_repo(self)
    end

654
655
656
657
    def feature_usage
      super.presence || build_feature_usage
    end

658
    # LFS and hashed repository storage are required for using Design Management.
Bob Van Landuyt's avatar
Bob Van Landuyt committed
659
    def design_management_enabled?
660
      lfs_enabled? &&
661
662
663
664
665
666
667
668
669
        # We will allow the hashed storage requirement to be disabled for
        # a few releases until we are able to understand the impact of the
        # hashed storage requirement for existing design management projects.
        # See https://gitlab.com/gitlab-org/gitlab/issues/13428#note_238729038
        (hashed_storage?(:repository) || ::Feature.disabled?(:design_management_require_hashed_storage, self, default_enabled: true)) &&
        # Check both feature availability on the license, as well as the feature
        # flag, because we don't want to enable design_management by default on
        # on prem installs yet.
        # See https://gitlab.com/gitlab-org/gitlab/issues/13709
670
        feature_available?(:design_management) &&
671
        ::Feature.enabled?(:design_management_flag, self, default_enabled: true)
Bob Van Landuyt's avatar
Bob Van Landuyt committed
672
673
    end

Luke Duncalfe's avatar
Luke Duncalfe committed
674
675
676
677
    def design_repository
      @design_repository ||= DesignManagement::Repository.new(self)
    end

Simon Knox's avatar
Simon Knox committed
678
    def alerts_service_available?
679
      feature_available?(:incident_management)
Simon Knox's avatar
Simon Knox committed
680
681
    end

682
    def alerts_service_activated?
Vitali Tatarintev's avatar
Vitali Tatarintev committed
683
      alerts_service_available? && alerts_service&.active?
684
685
    end

686
687
688
689
690
691
692
693
    def package_already_taken?(package_name)
      namespace.root_ancestor.all_projects
        .joins(:packages)
        .where.not(id: id)
        .merge(Packages::Package.with_name(package_name))
        .exists?
    end

694
695
696
697
698
699
700
701
702
703
704
    def adjourned_deletion?
      feature_available?(:marking_project_for_deletion) &&
        ::Gitlab::CurrentSettings.deletion_adjourned_period > 0
    end

    def marked_for_deletion?
      return false unless feature_available?(:marking_project_for_deletion)

      marked_for_deletion_at.present?
    end

705
706
707
708
709
710
    def has_packages?(package_type)
      return false unless feature_available?(:packages)

      packages.where(package_type: package_type).exists?
    end

711
712
713
714
    def license_compliance
      strong_memoize(:license_compliance) { SCA::LicenseCompliance.new(self) }
    end

715
    private
716

717
718
719
720
721
    def set_override_pull_mirror_available
      self.pull_mirror_available_overridden = read_attribute(:mirror)
      true
    end

Tiago Botelho's avatar
Tiago Botelho committed
722
723
724
725
    def set_next_execution_timestamp_to_now
      import_state.set_next_execution_to_now
    end

726
727
728
729
    def licensed_feature_available?(feature, user = nil)
      # This feature might not be behind a feature flag at all, so default to true
      return false unless ::Feature.enabled?(feature, user, default_enabled: true)

Lin Jen-Shin's avatar
Lin Jen-Shin committed
730
      available_features = strong_memoize(:licensed_feature_available) do
731
732
        Hash.new do |h, f|
          h[f] = load_licensed_feature_available(f)
Lin Jen-Shin's avatar
Lin Jen-Shin committed
733
        end
Bob Van Landuyt's avatar
Bob Van Landuyt committed
734
735
      end

Lin Jen-Shin's avatar
Lin Jen-Shin committed
736
      available_features[feature]
Bob Van Landuyt's avatar
Bob Van Landuyt committed
737
738
739
    end

    def load_licensed_feature_available(feature)
740
      globally_available = License.feature_available?(feature)
741

742
      if ::Gitlab::CurrentSettings.should_check_namespace_plan? && namespace
743
        globally_available &&
Bob Van Landuyt's avatar
Bob Van Landuyt committed
744
          (public? && namespace.public? || namespace.feature_available_in_plan?(feature))
745
746
747
748
749
      else
        globally_available
      end
    end

750
751
752
753
754
755
756
757
    def check_pull_mirror_branch_prefix
      return if pull_mirror_branch_prefix.blank?
      return unless pull_mirror_branch_prefix_changed?

      unless ::Gitlab::GitRefValidator.validate("#{pull_mirror_branch_prefix}master")
        errors.add(:pull_mirror_branch_prefix, _('Invalid Git ref'))
      end
    end
758
759
  end
end