Class: Project

Constant Summary collapse

BoardLimitExceeded =
Class.new(StandardError)
STATISTICS_ATTRIBUTE =
'repositories_count'
UNKNOWN_IMPORT_URL =
'http://unknown.git'
LATEST_STORAGE_VERSION =

Hashed Storage versions handle rolling out new storage to project and dependents models: nil: legacy 1: repository 2: attachments

2
HASHED_STORAGE_FEATURES =
{
  repository: 1,
  attachments: 2
}.freeze
VALID_IMPORT_PORTS =
[80, 443].freeze
VALID_IMPORT_PROTOCOLS =
%w(http https git).freeze
VALID_MIRROR_PORTS =
[22, 80, 443].freeze
VALID_MIRROR_PROTOCOLS =
%w(http https ssh git).freeze
ACCESS_REQUEST_APPROVERS_TO_BE_NOTIFIED_LIMIT =
10
SORTING_PREFERENCE_FIELD =
:projects_sort
MAX_BUILD_TIMEOUT =
1.month

Constants included from BatchDestroyDependentAssociations

BatchDestroyDependentAssociations::DEPENDENT_ASSOCIATIONS_BATCH_SIZE

Constants included from WithUploads

WithUploads::FILE_UPLOADERS

Constants included from Gitlab::SQL::Pattern

Gitlab::SQL::Pattern::MIN_CHARS_FOR_PARTIAL_MATCHING, Gitlab::SQL::Pattern::REGEX_QUOTED_WORD

Constants included from CacheMarkdownField

CacheMarkdownField::INVALIDATED_BY

Constants included from Avatarable

Avatarable::ALLOWED_IMAGE_SCALER_WIDTHS

Constants included from Gitlab::VisibilityLevel

Gitlab::VisibilityLevel::INTERNAL, Gitlab::VisibilityLevel::PRIVATE, Gitlab::VisibilityLevel::PUBLIC

Instance Attribute Summary collapse

Attributes included from Gitlab::Cache::RequestCache

#request_cache_key_block

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Gitlab::ConfigHelper

gitlab_config, gitlab_config_features

Methods included from Gitlab::Cache::RequestCache

extended, request_cache, request_cache_key

Methods included from FeatureGate

#flipper_id

Methods included from BatchDestroyDependentAssociations

#dependent_associations_to_destroy, #destroy_dependent_associations_in_batches

Methods included from WithUploads

#retrieve_upload

Methods included from FastDestroyAll::Helpers

#perform_fast_destroy

Methods included from AfterCommitQueue

#run_after_commit, #run_after_commit_or_now

Methods included from ChronicDurationAttribute

#chronic_duration_attributes, #output_chronic_duration_attribute

Methods included from Gitlab::Utils::StrongMemoize

#clear_memoization, #strong_memoize, #strong_memoized?

Methods included from DeploymentPlatform

#deployment_platform

Methods included from GroupDescendant

build_hierarchy, #hierarchy

Methods included from Routable

#build_full_path, #full_name, #full_path, #full_path_components, #owned_by?

Methods included from HasWiki

#create_wiki, #wiki, #wiki_repository_exists?

Methods included from HasRepository

#commit, #commit_by, #commits_by, #default_branch, #default_branch_from_preferences, #empty_repo?, #full_path, #http_url_to_repo, #reload_default_branch, #repo_exists?, #repository_exists?, #repository_size_checker, #root_ref?, #ssh_url_to_repo, #url_to_repo, #valid_repo?, #web_url

Methods included from Gitlab::ShellAdapter

#gitlab_shell

Methods included from Referable

#referable_inspect, #reference_link_text

Methods included from Presentable

#present

Methods included from ProjectFeaturesCompatibility

#builds_access_level=, #builds_enabled=, #forking_access_level=, #issues_access_level=, #issues_enabled=, #merge_requests_access_level=, #merge_requests_enabled=, #metrics_dashboard_access_level=, #pages_access_level=, #repository_access_level=, #snippets_access_level=, #snippets_enabled=, #wiki_access_level=, #wiki_enabled=

Methods included from ProjectAPICompatibility

#auto_devops_deploy_strategy=, #auto_devops_enabled=, #build_git_strategy=

Methods included from ValidAttribute

#valid_attribute?

Methods included from CacheMarkdownField

#attribute_invalidated?, #banzai_render_context, #cached_html_for, #cached_html_up_to_date?, #can_cache_field?, #invalidated_markdown_cache?, #latest_cached_markdown_version, #local_version, #parent_user, #refresh_markdown_cache, #refresh_markdown_cache!, #rendered_field_content, #skip_project_check?, #updated_cached_html_for

Methods included from Avatarable

#avatar_path, #avatar_type, #uncached_avatar_path, #upload_paths

Methods included from AccessRequestable

#request_access

Methods included from Gitlab::VisibilityLevel

allowed_for?, allowed_level?, allowed_levels, closest_allowed_level, #internal?, level_name, level_value, levels_for_user, non_restricted_level?, options, #private?, #public?, public_visibility_restricted?, restricted_level?, string_level, string_options, string_values, valid_level?, #visibility, #visibility=, #visibility_attribute_present?, #visibility_level_attributes, #visibility_level_decreased?, #visibility_level_previous_changes, #visibility_level_value

Methods inherited from ApplicationRecord

at_most, id_in, id_not_in, iid_in, pluck_primary_key, primary_key_in, safe_ensure_unique, safe_find_or_create_by, safe_find_or_create_by!, underscore, without_order

Constructor Details

#initialize(attributes = nil) ⇒ Project

Returns a new instance of Project.


773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
# File 'app/models/project.rb', line 773

def initialize(attributes = nil)
  # We can't use default_value_for because the database has a default
  # value of 0 for visibility_level. If someone attempts to create a
  # private project, default_value_for will assume that the
  # visibility_level hasn't changed and will use the application
  # setting default, which could be internal or public. For projects
  # inside a private group, those levels are invalid.
  #
  # To fix the problem, we assign the actual default in the application if
  # no explicit visibility has been initialized.
  attributes ||= {}

  unless visibility_attribute_present?(attributes)
    attributes[:visibility_level] = Gitlab::CurrentSettings.default_project_visibility
  end

  super
end

Instance Attribute Details

#old_path_with_namespaceObject

Returns the value of attribute old_path_with_namespace


125
126
127
# File 'app/models/project.rb', line 125

def old_path_with_namespace
  @old_path_with_namespace
end

#pipeline_statusObject

Lazy loading of the `pipeline_status` attribute


1887
1888
1889
# File 'app/models/project.rb', line 1887

def pipeline_status
  @pipeline_status ||= Gitlab::Cache::Ci::ProjectPipelineStatus.load_for_project(self)
end

#skip_disk_validationObject

Returns the value of attribute skip_disk_validation


128
129
130
# File 'app/models/project.rb', line 128

def skip_disk_validation
  @skip_disk_validation
end

#template_nameObject

Returns the value of attribute template_name


126
127
128
# File 'app/models/project.rb', line 126

def template_name
  @template_name
end

Class Method Details

.cached_countObject


745
746
747
748
749
# File 'app/models/project.rb', line 745

def cached_count
  Rails.cache.fetch('total_project_count', expires_in: 5.minutes) do
    Project.count
  end
end

.eager_load_namespace_and_ownerObject


584
585
586
# File 'app/models/project.rb', line 584

def self.eager_load_namespace_and_owner
  includes(namespace: :owner)
end

.filter_by_feature_visibility(feature, user) ⇒ Object

This scope returns projects where user has access to both the project and the feature.


646
647
648
649
650
651
652
# File 'app/models/project.rb', line 646

def self.filter_by_feature_visibility(feature, user)
  with_feature_available_for_user(feature, user)
    .public_or_visible_to_user(
      user,
      ProjectFeature.required_minimum_access_level_for_private_project(feature)
    )
end

.find_by_service_desk_project_key(key) ⇒ Object


766
767
768
769
770
# File 'app/models/project.rb', line 766

def find_by_service_desk_project_key(key)
  # project_key is not indexed for now
  # see https://gitlab.com/gitlab-org/gitlab/-/merge_requests/24063#note_282435524 for details
  joins(:service_desk_setting).find_by('service_desk_settings.project_key' => key)
end

.group_idsObject


751
752
753
# File 'app/models/project.rb', line 751

def group_ids
  joins(:namespace).where(namespaces: { type: 'Group' }).select(:namespace_id)
end

.ids_with_issuables_available_for(user) ⇒ Object

Returns ids of projects with issuables available for given user

Used on queries to find milestones or labels which user can see For example: Milestone.where(project_id: ids_with_issuables_available_for(user))


759
760
761
762
763
764
# File 'app/models/project.rb', line 759

def ids_with_issuables_available_for(user)
  with_issues_enabled = with_issues_available_for_user(user).select(:id)
  with_merge_requests_enabled = with_merge_requests_available_for_user(user).select(:id)

  from_union([with_issues_enabled, with_merge_requests_enabled]).select(:id)
end

.markdown_reference_patternObject

Pattern used to extract `namespace/project>` project references from text. '>' or its escaped form ('>') are checked for because '>' is sometimes escaped when the reference comes from an external source.


732
733
734
735
736
737
738
# File 'app/models/project.rb', line 732

