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
SORTING_PREFERENCE_FIELD =
:projects_sort
MAX_BUILD_TIMEOUT =
1.month
GL_REPOSITORY_TYPES =
[Gitlab::GlRepository::PROJECT, Gitlab::GlRepository::WIKI, Gitlab::GlRepository::DESIGN].freeze
MAX_SUGGESTIONS_TEMPLATE_LENGTH =
255
MAX_COMMIT_TEMPLATE_LENGTH =
500
DEFAULT_MERGE_COMMIT_TEMPLATE =
<<~MSG.rstrip.freeze
  Merge branch '%{source_branch}' into '%{target_branch}'

  %{title}

  %{issues}

  See merge request %{reference}
MSG
DEFAULT_SQUASH_COMMIT_TEMPLATE =
'%{title}'

Constants included from BlocksUnsafeSerialization

BlocksUnsafeSerialization::UnsafeSerializationError

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 CanMoveRepositoryStorage

CanMoveRepositoryStorage::RepositoryReadOnlyError

Constants included from CacheMarkdownField

CacheMarkdownField::INVALIDATED_BY

Constants included from Avatarable

Avatarable::ALLOWED_IMAGE_SCALER_WIDTHS, Avatarable::GROUP_AVATAR_SIZES, Avatarable::MAXIMUM_FILE_SIZE, Avatarable::PROJECT_AVATAR_SIZES, Avatarable::USER_AVATAR_SIZES

Constants included from Gitlab::VisibilityLevel

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

Constants inherited from ApplicationRecord

ApplicationRecord::MAX_PLUCK

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::Cache::RequestCache

extended, request_cache, request_cache_key

Methods included from Gitlab::Utils::Override

extended, extensions, included, method_added, override, prepended, queue_verification, verify!

Methods included from Gitlab::ConfigHelper

gitlab_config, gitlab_config_features

Methods included from BlocksUnsafeSerialization

#serializable_hash

Methods included from RunnerTokenExpirationInterval

#effective_runner_token_expiration_interval, #effective_runner_token_expiration_interval_human_readable, #enforced_runner_token_expiration_interval_human_readable

Methods included from Routing::PseudonymizationHelper

#masked_page_url

Methods included from Routing::GraphqlHelper

#graphql_etag_pipeline_path, #graphql_etag_pipeline_sha_path, #graphql_etag_project_on_demand_scan_counts_path

Methods included from Routing::WikiHelper

#wiki_page_path, #wiki_path

Methods included from Routing::SnippetsHelper

#gitlab_dashboard_snippets_path, #gitlab_raw_snippet_blob_path, #gitlab_raw_snippet_blob_url, #gitlab_raw_snippet_path, #gitlab_raw_snippet_url, #gitlab_snippet_note_path, #gitlab_snippet_note_url, #gitlab_snippet_notes_path, #gitlab_snippet_notes_url, #gitlab_snippet_path, #gitlab_snippet_url, #gitlab_toggle_award_emoji_snippet_note_path, #gitlab_toggle_award_emoji_snippet_note_url, #gitlab_toggle_award_emoji_snippet_path, #gitlab_toggle_award_emoji_snippet_url, #preview_markdown_path, #toggle_award_emoji_personal_snippet_path, #toggle_award_emoji_project_project_snippet_path, #toggle_award_emoji_project_project_snippet_url

Methods included from Routing::PipelineSchedulesHelper

#edit_pipeline_schedule_path, #pipeline_schedule_path, #pipeline_schedules_path, #play_pipeline_schedule_path, #take_ownership_pipeline_schedule_path

Methods included from Routing::ArtifactsHelper

#artifacts_action_path, #expose_fast_artifacts_path, #fast_browse_project_job_artifacts_path, #fast_download_project_job_artifacts_path, #fast_keep_project_job_artifacts_path

Methods included from Routing::MembersHelper

#source_members_url

Methods included from Routing::Groups::MembersHelper

#approve_access_request_group_member_path, #group_member_path, #group_members_url, #leave_group_members_path, #request_access_group_members_path, #resend_invite_group_member_path

Methods included from Routing::Projects::MembersHelper

#approve_access_request_project_member_path, #leave_project_members_path, #project_member_path, #project_members_url, #request_access_project_members_path, #resend_invite_project_member_path

Methods included from Routing::ProjectsHelper

#commit_url, #commits_url, #edit_milestone_path, #environment_delete_path, #environment_path, #issue_path, #issue_url, #merge_request_path, #merge_request_url, #pipeline_job_url, #pipeline_path, #pipeline_url, #project_commits_path, #project_ref_path, #project_tree_path, #release_url, #toggle_subscription_path

Methods included from API::Helpers::RelatedResourcesHelpers

#expose_path, #expose_url, #issues_available?, #mrs_available?

Methods included from ApplicationSettingsHelper

#all_protocols_enabled?, #allowed_protocols_present?, #deprecated_attributes, #enabled_protocol, #enabled_protocol_button, #expanded_by_default?, #external_authorization_client_certificate_help_text, #external_authorization_client_key_help_text, #external_authorization_client_pass_help_text, #external_authorization_client_url_help_text, #external_authorization_description, #external_authorization_service_attributes, #external_authorization_timeout_help_text, #external_authorization_url_help_text, #http_enabled?, #import_sources_checkboxes, #instance_clusters_enabled?, #integration_expanded?, #key_restriction_options_for_select, #kroki_available_formats, #oauth_providers_checkboxes, #omnibus_protected_paths_throttle?, #pending_user_count, #registration_features_can_be_prompted?, #repository_storages_options_json, #restricted_level_checkboxes, #self_monitoring_project_data, #sidekiq_job_limiter_mode_help_text, #sidekiq_job_limiter_modes_for_select, #signup_enabled?, #ssh_enabled?, #storage_weights, #user_oauth_applications?, #valid_runner_registrars, #visible_attributes

Methods included from ProjectsHelper

#able_to_see_issues?, #able_to_see_merge_requests?, #any_projects?, #author_content_tag, #autodeploy_flash_notice, #can_admin_project_member?, #can_change_visibility_level?, #can_disable_emails?, #delete_confirm_phrase, #directory?, #error_tracking_setting_project_json, #explore_projects_tab?, #external_classification_label_help_message, #fork_button_disabled_tooltip, #grafana_integration_enabled?, #grafana_integration_masked_token, #grafana_integration_url, #import_from_bitbucket_message, #import_from_gitlab_message, #last_push_event, #link_to_autodeploy_doc, #link_to_member, #link_to_member_avatar, #link_to_project, #load_pipeline_status, #metrics_dashboard_timezone, #metrics_external_dashboard_url, #no_password_message, #project_can_be_shared?, #project_classes, #project_incident_management_setting, #project_license_name, #project_list_cache_key, #project_permissions_panel_data, #project_search_tabs?, #project_title, #push_to_create_project_command, #remove_fork_project_confirm_json, #remove_fork_project_description_message, #remove_fork_project_warning_message, #remove_project_message, #share_project_description, #show_auto_devops_implicitly_enabled_banner?, #show_issue_count?, #show_merge_request_count?, #show_no_password_message?, #show_no_ssh_key_message?, #show_projects?, #show_terraform_banner?, #show_xcode_link?, #transfer_project_message, #visible_fork_source, #xcode_uri_to_repo

Methods included from Repositories::CanHousekeepRepository

#increment_pushes_since_gc, #pushes_since_gc, #reset_pushes_since_gc

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, find_by_full_path, #full_name, #full_path, #full_path_components, #owned_by?, #route_loaded?

Methods included from CanMoveRepositoryStorage

#reference_counter, #set_repository_read_only!, #set_repository_writable!

Methods included from HasWiki

#create_wiki, #wiki, #wiki_repository_exists?

Methods included from HasRepository

#commit, #commit_by, #commits_by, #default_branch, #default_branch_from_group_preferences, #default_branch_from_preferences, #empty_repo?, #full_path, #http_url_to_repo, #lfs_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

#analytics_access_level=, #builds_access_level=, #builds_enabled=, #container_registry_access_level=, #container_registry_enabled=, #forking_access_level=, #issues_access_level=, #issues_enabled=, #merge_requests_access_level=, #merge_requests_enabled=, #metrics_dashboard_access_level=, #operations_access_level=, #pages_access_level=, #repository_access_level=, #security_and_compliance_access_level=, #security_and_compliance_enabled=, #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, #mentionable_attributes_changed?, #parent_user, #refresh_markdown_cache, #refresh_markdown_cache!, #rendered_field_content, #skip_project_check?, #store_mentions!, #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_attribute_value, #visibility_level_attributes, #visibility_level_previous_changes, #visibility_level_value

Methods inherited from ApplicationRecord

cached_column_list, #create_or_load_association, declarative_enum, default_select_columns, id_in, id_not_in, iid_in, pluck_primary_key, primary_key_in, #readable_by?, safe_ensure_unique, safe_find_or_create_by, safe_find_or_create_by!, #to_ability_name, underscore, where_exists, where_not_exists, with_fast_read_statement_timeout, without_order

Methods included from SensitiveSerializableHash

#serializable_hash

Constructor Details

#initialize(attributes = nil) ⇒ Project

Returns a new instance of Project.


882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
# File 'app/models/project.rb', line 882

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.


158
159
160
# File 'app/models/project.rb', line 158

def old_path_with_namespace
  @old_path_with_namespace
end

#pipeline_statusObject

Lazy loading of the `pipeline_status` attribute


2052
2053
2054
# File 'app/models/project.rb', line 2052

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.


161
162
163
# File 'app/models/project.rb', line 161

def skip_disk_validation
  @skip_disk_validation
end

#template_nameObject

Returns the value of attribute template_name.


159
160
161
# File 'app/models/project.rb', line 159

def template_name
  @template_name
end

#topic_listObject


2809
2810
2811
# File 'app/models/project.rb', line 2809

def topic_list
  self.topics.map(&:name)
end

Class Method Details

.cached_countObject


833
834
835
836
837
# File 'app/models/project.rb', line 833

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

.eager_load_namespace_and_ownerObject


702
703
704
# File 'app/models/project.rb', line 702

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.


735
736
737
738
739
740
741
# File 'app/models/project.rb', line 735

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_url(url) ⇒ Object


854
855
856
857
858
859
860
861
862
863
864
865
866
867
# File 'app/models/project.rb', line 854

def find_by_url(url)
  uri = URI(url)

  return unless uri.host == Gitlab.config.gitlab.host

  match = Rails.application.routes.recognize_path(url)

  return if match[:unmatched_route].present?
  return if match[:namespace_id].blank? || match[:id].blank?

  find_by_full_path(match.values_at(:namespace_id, :id).join("/"))
rescue ActionController::RoutingError, URI::InvalidURIError
  nil
end

.group_idsObject


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

def group_ids
  joins(:namespace).where(namespaces: { type: Group.sti_name }).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))


847
848
849
850
851
852
# File 'app/models/project.rb', line 847

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

.integration_association_name(name) ⇒ Object


180
181
182
# File 'app/models/project.rb', line 180

def self.integration_association_name(name)
  "#{name}_integration"
end

.markdown_reference_patternObject

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


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

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

.projects_user_can(projects, user, action) ⇒ Object


726
727
728
729
730
731
732
# File 'app/models/project.rb', line 726

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.


708
709
710
711
712
713
714
715
716
717
718
719
720
# File 'app/models/project.rb', line 708

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

  return public_to_user unless user

  if user.is_a?(DeployToken)
    user.accessible_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


801
802
803
804
805
806
807
# File 'app/models/project.rb', line 801

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


809
810
811
# File 'app/models/project.rb', line 809

def reference_postfix
  '>'
end

.reference_postfix_escapedObject


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

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.


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

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


774
775
776
# File 'app/models/project.rb', line 774

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

.sort_by_attribute(method) ⇒ Object


782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
# File 'app/models/project.rb', line 782

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'
    sorted_by_updated_desc
  when 'latest_activity_asc'
    sorted_by_updated_asc
  when 'stars_desc'
    sorted_by_stars_desc
  when 'stars_asc'
    sorted_by_stars_asc
  else
    order_by(method)
  end
end

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

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

.visibility_levelsObject


778
779
780
# File 'app/models/project.rb', line 778

def visibility_levels
  Gitlab::VisibilityLevel.options
end

.with_api_entity_associationsObject


694
695
696
# File 'app/models/project.rb', line 694

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

.with_feature_available_for_user(feature, user) ⇒ Object


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

def self.with_feature_available_for_user(feature, user)
  with_project_feature.merge(ProjectFeature.with_feature_available_for_user(feature, user))
end

.with_web_entity_associationsObject


698
699
700
# File 'app/models/project.rb', line 698

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

.without_integration(integration) ⇒ Object


869
870
871
872
873
874
875
876
877
878
879
# File 'app/models/project.rb', line 869

def without_integration(integration)
  integrations = Integration
    .select('1')
    .where("#{Integration.table_name}.project_id = projects.id")
    .where(type: integration.type)

  Project
    .where('NOT EXISTS (?)', integrations)
    .where(pending_delete: false)
    .where(archived: false)
end

.wrap_with_cte(collection) ⇒ Object


743
744
745
746
# File 'app/models/project.rb', line 743

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


2563
2564
2565
2566
2567
2568
2569
2570
2571
2572
2573
2574
2575
2576
# File 'app/models/project.rb', line 2563

def access_request_approvers_to_be_notified
  # For a personal project:
  # The creator is added as a member with `Owner` access level, starting from GitLab 14.8
  # The creator was added as a member with `Maintainer` access level, before GitLab 14.8
  # So, to make sure access requests for all personal projects work as expected,
  # we need to filter members with the scope `owners_and_maintainers`.
  access_request_approvers = if personal?
                               members.owners_and_maintainers
                             else
                               members.maintainers
                             end

  access_request_approvers.connected_to_user..limit(Member::ACCESS_REQUEST_APPROVERS_TO_BE_NOTIFIED_LIMIT)
end

#active_runnersObject


1856
1857
1858
1859
1860
# File 'app/models/project.rb', line 1856

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

#active_webide_pipelines(user:) ⇒ Object


937
938
939
# File 'app/models/project.rb', line 937

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

#activity_pathObject


2759
2760
2761
# File 'app/models/project.rb', line 2759

def activity_path
  Gitlab::Routing.url_helpers.activity_project_path(self)
end

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


2056
2057
2058
2059
2060
2061
2062
2063
2064
# File 'app/models/project.rb', line 2056

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


1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
# File 'app/models/project.rb', line 1143

def add_import_job
  job_id =
    if forked?
      RepositoryForkWorker.perform_async(id)
    else
      RepositoryImportWorker.perform_async(self.id)
    end

  log_import_activity(job_id)

  job_id
end

#after_change_head_branch_does_not_exist(branch) ⇒ Object


2814
2815
2816
# File 'app/models/project.rb', line 2814

def after_change_head_branch_does_not_exist(branch)
  self.errors.add(:base, _("Could not change HEAD: branch '%{branch}' does not exist") % { branch: branch })
end

#after_create_default_branchObject

rubocop: disable CodeReuse/ServiceClass