def markdown_reference_pattern
  @markdown_reference_pattern ||=
    %r{
      #{reference_pattern}
      (#{reference_postfix}|#{reference_postfix_escaped})
    }x
end

.projects_user_can(projects, user, action) ⇒ Object


637
638
639
640
641
642
643
# File 'app/models/project.rb', line 637

def self.projects_user_can(projects, user, action)
  projects = where(id: projects)

  DeclarativePolicy.user_scope do
    projects.select { |project| Ability.allowed?(user, action, project) }
  end
end

.public_or_visible_to_user(user = nil, min_access_level = nil) ⇒ Object

Returns a collection of projects that is either public or visible to the logged in user.


590
591
592
593
594
595
596
597
598
599
600
601
602
# File 'app/models/project.rb', line 590

def self.public_or_visible_to_user(user = nil, min_access_level = nil)
  min_access_level = nil if user&.admin?

  return public_to_user unless user

  if user.is_a?(DeployToken)
    user.projects
  else
    where('EXISTS (?) OR projects.visibility_level IN (?)',
          user.authorizations_for_projects(min_access_level: min_access_level),
          Gitlab::VisibilityLevel.levels_for_user(user))
  end
end

.reference_patternObject


713
714
715
716
717
718
719
# File 'app/models/project.rb', line 713

def reference_pattern
  %r{
    (?<!#{Gitlab::PathRegex::PATH_START_CHAR})
    ((?<namespace>#{Gitlab::PathRegex::FULL_NAMESPACE_FORMAT_REGEX})\/)?
    (?<project>#{Gitlab::PathRegex::PROJECT_PATH_FORMAT_REGEX})
  }x
end

.reference_postfixObject


721
722
723
# File 'app/models/project.rb', line 721

def reference_postfix
  '>'
end

.reference_postfix_escapedObject


725
726
727
# File 'app/models/project.rb', line 725

def reference_postfix_escaped
  '&gt;'
end

.search(query, include_namespace: false) ⇒ Object

Searches for a list of projects based on the query given in `query`.

On PostgreSQL this method uses “ILIKE” to perform a case-insensitive search.

query - The search query as a String.


678
679
680
681
682
683
684
# File 'app/models/project.rb', line 678

def search(query, include_namespace: false)
  if include_namespace
    joins(:route).fuzzy_search(query, [Route.arel_table[:path], Route.arel_table[:name], :description])
  else
    fuzzy_search(query, [:path, :name, :description])
  end
end

.search_by_title(query) ⇒ Object


686
687
688
# File 'app/models/project.rb', line 686

def search_by_title(query)
  non_archived.fuzzy_search(query, [:name])
end

.sort_by_attribute(method) ⇒ Object


694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
# File 'app/models/project.rb', line 694

def sort_by_attribute(method)
  case method.to_s
  when 'storage_size_desc'
    # storage_size is a joined column so we need to
    # pass a string to avoid AR adding the table name
    reorder('project_statistics.storage_size DESC, projects.id DESC')
  when 'latest_activity_desc'
    reorder(self.arel_table['last_activity_at'].desc)
  when 'latest_activity_asc'
    reorder(self.arel_table['last_activity_at'].asc)
  when 'stars_desc'
    sorted_by_stars_desc
  when 'stars_asc'
    sorted_by_stars_asc
  else
    order_by(method)
  end
end

740
741
742
743
# File 'app/models/project.rb', line 740

def trending
  joins('INNER JOIN trending_projects ON projects.id = trending_projects.project_id')
    .reorder('trending_projects.id ASC')
end

.visibility_levelsObject


690
691
692
# File 'app/models/project.rb', line 690

def visibility_levels
  Gitlab::VisibilityLevel.options
end

.with_api_entity_associationsObject


576
577
578
# File 'app/models/project.rb', line 576

def self.with_api_entity_associations
  preload(:project_feature, :route, :tags, :group, namespace: [:route, :owner])
end

.with_feature_available_for_user(feature, user) ⇒ Object

project features may be “disabled”, “internal”, “enabled” or “public”. If “internal”, they are only available to team members. This scope returns projects where the feature is either public, enabled, or internal with permission for the user. Note: this scope doesn't enforce that the user has access to the projects, it just checks that the user has access to the feature. It's important to use this scope with others that checks project authorizations first (e.g. `filter_by_feature_visibility`).

This method uses an optimised version of `with_feature_access_level` for logged in users to more efficiently get private projects with the given feature.


614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
# File 'app/models/project.rb', line 614

def self.with_feature_available_for_user(feature, user)
  visible = [ProjectFeature::ENABLED, ProjectFeature::PUBLIC]

  if user&.admin?
    with_feature_enabled(feature)
  elsif user
    min_access_level = ProjectFeature.required_minimum_access_level(feature)
    column = ProjectFeature.quoted_access_level_column(feature)

    with_project_feature
    .where("#{column} IS NULL OR #{column} IN (:public_visible) OR (#{column} = :private_visible AND EXISTS (:authorizations))",
          {
            public_visible: visible,
            private_visible: ProjectFeature::PRIVATE,
            authorizations: user.authorizations_for_projects(min_access_level: min_access_level)
          })
  else
    # This has to be added to include features whose value is nil in the db
    visible << nil
    with_feature_access_level(feature, visible)
  end
end

.with_web_entity_associationsObject


580
581
582
# File 'app/models/project.rb', line 580

def self.with_web_entity_associations
  preload(:project_feature, :route, :creator, :group, namespace: [:route, :owner])
end

.wrap_with_cte(collection) ⇒ Object


654
655
656
657
# File 'app/models/project.rb', line 654

def self.wrap_with_cte(collection)
  cte = Gitlab::SQL::CTE.new(:projects_cte, collection)
  Project.with(cte.to_arel).from(cte.alias_to(Project.arel_table))
end

Instance Method Details

#access_request_approvers_to_be_notifiedObject


2400
2401
2402
# File 'app/models/project.rb', line 2400

def access_request_approvers_to_be_notified
  members.maintainers..limit(ACCESS_REQUEST_APPROVERS_TO_BE_NOTIFIED_LIMIT)
end

#active_runnersObject


1710
1711
1712
1713
1714
# File 'app/models/project.rb', line 1710

def active_runners
  strong_memoize(:active_runners) do
    all_runners.active
  end
end

#active_webide_pipelines(user:) ⇒ Object


812
813
814
# File 'app/models/project.rb', line 812

def active_webide_pipelines(user:)
  webide_pipelines.running_or_pending.for_user(user)
end

#add_export_job(current_user:, after_export_strategy: nil, params: {}) ⇒ Object


1891
1892
1893
1894
1895
1896
1897
1898
1899
# File 'app/models/project.rb', line 1891

def add_export_job(current_user:, after_export_strategy: nil, params: {})
  job_id = ProjectExportWorker.perform_async(current_user.id, self.id, after_export_strategy, params)

  if job_id
    Gitlab::AppLogger.info "Export job started for project ID #{self.id} with job ID #{job_id}"
  else
    Gitlab::AppLogger.error "Export job failed to start for project ID #{self.id}"
  end
end

#add_import_jobObject


988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
# File 'app/models/project.rb', line 988

def add_import_job
  job_id =
    if forked?
      RepositoryForkWorker.perform_async(id)
    elsif gitlab_project_import?
      # Do not retry on Import/Export until https://gitlab.com/gitlab-org/gitlab-foss/issues/26189 is solved.
      RepositoryImportWorker.set(retry: false).perform_async(self.id)
    else
      RepositoryImportWorker.perform_async(self.id)
    end

  log_import_activity(job_id)

  job_id
end

#after_create_default_branchObject

rubocop: disable CodeReuse/ServiceClass


1881
1882
1883
# File 'app/models/project.rb', line 1881

def after_create_default_branch
  Projects::ProtectDefaultBranchService.new(self).execute
end

#after_importObject


1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
# File 'app/models/project.rb', line 1849

def after_import
  repository.expire_content_cache
  wiki.repository.expire_content_cache

  DetectRepositoryLanguagesWorker.perform_async(id)

  # The import assigns iid values on its own, e.g. by re-using GitHub ids.
  # Flush existing InternalId records for this project for consistency reasons.
  # Those records are going to be recreated with the next normal creation
  # of a model instance (e.g. an Issue).
  InternalId.flush_records!(project: self)

  import_state.finish
  update_project_counter_caches
  after_create_default_branch
  join_pool_repository
  refresh_markdown_cache!
  write_repository_config
end

#alerts_service_activated?Boolean

Returns:

  • (Boolean)

2441
2442
2443
# File 'app/models/project.rb', line 2441

def alerts_service_activated?
  alerts_service&.active?
end

#all_clustersObject


1381
1382
1383
1384
1385
1386
# File 'app/models/project.rb', line 1381

def all_clusters
  group_clusters = Clusters::Cluster.joins(:groups).where(cluster_groups: { group_id: ancestors_upto } )
  instance_clusters = Clusters::Cluster.instance_type

  Clusters::Cluster.from_union([clusters, group_clusters, instance_clusters])
end

#all_lfs_objectsObject

This will return all `lfs_objects` that are accessible to the project and the fork source. This is needed since older forks won't have access to some LFS objects directly and have to get it from the fork source.

TODO: Remove this method once all LfsObjectsProject records are backfilled for forks. At that point, projects can look at their own `lfs_objects`.

See gitlab.com/gitlab-org/gitlab/issues/122002 for more info.


1496
1497
1498
1499
1500
1501
# File 'app/models/project.rb', line 1496

def all_lfs_objects
  LfsObject
    .distinct
    .joins(:lfs_objects_projects)
    .where(lfs_objects_projects: { project_id: [self, lfs_storage_project] })
end

#all_lfs_objects_oids(oids: []) ⇒ Object

TODO: Remove this method once all LfsObjectsProject records are backfilled for forks. At that point, projects can look at their own `lfs_objects` so `lfs_objects_oids` can be used instead.

See gitlab.com/gitlab-org/gitlab/issues/122002 for more info.


1508
1509
1510
# File 'app/models/project.rb', line 1508

def all_lfs_objects_oids(oids: [])
  oids(all_lfs_objects, oids: oids)
end

#all_pipelinesObject


796
797
798
799
800
801
802
# File 'app/models/project.rb', line 796

def all_pipelines
  if builds_enabled?
    super
  else
    super.external
  end
end

#all_runnersObject


1706
1707
1708
# File 'app/models/project.rb', line 1706

def all_runners
  Ci::Runner.from_union([runners, group_runners, shared_runners])
end

#allowed_to_share_with_group?Boolean

Returns:

  • (Boolean)

1669
1670
1671
# File 'app/models/project.rb', line 1669

def allowed_to_share_with_group?
  !namespace.share_with_group_lock
end

#ancestors_upto(top = nil, hierarchy_order: nil) ⇒ Object Also known as: ancestors

returns all ancestor-groups upto but excluding the given namespace when no namespace is given, all ancestors upto the top are returned


829
830
831
832
# File 'app/models/project.rb', line 829

def ancestors_upto(top = nil, hierarchy_order: nil)
  Gitlab::ObjectHierarchy.new(Group.where(id: namespace_id))
    .base_and_ancestors(upto: top, hierarchy_order: hierarchy_order)
end

#any_branch_allows_collaboration?(user) ⇒ Boolean

Returns:

  • (Boolean)

2310
2311
2312
# File 'app/models/project.rb', line 2310

def any_branch_allows_collaboration?(user)
  fetch_branch_allows_collaboration(user)
end

#any_lfs_file_locks?Boolean

Returns:

  • (Boolean)

2335
2336
2337
# File 'app/models/project.rb', line 2335

def any_lfs_file_locks?
  lfs_file_locks.any?
end

#any_runners?(&block) ⇒ Boolean

Returns:

  • (Boolean)

1716
1717
1718
# File 'app/models/project.rb', line 1716

def any_runners?(&block)
  active_runners.any?(&block)
end

#api_variablesObject


2015
2016
2017
2018
2019
# File 'app/models/project.rb', line 2015

def api_variables
  Gitlab::Ci::Variables::Collection.new.tap do |variables|
    variables.append(key: 'CI_API_V4_URL', value: API::Helpers::Version.new('v4').root_url)
  end
end

#auto_cancel_pending_pipelines?Boolean

Returns:

  • (Boolean)

2340
2341
2342
# File 'app/models/project.rb', line 2340

def auto_cancel_pending_pipelines?
  auto_cancel_pending_pipelines == 'enabled'
end

#auto_devops_enabled?Boolean

Returns:

  • (Boolean)

851
852
853
854
855
856
857
# File 'app/models/project.rb', line 851

def auto_devops_enabled?
  if auto_devops&.enabled.nil?
    has_auto_devops_implicitly_enabled?
  else
    auto_devops.enabled?
  end
end

#auto_devops_variablesObject


2103
2104
2105
2106
2107
# File 'app/models/project.rb', line 2103

def auto_devops_variables
  return [] unless auto_devops_enabled?

  (auto_devops || build_auto_devops)&.predefined_variables
end

#autoclose_referenced_issuesObject


816
817
818
819
820
# File 'app/models/project.rb', line 816

def autoclose_referenced_issues
  return true if super.nil?

  super
end

#avatar_in_gitObject


1368
1369
1370
# File 'app/models/project.rb', line 1368

def avatar_in_git
  repository.avatar
end

#avatar_url(**args) ⇒ Object


1372
1373
1374
# File 'app/models/project.rb', line 1372

def avatar_url(**args)
  Gitlab::Routing.url_helpers.project_avatar_url(self) if avatar_in_git
end

#badgesObject


2290
2291
2292
2293
2294
2295
2296
2297
# File 'app/models/project.rb', line 2290

def badges
  return project_badges unless group

  Badge.from_union([
    project_badges,
    GroupBadge.where(group: group.self_and_ancestors)
  ])
end

#bare_repository_import?Boolean

Returns:

  • (Boolean)

1083
1084
1085
# File 'app/models/project.rb', line 1083

def bare_repository_import?
  import_type == 'bare_repository'
end

#botsObject


1604
1605
1606
# File 'app/models/project.rb', line 1604

def bots
  users.project_bot
end

#branch_allows_collaboration?(user, branch_name) ⇒ Boolean

Returns:

  • (Boolean)

2314
2315
2316
# File 'app/models/project.rb', line 2314

def branch_allows_collaboration?(user, branch_name)
  fetch_branch_allows_collaboration(user, branch_name)
end

#build_commit_note(commit) ⇒ Object


1245
1246
1247
# File 'app/models/project.rb', line 1245

def build_commit_note(commit)
  notes.new(commit_id: commit.id, noteable_type: 'Commit')
end

#cache_has_external_issue_trackerObject


1297
1298
1299
# File 'app/models/project.rb', line 1297

def cache_has_external_issue_tracker
  update_column(:has_external_issue_tracker, services.external_issue_trackers.any?) if Gitlab::Database.read_write?
end

#cache_has_external_wikiObject


1321
1322
1323
# File 'app/models/project.rb', line 1321

def cache_has_external_wiki
  update_column(:has_external_wiki, services.external_wikis.any?) if Gitlab::Database.read_write?
end

#change_head(branch) ⇒ Object


1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
# File 'app/models/project.rb', line 1624

def change_head(branch)
  if repository.branch_exists?(branch)
    repository.before_change_head
    repository.raw_repository.write_ref('HEAD', "refs/heads/#{branch}")
    repository.copy_gitattributes(branch)
    repository.after_change_head
    ProjectCacheWorker.perform_async(self.id, [], [:commit_count])
    reload_default_branch
  else
    errors.add(:base, _("Could not change HEAD: branch '%{branch}' does not exist") % { branch: branch })
    false
  end
end

#check_personal_projects_limitObject


1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
# File 'app/models/project.rb', line 1130

def check_personal_projects_limit
  # Since this method is called as validation hook, `creator` might not be
  # present. Since the validation for that will fail, we can just return
  # early.
  return if !creator || creator.can_create_project? ||
      namespace.kind == 'group'

  limit = creator.projects_limit
  error =
    if limit == 0
      _('Personal project creation is not allowed. Please contact your administrator with questions')
    else
      _('Your project limit is %{limit} projects! Please contact your administrator to increase it')
    end

  self.errors.add(:limit_reached, error % { limit: limit })
end

#check_repository_path_availabilityObject

Check if repository already exists on disk


1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
# File 'app/models/project.rb', line 1532

def check_repository_path_availability
  return true if skip_disk_validation
  return false unless repository_storage

  # Check if repository with same path already exists on disk we can
  # skip this for the hashed storage because the path does not change
  if legacy_storage? && repository_with_same_path_already_exists?
    errors.add(:base, _('There is already a repository with that name on disk'))
    return false
  end

  true
rescue GRPC::Internal # if the path is too long
  false
end

#ci_config_path=(value) ⇒ Object


1028
1029
1030
1031
# File 'app/models/project.rb', line 1028

def ci_config_path=(value)
  # Strip all leading slashes so that //foo -> foo
  super(value&.delete("\0"))
end

#ci_config_path_or_defaultObject


2526
2527
2528
# File 'app/models/project.rb', line 2526

def ci_config_path_or_default
  ci_config_path.presence || Ci::Pipeline::DEFAULT_CONFIG_PATH
end

#ci_instance_variables_for(ref:) ⇒ Object


2064
2065
2066
2067
2068
2069
2070
# File 'app/models/project.rb', line 2064

def ci_instance_variables_for(ref:)
  if protected_for?(ref)
    Ci::InstanceVariable.all_cached
  else
    Ci::InstanceVariable.unprotected_cached
  end
end

#ci_pipelinesObject


804
805
806
807
808
809
810
# File 'app/models/project.rb', line 804

def ci_pipelines
  if builds_enabled?
    super
  else
    super.external
  end
end

#ci_serviceObject


1356
1357
1358
# File 'app/models/project.rb', line 1356

def ci_service
  @ci_service ||= ci_services.reorder(nil).find_by(active: true)
end

#ci_servicesObject

rubocop: enable CodeReuse/ServiceClass


1352
1353
1354
# File 'app/models/project.rb', line 1352

def ci_services
  services.where(category: :ci)
end

#ci_variables_for(ref:, environment: nil) ⇒ Object


2042
2043
2044
2045
2046
2047
2048
# File 'app/models/project.rb', line 2042

def ci_variables_for(ref:, environment: nil)
  cache_key = "ci_variables_for:project:#{self&.id}:ref:#{ref}:environment:#{environment}"

  ::Gitlab::SafeRequestStore.fetch(cache_key) do
    uncached_ci_variables_for(ref: ref, environment: environment)
  end
end

#cleanupObject Also known as: reload_repository!


917
918
919
# File 'app/models/project.rb', line 917

def cleanup
  @repository = nil
end

#closest_setting(name) ⇒ Object


2408
2409
2410
2411
2412
2413
# File 'app/models/project.rb', line 2408

def closest_setting(name)
  setting = read_attribute(name)
  setting = closest_namespace_setting(name) if setting.nil?
  setting = app_settings_for(name) if setting.nil?
  setting
end

#codeObject

For compatibility with old code


1377
1378
1379
# File 'app/models/project.rb', line 1377

def code
  path
end

#container_registry_urlObject


923
924
925
926
927
# File 'app/models/project.rb', line 923

def container_registry_url
  if Gitlab.config.registry.enabled
    "#{Gitlab.config.registry.host_port}/#{full_path.downcase}"
  end
end

#container_registry_variablesObject


2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
# File 'app/models/project.rb', line 2021

def container_registry_variables
  Gitlab::Ci::Variables::Collection.new.tap do |variables|
    break variables unless Gitlab.config.registry.enabled

    variables.append(key: 'CI_REGISTRY', value: Gitlab.config.registry.host_port)

    if container_registry_enabled?
      variables.append(key: 'CI_REGISTRY_IMAGE', value: container_registry_url)
    end
  end
end

#context_commits_enabled?Boolean

Returns:

  • (Boolean)

885
886
887
# File 'app/models/project.rb', line 885

def context_commits_enabled?
  Feature.enabled?(:context_commits, default_enabled: true)
end

#create_labelsObject

rubocop: disable CodeReuse/ServiceClass


1344
1345
1346
1347
1348
1349
# File 'app/models/project.rb', line 1344

def create_labels
  Label.templates.each do |label|
    params = label.attributes.except('id', 'template', 'created_at', 'updated_at', 'type')
    Labels::FindOrCreateService.new(nil, self, params).execute(skip_authorization: true)
  end
end

#create_or_update_import_data(data: nil, credentials: nil) ⇒ Object


1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
# File 'app/models/project.rb', line 1060

def create_or_update_import_data(data: nil, credentials: nil)
  return if data.nil? && credentials.nil?

  project_import_data = import_data || build_import_data

  project_import_data.merge_data(data.to_h)
  project_import_data.merge_credentials(credentials.to_h)

  project_import_data
end

#create_repository(force: false) ⇒ Object


1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
# File 'app/models/project.rb', line 1553

def create_repository(force: false)
  # Forked import is handled asynchronously
  return if forked? && !force

  repository.create_repository
  repository.after_create

  true
rescue => err
  Gitlab::ErrorTracking.track_exception(err, project: { id: id, full_path: full_path, disk_path: disk_path })
  errors.add(:base, _('Failed to create repository'))
  false
end

#default_branch_or_masterObject


2522
2523
2524
# File 'app/models/project.rb', line 2522

def default_branch_or_master
  default_branch || 'master'
end

#default_branch_protected?Boolean

Returns:

  • (Boolean)

2457
2458
2459
2460
2461
# File 'app/models/project.rb', line 2457

def default_branch_protected?
  branch_protection = Gitlab::Access::BranchProtection.new(self.namespace.default_branch_protection)

  branch_protection.fully_protected? || branch_protection.developer_can_merge?
end

#default_environmentObject


2033
2034
2035
2036
2037
2038
2039
2040
# File 'app/models/project.rb', line 2033

def default_environment
  production_first = Arel.sql("(CASE WHEN name = 'production' THEN 0 ELSE 1 END), id ASC")

  environments
    .with_state(:available)
    .reorder(production_first)
    .first
end

#default_issues_tracker?Boolean

Returns:

  • (Boolean)

1279
1280
1281
# File 'app/models/project.rb', line 1279

def default_issues_tracker?
  !external_issue_tracker
end

#default_merge_request_targetObject


2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
# File 'app/models/project.rb', line 2164

def default_merge_request_target
  return self unless forked_from_project
  return self unless forked_from_project.merge_requests_enabled?

  # When our current visibility is more restrictive than the source project,
  # (e.g., the fork is `private` but the parent is `public`), target the less
  # permissive project
  if visibility_level_value < forked_from_project.visibility_level_value
    self
  else
    forked_from_project
  end
end

#default_ownerObject


1411
1412
1413
1414
1415
1416
1417
1418
1419
# File 'app/models/project.rb', line 1411

def default_owner
  obj = owner

  if obj.respond_to?(:default_owner)
    obj.default_owner
  else
    obj
  end
end

#deploy_token_create_url(opts = {}) ⇒ Object


2449
2450
2451
# File 'app/models/project.rb', line 2449

def deploy_token_create_url(opts = {})
  Gitlab::Routing.url_helpers.create_deploy_token_project_settings_repository_path(self, opts)
end

#deploy_token_revoke_url_for(token) ⇒ Object


2453
2454
2455
# File 'app/models/project.rb', line 2453

def deploy_token_revoke_url_for(token)
  Gitlab::Routing.url_helpers.revoke_project_deploy_token_path(self, token)
end

#deployment_variables(environment:, kubernetes_namespace: nil) ⇒ Object


2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
# File 'app/models/project.rb', line 2091

def deployment_variables(environment:, kubernetes_namespace: nil)
  platform = deployment_platform(environment: environment)

  return [] unless platform.present?

  platform.predefined_variables(
    project: self,
    environment_name: environment,
    kubernetes_namespace: kubernetes_namespace
  )
end

#design_management_enabled?Boolean

LFS and hashed repository storage are required for using Design Management.

Returns:

  • (Boolean)

890
891
892
# File 'app/models/project.rb', line 890

def design_management_enabled?
  lfs_enabled? && hashed_storage?(:repository)
end

#design_repositoryObject


902
903
904
905
906
# File 'app/models/project.rb', line 902

def design_repository
  strong_memoize(:design_repository) do
    Gitlab::GlRepository::DESIGN.repository_for(self)
  end
end

#disabled_servicesObject


1333
1334
1335
# File 'app/models/project.rb', line 1333

def disabled_services
  []
end

#drop_visibility_level!Object


2415
2416
2417
2418
2419
2420
2421
2422
2423
# File 'app/models/project.rb', line 2415

def drop_visibility_level!
  if group && group.visibility_level < visibility_level
    self.visibility_level = group.visibility_level
  end

  if Gitlab::CurrentSettings.restricted_visibility_levels.include?(visibility_level)
    self.visibility_level = Gitlab::VisibilityLevel::PRIVATE
  end
end

#emails_disabled?Boolean

Returns:

  • (Boolean)

836
837
838
839
840
841
# File 'app/models/project.rb', line 836

def emails_disabled?
  strong_memoize(:emails_disabled) do
    # disabling in the namespace overrides the project setting
    super || namespace.emails_disabled?
  end
end

#enable_ciObject


1690
1691
1692
# File 'app/models/project.rb', line 1690

def enable_ci
  project_feature.update_attribute(:builds_access_level, ProjectFeature::ENABLED)
end

#enabled_group_deploy_keysObject


2530
2531
2532
2533
2534
# File 'app/models/project.rb', line 2530

def enabled_group_deploy_keys
  return GroupDeployKey.none unless group

  GroupDeployKey.for_groups(group.self_and_ancestors_ids)
end

#ensure_repositoryObject


1652
1653
1654
# File 'app/models/project.rb', line 1652

def ensure_repository
  create_repository(force: true) unless repository_exists?
end

#environments_for_scope(scope) ⇒ Object


2463
2464
2465
2466
2467
# File 'app/models/project.rb', line 2463

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

#execute_hooks(data, hooks_scope = :push_hooks) ⇒ Object

rubocop: disable CodeReuse/ServiceClass


1426
1427
1428
1429
1430
1431
1432
1433
# File 'app/models/project.rb', line 1426

def execute_hooks(data, hooks_scope = :push_hooks)
  run_after_commit_or_now do
    hooks.hooks_for(hooks_scope).select_active(hooks_scope, data).each do |hook|
      hook.async_execute(data, hooks_scope.to_s)
    end
    SystemHooksService.new.execute_hooks(data, hooks_scope)
  end
end

#execute_services(data, hooks_scope = :push_hooks) ⇒ Object

rubocop: enable CodeReuse/ServiceClass


1436
1437
1438
1439
1440
1441
1442
1443
# File 'app/models/project.rb', line 1436

def execute_services(data, hooks_scope = :push_hooks)
  # Call only service hooks that are active for this scope
  run_after_commit_or_now do
    services.public_send(hooks_scope).each do |service| # rubocop:disable GitlabSecurity/PublicSend
      service.async_execute(data)
    end
  end
end

#expire_caches_before_rename(old_path) ⇒ Object

Expires various caches before a project is renamed.


1521
1522
1523
1524
1525
1526
1527
1528
1529
# File 'app/models/project.rb', line 1521

def expire_caches_before_rename(old_path)
  project_repo = Repository.new(old_path, self, shard: repository_storage)
  wiki_repo = Repository.new("#{old_path}#{Gitlab::GlRepository::WIKI.path_suffix}", self, shard: repository_storage, repo_type: Gitlab::GlRepository::WIKI)
  design_repo = Repository.new("#{old_path}#{Gitlab::GlRepository::DESIGN.path_suffix}", self, shard: repository_storage, repo_type: Gitlab::GlRepository::DESIGN)

  [project_repo, wiki_repo, design_repo].each do |repo|
    repo.before_delete if repo.exists?
  end
end

#export_enqueued?Boolean

Returns:

  • (Boolean)

1931
1932
1933
1934
1935
# File 'app/models/project.rb', line 1931

def export_enqueued?
  strong_memoize(:export_enqueued) do
    ::Projects::ExportJobFinder.new(self, { status: :queued }).execute.present?
  end
end

#export_fileObject


1952
1953
1954
# File 'app/models/project.rb', line 1952

def export_file
  import_export_upload&.export_file
end

#export_file_exists?Boolean

Returns:

  • (Boolean)

1948
1949
1950
# File 'app/models/project.rb', line 1948

def export_file_exists?
  export_file&.file
end

#export_in_progress?Boolean

Returns:

  • (Boolean)

1925
1926
1927
1928
1929
# File 'app/models/project.rb', line 1925

def export_in_progress?
  strong_memoize(:export_in_progress) do
    ::Projects::ExportJobFinder.new(self, { status: :started }).execute.present?
  end
end

#export_pathObject


1905
1906
1907
1908
1909
# File 'app/models/project.rb', line 1905

def export_path
  return unless namespace.present? || hashed_storage?(:repository)

  import_export_shared.archive_path
end

#export_statusObject


1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
# File 'app/models/project.rb', line 1911

def export_status
  if regeneration_in_progress?
    :regeneration_in_progress
  elsif export_enqueued?
    :queued
  elsif export_in_progress?
    :started
  elsif export_file_exists?
    :finished
  else
    :none
  end
end

#external_authorization_classification_labelObject


2318
2319
2320
2321
# File 'app/models/project.rb', line 2318

def external_authorization_classification_label
  super || ::Gitlab::CurrentSettings.current_application_settings
             .external_authorization_service_default_label
end

#external_import?Boolean

Returns:

  • (Boolean)

1075
1076
1077
# File 'app/models/project.rb', line 1075

def external_import?
  import_url.present?
end

#external_issue_reference_patternObject


1275
1276
1277
# File 'app/models/project.rb', line 1275

def external_issue_reference_pattern
  external_issue_tracker.class.reference_pattern(only_long: issues_enabled?)
end

#external_issue_trackerObject


1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
# File 'app/models/project.rb', line 1283

def external_issue_tracker
  if has_external_issue_tracker.nil?
    cache_has_external_issue_tracker
  end

  if has_external_issue_tracker?
    strong_memoize(:external_issue_tracker) do
      services.external_issue_trackers.first
    end
  else
    nil
  end
end

#external_references_supported?Boolean

Returns:

  • (Boolean)

1301
1302
1303
# File 'app/models/project.rb', line 1301

def external_references_supported?
  external_issue_tracker&.support_cross_reference?
end

#external_wikiObject


1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
# File 'app/models/project.rb', line 1309

def external_wiki
  if has_external_wiki.nil?
    cache_has_external_wiki
  end

  if has_external_wiki
    @external_wiki ||= services.external_wikis.first
  else
    nil
  end
end

#feature_usageObject


1458
1459
1460
# File 'app/models/project.rb', line 1458

def feature_usage
  super.presence || build_feature_usage
end

#ff_merge_must_be_possible?Boolean

Returns:

  • (Boolean)

2252
2253
2254
# File 'app/models/project.rb', line 2252

def ff_merge_must_be_possible?
  self.merge_requests_ff_only_enabled || self.merge_requests_rebase_enabled
end

#find_or_initialize_service(name) ⇒ Object


1337
1338
1339
1340
1341
# File 'app/models/project.rb', line 1337

def find_or_initialize_service(name)
  return if disabled_services.include?(name)

  find_service(services, name) || build_from_instance_or_template(name) || public_send("build_#{name}_service") # rubocop:disable GitlabSecurity/PublicSend
end

#find_or_initialize_servicesObject


1325
1326
1327
1328
1329
1330
1331
# File 'app/models/project.rb', line 1325

def find_or_initialize_services
  available_services_names = Service.available_services_names - disabled_services

  available_services_names.map do |service_name|
    find_or_initialize_service(service_name)
  end.sort_by(&:title)
end

#first_auto_devops_configObject


875
876
877
878
879
# File 'app/models/project.rb', line 875

def first_auto_devops_config
  return namespace.first_auto_devops_config if auto_devops&.enabled.nil?

  { scope: :project, status: auto_devops&.enabled || Feature.enabled?(:force_autodevops_on_by_default, self) }
end

#fork_sourceObject


1466
1467
1468
1469
1470
# File 'app/models/project.rb', line 1466

def fork_source
  return unless forked?

  forked_from_project || fork_network&.root_project
end

#forked?Boolean

Returns:

  • (Boolean)

1462
1463
1464
# File 'app/models/project.rb', line 1462

def forked?
  fork_network && fork_network.root_project != self
end

#forked_from?(other_project) ⇒ Boolean

Returns:

  • (Boolean)

1638
1639
1640
# File 'app/models/project.rb', line 1638

def forked_from?(other_project)
  forked? && forked_from_project == other_project
end

#forks_countObject

rubocop: disable CodeReuse/ServiceClass


2192
2193
2194
2195
2196
2197
2198
2199
2200
# File 'app/models/project.rb', line 2192

def forks_count
  BatchLoader.for(self).batch do |projects, loader|
    fork_count_per_project = ::Projects::BatchForksCountService.new(projects).refresh_cache_and_retrieve_data

    fork_count_per_project.each do |project, count|
      loader.call(project, count)
    end
  end
end

#full_path_before_last_saveObject


2182
2183
2184
# File 'app/models/project.rb', line 2182

def full_path_before_last_save
  File.join(namespace.full_path, path_before_last_save)
end

#full_path_slugObject


1956
1957
1958
# File 'app/models/project.rb', line 1956

def full_path_slug
  Gitlab::Utils.slugify(full_path.to_s)
end

#get_issue(issue_id, current_user) ⇒ Object


1261
1262
1263
1264
1265
1266
1267
1268
1269
# File 'app/models/project.rb', line 1261

def get_issue(issue_id, current_user)
  issue = IssuesFinder.new(current_user, project_id: id).find_by(iid: issue_id) if issues_enabled?

  if issue
    issue
  elsif external_issue_tracker
    ExternalIssue.new(issue_id, self)
  end
end

#git_objects_poolable?Boolean

Git objects are only poolable when the project is or has:

  • Hashed storage -> The object pool will have a remote to its members, using relative paths.

    If the repository path changes we would have to update the remote.
    
  • not private -> The visibility level or repository access level has to be greater than private

    to prevent fetching objects that might not exist
    
  • Repository -> Else the disk path will be empty, and there's nothing to pool

Returns:

  • (Boolean)

2380
2381
2382
2383
2384
2385
2386
# File 'app/models/project.rb', line 2380

def git_objects_poolable?
  hashed_storage?(:repository) &&
    visibility_level > Gitlab::VisibilityLevel::PRIVATE &&
    repository_access_level > ProjectFeature::PRIVATE &&
    repository_exists? &&
    Gitlab::CurrentSettings.hashed_storage_enabled
end

#git_transfer_in_progress?Boolean

Returns:

  • (Boolean)

2276
2277
2278
# File 'app/models/project.rb', line 2276

def git_transfer_in_progress?
  repo_reference_count > 0 || wiki_reference_count > 0
end

#gitea_import?Boolean

Returns:

  • (Boolean)

1095
1096
1097
# File 'app/models/project.rb', line 1095

def gitea_import?
  import_type == 'gitea'
end

#gitlab_deploy_tokenObject


2331
2332
2333
# File 'app/models/project.rb', line 2331

def gitlab_deploy_token
  @gitlab_deploy_token ||= deploy_tokens.gitlab_deploy_token
end

#gitlab_project_import?Boolean

Returns:

  • (Boolean)

1091
1092
1093
# File 'app/models/project.rb', line 1091

def gitlab_project_import?
  import_type == 'gitlab_project'
end

#group_runnersObject


1702
1703
1704
# File 'app/models/project.rb', line 1702

def group_runners
  @group_runners ||= group_runners_enabled? ? Ci::Runner.belonging_to_parent_group_of_project(self.id) : Ci::Runner.none
end

#has_active_hooks?(hooks_scope = :push_hooks) ⇒ Boolean

Returns:

  • (Boolean)

1445
1446
1447
# File 'app/models/project.rb', line 1445

def has_active_hooks?(hooks_scope = :push_hooks)
  hooks.hooks_for(hooks_scope).any? || SystemHook.hooks_for(hooks_scope).any? || Gitlab::FileHook.any?
end

#has_active_services?(hooks_scope = :push_hooks) ⇒ Boolean

Returns:

  • (Boolean)

1449
1450
1451
# File 'app/models/project.rb', line 1449

def has_active_services?(hooks_scope = :push_hooks)
  services.public_send(hooks_scope).any? # rubocop:disable GitlabSecurity/PublicSend
end

#has_auto_devops_implicitly_disabled?Boolean

Returns:

  • (Boolean)

865
866
867
868
869
# File 'app/models/project.rb', line 865

def has_auto_devops_implicitly_disabled?
  auto_devops_config = first_auto_devops_config

  auto_devops_config[:scope] != :project && !auto_devops_config[:status]
end

#has_auto_devops_implicitly_enabled?Boolean

Returns:

  • (Boolean)

859
860
861
862
863
# File 'app/models/project.rb', line 859

def has_auto_devops_implicitly_enabled?
  auto_devops_config = first_auto_devops_config

  auto_devops_config[:scope] != :project && auto_devops_config[:status]
end

#has_ci?Boolean

Returns:

  • (Boolean)

1960
1961
1962
# File 'app/models/project.rb', line 1960

def has_ci?
  repository.gitlab_ci_yml || auto_devops_enabled?
end

#has_container_registry_tags?Boolean

Returns:

  • (Boolean)

929
930
931
932
933
934
# File 'app/models/project.rb', line 929

def has_container_registry_tags?
  return @images if defined?(@images)

  @images = container_repositories.to_a.any?(&:has_tags?) ||
    has_root_container_repository_tags?
end

#has_packages?(package_type) ⇒ Boolean

Returns:

  • (Boolean)

871
872
873
# File 'app/models/project.rb', line 871

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

#has_pool_repository?Boolean

Returns:

  • (Boolean)

2396
2397
2398
# File 'app/models/project.rb', line 2396

def has_pool_repository?
  pool_repository.present?
end

#has_remote_mirror?Boolean

Returns:

  • (Boolean)

1099
1100
1101
# File 'app/models/project.rb', line 1099

def has_remote_mirror?
  remote_mirror_available? && remote_mirrors.enabled.exists?
end

#has_wiki?Boolean

Returns:

  • (Boolean)

1305
1306
1307
# File 'app/models/project.rb', line 1305

def has_wiki?
  wiki_enabled? || has_external_wiki?
end

#hashed_storage?(feature) ⇒ Boolean

Check if Hashed Storage is enabled for the project with at least informed feature rolled out

Parameters:

  • feature (Symbol)

    that needs to be rolled out for the project (:repository, :attachments)

Returns:

  • (Boolean)

Raises:

  • (ArgumentError)

2210
2211
2212
2213
2214
# File 'app/models/project.rb', line 2210

def hashed_storage?(feature)
  raise ArgumentError, _("Invalid feature") unless HASHED_STORAGE_FEATURES.include?(feature)

  self.storage_version && self.storage_version >= HASHED_STORAGE_FEATURES[feature]
end

#hook_attrs(backward: true) ⇒ Object


1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
# File 'app/models/project.rb', line 1567

def hook_attrs(backward: true)
  attrs = {
    id: id,
    name: name,
    description: description,
    web_url: web_url,
    avatar_url: avatar_url(only_path: false),
    git_ssh_url: ssh_url_to_repo,
    git_http_url: http_url_to_repo,
    namespace: namespace.name,
    visibility_level: visibility_level,
    path_with_namespace: full_path,
    default_branch: default_branch,
    ci_config_path: ci_config_path
  }

  # Backward compatibility
  if backward
    attrs.merge!({
                  homepage: web_url,
                  url: url_to_repo,
                  ssh_url: ssh_url_to_repo,
                  http_url: http_url_to_repo
                })
  end

  attrs
end

#human_import_status_nameObject


984
985
986
# File 'app/models/project.rb', line 984

def human_import_status_name
  import_state&.human_status_name || 'none'
end

#human_merge_methodObject


2220
2221
2222
2223
2224
2225
2226
# File 'app/models/project.rb', line 2220

def human_merge_method
  if merge_method == :ff
    'Fast-forward'
  else
    merge_method.to_s.humanize
  end
end

#import?Boolean

Returns:

  • (Boolean)

1071
1072
1073
# File 'app/models/project.rb', line 1071

def import?
  external_import? || forked? || gitlab_project_import? || jira_import? || bare_repository_import?
end

#import_export_sharedObject


1901
1902
1903
# File 'app/models/project.rb', line 1901

def import_export_shared
  @import_export_shared ||= Gitlab::ImportExport::Shared.new(self)
end

#import_statusObject


976
977
978
# File 'app/models/project.rb', line 976

def import_status
  import_state&.status || 'none'
end

#import_urlObject


1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
# File 'app/models/project.rb', line 1045

def import_url
  if import_data && super.present?
    import_url = Gitlab::UrlSanitizer.new(super, credentials: import_data.credentials)
    import_url.full_url
  else
    super
  end
rescue
  super
end

#import_url=(value) ⇒ Object


1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
# File 'app/models/project.rb', line 1033

def import_url=(value)
  if Gitlab::UrlSanitizer.valid?(value)
    import_url = Gitlab::UrlSanitizer.new(value)
    super(import_url.sanitized_url)

    credentials = import_url.credentials.to_h.transform_values { |value| CGI.unescape(value.to_s) }
    create_or_update_import_data(credentials: credentials)
  else
    super(value)
  end
end

#in_fork_network_of?(other_project) ⇒ Boolean

Returns:

  • (Boolean)

1642
1643
1644
1645
1646
# File 'app/models/project.rb', line 1642

def in_fork_network_of?(other_project)
  return false if fork_network.nil? || other_project.fork_network.nil?

  fork_network == other_project.fork_network
end

#increment_pushes_since_gcObject


2131
2132
2133
# File 'app/models/project.rb', line 2131

def increment_pushes_since_gc
  Gitlab::Redis::SharedState.with { |redis| redis.incr(pushes_since_gc_redis_shared_state_key) }
end

#issue_exists?(issue_id) ⇒ Boolean

Returns:

  • (Boolean)

1271
1272
1273
# File 'app/models/project.rb', line 1271

def issue_exists?(issue_id)
  get_issue(issue_id)
end

#items_for(entity) ⇒ Object


1388
1389
1390
1391
1392
1393
1394
1395
# File 'app/models/project.rb', line 1388

def items_for(entity)
  case entity
  when 'issue' then
    issues
  when 'merge_request' then
    merge_requests
  end
end

#jira_import?Boolean

Returns:

  • (Boolean)

1087
1088
1089
# File 'app/models/project.rb', line 1087

def jira_import?
  import_type == 'jira' && latest_jira_import.present?
end

#jira_import_statusObject


980
981
982
# File 'app/models/project.rb', line 980

def jira_import_status
  latest_jira_import&.status || 'initial'
end

#jira_subscription_exists?Boolean

Returns:

  • (Boolean)

2429
2430
2431
# File 'app/models/project.rb', line 2429

def jira_subscription_exists?
  JiraConnectSubscription.for_project(self).exists?
end

#last_activityObject


1249
1250
1251
# File 'app/models/project.rb', line 1249

def last_activity
  last_event
end

#last_activity_dateObject


1253
1254
1255
# File 'app/models/project.rb', line 1253

def last_activity_date
  [last_activity_at, last_repository_updated_at, updated_at].compact.max
end

#latest_jira_importObject


2469
2470
2471
# File 'app/models/project.rb', line 2469

def latest_jira_import
  jira_imports.last
end

#latest_pipeline(ref = default_branch, sha = nil) ⇒ Object


959
960
961
962
963
964
965
# File 'app/models/project.rb', line 959

def latest_pipeline(ref = default_branch, sha = nil)
  ref = ref.presence || default_branch
  sha ||= commit(ref)&.sha
  return unless sha

  ci_pipelines.newest_first(ref: ref, sha: sha).take
end

#latest_successful_build_for_ref(job_name, ref = default_branch) ⇒ Object

ref can't be HEAD, can only be branch/tag name


937
938
939
940
941
942
943
944
# File 'app/models/project.rb', line 937

def latest_successful_build_for_ref(job_name, ref = default_branch)
  return unless ref

  latest_pipeline = ci_pipelines.latest_successful_for_ref(ref)
  return unless latest_pipeline

  latest_pipeline.builds.latest.with_downloadable_artifacts.find_by(name: job_name)
end

#latest_successful_build_for_ref!(job_name, ref = default_branch) ⇒ Object


955
956
957
# File 'app/models/project.rb', line 955

def latest_successful_build_for_ref!(job_name, ref = default_branch)
  latest_successful_build_for_ref(job_name, ref) || raise(ActiveRecord::RecordNotFound.new("Couldn't find job #{job_name}"))
end

#latest_successful_build_for_sha(job_name, sha) ⇒ Object


946
947
948
949
950
951
952
953
# File 'app/models/project.rb', line 946

def latest_successful_build_for_sha(job_name, sha)
  return unless sha

  latest_pipeline = ci_pipelines.latest_successful_for_sha(sha)
  return unless latest_pipeline

  latest_pipeline.builds.latest.with_downloadable_artifacts.find_by(name: job_name)
end

#latest_successful_pipeline_for(ref = nil) ⇒ Object


1682
1683
1684
1685
1686
1687
1688
# File 'app/models/project.rb', line 1682

def latest_successful_pipeline_for(ref = nil)
  if ref && ref != default_branch
    ci_pipelines.latest_successful_for_ref(ref)
  else
    latest_successful_pipeline_for_default_branch
  end
end

#latest_successful_pipeline_for_default_branchObject


1673
1674
1675
1676
1677
1678
1679
1680
# File 'app/models/project.rb', line 1673

def latest_successful_pipeline_for_default_branch
  if defined?(@latest_successful_pipeline_for_default_branch)
    return @latest_successful_pipeline_for_default_branch
  end

  @latest_successful_pipeline_for_default_branch =
    ci_pipelines.latest_successful_for_ref(default_branch)
end

#leave_pool_repositoryObject


2388
2389
2390
# File 'app/models/project.rb', line 2388

def leave_pool_repository
  pool_repository&.mark_obsolete_if_last(repository) && update_column(:pool_repository_id, nil)
end

#legacy_storage?Boolean

rubocop: enable CodeReuse/ServiceClass

Returns:

  • (Boolean)

2203
2204
2205
# File 'app/models/project.rb', line 2203

def legacy_storage?
  [nil, 0].include?(self.storage_version)
end

#lfs_enabled?Boolean Also known as: lfs_enabled

Returns:

  • (Boolean)

843
844
845
846
847
# File 'app/models/project.rb', line 843

def lfs_enabled?
  return namespace.lfs_enabled? if self[:lfs_enabled].nil?

  self[:lfs_enabled] && Gitlab.config.lfs.enabled
end

#lfs_http_url_to_repo(_) ⇒ Object

Is overridden in EE


1454
1455
1456
# File 'app/models/project.rb', line 1454

def lfs_http_url_to_repo(_)
  http_url_to_repo
end

#lfs_objects_oids(oids: []) ⇒ Object


1512
1513
1514
# File 'app/models/project.rb', line 1512

def lfs_objects_oids(oids: [])
  oids(lfs_objects, oids: oids)
end

#lfs_storage_projectObject

TODO: Remove this method once all LfsObjectsProject records are backfilled for forks.

See gitlab.com/gitlab-org/gitlab/issues/122002 for more info.


1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
# File 'app/models/project.rb', line 1476

def lfs_storage_project
  @lfs_storage_project ||= begin
    result = self

    # TODO: Make this go to the fork_network root immediately
    # dependant on the discussion in: https://gitlab.com/gitlab-org/gitlab-foss/issues/39769
    result = result.fork_source while result&.forked?

    result || self
  end
end

#licensed_featuresObject


2323
2324
2325
# File 'app/models/project.rb', line 2323

def licensed_features
  []
end

#limited_protected_branches(limit) ⇒ Object


2437
2438
2439
# File 'app/models/project.rb', line 2437

def limited_protected_branches(limit)
  protected_branches.limit(limit)
end

2392
2393
2394
# File 'app/models/project.rb', line 2392

def link_pool_repository
  pool_repository&.link_repository(repository)
end

#log_import_activity(job_id, type: :import) ⇒ Object


1004
1005
1006
1007
1008
1009
1010
1011
1012
# File 'app/models/project.rb', line 1004

def log_import_activity(job_id, type: :import)
  job_type = type.to_s.capitalize

  if job_id
    Gitlab::AppLogger.info("#{job_type} job scheduled for #{full_path} with job ID #{job_id}.")
  else
    Gitlab::AppLogger.error("#{job_type} job failed to create for #{full_path}.")
  end
end

#mark_pages_as_deployed(artifacts_archive: nil) ⇒ Object

rubocop: enable CodeReuse/ServiceClass


1831
1832
1833
# File 'app/models/project.rb', line 1831

def mark_pages_as_deployed(artifacts_archive: nil)
  ensure_pages_metadatum.update!(deployed: true, artifacts_archive: artifacts_archive)
end

#mark_pages_as_not_deployedObject


1835
1836
1837
# File 'app/models/project.rb', line 1835

def mark_pages_as_not_deployed
  ensure_pages_metadatum.update!(deployed: false, artifacts_archive: nil)
end

#mark_remote_mirrors_for_removalObject


1121
1122
1123
# File 'app/models/project.rb', line 1121

def mark_remote_mirrors_for_removal
  remote_mirrors.each(&:mark_for_delete_if_blank_url)
end

#mark_stuck_remote_mirrors_as_failed!Object


1113
1114
1115
1116
1117
1118
1119
# File 'app/models/project.rb', line 1113

def mark_stuck_remote_mirrors_as_failed!
  remote_mirrors.stuck.update_all(
    update_status: :failed,
    last_error: _('The remote mirror took to long to complete.'),
    last_update_at: Time.current
  )
end

#max_attachment_sizeObject


2361
2362
2363
# File 'app/models/project.rb', line 2361

def max_attachment_size
  Gitlab::CurrentSettings.max_attachment_size.megabytes.to_i
end

#members_among(users) ⇒ Object

Filters `users` to return only authorized users of the project


1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
# File 'app/models/project.rb', line 1609

def members_among(users)
  if users.is_a?(ActiveRecord::Relation) && !users.loaded?
    authorized_users.merge(users)
  else
    return [] if users.empty?

    user_ids = authorized_users.where(users: { id: users.map(&:id) }).pluck(:id)
    users.select { |user| user_ids.include?(user.id) }
  end
end

#merge_base_commit(first_commit_id, second_commit_id) ⇒ Object


967
968
969
970
# File 'app/models/project.rb', line 967

def merge_base_commit(first_commit_id, second_commit_id)
  sha = repository.merge_base(first_commit_id, second_commit_id)
  commit_by(oid: sha) if sha
end

#merge_methodObject


2228
2229
2230
2231
2232
2233
2234
2235
2236
# File 'app/models/project.rb', line 2228

def merge_method
  if self.merge_requests_ff_only_enabled
    :ff
  elsif self.merge_requests_rebase_enabled
    :rebase_merge
  else
    :merge
  end
end

#merge_method=(method) ⇒ Object


2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
# File 'app/models/project.rb', line 2238

def merge_method=(method)
  case method.to_s
  when "ff"
    self.merge_requests_ff_only_enabled = true
    self.merge_requests_rebase_enabled = true
  when "rebase_merge"
    self.merge_requests_ff_only_enabled = false
    self.merge_requests_rebase_enabled = true
  when "merge"
    self.merge_requests_ff_only_enabled = false
    self.merge_requests_rebase_enabled = false
  end
end

#merge_requests_allowing_push_to_user(user) ⇒ Object


2299
2300
2301
2302
2303
2304
2305
2306
2307
2308
# File 'app/models/project.rb', line 2299

def merge_requests_allowing_push_to_user(user)
  return MergeRequest.none unless user

  developer_access_exists = user.project_authorizations
                              .where('access_level >= ? ', Gitlab::Access::DEVELOPER)
                              .where('project_authorizations.project_id = merge_requests.target_project_id')
                              .limit(1)
                              .select(1)
  merge_requests_allowing_collaboration.where('EXISTS (?)', developer_access_exists)
end

#metrics_settingObject


2473
2474
2475
# File 'app/models/project.rb', line 2473

def metrics_setting
  super || build_metrics_setting
end

#migrate_to_hashed_storage!Object


2256
2257
2258
2259
2260
2261
2262
2263
2264
# File 'app/models/project.rb', line 2256

def migrate_to_hashed_storage!
  return unless storage_upgradable?

  if git_transfer_in_progress?
    HashedStorage::ProjectMigrateWorker.perform_in(Gitlab::ReferenceCounter::REFERENCE_EXPIRE_TIME, id)
  else
    HashedStorage::ProjectMigrateWorker.perform_async(id)
  end
end

#monitoring_serviceObject


1364
1365
1366
# File 'app/models/project.rb', line 1364

def monitoring_service
  @monitoring_service ||= monitoring_services.reorder(nil).find_by(active: true)
end

#monitoring_servicesObject


1360
1361
1362
# File 'app/models/project.rb', line 1360

def monitoring_services
  services.where(category: :monitoring)
end

#multiple_issue_boards_available?Boolean

Returns:

  • (Boolean)

2178
2179
2180
# File 'app/models/project.rb', line 2178

def multiple_issue_boards_available?
  true
end

#new_issuable_address(author, address_type) ⇒ Object


1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
# File 'app/models/project.rb', line 1230

def new_issuable_address(author, address_type)
  return unless Gitlab::IncomingEmail.supports_issue_creation? && author

  # check since this can come from a request parameter
  return unless %w(issue merge_request).include?(address_type)

  author.ensure_incoming_email_token!

  suffix = address_type.dasherize

  # example: in[email protected]localhost.com
  # example: incoming+h[email protected]localhost.com
  Gitlab::IncomingEmail.reply_address("#{full_path_slug}-#{project_id}-#{author.incoming_email_token}-#{suffix}")
end

#object_pool_paramsObject


2365
2366
2367
2368
2369
2370
2371
2372
# File 'app/models/project.rb', line 2365

def object_pool_params
  return {} unless !forked? && git_objects_poolable?

  {
    repository_storage: repository_storage,
    pool_repository:    pool_repository || create_new_pool_repository
  }
end

#open_issues_count(current_user = nil) ⇒ Object

rubocop: disable CodeReuse/ServiceClass


1725
1726
1727
# File 'app/models/project.rb', line 1725

def open_issues_count(current_user = nil)
  Projects::OpenIssuesCountService.new(self, current_user).count
end

#open_merge_requests_countObject

rubocop: disable CodeReuse/ServiceClass


1731
1732
1733
# File 'app/models/project.rb', line 1731

def open_merge_requests_count
  Projects::OpenMergeRequestsCountService.new(self).count
end

#origin_merge_requestsObject


1648
1649
1650
# File 'app/models/project.rb', line 1648

def origin_merge_requests
  merge_requests.where(source_project_id: self.id)
end

#ownerObject

rubocop: enable CodeReuse/ServiceClass


1407
1408
1409
# File 'app/models/project.rb', line 1407

def owner
  group || namespace.try(:owner)
end

#package_already_taken?(package_name) ⇒ Boolean

Returns:

  • (Boolean)

2514
2515
2516
2517
2518
2519
2520
# File 'app/models/project.rb', line 2514

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

#packages_enabled=(value) ⇒ Object

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.


911
912
913
914
915
# File 'app/models/project.rb', line 911

def packages_enabled=(value)
  if has_attribute?(:packages_enabled)
    write_attribute(:packages_enabled, value)
  end
end

#pages_available?Boolean

Returns:

  • (Boolean)

1793
1794
1795
# File 'app/models/project.rb', line 1793

def pages_available?
  Gitlab.config.pages.enabled
end

#pages_deployed?Boolean

Returns:

  • (Boolean)

1759
1760
1761
# File 'app/models/project.rb', line 1759

def pages_deployed?
  pages_metadatum&.deployed?
end

#pages_group_root?Boolean

Returns:

  • (Boolean)

1780
1781
1782
# File 'app/models/project.rb', line 1780

def pages_group_root?
  pages_group_url == pages_url
end

#pages_group_urlObject


1763
1764
1765
1766
1767
1768
# File 'app/models/project.rb', line 1763

def pages_group_url
  # The host in URL always needs to be downcased
  Gitlab.config.pages.url.sub(%r{^https?://}) do |prefix|
    "#{prefix}#{pages_subdomain}."
  end.downcase
end

#pages_https_onlyObject


1167
1168
1169
1170
1171
# File 'app/models/project.rb', line 1167

def pages_https_only
  return false unless Gitlab.config.pages.external_https

  super
end

#pages_https_only?Boolean

Returns:

  • (Boolean)

1173
1174
1175
1176
1177
# File 'app/models/project.rb', line 1173

def pages_https_only?
  return false unless Gitlab.config.pages.external_https

  super
end

#pages_lookup_path(trim_prefix: nil, domain: nil) ⇒ Object


2404
2405
2406
# File 'app/models/project.rb', line 2404

def pages_lookup_path(trim_prefix: nil, domain: nil)
  Pages::LookupPath.new(self, trim_prefix: trim_prefix, domain: domain)
end

#pages_pathObject


1788
1789
1790
1791
# File 'app/models/project.rb', line 1788

def pages_path
  # TODO: when we migrate Pages to work with new storage types, change here to use disk_path
  File.join(Settings.pages.path, full_path)
end

#pages_subdomainObject


1784
1785
1786
# File 'app/models/project.rb', line 1784

def pages_subdomain
  full_path.partition('/').first
end

#pages_urlObject


1770
1771
1772
1773
1774
1775
1776
1777
1778
# File 'app/models/project.rb', line 1770

def pages_url
  url = pages_group_url
  url_path = full_path.partition('/').last

  # If the project path is the same as host, we serve it as group page
  return url if url == "#{Settings.pages.protocol}://#{url_path}".downcase

  "#{url}/#{url_path}"
end

#pages_variablesObject


2006
2007
2008
2009
2010
2011
2012
2013
# File 'app/models/project.rb', line 2006

def pages_variables
  Gitlab::Ci::Variables::Collection.new.tap do |variables|
    break unless pages_enabled?

    variables.append(key: 'CI_PAGES_DOMAIN', value: Gitlab.config.pages.host)
    variables.append(key: 'CI_PAGES_URL', value: pages_url)
  end
end

#parent_changed?Boolean

Returns:

  • (Boolean)

2160
2161
2162
# File 'app/models/project.rb', line 2160

def parent_changed?
  namespace_id_changed?
end

#personal?Boolean

Returns:

  • (Boolean)

1516
1517
1518
# File 'app/models/project.rb', line 1516

def personal?
  !group
end

#predefined_ci_server_variablesObject


1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
# File 'app/models/project.rb', line 1990

def predefined_ci_server_variables
  Gitlab::Ci::Variables::Collection.new
    .append(key: 'CI', value: 'true')
    .append(key: 'GITLAB_CI', value: 'true')
    .append(key: 'CI_SERVER_URL', value: Gitlab.config.gitlab.url)
    .append(key: 'CI_SERVER_HOST', value: Gitlab.config.gitlab.host)
    .append(key: 'CI_SERVER_PORT', value: Gitlab.config.gitlab.port.to_s)
    .append(key: 'CI_SERVER_PROTOCOL', value: Gitlab.config.gitlab.protocol)
    .append(key: 'CI_SERVER_NAME', value: 'GitLab')
    .append(key: 'CI_SERVER_VERSION', value: Gitlab::VERSION)
    .append(key: 'CI_SERVER_VERSION_MAJOR', value: Gitlab.version_info.major.to_s)
    .append(key: 'CI_SERVER_VERSION_MINOR', value: Gitlab.version_info.minor.to_s)
    .append(key: 'CI_SERVER_VERSION_PATCH', value: Gitlab.version_info.patch.to_s)
    .append(key: 'CI_SERVER_REVISION', value: Gitlab.revision)
end

#predefined_project_variablesObject


1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
# File 'app/models/project.rb', line 1974

def predefined_project_variables
  Gitlab::Ci::Variables::Collection.new
    .append(key: 'GITLAB_FEATURES', value: licensed_features.join(','))
    .append(key: 'CI_PROJECT_ID', value: id.to_s)
    .append(key: 'CI_PROJECT_NAME', value: path)
    .append(key: 'CI_PROJECT_TITLE', value: title)
    .append(key: 'CI_PROJECT_PATH', value: full_path)
    .append(key: 'CI_PROJECT_PATH_SLUG', value: full_path_slug)
    .append(key: 'CI_PROJECT_NAMESPACE', value: namespace.full_path)
    .append(key: 'CI_PROJECT_ROOT_NAMESPACE', value: namespace.root_ancestor.path)
    .append(key: 'CI_PROJECT_URL', value: web_url)
    .append(key: 'CI_PROJECT_VISIBILITY', value: Gitlab::VisibilityLevel.string_level(visibility_level))
    .append(key: 'CI_PROJECT_REPOSITORY_LANGUAGES', value: repository_languages.map(&:name).join(',').downcase)
    .append(key: 'CI_DEFAULT_BRANCH', value: default_branch)
end

#predefined_variablesObject


1964
1965
1966
1967
1968
1969
1970
1971
1972
# File 'app/models/project.rb', line 1964

def predefined_variables
  Gitlab::Ci::Variables::Collection.new
    .concat(predefined_ci_server_variables)
    .concat(predefined_project_variables)
    .concat(pages_variables)
    .concat(container_registry_variables)
    .concat(auto_devops_variables)
    .concat(api_variables)
end

#preload_protected_branchesObject


822
823
824
825
# File 'app/models/project.rb', line 822

def preload_protected_branches
  preloader = ActiveRecord::Associations::Preloader.new
  preloader.preload(self, protected_branches: [:push_access_levels, :merge_access_levels])
end

#project_idObject


1257
1258
1259
# File 'app/models/project.rb', line 1257

def project_id
  self.id
end

#project_member(user) ⇒ Object


1596
1597
1598
1599
1600
1601
1602
# File 'app/models/project.rb', line 1596

def project_member(user)
  if project_members.loaded?
    project_members.find { |member| member.user_id == user.id }
  else
    project_members.find_by(user_id: user)
  end
end

#project_settingObject


792
793
794
# File 'app/models/project.rb', line 792

def project_setting
  super.presence || build_project_setting
end

#protected_for?(ref) ⇒ Boolean

Returns:

  • (Boolean)

Raises:


2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
# File 'app/models/project.rb', line 2072

def protected_for?(ref)
  raise Repository::AmbiguousRefError if repository.ambiguous_ref?(ref)

  resolved_ref = repository.expand_ref(ref) || ref
  return false unless Gitlab::Git.tag_ref?(resolved_ref) || Gitlab::Git.branch_ref?(resolved_ref)

  ref_name = if resolved_ref == ref
               Gitlab::Git.ref_name(resolved_ref)
             else
               ref
             end

  if Gitlab::Git.branch_ref?(resolved_ref)
    ProtectedBranch.protected?(self, ref_name)
  elsif Gitlab::Git.tag_ref?(resolved_ref)
    ProtectedTag.protected?(self, ref_name)
  end
end

#public_path_for_source_path(path, commit_sha) ⇒ Object


2153
2154
2155
2156
2157
2158
# File 'app/models/project.rb', line 2153

def public_path_for_source_path(path, commit_sha)
  map = route_map_for(commit_sha)
  return unless map

  map.public_path_for_source_path(path)
end

#pushes_since_gcObject


2127
2128
2129
# File 'app/models/project.rb', line 2127

def pushes_since_gc
  Gitlab::Redis::SharedState.with { |redis| redis.get(pushes_since_gc_redis_shared_state_key).to_i }
end

#readme_urlObject


1223
1224
1225
1226
1227
1228
# File 'app/models/project.rb', line 1223

def readme_url
  readme_path = repository.readme_path
  if readme_path
    Gitlab::Routing.url_helpers.project_blob_url(self, File.join(default_branch, readme_path))
  end
end

#reference_counter(type: Gitlab::GlRepository::PROJECT) ⇒ Object


2286
2287
2288
# File 'app/models/project.rb', line 2286

def reference_counter(type: Gitlab::GlRepository::PROJECT)
  Gitlab::ReferenceCounter.new(type.identifier_for_container(self))
end

#regeneration_in_progress?Boolean

Returns:

  • (Boolean)

1937
1938
1939
# File 'app/models/project.rb', line 1937

def regeneration_in_progress?
  (export_enqueued? || export_in_progress?) && export_file_exists?
end

#remote_mirror_available?Boolean

Returns:

  • (Boolean)

1125
1126
1127
1128
# File 'app/models/project.rb', line 1125

def remote_mirror_available?
  remote_mirror_available_overridden ||
    ::Gitlab::CurrentSettings.mirror_available
end

#remove_exportsObject


1941
1942
1943
1944
1945
1946
# File 'app/models/project.rb', line 1941

def remove_exports
  return unless export_file_exists?

  import_export_upload.remove_export_file!
  import_export_upload.save unless import_export_upload.destroyed?
end

#remove_import_dataObject

This method is overridden in EE::Project model


1024
1025
1026
# File 'app/models/project.rb', line 1024

def remove_import_data
  import_data&.destroy
end

#remove_pagesObject

TODO: what to do here when not using Legacy Storage? Do we still need to rename and delay removal? rubocop: disable CodeReuse/ServiceClass


1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
# File 'app/models/project.rb', line 1814

def remove_pages
  # Projects with a missing namespace cannot have their pages removed
  return unless namespace

  mark_pages_as_not_deployed unless destroyed?

  # 1. We rename pages to temporary directory
  # 2. We wait 5 minutes, due to NFS caching
  # 3. We asynchronously remove pages with force
  temp_path = "#{path}.#{SecureRandom.hex}.deleted"

  if Gitlab::PagesTransfer.new.rename_project(path, temp_path, namespace.full_path)
    PagesWorker.perform_in(5.minutes, :remove, namespace.full_path, temp_path)
  end
end

#remove_private_deploy_keysObject


1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
# File 'app/models/project.rb', line 1797

def remove_private_deploy_keys
  exclude_keys_linked_to_other_projects = <<-SQL
    NOT EXISTS (
      SELECT 1
      FROM deploy_keys_projects dkp2
      WHERE dkp2.deploy_key_id = deploy_keys_projects.deploy_key_id
      AND dkp2.project_id != deploy_keys_projects.project_id
    )
  SQL

  deploy_keys.where(public: false)
             .where(exclude_keys_linked_to_other_projects)
             .delete_all
end

#renamed?Boolean

Returns:

  • (Boolean)

2216
2217
2218
# File 'app/models/project.rb', line 2216

def renamed?
  persisted? && path_changed?
end

#repositoryObject


898
899
900
# File 'app/models/project.rb', line 898

def repository
  @repository ||= Gitlab::GlRepository::PROJECT.repository_for(self)
end

#reset_cache_and_import_attrsObject


1014
1015
1016
1017
1018
1019
1020
1021
# File 'app/models/project.rb', line 1014

def reset_cache_and_import_attrs
  run_after_commit do
    ProjectCacheWorker.perform_async(self.id)
  end

  import_state.update(last_error: nil)
  remove_import_data
end

#reset_pushes_since_gcObject


2135
2136
2137
# File 'app/models/project.rb', line 2135

def reset_pushes_since_gc
  Gitlab::Redis::SharedState.with { |redis| redis.del(pushes_since_gc_redis_shared_state_key) }
end

#rollback_to_legacy_storage!Object


2266
2267
2268
2269
2270
2271
2272
2273
2274
# File 'app/models/project.rb', line 2266

def rollback_to_legacy_storage!
  return if legacy_storage?

  if git_transfer_in_progress?
    HashedStorage::ProjectRollbackWorker.perform_in(Gitlab::ReferenceCounter::REFERENCE_EXPIRE_TIME, id)
  else
    HashedStorage::ProjectRollbackWorker.perform_async(id)
  end
end

#root_namespaceObject


2506
2507
2508
2509
2510
2511
2512
# File 'app/models/project.rb', line 2506

def root_namespace
  if namespace.has_parent?
    namespace.root_ancestor
  else
    namespace
  end
end

#route_map_for(commit_sha) ⇒ Object


2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
# File 'app/models/project.rb', line 2139

def route_map_for(commit_sha)
  @route_maps_by_commit ||= Hash.new do |h, sha|
    h[sha] = begin
      data = repository.route_map_for(sha)

      Gitlab::RouteMap.new(data) if data
             rescue Gitlab::RouteMap::FormatError
               nil
    end
  end

  @route_maps_by_commit[commit_sha]
end

#runners_tokenObject


1755
1756
1757
# File 'app/models/project.rb', line 1755

def runners_token
  ensure_runners_token!
end

#safe_import_urlObject


1079
1080
1081
# File 'app/models/project.rb', line 1079

def safe_import_url
  Gitlab::UrlSanitizer.new(import_url).masked_url
end

#saved?Boolean

Returns:

  • (Boolean)

972
973
974
# File 'app/models/project.rb', line 972

def saved?
  id && persisted?
end

#self_monitoring?Boolean

Returns:

  • (Boolean)

2445
2446
2447
# File 'app/models/project.rb', line 2445

def self_monitoring?
  Gitlab::CurrentSettings.self_monitoring_project_id == id
end

#send_move_instructions(old_path_with_namespace) ⇒ Object

rubocop: disable CodeReuse/ServiceClass


1398
1399
1400
1401
1402
1403
1404
# File 'app/models/project.rb', line 1398

def send_move_instructions(old_path_with_namespace)
  # New project path needs to be committed to the DB or notification will
  # retrieve stale information
  run_after_commit do
    NotificationService.new.project_was_moved(self, old_path_with_namespace)
  end
end

#service_desk_addressObject


2483
2484
2485
# File 'app/models/project.rb', line 2483

def service_desk_address
  service_desk_custom_address || service_desk_incoming_address
end

#service_desk_custom_addressObject


2496
2497
2498
2499
2500
2501
2502
2503
2504
# File 'app/models/project.rb', line 2496

def service_desk_custom_address
  return unless ::Gitlab::ServiceDeskEmail.enabled?
  return unless ::Feature.enabled?(:service_desk_custom_address, self)

  key = service_desk_setting&.project_key
  return unless key.present?

  ::Gitlab::ServiceDeskEmail.address_for_key("#{full_path_slug}-#{key}")
end

#service_desk_enabledObject Also known as: service_desk_enabled?


2477
2478
2479
# File 'app/models/project.rb', line 2477

def service_desk_enabled
  Gitlab::ServiceDesk.enabled?(project: self)
end

#service_desk_incoming_addressObject


2487
2488
2489
2490
2491
2492
2493
2494
# File 'app/models/project.rb', line 2487

def service_desk_incoming_address
  return unless service_desk_enabled?

  config = Gitlab.config.incoming_email
  wildcard = Gitlab::IncomingEmail::WILDCARD_PLACEHOLDER

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

#set_repository_read_only!Boolean

Tries to set repository as read_only, checking for existing Git transfers in progress beforehand

Returns:

  • (Boolean)

    true when set to read_only or false when an existing git transfer is in progress


2112
2113
2114
2115
2116
2117
2118
# File 'app/models/project.rb', line 2112

def set_repository_read_only!
  with_lock do
    break false if git_transfer_in_progress?

    update_column(:repository_read_only, true)
  end
end

#set_repository_writable!Object

Set repository as writable again


2121
2122
2123
2124
2125
# File 'app/models/project.rb', line 2121

def set_repository_writable!
  with_lock do
    update_column(:repository_read_only, false)
  end
end

#shared_runnersObject


1698
1699
1700
# File 'app/models/project.rb', line 1698

def shared_runners
  @shared_runners ||= shared_runners_available? ? Ci::Runner.instance_type : Ci::Runner.none
end

#shared_runners_available?Boolean

Returns:

  • (Boolean)

1694
1695
1696
# File 'app/models/project.rb', line 1694

def shared_runners_available?
  shared_runners_enabled?
end

#should_validate_visibility_level?Boolean

Returns:

  • (Boolean)

1148
1149
1150
# File 'app/models/project.rb', line 1148

def should_validate_visibility_level?
  new_record? || changes.has_key?(:visibility_level)
end

#snippets_visible?(user = nil) ⇒ Boolean

Returns:

  • (Boolean)

2357
2358
2359
# File 'app/models/project.rb', line 2357

def snippets_visible?(user = nil)
  Ability.allowed?(user, :read_snippet, self)
end

#storageObject


2344
2345
2346
2347
2348
2349
2350
2351
# File 'app/models/project.rb', line 2344

def storage
  @storage ||=
    if hashed_storage?(:repository)
      Storage::Hashed.new(self)
    else
      Storage::LegacyProject.new(self)
    end
end

#storage_upgradable?Boolean

Returns:

  • (Boolean)

2353
2354
2355
# File 'app/models/project.rb', line 2353

def storage_upgradable?
  storage_version != LATEST_STORAGE_VERSION
end

#storage_version=(value) ⇒ Object


2280
2281
2282
2283
2284
# File 'app/models/project.rb', line 2280

def storage_version=(value)
  super

  @storage = nil if storage_version_changed?
end

#teamObject


894
895
896
# File 'app/models/project.rb', line 894

def team
  @team ||= ProjectTeam.new(self)
end

#template_source?Boolean

Returns:

  • (Boolean)

2425
2426
2427
# File 'app/models/project.rb', line 2425

def template_source?
  false
end

#to_ability_nameObject


1421
1422
1423
# File 'app/models/project.rb', line 1421

def to_ability_name
  model_name.singular
end

#to_human_reference(from = nil) ⇒ Object


1215
1216
1217
1218
1219
1220
1221
# File 'app/models/project.rb', line 1215

def to_human_reference(from = nil)
  if cross_namespace_reference?(from)
    name_with_namespace
  elsif cross_project_reference?(from)
    name
  end
end

#to_paramObject


1187
1188
1189
1190
1191
1192
1193
# File 'app/models/project.rb', line 1187

def to_param
  if persisted? && errors.include?(:path)
    path_was
  else
    path
  end
end

#to_reference(from = nil, full: false) ⇒ Object

Produce a valid reference (see Referable#to_reference)

NB: For projects, all references are 'full' - i.e. they all include the full_path, rather than just the project name. For this reason, we ignore the value of `full:` passed to this method, which is part of the Referable interface.


1201
1202
1203
1204
# File 'app/models/project.rb', line 1201

def to_reference(from = nil, full: false)
  base = to_reference_base(from, full: true)
  "#{base}#{self.class.reference_postfix}"
end

#to_reference_base(from = nil, full: false) ⇒ Object

`from` argument can be a Namespace or Project.


1207
1208
1209
1210
1211
1212
1213
# File 'app/models/project.rb', line 1207

def to_reference_base(from = nil, full: false)
  if full || cross_namespace_reference?(from)
    full_path
  elsif cross_project_reference?(from)
    path
  end
end

#toggle_ci_cd_settings!(settings_attribute) ⇒ Object


2327
2328
2329
# File 'app/models/project.rb', line 2327

def toggle_ci_cd_settings!(settings_attribute)
  ci_cd_settings.toggle!(settings_attribute)
end

#track_project_repositoryObject


1548
1549
1550
1551
# File 'app/models/project.rb', line 1548

def track_project_repository
  repository = project_repository || build_project_repository
  repository.update!(shard_name: repository_storage, disk_path: disk_path)
end

#uncached_ci_variables_for(ref:, environment: nil) ⇒ Object


2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
# File 'app/models/project.rb', line 2050

def uncached_ci_variables_for(ref:, environment: nil)
  result = if protected_for?(ref)
             variables
           else
             variables.unprotected
           end

  if environment
    result.on_environment(environment)
  else
    result.where(environment_scope: '*')
  end
end

Returns:

  • (Boolean)

881
882
883
# File 'app/models/project.rb', line 881

def unlink_forks_upon_visibility_decrease_enabled?
  Feature.enabled?(:unlink_fork_network_upon_visibility_decrease, self, default_enabled: true)
end

#update_forks_visibility_levelObject

update visibility_level of forks


1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
# File 'app/models/project.rb', line 1657

def update_forks_visibility_level
  return if unlink_forks_upon_visibility_decrease_enabled?
  return unless visibility_level < visibility_level_before_last_save

  forks.each do |forked_project|
    if forked_project.visibility_level > visibility_level
      forked_project.visibility_level = visibility_level
      forked_project.save!
    end
  end
end

#update_project_counter_cachesObject


1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
# File 'app/models/project.rb', line 1869

def update_project_counter_caches
  classes = [
    Projects::OpenIssuesCountService,
    Projects::OpenMergeRequestsCountService
  ]

  classes.each do |klass|
    klass.new(self).refresh_cache
  end
end

#update_remote_mirrorsObject


1107
1108
1109
1110
1111
# File 'app/models/project.rb', line 1107

def update_remote_mirrors
  return unless remote_mirror_available?

  remote_mirrors.enabled.each(&:sync)
end

#updating_remote_mirror?Boolean

Returns:

  • (Boolean)

1103
1104
1105
# File 'app/models/project.rb', line 1103

def updating_remote_mirror?
  remote_mirrors.enabled.started.exists?
end

#uses_default_ci_config?Boolean

Returns:

  • (Boolean)

2433
2434
2435
# File 'app/models/project.rb', line 2433

def uses_default_ci_config?
  ci_config_path.blank? || ci_config_path == Gitlab::FileDetector::PATTERNS[:gitlab_ci]
end

#valid_import_url?Boolean

Returns:

  • (Boolean)

1056
1057
1058
# File 'app/models/project.rb', line 1056

def valid_import_url?
  valid?(:import_url) || errors.messages[:import_url].nil?
end

#valid_runners_token?(token) ⇒ Boolean

Returns:

  • (Boolean)

1720
1721
1722
# File 'app/models/project.rb', line 1720

def valid_runners_token?(token)
  self.runners_token && ActiveSupport::SecurityUtils.secure_compare(token, self.runners_token)
end

#validate_pages_https_onlyObject


1179
1180
1181
1182
1183
1184
1185
# File 'app/models/project.rb', line 1179

def validate_pages_https_only
  return unless pages_https_only?

  unless pages_domains.all?(&:https?)
    errors.add(:pages_https_only, _("cannot be enabled unless all domains have TLS certificates"))
  end
end

#visibility_level_allowed?(level = self.visibility_level) ⇒ Boolean

Returns:

  • (Boolean)

1751
1752
1753
# File 'app/models/project.rb', line 1751

def visibility_level_allowed?(level = self.visibility_level)
  visibility_level_allowed_as_fork?(level) && visibility_level_allowed_by_group?(level)
end

#visibility_level_allowed_as_forkObject


1160
1161
1162
1163
1164
1165
# File 'app/models/project.rb', line 1160

def visibility_level_allowed_as_fork
  return if visibility_level_allowed_as_fork?

  level_name = Gitlab::VisibilityLevel.level_name(self.visibility_level).downcase
  self.errors.add(:visibility_level, _("%{level_name} is not allowed since the fork source project has lower visibility.") % { level_name: level_name })
end

#visibility_level_allowed_as_fork?(level = self.visibility_level) ⇒ Boolean

rubocop: enable CodeReuse/ServiceClass

Returns:

  • (Boolean)

1736
1737
1738
1739
1740
1741
1742
1743
# File 'app/models/project.rb', line 1736

def visibility_level_allowed_as_fork?(level = self.visibility_level)
  return true unless forked?

  original_project = fork_source
  return true unless original_project

  level <= original_project.visibility_level
end

#visibility_level_allowed_by_groupObject


1152
1153
1154
1155
1156
1157
1158
# File 'app/models/project.rb', line 1152

def visibility_level_allowed_by_group
  return if visibility_level_allowed_by_group?

  level_name = Gitlab::VisibilityLevel.level_name(self.visibility_level).downcase
  group_level_name = Gitlab::VisibilityLevel.level_name(self.group.visibility_level).downcase
  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 })
end

#visibility_level_allowed_by_group?(level = self.visibility_level) ⇒ Boolean

Returns:

  • (Boolean)

1745
1746
1747
1748
1749
# File 'app/models/project.rb', line 1745

def visibility_level_allowed_by_group?(level = self.visibility_level)
  return true unless group

  level <= group.visibility_level
end

#visibility_level_fieldObject


1620
1621
1622
# File 'app/models/project.rb', line 1620

def visibility_level_field
  :visibility_level
end

#write_repository_config(gl_full_path: full_path) ⇒ Object


1839
1840
1841
1842
1843
1844
1845
1846
1847
# File 'app/models/project.rb', line 1839

def write_repository_config(gl_full_path: full_path)
  # We'd need to keep track of project full path otherwise directory tree
  # created with hashed storage enabled cannot be usefully imported using
  # the import rake task.
  repository.raw_repository.write_config(full_path: gl_full_path)
rescue Gitlab::Git::Repository::NoRepository => e
  Gitlab::AppLogger.error("Error writing to .git/config for project #{full_path} (#{id}): #{e.message}.")
  nil
end