2046
2047
2048
# File 'app/models/project.rb', line 2046

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

#after_importObject


2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
# File 'app/models/project.rb', line 2010

def after_import
  repository.expire_content_cache
  wiki.repository.expire_content_cache

  DetectRepositoryLanguagesWorker.perform_async(id)
  ProjectCacheWorker.perform_async(self.id, [], [:repository_size])
  AuthorizedProjectUpdate::ProjectRecalculateWorker.perform_async(id)

  enqueue_record_project_target_platforms

  # 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!
  set_full_path
end

#after_repository_change_headObject


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

def after_repository_change_head
  ProjectCacheWorker.perform_async(self.id, [], [:commit_count])

  super
end

#all_available_runnersObject


1852
1853
1854
# File 'app/models/project.rb', line 1852

def all_available_runners
  Ci::Runner.from_union([runners, group_runners, available_shared_runners])
end

#all_clustersObject


1540
1541
1542
1543
1544
1545
# File 'app/models/project.rb', line 1540

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_pipelinesObject


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

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

#all_runnersObject


1848
1849
1850
# File 'app/models/project.rb', line 1848

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

#allowed_to_share_with_group?Boolean

Returns:

  • (Boolean)

1807
1808
1809
# File 'app/models/project.rb', line 1807

def allowed_to_share_with_group?
  !namespace.share_with_group_lock
end

#ancestors(hierarchy_order: nil) ⇒ Object


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

def ancestors(hierarchy_order: nil)
  if Feature.enabled?(:linear_project_ancestors, self)
    group&.self_and_ancestors(hierarchy_order: hierarchy_order) || Group.none
  else
    ancestors_upto(hierarchy_order: hierarchy_order)
  end
end

#ancestors_upto(top = nil, hierarchy_order: nil) ⇒ Object

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


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

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

#ancestors_upto_idsObject


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

def ancestors_upto_ids(...)
  ancestors_upto(...).pluck(:id)
end

#any_branch_allows_collaboration?(user) ⇒ Boolean

Returns:

  • (Boolean)

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

def any_branch_allows_collaboration?(user)
  fetch_branch_allows_collaboration(user)
end

#any_lfs_file_locks?Boolean

Returns:

  • (Boolean)

2498
2499
2500
# File 'app/models/project.rb', line 2498

def any_lfs_file_locks?
  lfs_file_locks.any?
end

#any_online_runners?(&block) ⇒ Boolean

Returns:

  • (Boolean)

1862
1863
1864
# File 'app/models/project.rb', line 1862

def any_online_runners?(&block)
  online_runners_with_tags.any?(&block)
end

#api_variablesObject


2189
2190
2191
2192
2193
# File 'app/models/project.rb', line 2189

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)

2503
2504
2505
# File 'app/models/project.rb', line 2503

def auto_cancel_pending_pipelines?
  auto_cancel_pending_pipelines == 'enabled'
end

#auto_devops_enabled?Boolean

Returns:

  • (Boolean)

995
996
997
998
999
1000
1001
# File 'app/models/project.rb', line 995

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

#auto_devops_variablesObject


2287
2288
2289
2290
2291
# File 'app/models/project.rb', line 2287

def auto_devops_variables
  return [] unless auto_devops_enabled?

  (auto_devops || build_auto_devops)&.predefined_variables
end

#autoclose_referenced_issuesObject


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

def autoclose_referenced_issues
  return true if super.nil?

  super
end

#available_shared_runnersObject


1840
1841
1842
# File 'app/models/project.rb', line 1840

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

#avatar_in_gitObject


1527
1528
1529
# File 'app/models/project.rb', line 1527

def avatar_in_git
  repository.avatar
end

#avatar_url(**args) ⇒ Object


1531
1532
1533
# File 'app/models/project.rb', line 1531

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

#badgesObject


2444
2445
2446
2447
2448
2449
2450
2451
# File 'app/models/project.rb', line 2444

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)

1235
1236
1237
# File 'app/models/project.rb', line 1235

def bare_repository_import?
  import_type == 'bare_repository'
end

#botsObject


1749
1750
1751
# File 'app/models/project.rb', line 1749

def bots
  users.project_bot
end

#branch_allows_collaboration?(user, branch_name) ⇒ Boolean

Returns:

  • (Boolean)

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

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

#build_commit_note(commit) ⇒ Object


1428
1429
1430
# File 'app/models/project.rb', line 1428

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

#changing_shared_runners_enabled_is_allowedObject


1352
1353
1354
1355
1356
1357
1358
# File 'app/models/project.rb', line 1352

def changing_shared_runners_enabled_is_allowed
  return unless new_record? || changes.has_key?(:shared_runners_enabled)

  if shared_runners_setting_conflicting_with_group?
    errors.add(:shared_runners_enabled, _('cannot be enabled because parent group does not allow it'))
  end
end

#check_personal_projects_limitObject


1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
# File 'app/models/project.rb', line 1295

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


1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
# File 'app/models/project.rb', line 1673

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_external_projectObject


2735
2736
2737
# File 'app/models/project.rb', line 2735

def ci_config_external_project
  Project.find_by_full_path(ci_config_path.split('@', 2).last)
end

#ci_config_for(sha) ⇒ Object


2731
2732
2733
# File 'app/models/project.rb', line 2731

def ci_config_for(sha)
  repository.gitlab_ci_yml_for(sha, ci_config_path_or_default)
end

#ci_config_path=(value) ⇒ Object


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

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

#ci_config_path_or_defaultObject


2727
2728
2729
# File 'app/models/project.rb', line 2727

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

#ci_forward_deployment_enabled?Boolean

Returns:

  • (Boolean)

2769
2770
2771
2772
2773
# File 'app/models/project.rb', line 2769

def ci_forward_deployment_enabled?
  return false unless ci_cd_settings

  ci_cd_settings.forward_deployment_enabled?
end

#ci_integrationObject


1523
1524
1525
# File 'app/models/project.rb', line 1523

def ci_integration
  @ci_integration ||= ci_integrations.reorder(nil).find_by(active: true)
end

#ci_integrationsObject

rubocop: enable CodeReuse/ServiceClass


1519
1520
1521
# File 'app/models/project.rb', line 1519

def ci_integrations
  integrations.where(category: :ci)
end

#ci_job_token_scope_enabled?Boolean

Returns:

  • (Boolean)

2775
2776
2777
2778
2779
# File 'app/models/project.rb', line 2775

def ci_job_token_scope_enabled?
  return false unless ci_cd_settings

  ci_cd_settings.job_token_scope_enabled?
end

#ci_pipelinesObject


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

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

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


2234
2235
2236
2237
2238
2239
2240
# File 'app/models/project.rb', line 2234

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!


1061
1062
1063
# File 'app/models/project.rb', line 1061

def cleanup
  @repository = nil
end

#closest_setting(name) ⇒ Object


2582
2583
2584
2585
2586
2587
# File 'app/models/project.rb', line 2582

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


1536
1537
1538
# File 'app/models/project.rb', line 1536

def code
  path
end

#container_registry_urlObject


1067
1068
1069
1070
1071
# File 'app/models/project.rb', line 1067

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

#container_registry_variablesObject


2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
# File 'app/models/project.rb', line 2213

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

#container_repositories_sizeObject


1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
# File 'app/models/project.rb', line 1073

def container_repositories_size
  strong_memoize(:container_repositories_size) do
    next unless Gitlab.com?
    next 0 if container_repositories.empty?
    next unless container_repositories.all_migrated?
    next unless ContainerRegistry::GitlabApiClient.supports_gitlab_api?

    ContainerRegistry::GitlabApiClient.deduplicated_size(full_path)
  end
end

#create_labelsObject

rubocop: disable CodeReuse/ServiceClass


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

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


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

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


1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
# File 'app/models/project.rb', line 1694

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

  repository.create_repository
  repository.after_create

  true
rescue StandardError => 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_mainObject


2721
2722
2723
2724
2725
# File 'app/models/project.rb', line 2721

def default_branch_or_main
  return default_branch if default_branch

  Gitlab::DefaultBranch.value(object: self)
end

#default_branch_protected?Boolean

Returns:

  • (Boolean)

2631
2632
2633
2634
2635
# File 'app/models/project.rb', line 2631

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


2225
2226
2227
2228
2229
2230
2231
2232
# File 'app/models/project.rb', line 2225

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)

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

def default_issues_tracker?
  !external_issue_tracker
end

#default_merge_request_targetObject


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

def default_merge_request_target
  return self if project_setting.mr_default_target_self
  return self unless mr_can_target_upstream?

  forked_from_project
end

#default_pipeline_lockObject


941
942
943
944
945
946
947
# File 'app/models/project.rb', line 941

def default_pipeline_lock
  if keep_latest_artifacts_available?
    return :artifacts_locked
  end

  :unlocked
end

#default_service_desk_suffixObject


2678
2679
2680
# File 'app/models/project.rb', line 2678

def default_service_desk_suffix
  "#{id}-issue-"
end

#dependency_proxy_variablesObject


2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
# File 'app/models/project.rb', line 2195

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

    variables.append(key: 'CI_DEPENDENCY_PROXY_SERVER', value: Gitlab.host_with_port)
    variables.append(
      key: 'CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX',
      # The namespace path can include uppercase letters, which
      # Docker doesn't allow. The proxy expects it to be downcased.
      value: "#{Gitlab.host_with_port}/#{namespace.root_ancestor.path.downcase}#{DependencyProxy::URL_SUFFIX}"
    )
    variables.append(
      key: 'CI_DEPENDENCY_PROXY_DIRECT_GROUP_IMAGE_PREFIX',
      value: "#{Gitlab.host_with_port}/#{namespace.full_path.downcase}#{DependencyProxy::URL_SUFFIX}"
    )
  end
end

#deploy_token_create_url(opts = {}) ⇒ Object


2623
2624
2625
# File 'app/models/project.rb', line 2623

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


2627
2628
2629
# File 'app/models/project.rb', line 2627

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


2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
# File 'app/models/project.rb', line 2275

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

#deprecated_ownerObject


1573
1574
1575
1576
1577
# File 'app/models/project.rb', line 1573

def deprecated_owner
  # Kept in order to maintain webhook structures until we remove owner_name and owner_email
  # See https://gitlab.com/gitlab-org/gitlab/-/issues/350603
  group || namespace.try(:owner)
end

#design_management_enabled?Boolean

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

Returns:

  • (Boolean)

1034
1035
1036
# File 'app/models/project.rb', line 1034

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

#design_repositoryObject


1046
1047
1048
1049
1050
# File 'app/models/project.rb', line 1046

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

#disabled_integrationsObject


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

def disabled_integrations
  disabled_integrations = []
  disabled_integrations << 'shimo' unless Feature.enabled?(:shimo_integration, self)
  disabled_integrations
end

#drop_visibility_level!Object


2589
2590
2591
2592
2593
2594
2595
2596
2597
# File 'app/models/project.rb', line 2589

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)

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

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

#enable_ciObject


1828
1829
1830
# File 'app/models/project.rb', line 1828

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

#enabled_group_deploy_keysObject


2739
2740
2741
2742
2743
# File 'app/models/project.rb', line 2739

def enabled_group_deploy_keys
  return GroupDeployKey.none unless group

  GroupDeployKey.for_groups(group.self_and_ancestors_ids)
end

#enforced_runner_token_expiration_intervalObject


2834
2835
2836
2837
2838
2839
2840
2841
2842
2843
# File 'app/models/project.rb', line 2834

def enforced_runner_token_expiration_interval
  all_parent_groups = Gitlab::ObjectHierarchy.new(Group.where(id: group)).base_and_ancestors
  all_group_settings = NamespaceSetting.where(namespace_id: all_parent_groups)
  group_interval = all_group_settings.where.not(project_runner_token_expiration_interval: nil).minimum(:project_runner_token_expiration_interval)&.seconds

  [
    Gitlab::CurrentSettings.project_runner_token_expiration_interval&.seconds,
    group_interval
  ].compact.min
end

#enqueue_record_project_target_platformsObject


2879
2880
2881
2882
2883
2884
# File 'app/models/project.rb', line 2879

def enqueue_record_project_target_platforms
  return unless Gitlab.com?
  return unless Feature.enabled?(:record_projects_target_platforms, self)

  Projects::RecordTargetPlatformsWorker.perform_async(id)
end

#ensure_repositoryObject


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

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

#environments_for_scope(scope) ⇒ Object


2637
2638
2639
2640
2641
# File 'app/models/project.rb', line 2637

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


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

def execute_hooks(data, hooks_scope = :push_hooks)
  run_after_commit_or_now do
    triggered_hooks(hooks_scope, data).execute
    SystemHooksService.new.execute_hooks(data, hooks_scope)
  end
end

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


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

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

#expire_caches_before_rename(old_path) ⇒ Object

Expires various caches before a project is renamed.


1662
1663
1664
1665
1666
1667
1668
1669
1670
# File 'app/models/project.rb', line 1662

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_archive_exists?Boolean

Returns:

  • (Boolean)

2117
2118
2119
# File 'app/models/project.rb', line 2117

def export_archive_exists?
  import_export_upload&.export_archive_exists?
end

#export_enqueued?Boolean

Returns:

  • (Boolean)

2096
2097
2098
2099
2100
# File 'app/models/project.rb', line 2096

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

#export_fileObject


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

def export_file
  import_export_upload&.export_file
end

#export_file_exists?Boolean

Returns:

  • (Boolean)

2113
2114
2115
# File 'app/models/project.rb', line 2113

def export_file_exists?
  import_export_upload&.export_file_exists?
end

#export_in_progress?Boolean

Returns:

  • (Boolean)

2090
2091
2092
2093
2094
# File 'app/models/project.rb', line 2090

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

#export_pathObject


2070
2071
2072
2073
2074
# File 'app/models/project.rb', line 2070

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

  import_export_shared.archive_path
end

#export_statusObject


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

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


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

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

#external_import?Boolean

Returns:

  • (Boolean)

1227
1228
1229
# File 'app/models/project.rb', line 1227

def external_import?
  import_url.present?
end

#external_issue_reference_patternObject


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

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

#external_issue_trackerObject


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

def external_issue_tracker
  cache_has_external_issue_tracker if has_external_issue_tracker.nil?

  return unless has_external_issue_tracker?

  @external_issue_tracker ||= integrations.external_issue_trackers.first
end

#external_references_supported?Boolean

Returns:

  • (Boolean)

1474
1475
1476
# File 'app/models/project.rb', line 1474

def external_references_supported?
  external_issue_tracker&.support_cross_reference?
end

#external_wikiObject


1482
1483
1484
1485
1486
1487
1488
# File 'app/models/project.rb', line 1482

def external_wiki
  cache_has_external_wiki if has_external_wiki.nil?

  return unless has_external_wiki?

  @external_wiki ||= integrations.external_wikis.first
end

#feature_flags_client_tokenObject


2745
2746
2747
2748
# File 'app/models/project.rb', line 2745

def feature_flags_client_token
  instance = operations_feature_flags_client || create_operations_feature_flags_client!
  instance.token
end

#feature_usageObject


1627
1628
1629
# File 'app/models/project.rb', line 1627

def feature_usage
  super.presence || build_feature_usage
end

#ff_merge_must_be_possible?Boolean

Returns:

  • (Boolean)

2407
2408
2409
# File 'app/models/project.rb', line 2407

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

#find_or_initialize_integration(name) ⇒ Object


1504
1505
1506
1507
1508
# File 'app/models/project.rb', line 1504

def find_or_initialize_integration(name)
  return if disabled_integrations.include?(name) || Integration.available_integration_names.exclude?(name)

  find_integration(integrations, name) || build_from_instance(name) || build_integration(name)
end

#find_or_initialize_integrationsObject


1490
1491
1492
1493
1494
1495
1496
# File 'app/models/project.rb', line 1490

def find_or_initialize_integrations
  Integration
    .available_integration_names
    .difference(disabled_integrations)
    .map { find_or_initialize_integration(_1) }
    .sort_by(&:title)
end

#first_auto_devops_configObject


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

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

#first_ownerObject


1586
1587
1588
1589
1590
1591
1592
1593
1594
# File 'app/models/project.rb', line 1586

def first_owner
  obj = owner

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

#fork_sourceObject


1635
1636
1637
1638
1639
# File 'app/models/project.rb', line 1635

def fork_source
  return unless forked?

  forked_from_project || fork_network&.root_project
end

#forked?Boolean

Returns:

  • (Boolean)

1631
1632
1633
# File 'app/models/project.rb', line 1631

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

#forked_from?(other_project) ⇒ Boolean

Returns:

  • (Boolean)

1776
1777
1778
# File 'app/models/project.rb', line 1776

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

#forks_countObject

rubocop: disable CodeReuse/ServiceClass


2347
2348
2349
2350
2351
2352
2353
2354
2355
# File 'app/models/project.rb', line 2347

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

#format_runners_token(token) ⇒ Object


1914
1915
1916
# File 'app/models/project.rb', line 1914

def format_runners_token(token)
  "#{RunnersTokenPrefixable::RUNNERS_TOKEN_PREFIX}#{token}"
end

#full_path_before_last_saveObject


2337
2338
2339
# File 'app/models/project.rb', line 2337

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

#full_path_slugObject


2125
2126
2127
# File 'app/models/project.rb', line 2125

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

#get_issue(issue_id, current_user) ⇒ Object


1444
1445
1446
1447
1448
1449
1450
1451
1452
# File 'app/models/project.rb', line 1444

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_garbage_collect_worker_klassObject


2755
2756
2757
# File 'app/models/project.rb', line 2755

def git_garbage_collect_worker_klass
  Projects::GitGarbageCollectWorker
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)

2543
2544
2545
2546
2547
2548
2549
# File 'app/models/project.rb', line 2543

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)

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

def git_transfer_in_progress?
  GL_REPOSITORY_TYPES.any? do |type|
    reference_counter(type: type).value > 0
  end
end

#gitea_import?Boolean

Returns:

  • (Boolean)

1251
1252
1253
# File 'app/models/project.rb', line 1251

def gitea_import?
  import_type == 'gitea'
end

#github_enterprise_import?Boolean

Returns:

  • (Boolean)

1259
1260
1261
1262
# File 'app/models/project.rb', line 1259

def github_enterprise_import?
  github_import? &&
    URI.parse(import_url).host != URI.parse(Octokit::Default::API_ENDPOINT).host
end

#github_import?Boolean

Returns:

  • (Boolean)

1255
1256
1257
# File 'app/models/project.rb', line 1255

def github_import?
  import_type == 'github'
end

#gitlab_deploy_tokenObject


2494
2495
2496
# File 'app/models/project.rb', line 2494

def gitlab_deploy_token
  @gitlab_deploy_token ||= deploy_tokens.gitlab_deploy_token
end

#gitlab_project_import?Boolean

Returns:

  • (Boolean)

1243
1244
1245
# File 'app/models/project.rb', line 1243

def gitlab_project_import?
  import_type == 'gitlab_project'
end

#gitlab_project_migration?Boolean

Returns:

  • (Boolean)

1247
1248
1249
# File 'app/models/project.rb', line 1247

def gitlab_project_migration?
  import_type == 'gitlab_project_migration'
end

#group_runnersObject


1844
1845
1846
# File 'app/models/project.rb', line 1844

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

#group_runners_enabled?Boolean

Returns:

  • (Boolean)

2803
2804
2805
2806
2807
# File 'app/models/project.rb', line 2803

def group_runners_enabled?
  return false unless ci_cd_settings

  ci_cd_settings.group_runners_enabled?
end

#has_active_hooks?(hooks_scope = :push_hooks) ⇒ Boolean

Returns:

  • (Boolean)

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

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_integrations?(hooks_scope = :push_hooks) ⇒ Boolean

Returns:

  • (Boolean)

1623
1624
1625
# File 'app/models/project.rb', line 1623

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

#has_auto_devops_implicitly_disabled?Boolean

Returns:

  • (Boolean)

1009
1010
1011
1012
1013
# File 'app/models/project.rb', line 1009

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)

1003
1004
1005
1006
1007
# File 'app/models/project.rb', line 1003

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)

2129
2130
2131
# File 'app/models/project.rb', line 2129

def has_ci?
  repository.gitlab_ci_yml || auto_devops_enabled?
end

#has_container_registry_tags?Boolean

Returns:

  • (Boolean)

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

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)

1015
1016
1017
# File 'app/models/project.rb', line 1015

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

#has_pool_repository?Boolean

Returns:

  • (Boolean)

2559
2560
2561
# File 'app/models/project.rb', line 2559

def has_pool_repository?
  pool_repository.present?
end

#has_remote_mirror?Boolean

Returns:

  • (Boolean)

1264
1265
1266
# File 'app/models/project.rb', line 1264

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

#has_wiki?Boolean

Returns:

  • (Boolean)

1478
1479
1480
# File 'app/models/project.rb', line 1478

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)

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

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


1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
# File 'app/models/project.rb', line 1708

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


1139
1140
1141
# File 'app/models/project.rb', line 1139

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

#human_merge_methodObject


2375
2376
2377
2378
2379
2380
2381
# File 'app/models/project.rb', line 2375

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

#import?Boolean

Returns:

  • (Boolean)

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

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

#import_export_sharedObject


2066
2067
2068
# File 'app/models/project.rb', line 2066

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

#import_statusObject


1131
1132
1133
# File 'app/models/project.rb', line 1131

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

#import_urlObject


1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
# File 'app/models/project.rb', line 1197

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 StandardError
  super
end

#import_url=(value) ⇒ Object


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

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)

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

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

#inactive?Boolean

Returns:

  • (Boolean)

2886
2887
2888
2889
# File 'app/models/project.rb', line 2886

def inactive?
  (statistics || build_statistics).storage_size > ::Gitlab::CurrentSettings.inactive_projects_min_size_mb.megabytes &&
    last_activity_at < ::Gitlab::CurrentSettings.inactive_projects_send_warning_email_after_months.months.ago
end

#increment_statistic_value(statistic, delta) ⇒ Object


2763
2764
2765
2766
2767
# File 'app/models/project.rb', line 2763

def increment_statistic_value(statistic, delta)
  return if pending_delete?

  ProjectStatistics.increment_statistic(self, statistic, delta)
end

#issue_exists?(issue_id) ⇒ Boolean

Returns:

  • (Boolean)

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

def issue_exists?(issue_id)
  get_issue(issue_id)
end

#items_for(entity) ⇒ Object


1547
1548
1549
1550
1551
1552
1553
1554
# File 'app/models/project.rb', line 1547

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

#jira_import?Boolean

Returns:

  • (Boolean)

1239
1240
1241
# File 'app/models/project.rb', line 1239

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

#jira_import_statusObject


1135
1136
1137
# File 'app/models/project.rb', line 1135

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

#jira_subscription_exists?Boolean

Returns:

  • (Boolean)

2603
2604
2605
# File 'app/models/project.rb', line 2603

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

#keep_latest_artifact?Boolean

Returns:

  • (Boolean)

2793
2794
2795
2796
2797
# File 'app/models/project.rb', line 2793

def keep_latest_artifact?
  return false unless ci_cd_settings

  ci_cd_settings.keep_latest_artifact?
end

#keep_latest_artifacts_available?Boolean

Returns:

  • (Boolean)

2787
2788
2789
2790
2791
# File 'app/models/project.rb', line 2787

def keep_latest_artifacts_available?
  return false unless ci_cd_settings

  ci_cd_settings.keep_latest_artifacts_available?
end

#last_activityObject


1432
1433
1434
# File 'app/models/project.rb', line 1432

def last_activity
  last_event
end

#last_activity_dateObject


1436
1437
1438
# File 'app/models/project.rb', line 1436

def last_activity_date
  updated_at
end

#latest_jira_importObject


2643
2644
2645
# File 'app/models/project.rb', line 2643

def latest_jira_import
  jira_imports.last
end

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


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

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


1092
1093
1094
1095
1096
1097
1098
1099
# File 'app/models/project.rb', line 1092

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.build_with_artifacts_in_self_and_descendants(job_name)
end

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


1110
1111
1112
# File 'app/models/project.rb', line 1110

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

#latest_successful_build_for_sha(job_name, sha) ⇒ Object


1101
1102
1103
1104
1105
1106
1107
1108
# File 'app/models/project.rb', line 1101

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.build_with_artifacts_in_self_and_descendants(job_name)
end

#latest_successful_pipeline_for(ref = nil) ⇒ Object


1820
1821
1822
1823
1824
1825
1826
# File 'app/models/project.rb', line 1820

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


1811
1812
1813
1814
1815
1816
1817
1818
# File 'app/models/project.rb', line 1811

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


2551
2552
2553
# File 'app/models/project.rb', line 2551

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)

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

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

#lfs_enabled?Boolean Also known as: lfs_enabled

Returns:

  • (Boolean)

987
988
989
990
991
# File 'app/models/project.rb', line 987

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

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

#lfs_objects_for_repository_types(*types) ⇒ Object


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

def lfs_objects_for_repository_types(*types)
  LfsObject
    .joins(:lfs_objects_projects)
    .where(lfs_objects_projects: { project: self, repository_type: types })
end

#lfs_objects_oids(oids: []) ⇒ Object


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

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

#lfs_objects_oids_from_fork_source(oids: []) ⇒ Object


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

def lfs_objects_oids_from_fork_source(oids: [])
  return [] unless forked?

  oids(fork_source.lfs_objects, oids: oids)
end

#licensed_feature_available?(_feature) ⇒ Boolean

Overridden in EE::Project

Returns:

  • (Boolean)

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

def licensed_feature_available?(_feature)
  false
end

#licensed_featuresObject


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

def licensed_features
  []
end

#limited_protected_branches(limit) ⇒ Object


2615
2616
2617
# File 'app/models/project.rb', line 2615

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

2555
2556
2557
# File 'app/models/project.rb', line 2555

def link_pool_repository
  pool_repository&.link_repository(repository)
end

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


1156
1157
1158
1159
1160
1161
1162
1163
1164
# File 'app/models/project.rb', line 1156

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_deployedObject


1979
1980
1981
# File 'app/models/project.rb', line 1979

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

#mark_pages_as_not_deployedObject


1983
1984
1985
# File 'app/models/project.rb', line 1983

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

#mark_pages_onboarding_completeObject


1975
1976
1977
# File 'app/models/project.rb', line 1975

def mark_pages_onboarding_complete
  ensure_pages_metadatum.update!(onboarding_complete: true)
end

#mark_primary_write_locationObject


2486
2487
2488
# File 'app/models/project.rb', line 2486

def mark_primary_write_location
  self.class.sticking.mark_primary_write_location(:project, self.id)
end

#mark_remote_mirrors_for_removalObject


1286
1287
1288
# File 'app/models/project.rb', line 1286

def mark_remote_mirrors_for_removal
  remote_mirrors.each(&:mark_for_delete_if_blank_url)
end

#mark_stuck_remote_mirrors_as_failed!Object


1278
1279
1280
1281
1282
1283
1284
# File 'app/models/project.rb', line 1278

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


2524
2525
2526
# File 'app/models/project.rb', line 2524

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

#member(user) ⇒ Object


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

def 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

#members_among(users) ⇒ Object

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


1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
# File 'app/models/project.rb', line 1754

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

#membership_locked?Boolean

Returns:

  • (Boolean)

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

def membership_locked?
  false
end

#merge_base_commit(first_commit_id, second_commit_id) ⇒ Object


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

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_commit_template_or_defaultObject


2845
2846
2847
# File 'app/models/project.rb', line 2845

def merge_commit_template_or_default
  merge_commit_template.presence || DEFAULT_MERGE_COMMIT_TEMPLATE
end

#merge_commit_template_or_default=(value) ⇒ Object


2849
2850
2851
2852
2853
2854
2855
2856
# File 'app/models/project.rb', line 2849

def merge_commit_template_or_default=(value)
  project_setting.merge_commit_template =
    if value.blank? || value.delete("\r") == DEFAULT_MERGE_COMMIT_TEMPLATE
      nil
    else
      value
    end
end

#merge_methodObject


2383
2384
2385
2386
2387
2388
2389
2390
2391
# File 'app/models/project.rb', line 2383

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


2393
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
# File 'app/models/project.rb', line 2393

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


2453
2454
2455
2456
2457
2458
2459
2460
2461
2462
# File 'app/models/project.rb', line 2453

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


2647
2648
2649
# File 'app/models/project.rb', line 2647

def metrics_setting
  super || build_metrics_setting
end

#migrate_to_hashed_storage!Object


2411
2412
2413
2414
2415
2416
2417
2418
2419
# File 'app/models/project.rb', line 2411

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

#mr_can_target_upstream?Boolean

Returns:

  • (Boolean)

2325
2326
2327
2328
2329
2330
2331
# File 'app/models/project.rb', line 2325

def mr_can_target_upstream?
  # When our current visibility is more restrictive than the upstream project,
  # (e.g., the fork is `private` but the parent is `public`), don't allow target upstream
  forked_from_project &&
    forked_from_project.merge_requests_enabled? &&
    forked_from_project.visibility_level_value <= visibility_level_value
end

#multiple_issue_boards_available?Boolean

Returns:

  • (Boolean)

2333
2334
2335
# File 'app/models/project.rb', line 2333

def multiple_issue_boards_available?
  true
end

#new_issuable_address(author, address_type) ⇒ Object


1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
# File 'app/models/project.rb', line 1413

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


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

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


1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
# File 'app/models/project.rb', line 1871

def open_issues_count(current_user = nil)
  return Projects::OpenIssuesCountService.new(self, current_user).count unless current_user.nil?

  BatchLoader.for(self).batch do |projects, loader|
    issues_count_per_project = ::Projects::BatchOpenIssuesCountService.new(projects).refresh_cache_and_retrieve_data

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

#open_merge_requests_count(_current_user = nil) ⇒ Object

rubocop: disable CodeReuse/ServiceClass


1885
1886
1887
# File 'app/models/project.rb', line 1885

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

#origin_merge_requestsObject


1786
1787
1788
# File 'app/models/project.rb', line 1786

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

#ownerObject

rubocop: enable CodeReuse/ServiceClass


1566
1567
1568
1569
1570
1571
# File 'app/models/project.rb', line 1566

def owner
  # This will be phased out and replaced with `owners` relationship
  # backed by memberships with direct/inherited Owner access roles
  # See https://gitlab.com/groups/gitlab-org/-/epics/7405
  group || namespace.try(:owner)
end

#ownersObject


1579
1580
1581
1582
1583
1584
# File 'app/models/project.rb', line 1579

def owners
  # This will be phased out and replaced with `owners` relationship
  # backed by memberships with direct/inherited Owner access roles
  # See https://gitlab.com/groups/gitlab-org/-/epics/7405
  team.owners
end

#package_already_taken?(package_name, package_version, package_type:) ⇒ Boolean

Returns:

  • (Boolean)

2709
2710
2711
2712
2713
2714
2715
2716
2717
2718
2719
# File 'app/models/project.rb', line 2709

def package_already_taken?(package_name, package_version, package_type:)
  Packages::Package.with_name(package_name)
    .with_version(package_version)
    .with_package_type(package_type)
    .not_pending_destruction
    .for_projects(
      root_ancestor.all_projects
        .id_not_in(id)
        .select(:id)
    ).exists?
end

#packages_cleanup_policyObject


1019
1020
1021
# File 'app/models/project.rb', line 1019

def packages_cleanup_policy
  super || build_packages_cleanup_policy
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.


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

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

#pages_available?Boolean

Returns:

  • (Boolean)

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

def pages_available?
  Gitlab.config.pages.enabled
end

#pages_deployed?Boolean

Returns:

  • (Boolean)

1918
1919
1920
# File 'app/models/project.rb', line 1918

def pages_deployed?
  pages_metadatum&.deployed?
end

#pages_group_root?Boolean

Returns:

  • (Boolean)

1939
1940
1941
# File 'app/models/project.rb', line 1939

def pages_group_root?
  pages_group_url == pages_url
end

#pages_group_urlObject


1922
1923
1924
1925
1926
1927
# File 'app/models/project.rb', line 1922

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


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

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

  super
end

#pages_https_only?Boolean

Returns:

  • (Boolean)

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

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

  super
end

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


2578
2579
2580
# File 'app/models/project.rb', line 2578

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

#pages_pathObject


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

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_show_onboarding?Boolean

Returns:

  • (Boolean)

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

def pages_show_onboarding?
  !(pages_metadatum&.onboarding_complete || pages_metadatum&.deployed)
end

#pages_subdomainObject


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

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

#pages_urlObject


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

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


2180
2181
2182
2183
2184
2185
2186
2187
# File 'app/models/project.rb', line 2180

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)

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

def parent_changed?
  namespace_id_changed?
end

#parent_loaded?Boolean

Returns:

  • (Boolean)

901
902
903
# File 'app/models/project.rb', line 901

def parent_loaded?
  association(:namespace).loaded?
end

#pending_delete_or_hidden?Boolean

Returns:

  • (Boolean)

2871
2872
2873
# File 'app/models/project.rb', line 2871

def pending_delete_or_hidden?
  pending_delete? || hidden?
end

#personal?Boolean

Returns:

  • (Boolean)

1657
1658
1659
# File 'app/models/project.rb', line 1657

def personal?
  !group
end

#personal_namespace_holder?(user) ⇒ Boolean

Returns:

  • (Boolean)

905
906
907
908
909
910
911
912
913
914
915
# File 'app/models/project.rb', line 905

def personal_namespace_holder?(user)
  return false unless personal?
  return false unless user

  # We do not want to use a check like `project.team.owner?(user)`
  # here because that would depend upon the state of the `project_authorizations` cache,
  # and also perform the check across multiple `owners` of the project, but our intention
  # is to check if the user is the "holder" of the personal namespace, so need to make this
  # check against only a single user (ie, namespace.owner).
  namespace.owner == user
end

#predefined_ci_server_variablesObject


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

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


2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
# File 'app/models/project.rb', line 2146

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_PROJECT_CLASSIFICATION_LABEL', value: external_authorization_classification_label)
    .append(key: 'CI_DEFAULT_BRANCH', value: default_branch)
    .append(key: 'CI_CONFIG_PATH', value: ci_config_path_or_default)
end

#predefined_variablesObject


2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
# File 'app/models/project.rb', line 2133

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

#preload_protected_branchesObject


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

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

#project_idObject


1440
1441
1442
# File 'app/models/project.rb', line 1440

def project_id
  self.id
end

#project_settingObject


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

def project_setting
  super.presence || build_project_setting
end

#protected_for?(ref) ⇒ Boolean

Returns:

  • (Boolean)

Raises:


2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
# File 'app/models/project.rb', line 2256

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


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

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

#readme_urlObject


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

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

#reconcile_shared_runners_setting!Object


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

def reconcile_shared_runners_setting!
  if shared_runners_setting_conflicting_with_group?
    self.shared_runners_enabled = false
  end
end

#regeneration_in_progress?Boolean

Returns:

  • (Boolean)

2102
2103
2104
# File 'app/models/project.rb', line 2102

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

2701
2702
2703
2704
2705
2706
2707
# File 'app/models/project.rb', line 2701

def related_group_ids
  ids = invited_group_ids

  ids += group.self_and_ancestors_ids if group

  ids
end

#remote_mirror_available?Boolean

Returns:

  • (Boolean)

1290
1291
1292
1293
# File 'app/models/project.rb', line 1290

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

#remove_exportsObject


2106
2107
2108
2109
2110
2111
# File 'app/models/project.rb', line 2106

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


1176
1177
1178
# File 'app/models/project.rb', line 1176

def remove_import_data
  import_data&.destroy
end

#remove_private_deploy_keysObject


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

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

#remove_project_authorizations(user_ids, per_batch = 1000) ⇒ Object


2828
2829
2830
2831
2832
# File 'app/models/project.rb', line 2828

def remove_project_authorizations(user_ids, per_batch = 1000)
  user_ids.each_slice(per_batch) do |user_ids_batch|
    project_authorizations.where(user_id: user_ids_batch).delete_all
  end
end

#renamed?Boolean

Returns:

  • (Boolean)

2371
2372
2373
# File 'app/models/project.rb', line 2371

def renamed?
  persisted? && path_changed?
end

#repositoryObject


1042
1043
1044
# File 'app/models/project.rb', line 1042

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

#reset_cache_and_import_attrsObject


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

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

#restrict_user_defined_variables?Boolean

Returns:

  • (Boolean)

2781
2782
2783
2784
2785
# File 'app/models/project.rb', line 2781

def restrict_user_defined_variables?
  return false unless ci_cd_settings

  ci_cd_settings.restrict_user_defined_variables?
end

#rollback_to_legacy_storage!Object


2421
2422
2423
2424
2425
2426
2427
2428
2429
# File 'app/models/project.rb', line 2421

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


2682
2683
2684
2685
2686
2687
2688
# File 'app/models/project.rb', line 2682

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

#route_map_for(commit_sha) ⇒ Object


2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
# File 'app/models/project.rb', line 2293

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

#runner_token_expiration_intervalObject


2799
2800
2801
# File 'app/models/project.rb', line 2799

def runner_token_expiration_interval
  ci_cd_settings&.runner_token_expiration_interval
end

#runners_tokenObject


1909
1910
1911
# File 'app/models/project.rb', line 1909

def runners_token
  ensure_runners_token!
end

#safe_import_urlObject


1231
1232
1233
# File 'app/models/project.rb', line 1231

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

#saved?Boolean

Returns:

  • (Boolean)

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

def saved?
  id && persisted?
end

#self_monitoring?Boolean

Returns:

  • (Boolean)

2619
2620
2621
# File 'app/models/project.rb', line 2619

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

#self_or_root_group_idsObject

for projects that are part of user namespace, return project.


2691
2692
2693
2694
2695
2696
2697
2698
2699
# File 'app/models/project.rb', line 2691

def self_or_root_group_ids
  if group
    root_group = root_namespace
  else
    project = self
  end

  [project&.id, root_group&.id]
end

#send_move_instructions(old_path_with_namespace) ⇒ Object

rubocop: disable CodeReuse/ServiceClass


1557
1558
1559
1560
1561
1562
1563
# File 'app/models/project.rb', line 1557

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


2657
2658
2659
# File 'app/models/project.rb', line 2657

def service_desk_address
  service_desk_custom_address || service_desk_incoming_address
end

#service_desk_custom_addressObject


2670
2671
2672
2673
2674
2675
2676
# File 'app/models/project.rb', line 2670

def service_desk_custom_address
  return unless Gitlab::ServiceDeskEmail.enabled?

  key = service_desk_setting&.project_key || default_service_desk_suffix

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

#service_desk_enabledObject Also known as: service_desk_enabled?


2651
2652
2653
# File 'app/models/project.rb', line 2651

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

#service_desk_incoming_addressObject


2661
2662
2663
2664
2665
2666
2667
2668
# File 'app/models/project.rb', line 2661

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}-#{default_service_desk_suffix}")
end

#set_first_pages_deployment!(deployment) ⇒ Object


1991
1992
1993
1994
1995
1996
1997
1998
# File 'app/models/project.rb', line 1991

def set_first_pages_deployment!(deployment)
  ensure_pages_metadatum

  # where().update_all to perform update in the single transaction with check for null
  ProjectPagesMetadatum
    .where(project_id: id, pages_deployment_id: nil)
    .update_all(deployed: deployment.present?, pages_deployment_id: deployment&.id)
end

#set_full_path(gl_full_path: full_path) ⇒ Object


2000
2001
2002
2003
2004
2005
2006
2007
2008
# File 'app/models/project.rb', line 2000

def set_full_path(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.set_full_path(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

#shared_runnersObject


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

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

#shared_runners_available?Boolean

Returns:

  • (Boolean)

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

def shared_runners_available?
  shared_runners_enabled?
end

#shared_runners_setting_conflicting_with_group?Boolean

Returns:

  • (Boolean)

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

def shared_runners_setting_conflicting_with_group?
  shared_runners_enabled && group&.shared_runners_setting == Namespace::SR_DISABLED_AND_UNOVERRIDABLE
end

#should_validate_visibility_level?Boolean

Returns:

  • (Boolean)

1313
1314
1315
# File 'app/models/project.rb', line 1313

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

#snippets_visible?(user = nil) ⇒ Boolean

Returns:

  • (Boolean)

2520
2521
2522
# File 'app/models/project.rb', line 2520

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

#squash_commit_template_or_defaultObject


2858
2859
2860
# File 'app/models/project.rb', line 2858

def squash_commit_template_or_default
  squash_commit_template.presence || DEFAULT_SQUASH_COMMIT_TEMPLATE
end

#squash_commit_template_or_default=(value) ⇒ Object


2862
2863
2864
2865
2866
2867
2868
2869
# File 'app/models/project.rb', line 2862

def squash_commit_template_or_default=(value)
  project_setting.squash_commit_template =
    if value.blank? || value.delete("\r") == DEFAULT_SQUASH_COMMIT_TEMPLATE
      nil
    else
      value
    end
end

#storageObject


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

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

#storage_upgradable?Boolean

Returns:

  • (Boolean)

2516
2517
2518
# File 'app/models/project.rb', line 2516

def storage_upgradable?
  storage_version != LATEST_STORAGE_VERSION
end

#storage_version=(value) ⇒ Object


2438
2439
2440
2441
2442
# File 'app/models/project.rb', line 2438

def storage_version=(value)
  super

  @storage = nil if storage_version_changed?
end

#teamObject


1038
1039
1040
# File 'app/models/project.rb', line 1038

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

#template_source?Boolean

Returns:

  • (Boolean)

2599
2600
2601
# File 'app/models/project.rb', line 2599

def template_source?
  false
end

#to_human_reference(from = nil) ⇒ Object


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

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

#to_paramObject


1370
1371
1372
1373
1374
1375
1376
# File 'app/models/project.rb', line 1370

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.


1384
1385
1386
1387
# File 'app/models/project.rb', line 1384

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.


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

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


2490
2491
2492
# File 'app/models/project.rb', line 2490

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

#tracing_external_urlObject


2750
2751
2752
# File 'app/models/project.rb', line 2750

def tracing_external_url
  tracing_setting&.external_url
end

#track_project_repositoryObject


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

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

#triggered_hooks(hooks_scope, data) ⇒ Object

rubocop: enable CodeReuse/ServiceClass


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

def triggered_hooks(hooks_scope, data)
  triggered = ::Projects::TriggeredHooks.new(hooks_scope, data)
  triggered.add_hooks(hooks)
end

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


2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
# File 'app/models/project.rb', line 2242

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)

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

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

#update_forks_visibility_levelObject

update visibility_level of forks


1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
# File 'app/models/project.rb', line 1795

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_pages_deployment!(deployment) ⇒ Object


1987
1988
1989
# File 'app/models/project.rb', line 1987

def update_pages_deployment!(deployment)
  ensure_pages_metadatum.update!(pages_deployment: deployment)
end

#update_project_counter_cachesObject


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

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

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

#update_remote_mirrorsObject


1272
1273
1274
1275
1276
# File 'app/models/project.rb', line 1272

def update_remote_mirrors
  return unless remote_mirror_available?

  remote_mirrors.enabled.each(&:sync)
end

#updating_remote_mirror?Boolean

Returns:

  • (Boolean)

1268
1269
1270
# File 'app/models/project.rb', line 1268

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

#uses_default_ci_config?Boolean

Returns:

  • (Boolean)

2607
2608
2609
# File 'app/models/project.rb', line 2607

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

#uses_external_project_ci_config?Boolean

Returns:

  • (Boolean)

2611
2612
2613
# File 'app/models/project.rb', line 2611

def uses_external_project_ci_config?
  !!(ci_config_path =~ %r{@.+/.+})
end

#valid_import_url?Boolean

Returns:

  • (Boolean)

1208
1209
1210
# File 'app/models/project.rb', line 1208

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

#valid_runners_token?(token) ⇒ Boolean

Returns:

  • (Boolean)

1866
1867
1868
# File 'app/models/project.rb', line 1866

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

#validate_pages_https_onlyObject


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

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)

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

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


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

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)

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

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


1317
1318
1319
1320
1321
1322
1323
# File 'app/models/project.rb', line 1317

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)

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

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

  level <= group.visibility_level
end

#visibility_level_fieldObject


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

def visibility_level_field
  :visibility_level
end

2818
2819
2820
2821
2822
2823
2824
2825
2826
# File 'app/models/project.rb', line 2818

def visible_group_links(for_user:)
  user = for_user
  links = project_group_links_with_preload
  user.max_member_access_for_group_ids(links.map(&:group_id)) if user && links.any?

  DeclarativePolicy.user_scope do
    links.select { Ability.allowed?(user, :read_group, _1.group) }
  end
end

#work_items_feature_flag_enabled?Boolean

Returns:

  • (Boolean)

2875
2876
2877
# File 'app/models/project.rb', line 2875

def work_items_feature_flag_enabled?
  group&.work_items_feature_flag_enabled? || Feature.enabled?(:work_items, self)
end