2018-11-18 11:00:15 +05:30
# frozen_string_literal: true
2015-04-26 12:48:37 +05:30
require 'carrierwave/orm/activerecord'
2019-07-07 11:18:12 +05:30
class Project < ApplicationRecord
2015-09-11 14:41:01 +05:30
include Gitlab :: ConfigHelper
2014-09-02 18:07:02 +05:30
include Gitlab :: VisibilityLevel
2016-06-16 23:09:34 +05:30
include AccessRequestable
2017-09-10 17:25:29 +05:30
include Avatarable
2016-11-03 12:29:30 +05:30
include CacheMarkdownField
2015-09-11 14:41:01 +05:30
include Sortable
2015-10-24 18:46:33 +05:30
include AfterCommitQueue
include CaseSensitivity
2016-01-14 18:37:52 +05:30
include TokenAuthenticatable
2017-08-17 22:00:37 +05:30
include ValidAttribute
2019-09-30 21:07:59 +05:30
include ProjectAPICompatibility
2016-09-29 09:46:39 +05:30
include ProjectFeaturesCompatibility
2017-08-17 22:00:37 +05:30
include SelectForProjectAuthorization
2018-03-27 19:54:05 +05:30
include Presentable
2020-03-13 15:44:24 +05:30
include HasRepository
2020-05-24 23:13:21 +05:30
include HasWiki
2021-06-08 01:23:25 +05:30
include HasIntegrations
2021-02-22 17:27:13 +05:30
include CanMoveRepositoryStorage
2017-08-17 22:00:37 +05:30
include Routable
2018-03-17 18:26:18 +05:30
include GroupDescendant
include Gitlab :: SQL :: Pattern
include DeploymentPlatform
include :: Gitlab :: Utils :: StrongMemoize
2018-05-09 12:01:36 +05:30
include ChronicDurationAttribute
2018-10-15 14:42:47 +05:30
include FastDestroyAll :: Helpers
2018-11-08 19:23:39 +05:30
include WithUploads
include BatchDestroyDependentAssociations
2018-11-20 20:47:30 +05:30
include FeatureGate
include OptionallySearch
2018-12-05 23:21:45 +05:30
include FromUnion
2020-01-01 13:55:28 +05:30
include IgnorableColumns
2021-03-08 18:12:59 +05:30
include Repositories :: CanHousekeepRepository
2021-01-03 14:25:43 +05:30
include EachBatch
2021-04-29 21:17:54 +05:30
include GitlabRoutingHelper
2018-11-08 19:23:39 +05:30
extend Gitlab :: Cache :: RequestCache
2021-01-03 14:25:43 +05:30
extend Gitlab :: Utils :: Override
2015-12-23 02:04:40 +05:30
2014-09-02 18:07:02 +05:30
extend Gitlab :: ConfigHelper
2017-08-17 22:00:37 +05:30
BoardLimitExceeded = Class . new ( StandardError )
2016-11-03 12:29:30 +05:30
2019-12-04 20:38:33 +05:30
STATISTICS_ATTRIBUTE = 'repositories_count'
UNKNOWN_IMPORT_URL = 'http://unknown.git'
2018-03-17 18:26:18 +05:30
# Hashed Storage versions handle rolling out new storage to project and dependents models:
# nil: legacy
# 1: repository
# 2: attachments
LATEST_STORAGE_VERSION = 2
HASHED_STORAGE_FEATURES = {
repository : 1 ,
attachments : 2
} . freeze
2018-03-26 14:24:53 +05:30
2018-12-13 13:39:08 +05:30
VALID_IMPORT_PORTS = [ 80 , 443 ] . freeze
VALID_IMPORT_PROTOCOLS = %w( http https git ) . freeze
VALID_MIRROR_PORTS = [ 22 , 80 , 443 ] . freeze
VALID_MIRROR_PROTOCOLS = %w( http https ssh git ) . freeze
2015-09-25 12:07:36 +05:30
2019-12-04 20:38:33 +05:30
ACCESS_REQUEST_APPROVERS_TO_BE_NOTIFIED_LIMIT = 10
SORTING_PREFERENCE_FIELD = :projects_sort
MAX_BUILD_TIMEOUT = 1 . month
2021-02-22 17:27:13 +05:30
GL_REPOSITORY_TYPES = [ Gitlab :: GlRepository :: PROJECT , Gitlab :: GlRepository :: WIKI , Gitlab :: GlRepository :: DESIGN ] . freeze
2016-11-03 12:29:30 +05:30
cache_markdown_field :description , pipeline : :description
2020-07-28 23:09:34 +05:30
default_value_for :packages_enabled , true
2014-09-02 18:07:02 +05:30
default_value_for :archived , false
2018-03-17 18:26:18 +05:30
default_value_for :resolve_outdated_diff_discussions , false
2016-06-02 11:05:42 +05:30
default_value_for :container_registry_enabled , gitlab_config_features . container_registry
2020-04-08 14:13:33 +05:30
default_value_for ( :repository_storage ) do
2021-03-11 19:13:27 +05:30
Repository . pick_storage_shard
2020-04-08 14:13:33 +05:30
end
2018-03-17 18:26:18 +05:30
default_value_for ( :shared_runners_enabled ) { Gitlab :: CurrentSettings . shared_runners_enabled }
2016-11-24 13:41:30 +05:30
default_value_for :issues_enabled , gitlab_config_features . issues
default_value_for :merge_requests_enabled , gitlab_config_features . merge_requests
default_value_for :builds_enabled , gitlab_config_features . builds
default_value_for :wiki_enabled , gitlab_config_features . wiki
default_value_for :snippets_enabled , gitlab_config_features . snippets
2017-08-17 22:00:37 +05:30
default_value_for :only_allow_merge_if_all_discussions_are_resolved , false
2019-12-26 22:10:19 +05:30
default_value_for :remove_source_branch_after_merge , true
2020-03-13 15:44:24 +05:30
default_value_for :autoclose_referenced_issues , true
2019-12-26 22:10:19 +05:30
default_value_for ( :ci_config_path ) { Gitlab :: CurrentSettings . default_ci_config_path }
2014-09-02 18:07:02 +05:30
2019-07-07 11:18:12 +05:30
add_authentication_token_field :runners_token , encrypted : - > { Feature . enabled? ( :projects_tokens_optional_encryption , default_enabled : true ) ? :optional : :required }
2018-10-15 14:42:47 +05:30
2018-11-08 19:23:39 +05:30
before_validation :mark_remote_mirrors_for_removal , if : - > { RemoteMirror . table_exists? }
2018-10-15 14:42:47 +05:30
2018-03-17 18:26:18 +05:30
before_save :ensure_runners_token
2016-08-24 12:49:21 +05:30
2021-04-17 20:07:23 +05:30
# https://api.rubyonrails.org/v6.0.3.4/classes/ActiveRecord/AttributeMethods/Dirty.html#method-i-will_save_change_to_attribute-3F
before_update :set_container_registry_access_level , if : :will_save_change_to_container_registry_enabled?
2019-07-31 22:56:46 +05:30
after_save :update_project_statistics , if : :saved_change_to_namespace_id?
2018-10-15 14:42:47 +05:30
after_save :create_import_state , if : - > ( project ) { project . import? && project . import_state . nil? }
2021-06-08 01:23:25 +05:30
after_create - > { create_or_load_association ( :project_feature ) }
2018-10-15 14:42:47 +05:30
2021-06-08 01:23:25 +05:30
after_create - > { create_or_load_association ( :ci_cd_settings ) }
2018-10-15 14:42:47 +05:30
2021-06-08 01:23:25 +05:30
after_create - > { create_or_load_association ( :container_expiration_policy ) }
2020-01-01 13:55:28 +05:30
2021-06-08 01:23:25 +05:30
after_create - > { create_or_load_association ( :pages_metadatum ) }
2019-12-21 20:55:43 +05:30
2018-12-13 13:39:08 +05:30
after_create :set_timestamps_for_create
2018-03-17 18:26:18 +05:30
after_update :update_forks_visibility_level
2017-08-17 22:00:37 +05:30
2017-09-10 17:25:29 +05:30
before_destroy :remove_private_deploy_keys
2018-10-15 14:42:47 +05:30
use_fast_destroy :build_trace_chunks
2021-03-11 19:13:27 +05:30
after_destroy - > { run_after_commit { legacy_remove_pages } }
2018-03-17 18:26:18 +05:30
after_destroy :remove_exports
2016-01-14 18:37:52 +05:30
2017-08-17 22:00:37 +05:30
after_validation :check_pending_delete
2018-03-17 18:26:18 +05:30
# Storage specific hooks
after_initialize :use_hashed_storage
after_create :check_repository_absence!
2017-09-10 17:25:29 +05:30
2021-06-08 01:23:25 +05:30
acts_as_ordered_taggable_on :topics
# The 'tag_list' alias and the 'has_many' associations are required during the 'tags -> topics' migration
# TODO: eliminate 'tag_list', 'topic_taggings' and 'tags' in the further process of the migration
# https://gitlab.com/gitlab-org/gitlab/-/issues/331081
alias_attribute :tag_list , :topic_list
has_many :topic_taggings , - > { includes ( :tag ) . order ( " #{ ActsAsTaggableOn :: Tagging . table_name } .id " ) } ,
as : :taggable ,
class_name : 'ActsAsTaggableOn::Tagging' ,
after_add : :dirtify_tag_list ,
after_remove : :dirtify_tag_list
has_many :topics , - > { order ( " #{ ActsAsTaggableOn :: Tagging . table_name } .id " ) } ,
class_name : 'ActsAsTaggableOn::Tag' ,
through : :topic_taggings ,
source : :tag
has_many :tags , - > { order ( " #{ ActsAsTaggableOn :: Tagging . table_name } .id " ) } ,
class_name : 'ActsAsTaggableOn::Tag' ,
through : :topic_taggings ,
source : :tag
# Overwriting 'topic_list' and 'topic_list=' is necessary to ensure functionality during the background migration [1].
# [1] https://gitlab.com/gitlab-org/gitlab/-/merge_requests/61237
# TODO: remove 'topic_list' and 'topic_list=' once the background migration is complete
# https://gitlab.com/gitlab-org/gitlab/-/issues/331081
def topic_list
# Return both old topics (context 'tags') and new topics (context 'topics')
tag_list_on ( 'tags' ) + tag_list_on ( 'topics' )
end
def topic_list = ( new_tags )
# Old topics with context 'tags' are added as new topics with context 'topics'
super ( new_tags )
# Remove old topics with context 'tags'
set_tag_list_on ( 'tags' , '' )
end
2014-09-02 18:07:02 +05:30
2016-01-14 18:37:52 +05:30
attr_accessor :old_path_with_namespace
2017-09-10 17:25:29 +05:30
attr_accessor :template_name
2017-08-17 22:00:37 +05:30
attr_writer :pipeline_status
2018-03-17 18:26:18 +05:30
attr_accessor :skip_disk_validation
2014-09-02 18:07:02 +05:30
2016-06-02 11:05:42 +05:30
alias_attribute :title , :name
2014-09-02 18:07:02 +05:30
# Relations
2018-12-13 13:39:08 +05:30
belongs_to :pool_repository
2017-08-17 22:00:37 +05:30
belongs_to :creator , class_name : 'User'
2016-09-29 09:46:39 +05:30
belongs_to :group , - > { where ( type : 'Group' ) } , foreign_key : 'namespace_id'
2014-09-02 18:07:02 +05:30
belongs_to :namespace
2018-03-17 18:26:18 +05:30
alias_method :parent , :namespace
alias_attribute :parent_id , :namespace_id
2014-09-02 18:07:02 +05:30
2017-08-17 22:00:37 +05:30
has_one :last_event , - > { order 'events.created_at DESC' } , class_name : 'Event'
2019-07-07 11:18:12 +05:30
has_many :boards
2016-09-29 09:46:39 +05:30
2021-06-08 01:23:25 +05:30
# Project integrations
has_one :asana_service , class_name : 'Integrations::Asana'
has_one :assembla_service , class_name : 'Integrations::Assembla'
has_one :bamboo_service , class_name : 'Integrations::Bamboo'
has_one :campfire_service , class_name : 'Integrations::Campfire'
has_one :confluence_service , class_name : 'Integrations::Confluence'
has_one :datadog_service , class_name : 'Integrations::Datadog'
has_one :emails_on_push_service , class_name : 'Integrations::EmailsOnPush'
2019-02-15 15:39:39 +05:30
has_one :discord_service
2017-09-10 17:25:29 +05:30
has_one :drone_ci_service
2020-11-24 15:15:51 +05:30
has_one :ewm_service
2017-09-10 17:25:29 +05:30
has_one :pipelines_email_service
has_one :irker_service
has_one :pivotaltracker_service
has_one :flowdock_service
has_one :mattermost_slash_commands_service
has_one :mattermost_service
has_one :slack_slash_commands_service
has_one :slack_service
has_one :buildkite_service
has_one :teamcity_service
has_one :pushover_service
2021-02-22 17:27:13 +05:30
has_one :jenkins_service
2017-09-10 17:25:29 +05:30
has_one :jira_service
has_one :redmine_service
2019-07-07 11:18:12 +05:30
has_one :youtrack_service
2017-09-10 17:25:29 +05:30
has_one :custom_issue_tracker_service
has_one :bugzilla_service
has_one :external_wiki_service
has_one :prometheus_service , inverse_of : :project
has_one :mock_ci_service
has_one :mock_deployment_service
has_one :mock_monitoring_service
has_one :microsoft_teams_service
2018-03-17 18:26:18 +05:30
has_one :packagist_service
2018-11-18 11:00:15 +05:30
has_one :hangouts_chat_service
2020-01-01 13:55:28 +05:30
has_one :unify_circuit_service
2020-05-24 23:13:21 +05:30
has_one :webex_teams_service
2017-09-10 17:25:29 +05:30
2018-03-17 18:26:18 +05:30
has_one :root_of_fork_network ,
foreign_key : 'root_project_id' ,
inverse_of : :root_project ,
class_name : 'ForkNetwork'
has_one :fork_network_member
has_one :fork_network , through : :fork_network_member
2018-12-13 13:39:08 +05:30
has_one :forked_from_project , through : :fork_network_member
has_many :forked_to_members , class_name : 'ForkNetworkMember' , foreign_key : 'forked_from_project_id'
has_many :forks , through : :forked_to_members , source : :project , inverse_of : :forked_from_project
2020-01-01 13:55:28 +05:30
has_many :fork_network_projects , through : :fork_network , source : :projects
2015-04-26 12:48:37 +05:30
2020-07-28 23:09:34 +05:30
# Packages
has_many :packages , class_name : 'Packages::Package'
has_many :package_files , through : :packages , class_name : 'Packages::PackageFile'
2021-03-11 19:13:27 +05:30
# debian_distributions and associated component_files must be destroyed by ruby code in order to properly remove carrierwave uploads
2021-03-08 18:12:59 +05:30
has_many :debian_distributions , class_name : 'Packages::Debian::ProjectDistribution' , dependent : :destroy # rubocop:disable Cop/ActiveRecordDependent
2020-07-28 23:09:34 +05:30
2018-10-15 14:42:47 +05:30
has_one :import_state , autosave : true , class_name : 'ProjectImportState' , inverse_of : :project
2018-11-08 19:23:39 +05:30
has_one :import_export_upload , dependent : :destroy # rubocop:disable Cop/ActiveRecordDependent
2020-04-08 14:13:33 +05:30
has_many :export_jobs , class_name : 'ProjectExportJob'
2019-02-15 15:39:39 +05:30
has_one :project_repository , inverse_of : :project
2021-01-03 14:25:43 +05:30
has_one :tracing_setting , class_name : 'ProjectTracingSetting'
2020-03-13 15:44:24 +05:30
has_one :incident_management_setting , inverse_of : :project , class_name : 'IncidentManagement::ProjectIncidentManagementSetting'
2019-02-15 15:39:39 +05:30
has_one :error_tracking_setting , inverse_of : :project , class_name : 'ErrorTracking::ProjectErrorTrackingSetting'
2019-07-31 22:56:46 +05:30
has_one :metrics_setting , inverse_of : :project , class_name : 'ProjectMetricsSetting'
2019-12-21 20:55:43 +05:30
has_one :grafana_integration , inverse_of : :project
2020-06-23 00:09:42 +05:30
has_one :project_setting , inverse_of : :project , autosave : true
2020-04-22 19:07:51 +05:30
has_one :alerting_setting , inverse_of : :project , class_name : 'Alerting::ProjectAlertingSetting'
2020-07-28 23:09:34 +05:30
has_one :service_desk_setting , class_name : 'ServiceDeskSetting'
2018-10-15 14:42:47 +05:30
2021-04-29 21:17:54 +05:30
# Merge requests for target project should be removed with it
2018-12-13 13:39:08 +05:30
has_many :merge_requests , foreign_key : 'target_project_id' , inverse_of : :target_project
2021-03-11 19:13:27 +05:30
has_many :merge_request_metrics , foreign_key : 'target_project' , class_name : 'MergeRequest::Metrics' , inverse_of : :target_project
2018-03-27 19:54:05 +05:30
has_many :source_of_merge_requests , foreign_key : 'source_project_id' , class_name : 'MergeRequest'
2017-09-10 17:25:29 +05:30
has_many :issues
has_many :labels , class_name : 'ProjectLabel'
2021-06-08 01:23:25 +05:30
has_many :integrations
2017-09-10 17:25:29 +05:30
has_many :events
has_many :milestones
2020-05-24 23:13:21 +05:30
has_many :iterations
2017-09-10 17:25:29 +05:30
has_many :notes
has_many :snippets , class_name : 'ProjectSnippet'
has_many :hooks , class_name : 'ProjectHook'
has_many :protected_branches
2021-02-22 17:27:13 +05:30
has_many :exported_protected_branches
2017-09-10 17:25:29 +05:30
has_many :protected_tags
2018-11-18 11:00:15 +05:30
has_many :repository_languages , - > { order " share DESC " }
2020-05-24 23:13:21 +05:30
has_many :designs , inverse_of : :project , class_name : 'DesignManagement::Design'
2016-08-24 12:49:21 +05:30
2017-08-17 22:00:37 +05:30
has_many :project_authorizations
has_many :authorized_users , through : :project_authorizations , source : :user , class_name : 'User'
2017-09-10 17:25:29 +05:30
has_many :project_members , - > { where ( requested_at : nil ) } ,
as : :source , dependent : :delete_all # rubocop:disable Cop/ActiveRecordDependent
2016-06-16 23:09:34 +05:30
alias_method :members , :project_members
2016-08-24 12:49:21 +05:30
has_many :users , through : :project_members
2017-09-10 17:25:29 +05:30
has_many :requesters , - > { where . not ( requested_at : nil ) } ,
as : :source , class_name : 'ProjectMember' , dependent : :delete_all # rubocop:disable Cop/ActiveRecordDependent
2018-03-17 18:26:18 +05:30
has_many :members_and_requesters , as : :source , class_name : 'ProjectMember'
2016-08-24 12:49:21 +05:30
2019-10-12 21:52:04 +05:30
has_many :deploy_keys_projects , inverse_of : :project
2014-09-02 18:07:02 +05:30
has_many :deploy_keys , through : :deploy_keys_projects
2017-09-10 17:25:29 +05:30
has_many :users_star_projects
2014-09-02 18:07:02 +05:30
has_many :starrers , through : :users_star_projects , source : :user
2017-09-10 17:25:29 +05:30
has_many :releases
has_many :lfs_objects_projects , dependent : :destroy # rubocop:disable Cop/ActiveRecordDependent
2019-09-04 21:01:54 +05:30
has_many :lfs_objects , - > { distinct } , through : :lfs_objects_projects
2018-03-17 18:26:18 +05:30
has_many :lfs_file_locks
2017-09-10 17:25:29 +05:30
has_many :project_group_links
2016-06-02 11:05:42 +05:30
has_many :invited_groups , through : :project_group_links , source : :group
2017-09-10 17:25:29 +05:30
has_many :todos
has_many :notification_settings , as : :source , dependent : :delete_all # rubocop:disable Cop/ActiveRecordDependent
2018-05-09 12:01:36 +05:30
has_many :internal_ids
2017-09-10 17:25:29 +05:30
has_one :import_data , class_name : 'ProjectImportData' , inverse_of : :project , autosave : true
2018-03-17 18:26:18 +05:30
has_one :project_feature , inverse_of : :project
2017-09-10 17:25:29 +05:30
has_one :statistics , class_name : 'ProjectStatistics'
2020-11-24 15:15:51 +05:30
has_one :feature_usage , class_name : 'ProjectFeatureUsage'
2017-09-10 17:25:29 +05:30
2018-03-17 18:26:18 +05:30
has_one :cluster_project , class_name : 'Clusters::Project'
has_many :clusters , through : :cluster_project , class_name : 'Clusters::Cluster'
2019-02-15 15:39:39 +05:30
has_many :kubernetes_namespaces , class_name : 'Clusters::KubernetesNamespace'
2019-12-21 20:55:43 +05:30
has_many :management_clusters , class_name : 'Clusters::Cluster' , foreign_key : :management_project_id , inverse_of : :management_project
2020-10-24 23:57:45 +05:30
has_many :cluster_agents , class_name : 'Clusters::Agent'
2018-03-17 18:26:18 +05:30
2018-11-20 20:47:30 +05:30
has_many :prometheus_metrics
2020-03-13 15:44:24 +05:30
has_many :prometheus_alerts , inverse_of : :project
2020-04-22 19:07:51 +05:30
has_many :prometheus_alert_events , inverse_of : :project
has_many :self_managed_prometheus_alert_events , inverse_of : :project
2020-05-24 23:13:21 +05:30
has_many :metrics_users_starred_dashboards , class_name : 'Metrics::UsersStarredDashboard' , inverse_of : :project
has_many :alert_management_alerts , class_name : 'AlertManagement::Alert' , inverse_of : :project
2021-01-03 14:25:43 +05:30
has_many :alert_management_http_integrations , class_name : 'AlertManagement::HttpIntegration' , inverse_of : :project
2018-11-20 20:47:30 +05:30
2017-09-10 17:25:29 +05:30
# Container repositories need to remove data from the container registry,
# which is not managed by the DB. Hence we're still using dependent: :destroy
# here.
has_many :container_repositories , dependent : :destroy # rubocop:disable Cop/ActiveRecordDependent
2020-01-01 13:55:28 +05:30
has_one :container_expiration_policy , inverse_of : :project
2017-09-10 17:25:29 +05:30
has_many :commit_statuses
2019-07-07 11:18:12 +05:30
# The relation :all_pipelines is intended to be used when we want to get the
2019-02-15 15:39:39 +05:30
# whole list of pipelines associated to the project
has_many :all_pipelines , class_name : 'Ci::Pipeline' , inverse_of : :project
2020-11-24 15:15:51 +05:30
# The relation :ci_pipelines includes all those that directly contribute to the
# latest status of a ref. This does not include dangling pipelines such as those
# from webide, child pipelines, etc.
2019-02-15 15:39:39 +05:30
has_many :ci_pipelines ,
- > { ci_sources } ,
class_name : 'Ci::Pipeline' ,
inverse_of : :project
2018-11-08 19:23:39 +05:30
has_many :stages , class_name : 'Ci::Stage' , inverse_of : :project
2020-06-23 00:09:42 +05:30
has_many :ci_refs , class_name : 'Ci::Ref' , inverse_of : :project
2017-09-10 17:25:29 +05:30
# Ci::Build objects store data on the file system such as artifact files and
# build traces. Currently there's no efficient way of removing this data in
# bulk that doesn't involve loading the rows into memory. As a result we're
# still using `dependent: :destroy` here.
2018-03-17 18:26:18 +05:30
has_many :builds , class_name : 'Ci::Build' , inverse_of : :project , dependent : :destroy # rubocop:disable Cop/ActiveRecordDependent
2021-01-03 14:25:43 +05:30
has_many :processables , class_name : 'Ci::Processable' , inverse_of : :project
2018-03-17 18:26:18 +05:30
has_many :build_trace_section_names , class_name : 'Ci::BuildTraceSectionName'
2018-10-15 14:42:47 +05:30
has_many :build_trace_chunks , class_name : 'Ci::BuildTraceChunk' , through : :builds , source : :trace_chunks
2020-06-23 00:09:42 +05:30
has_many :build_report_results , class_name : 'Ci::BuildReportResult' , inverse_of : :project
2019-12-21 20:55:43 +05:30
has_many :job_artifacts , class_name : 'Ci::JobArtifact'
2020-10-24 23:57:45 +05:30
has_many :pipeline_artifacts , class_name : 'Ci::PipelineArtifact' , inverse_of : :project
2018-11-08 19:23:39 +05:30
has_many :runner_projects , class_name : 'Ci::RunnerProject' , inverse_of : :project
2015-12-23 02:04:40 +05:30
has_many :runners , through : :runner_projects , source : :runner , class_name : 'Ci::Runner'
2017-09-10 17:25:29 +05:30
has_many :variables , class_name : 'Ci::Variable'
has_many :triggers , class_name : 'Ci::Trigger'
has_many :environments
2019-12-26 22:10:19 +05:30
has_many :environments_for_dashboard , - > { from ( with_rank . unfoldered . available , :environments ) . where ( 'rank <= 3' ) } , class_name : 'Environment'
2019-12-21 20:55:43 +05:30
has_many :deployments
2017-09-10 17:25:29 +05:30
has_many :pipeline_schedules , class_name : 'Ci::PipelineSchedule'
2018-05-09 12:01:36 +05:30
has_many :project_deploy_tokens
has_many :deploy_tokens , through : :project_deploy_tokens
2020-03-13 15:44:24 +05:30
has_many :resource_groups , class_name : 'Ci::ResourceGroup' , inverse_of : :project
2020-05-24 23:13:21 +05:30
has_many :freeze_periods , class_name : 'Ci::FreezePeriod' , inverse_of : :project
2017-08-17 22:00:37 +05:30
2019-09-30 21:07:59 +05:30
has_one :auto_devops , class_name : 'ProjectAutoDevops' , inverse_of : :project , autosave : true
2018-03-17 18:26:18 +05:30
has_many :custom_attributes , class_name : 'ProjectCustomAttribute'
2018-03-27 19:54:05 +05:30
has_many :project_badges , class_name : 'ProjectBadge'
2018-10-15 14:42:47 +05:30
has_one :ci_cd_settings , class_name : 'ProjectCiCdSetting' , inverse_of : :project , autosave : true , dependent : :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :remote_mirrors , inverse_of : :project
2021-06-08 01:23:25 +05:30
has_many :cycle_analytics_stages , class_name : 'Analytics::CycleAnalytics::ProjectStage' , inverse_of : :project
has_many :value_streams , class_name : 'Analytics::CycleAnalytics::ProjectValueStream' , inverse_of : :project
2018-03-27 19:54:05 +05:30
2019-12-04 20:38:33 +05:30
has_many :external_pull_requests , inverse_of : :project
2019-12-21 20:55:43 +05:30
has_many :sourced_pipelines , class_name : 'Ci::Sources::Pipeline' , foreign_key : :source_project_id
has_many :source_pipelines , class_name : 'Ci::Sources::Pipeline' , foreign_key : :project_id
2020-01-01 13:55:28 +05:30
has_many :import_failures , inverse_of : :project
2020-04-22 19:07:51 +05:30
has_many :jira_imports , - > { order 'jira_imports.created_at' } , class_name : 'JiraImportState' , inverse_of : :project
2020-05-24 23:13:21 +05:30
has_many :daily_build_group_report_results , class_name : 'Ci::DailyBuildGroupReportResult'
2021-04-17 20:07:23 +05:30
has_many :repository_storage_moves , class_name : 'Projects::RepositoryStorageMove' , inverse_of : :container
2020-01-01 13:55:28 +05:30
2020-06-23 00:09:42 +05:30
has_many :webide_pipelines , - > { webide_source } , class_name : 'Ci::Pipeline' , inverse_of : :project
has_many :reviews , inverse_of : :project
2021-01-03 14:25:43 +05:30
has_many :terraform_states , class_name : 'Terraform::State' , inverse_of : :project
2020-11-24 15:15:51 +05:30
# GitLab Pages
has_many :pages_domains
has_one :pages_metadatum , class_name : 'ProjectPagesMetadatum' , inverse_of : :project
2021-01-29 00:20:46 +05:30
# we need to clean up files, not only remove records
has_many :pages_deployments , dependent : :destroy # rubocop:disable Cop/ActiveRecordDependent
2020-11-24 15:15:51 +05:30
2020-10-24 23:57:45 +05:30
# Can be too many records. We need to implement delete_all in batches.
# Issue https://gitlab.com/gitlab-org/gitlab/-/issues/228637
has_many :product_analytics_events , dependent : :destroy # rubocop:disable Cop/ActiveRecordDependent
2020-11-24 15:15:51 +05:30
has_many :operations_feature_flags , class_name : 'Operations::FeatureFlag'
has_one :operations_feature_flags_client , class_name : 'Operations::FeatureFlagsClient'
has_many :operations_feature_flags_user_lists , class_name : 'Operations::FeatureFlags::UserList'
2021-06-08 01:23:25 +05:30
has_many :timelogs
2015-12-23 02:04:40 +05:30
accepts_nested_attributes_for :variables , allow_destroy : true
2018-03-17 18:26:18 +05:30
accepts_nested_attributes_for :project_feature , update_only : true
2020-05-24 23:13:21 +05:30
accepts_nested_attributes_for :project_setting , update_only : true
2017-09-10 17:25:29 +05:30
accepts_nested_attributes_for :import_data
2018-03-17 18:26:18 +05:30
accepts_nested_attributes_for :auto_devops , update_only : true
2019-09-04 21:01:54 +05:30
accepts_nested_attributes_for :ci_cd_settings , update_only : true
2020-01-01 13:55:28 +05:30
accepts_nested_attributes_for :container_expiration_policy , update_only : true
2015-04-26 12:48:37 +05:30
2018-10-15 14:42:47 +05:30
accepts_nested_attributes_for :remote_mirrors ,
allow_destroy : true ,
reject_if : - > ( attrs ) { attrs [ :id ] . blank? && attrs [ :url ] . blank? }
2021-01-03 14:25:43 +05:30
accepts_nested_attributes_for :tracing_setting , update_only : true , allow_destroy : true
2020-03-13 15:44:24 +05:30
accepts_nested_attributes_for :incident_management_setting , update_only : true
2019-02-15 15:39:39 +05:30
accepts_nested_attributes_for :error_tracking_setting , update_only : true
2019-07-31 22:56:46 +05:30
accepts_nested_attributes_for :metrics_setting , update_only : true , allow_destroy : true
2019-12-21 20:55:43 +05:30
accepts_nested_attributes_for :grafana_integration , update_only : true , allow_destroy : true
2020-03-13 15:44:24 +05:30
accepts_nested_attributes_for :prometheus_service , update_only : true
2020-04-22 19:07:51 +05:30
accepts_nested_attributes_for :alerting_setting , update_only : true
2020-03-13 15:44:24 +05:30
delegate :feature_available? , :builds_enabled? , :wiki_enabled? ,
:merge_requests_enabled? , :forking_enabled? , :issues_enabled? ,
2021-02-22 17:27:13 +05:30
:pages_enabled? , :analytics_enabled? , :snippets_enabled? , :public_pages? , :private_pages? ,
2020-03-13 15:44:24 +05:30
:merge_requests_access_level , :forking_access_level , :issues_access_level ,
:wiki_access_level , :snippets_access_level , :builds_access_level ,
2021-02-22 17:27:13 +05:30
:repository_access_level , :pages_access_level , :metrics_dashboard_access_level , :analytics_access_level ,
2021-04-17 20:07:23 +05:30
:operations_enabled? , :operations_access_level , :security_and_compliance_access_level ,
:container_registry_access_level ,
to : :project_feature , allow_nil : true
2020-05-24 23:13:21 +05:30
delegate :show_default_award_emojis , :show_default_award_emojis = ,
:show_default_award_emojis? ,
to : :project_setting , allow_nil : true
2020-01-01 13:55:28 +05:30
delegate :scheduled? , :started? , :in_progress? , :failed? , :finished? ,
prefix : :import , to : :import_state , allow_nil : true
2020-07-28 23:09:34 +05:30
delegate :squash_always? , :squash_never? , :squash_enabled_by_default? , :squash_readonly? , to : :project_setting
2020-01-01 13:55:28 +05:30
delegate :no_import? , to : :import_state , allow_nil : true
2014-09-02 18:07:02 +05:30
delegate :name , to : :owner , allow_nil : true , prefix : true
delegate :members , to : :team , prefix : true
2017-08-17 22:00:37 +05:30
delegate :add_user , :add_users , to : :team
2018-11-18 11:00:15 +05:30
delegate :add_guest , :add_reporter , :add_developer , :add_maintainer , :add_role , to : :team
2018-10-15 14:42:47 +05:30
delegate :group_runners_enabled , :group_runners_enabled = , :group_runners_enabled? , to : :ci_cd_settings
2020-03-13 15:44:24 +05:30
delegate :root_ancestor , to : :namespace , allow_nil : true
2019-07-07 11:18:12 +05:30
delegate :last_pipeline , to : :commit , allow_nil : true
2019-09-04 21:01:54 +05:30
delegate :external_dashboard_url , to : :metrics_setting , allow_nil : true , prefix : true
2020-06-23 00:09:42 +05:30
delegate :dashboard_timezone , to : :metrics_setting , allow_nil : true , prefix : true
2019-09-04 21:01:54 +05:30
delegate :default_git_depth , :default_git_depth = , to : :ci_cd_settings , prefix : :ci
2021-01-03 14:25:43 +05:30
delegate :forward_deployment_enabled , :forward_deployment_enabled = , :forward_deployment_enabled? , to : :ci_cd_settings , prefix : :ci
2021-03-11 19:13:27 +05:30
delegate :keep_latest_artifact , :keep_latest_artifact = , :keep_latest_artifact? , :keep_latest_artifacts_available? , to : :ci_cd_settings
2021-03-08 18:12:59 +05:30
delegate :restrict_user_defined_variables , :restrict_user_defined_variables = , :restrict_user_defined_variables? ,
to : :ci_cd_settings
2020-05-24 23:13:21 +05:30
delegate :actual_limits , :actual_plan_name , to : :namespace , allow_nil : true
2020-07-28 23:09:34 +05:30
delegate :allow_merge_on_skipped_pipeline , :allow_merge_on_skipped_pipeline? ,
2021-02-22 17:27:13 +05:30
:allow_merge_on_skipped_pipeline = , :has_confluence? , :allow_editing_commit_messages? ,
2020-07-28 23:09:34 +05:30
to : :project_setting
delegate :active? , to : :prometheus_service , allow_nil : true , prefix : true
2014-09-02 18:07:02 +05:30
2020-11-24 15:15:51 +05:30
delegate :log_jira_dvcs_integration_usage , :jira_dvcs_server_last_sync_at , :jira_dvcs_cloud_last_sync_at , to : :feature_usage
2014-09-02 18:07:02 +05:30
# Validations
validates :creator , presence : true , on : :create
validates :description , length : { maximum : 2000 } , allow_blank : true
2017-09-10 17:25:29 +05:30
validates :ci_config_path ,
2018-03-17 18:26:18 +05:30
format : { without : %r{ ( \ . { 2 } | \ A/) } ,
2019-07-31 22:56:46 +05:30
message : _ ( 'cannot include leading slash or directory traversal.' ) } ,
2017-09-10 17:25:29 +05:30
length : { maximum : 255 } ,
allow_blank : true
2015-04-26 12:48:37 +05:30
validates :name ,
presence : true ,
2017-08-17 22:00:37 +05:30
length : { maximum : 255 } ,
2015-04-26 12:48:37 +05:30
format : { with : Gitlab :: Regex . project_name_regex ,
message : Gitlab :: Regex . project_name_regex_message }
validates :path ,
presence : true ,
2018-03-17 18:26:18 +05:30
project_path : true ,
length : { maximum : 255 }
2017-08-17 22:00:37 +05:30
2020-03-13 15:44:24 +05:30
validates :project_feature , presence : true
2014-09-02 18:07:02 +05:30
validates :namespace , presence : true
2017-08-17 22:00:37 +05:30
validates :name , uniqueness : { scope : :namespace_id }
2019-07-31 22:56:46 +05:30
validates :import_url , public_url : { schemes : - > ( project ) { project . persisted? ? VALID_MIRROR_PROTOCOLS : VALID_IMPORT_PROTOCOLS } ,
2019-01-03 12:48:30 +05:30
ports : - > ( project ) { project . persisted? ? VALID_MIRROR_PORTS : VALID_IMPORT_PORTS } ,
enforce_user : true } , if : [ :external_import? , :import_url_changed? ]
2014-09-02 18:07:02 +05:30
validates :star_count , numericality : { greater_than_or_equal_to : 0 }
2019-03-02 22:35:43 +05:30
validate :check_personal_projects_limit , on : :create
2018-03-17 18:26:18 +05:30
validate :check_repository_path_availability , on : :update , if : - > ( project ) { project . renamed? }
2019-06-05 12:25:43 +05:30
validate :visibility_level_allowed_by_group , if : :should_validate_visibility_level?
validate :visibility_level_allowed_as_fork , if : :should_validate_visibility_level?
2018-05-09 12:01:36 +05:30
validate :validate_pages_https_only , if : - > { changes . has_key? ( :pages_https_only ) }
2021-01-03 14:25:43 +05:30
validate :changing_shared_runners_enabled_is_allowed
2016-08-24 12:49:21 +05:30
validates :repository_storage ,
presence : true ,
inclusion : { in : - > ( _object ) { Gitlab . config . repositories . storages . keys } }
2021-03-11 19:13:27 +05:30
validates :variables , nested_attributes_duplicates : { scope : :environment_scope }
2019-02-15 15:39:39 +05:30
validates :bfg_object_map , file_size : { maximum : :max_attachment_size }
2020-03-13 15:44:24 +05:30
validates :max_artifacts_size , numericality : { only_integer : true , greater_than : 0 , allow_nil : true }
2015-04-26 12:48:37 +05:30
2014-09-02 18:07:02 +05:30
# Scopes
2017-09-10 17:25:29 +05:30
scope :pending_delete , - > { where ( pending_delete : true ) }
scope :without_deleted , - > { where ( pending_delete : false ) }
2017-08-17 22:00:37 +05:30
2020-01-01 13:55:28 +05:30
scope :with_storage_feature , - > ( feature ) do
where ( arel_table [ :storage_version ] . gteq ( HASHED_STORAGE_FEATURES [ feature ] ) )
end
scope :without_storage_feature , - > ( feature ) do
where ( arel_table [ :storage_version ] . lt ( HASHED_STORAGE_FEATURES [ feature ] )
. or ( arel_table [ :storage_version ] . eq ( nil ) ) )
end
scope :with_unmigrated_storage , - > do
where ( arel_table [ :storage_version ] . lt ( LATEST_STORAGE_VERSION )
. or ( arel_table [ :storage_version ] . eq ( nil ) ) )
end
2018-03-17 18:26:18 +05:30
2018-03-27 19:54:05 +05:30
# last_activity_at is throttled every minute, but last_repository_updated_at is updated with every push
2019-09-30 21:07:59 +05:30
scope :sorted_by_activity , - > { reorder ( Arel . sql ( " GREATEST(COALESCE(last_activity_at, '1970-01-01'), COALESCE(last_repository_updated_at, '1970-01-01')) DESC " ) ) }
2020-03-13 15:44:24 +05:30
scope :sorted_by_stars_desc , - > { reorder ( self . arel_table [ 'star_count' ] . desc ) }
scope :sorted_by_stars_asc , - > { reorder ( self . arel_table [ 'star_count' ] . asc ) }
# Sometimes queries (e.g. using CTEs) require explicit disambiguation with table name
scope :projects_order_id_desc , - > { reorder ( self . arel_table [ 'id' ] . desc ) }
2015-04-26 12:48:37 +05:30
2020-11-24 15:15:51 +05:30
scope :sorted_by_similarity_desc , - > ( search , include_in_select : false ) do
2020-10-24 23:57:45 +05:30
order_expression = Gitlab :: Database :: SimilarityScore . build_expression ( search : search , rules : [
{ column : arel_table [ " path " ] , multiplier : 1 } ,
{ column : arel_table [ " name " ] , multiplier : 0 . 7 } ,
{ column : arel_table [ " description " ] , multiplier : 0 . 2 }
] )
2021-04-17 20:07:23 +05:30
order = Gitlab :: Pagination :: Keyset :: Order . build ( [
Gitlab :: Pagination :: Keyset :: ColumnOrderDefinition . new (
attribute_name : 'similarity' ,
column_expression : order_expression ,
order_expression : order_expression . desc ,
order_direction : :desc ,
distinct : false ,
add_to_projections : true
) ,
Gitlab :: Pagination :: Keyset :: ColumnOrderDefinition . new (
attribute_name : 'id' ,
order_expression : Project . arel_table [ :id ] . desc
)
] )
2020-11-24 15:15:51 +05:30
2021-04-17 20:07:23 +05:30
order . apply_cursor_conditions ( reorder ( order ) )
2020-10-24 23:57:45 +05:30
end
2020-07-28 23:09:34 +05:30
scope :with_packages , - > { joins ( :packages ) }
2015-09-11 14:41:01 +05:30
scope :in_namespace , - > ( namespace_ids ) { where ( namespace_id : namespace_ids ) }
2014-09-02 18:07:02 +05:30
scope :personal , - > ( user ) { where ( namespace_id : user . namespace_id ) }
2021-04-29 21:17:54 +05:30
scope :joined , - > ( user ) { where . not ( namespace_id : user . namespace_id ) }
2017-09-10 17:25:29 +05:30
scope :starred_by , - > ( user ) { joins ( :users_star_projects ) . where ( 'users_star_projects.user_id' : user . id ) }
2016-06-02 11:05:42 +05:30
scope :visible_to_user , - > ( user ) { where ( id : user . authorized_projects . select ( :id ) . reorder ( nil ) ) }
2018-11-18 11:00:15 +05:30
scope :visible_to_user_and_access_level , - > ( user , access_level ) { where ( id : user . authorized_projects . where ( 'project_authorizations.access_level >= ?' , access_level ) . select ( :id ) . reorder ( nil ) ) }
2018-03-17 18:26:18 +05:30
scope :archived , - > { where ( archived : true ) }
2014-09-02 18:07:02 +05:30
scope :non_archived , - > { where ( archived : false ) }
2016-06-02 11:05:42 +05:30
scope :for_milestones , - > ( ids ) { joins ( :milestones ) . where ( 'milestones.id' = > ids ) . distinct }
2020-06-23 00:09:42 +05:30
scope :with_push , - > { joins ( :events ) . merge ( Event . pushed_action ) }
2016-11-24 13:41:30 +05:30
scope :with_project_feature , - > { joins ( 'LEFT JOIN project_features ON projects.id = project_features.project_id' ) }
2021-06-08 01:23:25 +05:30
scope :with_active_jira_services , - > { joins ( :integrations ) . merge ( :: JiraService . active ) } # rubocop:disable CodeReuse/ServiceClass
2020-11-24 15:15:51 +05:30
scope :with_jira_dvcs_cloud , - > { joins ( :feature_usage ) . merge ( ProjectFeatureUsage . with_jira_dvcs_integration_enabled ( cloud : true ) ) }
scope :with_jira_dvcs_server , - > { joins ( :feature_usage ) . merge ( ProjectFeatureUsage . with_jira_dvcs_integration_enabled ( cloud : false ) ) }
2020-01-01 13:55:28 +05:30
scope :inc_routes , - > { includes ( :route , namespace : :route ) }
2017-08-17 22:00:37 +05:30
scope :with_statistics , - > { includes ( :statistics ) }
2020-03-13 15:44:24 +05:30
scope :with_namespace , - > { includes ( :namespace ) }
scope :with_import_state , - > { includes ( :import_state ) }
2020-07-28 23:09:34 +05:30
scope :include_project_feature , - > { includes ( :project_feature ) }
2020-01-01 13:55:28 +05:30
scope :with_service , - > ( service ) { joins ( service ) . eager_load ( service ) }
2017-08-17 22:00:37 +05:30
scope :with_shared_runners , - > { where ( shared_runners_enabled : true ) }
2019-12-26 22:10:19 +05:30
scope :with_container_registry , - > { where ( container_registry_enabled : true ) }
2017-08-17 22:00:37 +05:30
scope :inside_path , - > ( path ) do
# We need routes alias rs for JOIN so it does not conflict with
# includes(:route) which we use in ProjectsFinder.
2017-09-10 17:25:29 +05:30
joins ( " INNER JOIN routes rs ON rs.source_id = projects.id AND rs.source_type = 'Project' " )
. where ( 'rs.path LIKE ?' , " #{ sanitize_sql_like ( path ) } /% " )
2017-08-17 22:00:37 +05:30
end
2016-11-24 13:41:30 +05:30
# "enabled" here means "not disabled". It includes private features!
scope :with_feature_enabled , - > ( feature ) {
2019-02-15 15:39:39 +05:30
access_level_attribute = ProjectFeature . arel_table [ ProjectFeature . access_level_attribute ( feature ) ]
enabled_feature = access_level_attribute . gt ( ProjectFeature :: DISABLED ) . or ( access_level_attribute . eq ( nil ) )
with_project_feature . where ( enabled_feature )
2016-11-24 13:41:30 +05:30
}
# Picks a feature where the level is exactly that given.
scope :with_feature_access_level , - > ( feature , level ) {
access_level_attribute = ProjectFeature . access_level_attribute ( feature )
with_project_feature . where ( project_features : { access_level_attribute = > level } )
}
2019-03-02 22:35:43 +05:30
# Picks projects which use the given programming language
scope :with_programming_language , - > ( language_name ) do
lang_id_query = ProgrammingLanguage
. with_name_case_insensitive ( language_name )
. select ( :id )
joins ( :repository_languages )
. where ( repository_languages : { programming_language_id : lang_id_query } )
end
2020-07-28 23:09:34 +05:30
scope :service_desk_enabled , - > { where ( service_desk_enabled : true ) }
2016-11-24 13:41:30 +05:30
scope :with_builds_enabled , - > { with_feature_enabled ( :builds ) }
scope :with_issues_enabled , - > { with_feature_enabled ( :issues ) }
2018-03-17 18:26:18 +05:30
scope :with_issues_available_for_user , - > ( current_user ) { with_feature_available_for_user ( :issues , current_user ) }
2019-06-05 12:25:43 +05:30
scope :with_merge_requests_available_for_user , - > ( current_user ) { with_feature_available_for_user ( :merge_requests , current_user ) }
2020-03-13 15:44:24 +05:30
scope :with_issues_or_mrs_available_for_user , - > ( user ) do
with_issues_available_for_user ( user ) . or ( with_merge_requests_available_for_user ( user ) )
end
2017-09-10 17:25:29 +05:30
scope :with_merge_requests_enabled , - > { with_feature_enabled ( :merge_requests ) }
2021-04-29 21:17:54 +05:30
scope :with_remote_mirrors , - > { joins ( :remote_mirrors ) . where ( remote_mirrors : { enabled : true } ) }
2020-01-01 13:55:28 +05:30
scope :with_limit , - > ( maximum ) { limit ( maximum ) }
2018-10-15 14:42:47 +05:30
scope :with_group_runners_enabled , - > do
joins ( :ci_cd_settings )
. where ( project_ci_cd_settings : { group_runners_enabled : true } )
end
2016-11-24 13:41:30 +05:30
2019-12-21 20:55:43 +05:30
scope :with_pages_deployed , - > do
joins ( :pages_metadatum ) . merge ( ProjectPagesMetadatum . deployed )
end
scope :pages_metadata_not_migrated , - > do
left_outer_joins ( :pages_metadatum )
. where ( project_pages_metadata : { project_id : nil } )
end
2020-07-28 23:09:34 +05:30
scope :with_api_commit_entity_associations , - > {
preload ( :project_feature , :route , namespace : [ :route , :owner ] )
2020-06-23 00:09:42 +05:30
}
2020-11-24 15:15:51 +05:30
scope :imported_from , - > ( type ) { where ( import_type : type ) }
2021-01-03 14:25:43 +05:30
scope :with_tracing_enabled , - > { joins ( :tracing_setting ) }
2021-01-29 00:20:46 +05:30
scope :with_enabled_error_tracking , - > { joins ( :error_tracking_setting ) . where ( project_error_tracking_settings : { enabled : true } ) }
2020-11-24 15:15:51 +05:30
2017-08-17 22:00:37 +05:30
enum auto_cancel_pending_pipelines : { disabled : 0 , enabled : 1 }
2019-02-15 15:39:39 +05:30
chronic_duration_attr :build_timeout_human_readable , :build_timeout ,
2019-07-31 22:56:46 +05:30
default : 3600 , error_message : _ ( 'Maximum job timeout has a value which could not be accepted' )
2018-05-09 12:01:36 +05:30
validates :build_timeout , allow_nil : true ,
2018-11-18 11:00:15 +05:30
numericality : { greater_than_or_equal_to : 10 . minutes ,
2019-12-04 20:38:33 +05:30
less_than : MAX_BUILD_TIMEOUT ,
2018-11-18 11:00:15 +05:30
only_integer : true ,
2019-09-30 21:07:59 +05:30
message : _ ( 'needs to be between 10 minutes and 1 month' ) }
2018-05-09 12:01:36 +05:30
2019-02-15 15:39:39 +05:30
# Used by Projects::CleanupService to hold a map of rewritten object IDs
mount_uploader :bfg_object_map , AttachmentUploader
2020-07-28 23:09:34 +05:30
def self . with_api_entity_associations
2021-06-08 01:23:25 +05:30
preload ( :project_feature , :route , :tags , :group , :timelogs , namespace : [ :route , :owner ] )
2020-07-28 23:09:34 +05:30
end
2020-06-23 00:09:42 +05:30
def self . with_web_entity_associations
2021-04-29 21:17:54 +05:30
preload ( :project_feature , :route , :creator , group : :parent , namespace : [ :route , :owner ] )
2020-06-23 00:09:42 +05:30
end
2018-11-20 20:47:30 +05:30
def self . eager_load_namespace_and_owner
includes ( namespace : :owner )
end
2017-09-10 17:25:29 +05:30
# Returns a collection of projects that is either public or visible to the
# logged in user.
2019-07-07 11:18:12 +05:30
def self . public_or_visible_to_user ( user = nil , min_access_level = nil )
2021-01-29 00:20:46 +05:30
min_access_level = nil if user & . can_read_all_resources?
2019-07-07 11:18:12 +05:30
2020-05-24 23:13:21 +05:30
return public_to_user unless user
if user . is_a? ( DeployToken )
2021-01-03 14:25:43 +05:30
user . accessible_projects
2020-05-24 23:13:21 +05:30
else
2018-03-17 18:26:18 +05:30
where ( 'EXISTS (?) OR projects.visibility_level IN (?)' ,
2019-07-07 11:18:12 +05:30
user . authorizations_for_projects ( min_access_level : min_access_level ) ,
2018-03-17 18:26:18 +05:30
Gitlab :: VisibilityLevel . levels_for_user ( user ) )
2017-09-10 17:25:29 +05:30
end
end
2018-12-05 23:21:45 +05:30
# project features may be "disabled", "internal", "enabled" or "public". If "internal",
2016-11-24 13:41:30 +05:30
# they are only available to team members. This scope returns projects where
2018-12-05 23:21:45 +05:30
# the feature is either public, enabled, or internal with permission for the user.
2019-07-07 11:18:12 +05:30
# Note: this scope doesn't enforce that the user has access to the projects, it just checks
# that the user has access to the feature. It's important to use this scope with others
2019-10-03 12:08:05 +05:30
# that checks project authorizations first (e.g. `filter_by_feature_visibility`).
2017-09-10 17:25:29 +05:30
#
# This method uses an optimised version of `with_feature_access_level` for
# logged in users to more efficiently get private projects with the given
# feature.
2016-11-24 13:41:30 +05:30
def self . with_feature_available_for_user ( feature , user )
2019-02-15 15:39:39 +05:30
visible = [ ProjectFeature :: ENABLED , ProjectFeature :: PUBLIC ]
2016-11-24 13:41:30 +05:30
2021-01-29 00:20:46 +05:30
if user & . can_read_all_resources?
2017-09-10 17:25:29 +05:30
with_feature_enabled ( feature )
elsif user
2019-07-07 11:18:12 +05:30
min_access_level = ProjectFeature . required_minimum_access_level ( feature )
2017-09-10 17:25:29 +05:30
column = ProjectFeature . quoted_access_level_column ( feature )
2016-11-24 13:41:30 +05:30
2017-09-10 17:25:29 +05:30
with_project_feature
2019-07-07 11:18:12 +05:30
. where ( " #{ column } IS NULL OR #{ column } IN (:public_visible) OR ( #{ column } = :private_visible AND EXISTS (:authorizations)) " ,
{
public_visible : visible ,
private_visible : ProjectFeature :: PRIVATE ,
authorizations : user . authorizations_for_projects ( min_access_level : min_access_level )
} )
2017-09-10 17:25:29 +05:30
else
2019-07-07 11:18:12 +05:30
# This has to be added to include features whose value is nil in the db
visible << nil
2017-09-10 17:25:29 +05:30
with_feature_access_level ( feature , visible )
end
2016-11-24 13:41:30 +05:30
end
2016-09-29 09:46:39 +05:30
2020-07-28 23:09:34 +05:30
def self . projects_user_can ( projects , user , action )
projects = where ( id : projects )
DeclarativePolicy . user_scope do
projects . select { | project | Ability . allowed? ( user , action , project ) }
end
end
2019-10-03 12:08:05 +05:30
# This scope returns projects where user has access to both the project and the feature.
def self . filter_by_feature_visibility ( feature , user )
2019-12-04 20:38:33 +05:30
with_feature_available_for_user ( feature , user )
. public_or_visible_to_user (
user ,
ProjectFeature . required_minimum_access_level_for_private_project ( feature )
)
2019-10-03 12:08:05 +05:30
end
2020-03-13 15:44:24 +05:30
def self . wrap_with_cte ( collection )
cte = Gitlab :: SQL :: CTE . new ( :projects_cte , collection )
Project . with ( cte . to_arel ) . from ( cte . alias_to ( Project . arel_table ) )
end
2016-06-02 11:05:42 +05:30
scope :active , - > { joins ( :issues , :notes , :merge_requests ) . order ( 'issues.created_at, notes.created_at, merge_requests.created_at DESC' ) }
scope :abandoned , - > { where ( 'projects.last_activity_at < ?' , 6 . months . ago ) }
2014-09-02 18:07:02 +05:30
2016-09-13 17:45:13 +05:30
scope :excluding_project , - > ( project ) { where . not ( id : project ) }
2017-09-10 17:25:29 +05:30
2019-02-15 15:39:39 +05:30
# We require an alias to the project_mirror_data_table in order to use import_state in our queries
scope :joins_import_state , - > { joins ( " INNER JOIN project_mirror_data import_state ON import_state.project_id = projects.id " ) }
2018-12-13 13:39:08 +05:30
scope :for_group , - > ( group ) { where ( group : group ) }
2019-12-04 20:38:33 +05:30
scope :for_group_and_its_subgroups , - > ( group ) { where ( namespace_id : group . self_and_descendants . select ( :id ) ) }
2014-09-02 18:07:02 +05:30
class << self
2016-06-02 11:05:42 +05:30
# Searches for a list of projects based on the query given in `query`.
#
# On PostgreSQL this method uses "ILIKE" to perform a case-insensitive
2020-06-23 00:09:42 +05:30
# search.
2016-06-02 11:05:42 +05:30
#
# query - The search query as a String.
2020-04-22 19:07:51 +05:30
def search ( query , include_namespace : false )
if include_namespace
joins ( :route ) . fuzzy_search ( query , [ Route . arel_table [ :path ] , Route . arel_table [ :name ] , :description ] )
2020-01-01 13:55:28 +05:30
else
fuzzy_search ( query , [ :path , :name , :description ] )
end
2014-09-02 18:07:02 +05:30
end
2015-04-26 12:48:37 +05:30
def search_by_title ( query )
2018-03-17 18:26:18 +05:30
non_archived . fuzzy_search ( query , [ :name ] )
2014-09-02 18:07:02 +05:30
end
def visibility_levels
Gitlab :: VisibilityLevel . options
end
2018-05-09 12:01:36 +05:30
def sort_by_attribute ( method )
2017-08-17 22:00:37 +05:30
case method . to_s
when 'storage_size_desc'
# storage_size is a joined column so we need to
# pass a string to avoid AR adding the table name
reorder ( 'project_statistics.storage_size DESC, projects.id DESC' )
when 'latest_activity_desc'
2020-03-13 15:44:24 +05:30
reorder ( self . arel_table [ 'last_activity_at' ] . desc )
2017-08-17 22:00:37 +05:30
when 'latest_activity_asc'
2020-03-13 15:44:24 +05:30
reorder ( self . arel_table [ 'last_activity_at' ] . asc )
2018-12-05 23:21:45 +05:30
when 'stars_desc'
2019-07-31 22:56:46 +05:30
sorted_by_stars_desc
when 'stars_asc'
sorted_by_stars_asc
2015-04-26 12:48:37 +05:30
else
order_by ( method )
2014-09-02 18:07:02 +05:30
end
end
2015-09-11 14:41:01 +05:30
def reference_pattern
2017-08-17 22:00:37 +05:30
%r{
2019-02-02 18:00:53 +05:30
( ?< ! #{Gitlab::PathRegex::PATH_START_CHAR})
2017-09-10 17:25:29 +05:30
( ( ? < namespace > #{Gitlab::PathRegex::FULL_NAMESPACE_FORMAT_REGEX})\/)?
( ? < project > #{Gitlab::PathRegex::PROJECT_PATH_FORMAT_REGEX})
2017-08-17 22:00:37 +05:30
} x
2015-09-11 14:41:01 +05:30
end
2015-10-24 18:46:33 +05:30
2018-11-20 20:47:30 +05:30
def reference_postfix
'>'
end
def reference_postfix_escaped
'>'
end
# Pattern used to extract `namespace/project>` project references from text.
# '>' or its escaped form ('>') are checked for because '>' is sometimes escaped
# when the reference comes from an external source.
def markdown_reference_pattern
2020-07-28 23:09:34 +05:30
@markdown_reference_pattern || =
%r{
#{reference_pattern}
( #{reference_postfix}|#{reference_postfix_escaped})
} x
2018-11-20 20:47:30 +05:30
end
2016-11-03 12:29:30 +05:30
def trending
2017-09-10 17:25:29 +05:30
joins ( 'INNER JOIN trending_projects ON projects.id = trending_projects.project_id' )
. reorder ( 'trending_projects.id ASC' )
2015-10-24 18:46:33 +05:30
end
2016-06-22 15:30:34 +05:30
2016-08-24 12:49:21 +05:30
def cached_count
Rails . cache . fetch ( 'total_project_count' , expires_in : 5 . minutes ) do
Project . count
end
2016-06-22 15:30:34 +05:30
end
2016-11-03 12:29:30 +05:30
def group_ids
2016-11-24 13:41:30 +05:30
joins ( :namespace ) . where ( namespaces : { type : 'Group' } ) . select ( :namespace_id )
2016-11-03 12:29:30 +05:30
end
2019-06-05 12:25:43 +05:30
2019-10-31 01:37:42 +05:30
# Returns ids of projects with issuables available for given user
2019-06-05 12:25:43 +05:30
#
2019-10-31 01:37:42 +05:30
# Used on queries to find milestones or labels which user can see
# For example: Milestone.where(project_id: ids_with_issuables_available_for(user))
def ids_with_issuables_available_for ( user )
2019-06-05 12:25:43 +05:30
with_issues_enabled = with_issues_available_for_user ( user ) . select ( :id )
with_merge_requests_enabled = with_merge_requests_available_for_user ( user ) . select ( :id )
from_union ( [ with_issues_enabled , with_merge_requests_enabled ] ) . select ( :id )
end
2020-07-28 23:09:34 +05:30
def find_by_service_desk_project_key ( key )
# project_key is not indexed for now
# see https://gitlab.com/gitlab-org/gitlab/-/merge_requests/24063#note_282435524 for details
joins ( :service_desk_setting ) . find_by ( 'service_desk_settings.project_key' = > key )
end
2014-09-02 18:07:02 +05:30
end
2019-09-30 21:07:59 +05:30
def initialize ( attributes = nil )
2019-09-04 21:01:54 +05:30
# 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.
2019-09-30 21:07:59 +05:30
attributes || = { }
2019-09-04 21:01:54 +05:30
unless visibility_attribute_present? ( attributes )
attributes [ :visibility_level ] = Gitlab :: CurrentSettings . default_project_visibility
end
super
end
2021-06-08 01:23:25 +05:30
def parent_loaded?
association ( :namespace ) . loaded?
end
2020-06-23 00:09:42 +05:30
def project_setting
super . presence || build_project_setting
end
2019-02-15 15:39:39 +05:30
def all_pipelines
2019-02-02 18:00:53 +05:30
if builds_enabled?
super
else
super . external
end
end
2019-07-07 11:18:12 +05:30
def ci_pipelines
if builds_enabled?
super
else
super . external
end
end
2020-06-23 00:09:42 +05:30
def active_webide_pipelines ( user : )
webide_pipelines . running_or_pending . for_user ( user )
end
2021-03-11 19:13:27 +05:30
def default_pipeline_lock
if keep_latest_artifacts_available?
return :artifacts_locked
end
:unlocked
2021-03-08 18:12:59 +05:30
end
2020-03-13 15:44:24 +05:30
def autoclose_referenced_issues
return true if super . nil?
super
end
2019-12-26 22:10:19 +05:30
def preload_protected_branches
preloader = ActiveRecord :: Associations :: Preloader . new
preloader . preload ( self , protected_branches : [ :push_access_levels , :merge_access_levels ] )
end
2018-03-17 18:26:18 +05:30
# returns all ancestor-groups upto but excluding the given namespace
# when no namespace is given, all ancestors upto the top are returned
2019-02-15 15:39:39 +05:30
def ancestors_upto ( top = nil , hierarchy_order : nil )
Gitlab :: ObjectHierarchy . new ( Group . where ( id : namespace_id ) )
. base_and_ancestors ( upto : top , hierarchy_order : hierarchy_order )
2018-03-17 18:26:18 +05:30
end
2019-02-15 15:39:39 +05:30
alias_method :ancestors , :ancestors_upto
2019-10-12 21:52:04 +05:30
def emails_disabled?
strong_memoize ( :emails_disabled ) do
# disabling in the namespace overrides the project setting
2019-12-21 20:55:43 +05:30
super || namespace . emails_disabled?
2019-10-12 21:52:04 +05:30
end
end
2021-01-03 14:25:43 +05:30
override :lfs_enabled?
2016-09-29 09:46:39 +05:30
def lfs_enabled?
return namespace . lfs_enabled? if self [ :lfs_enabled ] . nil?
self [ :lfs_enabled ] && Gitlab . config . lfs . enabled
end
2018-12-13 13:39:08 +05:30
alias_method :lfs_enabled , :lfs_enabled?
2018-03-17 18:26:18 +05:30
def auto_devops_enabled?
if auto_devops & . enabled . nil?
2018-11-20 20:47:30 +05:30
has_auto_devops_implicitly_enabled?
2018-03-17 18:26:18 +05:30
else
auto_devops . enabled?
end
end
2018-11-18 11:00:15 +05:30
def has_auto_devops_implicitly_enabled?
2019-07-07 11:18:12 +05:30
auto_devops_config = first_auto_devops_config
auto_devops_config [ :scope ] != :project && auto_devops_config [ :status ]
2018-11-18 11:00:15 +05:30
end
2018-03-17 18:26:18 +05:30
def has_auto_devops_implicitly_disabled?
2019-07-07 11:18:12 +05:30
auto_devops_config = first_auto_devops_config
auto_devops_config [ :scope ] != :project && ! auto_devops_config [ :status ]
end
2020-10-24 23:57:45 +05:30
def has_packages? ( package_type )
packages . where ( package_type : package_type ) . exists?
end
2019-07-07 11:18:12 +05:30
def first_auto_devops_config
return namespace . first_auto_devops_config if auto_devops & . enabled . nil?
{ scope : :project , status : auto_devops & . enabled || Feature . enabled? ( :force_autodevops_on_by_default , self ) }
end
2020-01-01 13:55:28 +05:30
def unlink_forks_upon_visibility_decrease_enabled?
Feature . enabled? ( :unlink_fork_network_upon_visibility_decrease , self , default_enabled : true )
end
2020-03-13 15:44:24 +05:30
def context_commits_enabled?
Feature . enabled? ( :context_commits , default_enabled : true )
2018-03-17 18:26:18 +05:30
end
2020-05-24 23:13:21 +05:30
# LFS and hashed repository storage are required for using Design Management.
def design_management_enabled?
lfs_enabled? && hashed_storage? ( :repository )
end
2014-09-02 18:07:02 +05:30
def team
@team || = ProjectTeam . new ( self )
end
def repository
2020-11-24 15:15:51 +05:30
@repository || = Gitlab :: GlRepository :: PROJECT . repository_for ( self )
2015-09-11 14:41:01 +05:30
end
2020-05-24 23:13:21 +05:30
def design_repository
strong_memoize ( :design_repository ) do
2020-11-24 15:15:51 +05:30
Gitlab :: GlRepository :: DESIGN . repository_for ( self )
2020-05-24 23:13:21 +05:30
end
end
2020-07-28 23:09:34 +05:30
# Because we use default_value_for we need to be sure
# packages_enabled= method does exist even if we rollback migration.
# Otherwise many tests from spec/migrations will fail.
def packages_enabled = ( value )
if has_attribute? ( :packages_enabled )
write_attribute ( :packages_enabled , value )
end
end
2018-03-17 18:26:18 +05:30
def cleanup
@repository = nil
end
alias_method :reload_repository! , :cleanup
2017-08-17 22:00:37 +05:30
def container_registry_url
2016-06-02 11:05:42 +05:30
if Gitlab . config . registry . enabled
2017-09-10 17:25:29 +05:30
" #{ Gitlab . config . registry . host_port } / #{ full_path . downcase } "
2016-06-02 11:05:42 +05:30
end
end
def has_container_registry_tags?
2017-09-10 17:25:29 +05:30
return @images if defined? ( @images )
@images = container_repositories . to_a . any? ( & :has_tags? ) ||
2017-08-17 22:00:37 +05:30
has_root_container_repository_tags?
2016-06-02 11:05:42 +05:30
end
2019-10-12 21:52:04 +05:30
# ref can't be HEAD, can only be branch/tag name
def latest_successful_build_for_ref ( job_name , ref = default_branch )
return unless ref
latest_pipeline = ci_pipelines . latest_successful_for_ref ( ref )
2019-03-02 22:35:43 +05:30
return unless latest_pipeline
2016-08-24 12:49:21 +05:30
2021-01-03 14:25:43 +05:30
latest_pipeline . build_with_artifacts_in_self_and_descendants ( job_name )
2014-09-02 18:07:02 +05:30
end
2019-10-12 21:52:04 +05:30
def latest_successful_build_for_sha ( job_name , sha )
return unless sha
latest_pipeline = ci_pipelines . latest_successful_for_sha ( sha )
return unless latest_pipeline
2021-01-03 14:25:43 +05:30
latest_pipeline . build_with_artifacts_in_self_and_descendants ( job_name )
2019-10-12 21:52:04 +05:30
end
def latest_successful_build_for_ref! ( job_name , ref = default_branch )
2021-06-08 01:23:25 +05:30
latest_successful_build_for_ref ( job_name , ref ) || raise ( ActiveRecord :: RecordNotFound , " Couldn't find job #{ job_name } " )
2019-02-15 15:39:39 +05:30
end
2020-11-24 15:15:51 +05:30
def latest_pipeline ( ref = default_branch , sha = nil )
2019-12-04 20:38:33 +05:30
ref = ref . presence || default_branch
2020-11-24 15:15:51 +05:30
sha || = commit ( ref ) & . sha
2019-12-04 20:38:33 +05:30
return unless sha
2020-11-24 15:15:51 +05:30
ci_pipelines . newest_first ( ref : ref , sha : sha ) . take
2019-12-04 20:38:33 +05:30
end
2016-02-05 20:25:01 +05:30
def merge_base_commit ( first_commit_id , second_commit_id )
sha = repository . merge_base ( first_commit_id , second_commit_id )
2018-03-17 18:26:18 +05:30
commit_by ( oid : sha ) if sha
2016-02-05 20:25:01 +05:30
end
2014-09-02 18:07:02 +05:30
def saved?
id && persisted?
end
2019-02-15 15:39:39 +05:30
def import_status
import_state & . status || 'none'
end
2020-04-22 19:07:51 +05:30
def jira_import_status
latest_jira_import & . status || 'initial'
end
2019-02-15 15:39:39 +05:30
def human_import_status_name
import_state & . human_status_name || 'none'
end
2014-09-02 18:07:02 +05:30
def add_import_job
2017-08-17 22:00:37 +05:30
job_id =
if forked?
2018-05-09 12:01:36 +05:30
RepositoryForkWorker . perform_async ( id )
2017-08-17 22:00:37 +05:30
else
RepositoryImportWorker . perform_async ( self . id )
end
2016-06-02 11:05:42 +05:30
2018-03-17 18:26:18 +05:30
log_import_activity ( job_id )
job_id
end
def log_import_activity ( job_id , type : :import )
job_type = type . to_s . capitalize
2016-06-02 11:05:42 +05:30
if job_id
2020-06-23 00:09:42 +05:30
Gitlab :: AppLogger . info ( " #{ job_type } job scheduled for #{ full_path } with job ID #{ job_id } . " )
2015-09-25 12:07:36 +05:30
else
2020-06-23 00:09:42 +05:30
Gitlab :: AppLogger . error ( " #{ job_type } job failed to create for #{ full_path } . " )
2015-09-25 12:07:36 +05:30
end
2014-09-02 18:07:02 +05:30
end
2016-06-02 11:05:42 +05:30
def reset_cache_and_import_attrs
2017-09-10 17:25:29 +05:30
run_after_commit do
ProjectCacheWorker . perform_async ( self . id )
end
2019-02-15 15:39:39 +05:30
import_state . update ( last_error : nil )
2017-09-10 17:25:29 +05:30
remove_import_data
end
2015-11-26 14:37:03 +05:30
2018-12-13 13:39:08 +05:30
# This method is overridden in EE::Project model
2017-09-10 17:25:29 +05:30
def remove_import_data
import_data & . destroy
end
def ci_config_path = ( value )
# Strip all leading slashes so that //foo -> foo
2018-03-17 18:26:18 +05:30
super ( value & . delete ( " \0 " ) )
2015-04-26 12:48:37 +05:30
end
2016-06-02 11:05:42 +05:30
def import_url = ( value )
2019-03-02 22:35:43 +05:30
if Gitlab :: UrlSanitizer . valid? ( value )
import_url = Gitlab :: UrlSanitizer . new ( value )
super ( import_url . sanitized_url )
2020-03-13 15:44:24 +05:30
credentials = import_url . credentials . to_h . transform_values { | value | CGI . unescape ( value . to_s ) }
create_or_update_import_data ( credentials : credentials )
2019-03-02 22:35:43 +05:30
else
super ( value )
end
2016-06-02 11:05:42 +05:30
end
def import_url
2016-11-03 12:29:30 +05:30
if import_data && super . present?
2016-06-02 11:05:42 +05:30
import_url = Gitlab :: UrlSanitizer . new ( super , credentials : import_data . credentials )
import_url . full_url
else
super
end
2021-06-08 01:23:25 +05:30
rescue StandardError
2018-12-13 13:39:08 +05:30
super
2016-06-02 11:05:42 +05:30
end
2016-08-24 12:49:21 +05:30
def valid_import_url?
2017-09-10 17:25:29 +05:30
valid? ( :import_url ) || errors . messages [ :import_url ] . nil?
2016-08-24 12:49:21 +05:30
end
2016-06-02 11:05:42 +05:30
def create_or_update_import_data ( data : nil , credentials : nil )
2018-05-09 12:01:36 +05:30
return if data . nil? && credentials . nil?
2016-08-24 12:49:21 +05:30
2016-06-02 11:05:42 +05:30
project_import_data = import_data || build_import_data
2018-03-17 18:26:18 +05:30
2019-02-15 15:39:39 +05:30
project_import_data . merge_data ( data . to_h )
project_import_data . merge_credentials ( credentials . to_h )
2018-11-18 11:00:15 +05:30
project_import_data
2016-06-02 11:05:42 +05:30
end
2014-09-02 18:07:02 +05:30
def import?
2020-04-22 19:07:51 +05:30
external_import? || forked? || gitlab_project_import? || jira_import? || bare_repository_import?
2015-09-25 12:07:36 +05:30
end
def external_import?
2014-09-02 18:07:02 +05:30
import_url . present?
end
2015-11-26 14:37:03 +05:30
def safe_import_url
2016-06-02 11:05:42 +05:30
Gitlab :: UrlSanitizer . new ( import_url ) . masked_url
2015-11-26 14:37:03 +05:30
end
2018-03-17 18:26:18 +05:30
def bare_repository_import?
import_type == 'bare_repository'
end
2020-04-22 19:07:51 +05:30
def jira_import?
2020-06-23 00:09:42 +05:30
import_type == 'jira' && latest_jira_import . present?
2020-04-22 19:07:51 +05:30
end
2016-06-22 15:30:34 +05:30
def gitlab_project_import?
import_type == 'gitlab_project'
end
2017-08-17 22:00:37 +05:30
def gitea_import?
import_type == 'gitea'
end
2018-10-15 14:42:47 +05:30
def has_remote_mirror?
remote_mirror_available? && remote_mirrors . enabled . exists?
end
def updating_remote_mirror?
remote_mirrors . enabled . started . exists?
end
def update_remote_mirrors
return unless remote_mirror_available?
remote_mirrors . enabled . each ( & :sync )
end
def mark_stuck_remote_mirrors_as_failed!
remote_mirrors . stuck . update_all (
update_status : :failed ,
2019-07-31 22:56:46 +05:30
last_error : _ ( 'The remote mirror took to long to complete.' ) ,
2020-06-23 00:09:42 +05:30
last_update_at : Time . current
2018-10-15 14:42:47 +05:30
)
end
def mark_remote_mirrors_for_removal
remote_mirrors . each ( & :mark_for_delete_if_blank_url )
end
def remote_mirror_available?
remote_mirror_available_overridden ||
:: Gitlab :: CurrentSettings . mirror_available
end
2019-03-02 22:35:43 +05:30
def check_personal_projects_limit
# Since this method is called as validation hook, `creator` might not be
# present. Since the validation for that will fail, we can just return
# early.
return if ! creator || creator . can_create_project? ||
namespace . kind == 'group'
2016-06-16 23:09:34 +05:30
2019-03-02 22:35:43 +05:30
limit = creator . projects_limit
error =
2020-10-24 23:57:45 +05:30
if limit == 0
2019-03-02 22:35:43 +05:30
_ ( 'Personal project creation is not allowed. Please contact your administrator with questions' )
2016-06-16 23:09:34 +05:30
else
2019-03-02 22:35:43 +05:30
_ ( 'Your project limit is %{limit} projects! Please contact your administrator to increase it' )
2016-06-16 23:09:34 +05:30
end
2019-03-02 22:35:43 +05:30
self . errors . add ( :limit_reached , error % { limit : limit } )
2016-06-02 11:05:42 +05:30
end
2019-06-05 12:25:43 +05:30
def should_validate_visibility_level?
new_record? || changes . has_key? ( :visibility_level )
end
2016-06-02 11:05:42 +05:30
def visibility_level_allowed_by_group
return if visibility_level_allowed_by_group?
level_name = Gitlab :: VisibilityLevel . level_name ( self . visibility_level ) . downcase
group_level_name = Gitlab :: VisibilityLevel . level_name ( self . group . visibility_level ) . downcase
2019-07-31 22:56:46 +05:30
self . errors . add ( :visibility_level , _ ( " %{level_name} is not allowed in a %{group_level_name} group. " ) % { level_name : level_name , group_level_name : group_level_name } )
2016-06-02 11:05:42 +05:30
end
def visibility_level_allowed_as_fork
return if visibility_level_allowed_as_fork?
level_name = Gitlab :: VisibilityLevel . level_name ( self . visibility_level ) . downcase
2019-07-31 22:56:46 +05:30
self . errors . add ( :visibility_level , _ ( " %{level_name} is not allowed since the fork source project has lower visibility. " ) % { level_name : level_name } )
2014-09-02 18:07:02 +05:30
end
2018-05-09 12:01:36 +05:30
def pages_https_only
return false unless Gitlab . config . pages . external_https
super
end
def pages_https_only?
return false unless Gitlab . config . pages . external_https
super
end
def validate_pages_https_only
return unless pages_https_only?
unless pages_domains . all? ( & :https? )
2019-07-31 22:56:46 +05:30
errors . add ( :pages_https_only , _ ( " cannot be enabled unless all domains have TLS certificates " ) )
2018-05-09 12:01:36 +05:30
end
end
2021-01-03 14:25:43 +05:30
def changing_shared_runners_enabled_is_allowed
return unless new_record? || changes . has_key? ( :shared_runners_enabled )
if shared_runners_enabled && group && group . shared_runners_setting == 'disabled_and_unoverridable'
errors . add ( :shared_runners_enabled , _ ( 'cannot be enabled because parent group does not allow it' ) )
end
end
2014-09-02 18:07:02 +05:30
def to_param
2016-09-13 17:45:13 +05:30
if persisted? && errors . include? ( :path )
path_was
else
path
end
2014-09-02 18:07:02 +05:30
end
2020-03-13 15:44:24 +05:30
# Produce a valid reference (see Referable#to_reference)
#
# NB: For projects, all references are 'full' - i.e. they all include the
# full_path, rather than just the project name. For this reason, we ignore
# the value of `full:` passed to this method, which is part of the Referable
# interface.
def to_reference ( from = nil , full : false )
base = to_reference_base ( from , full : true )
" #{ base } #{ self . class . reference_postfix } "
2018-11-20 20:47:30 +05:30
end
2017-08-17 22:00:37 +05:30
# `from` argument can be a Namespace or Project.
2020-03-13 15:44:24 +05:30
def to_reference_base ( from = nil , full : false )
2017-08-17 22:00:37 +05:30
if full || cross_namespace_reference? ( from )
2017-09-10 17:25:29 +05:30
full_path
2017-08-17 22:00:37 +05:30
elsif cross_project_reference? ( from )
path
end
2015-09-11 14:41:01 +05:30
end
2018-03-17 18:26:18 +05:30
def to_human_reference ( from = nil )
if cross_namespace_reference? ( from )
2017-08-17 22:00:37 +05:30
name_with_namespace
2018-03-17 18:26:18 +05:30
elsif cross_project_reference? ( from )
2017-08-17 22:00:37 +05:30
name
end
2014-09-02 18:07:02 +05:30
end
2018-11-08 19:23:39 +05:30
def readme_url
2019-02-15 15:39:39 +05:30
readme_path = repository . readme_path
if readme_path
Gitlab :: Routing . url_helpers . project_blob_url ( self , File . join ( default_branch , readme_path ) )
2018-11-08 19:23:39 +05:30
end
end
2018-03-17 18:26:18 +05:30
def new_issuable_address ( author , address_type )
2017-08-17 22:00:37 +05:30
return unless Gitlab :: IncomingEmail . supports_issue_creation? && author
2016-09-13 17:45:13 +05:30
2019-02-15 15:39:39 +05:30
# check since this can come from a request parameter
return unless %w( issue merge_request ) . include? ( address_type )
2017-08-17 22:00:37 +05:30
author . ensure_incoming_email_token!
2019-02-15 15:39:39 +05:30
suffix = address_type . dasherize
# example: incoming+h5bp-html5-boilerplate-8-1234567890abcdef123456789-issue@localhost.com
# example: incoming+h5bp-html5-boilerplate-8-1234567890abcdef123456789-merge-request@localhost.com
Gitlab :: IncomingEmail . reply_address ( " #{ full_path_slug } - #{ project_id } - #{ author . incoming_email_token } - #{ suffix } " )
2016-09-13 17:45:13 +05:30
end
2014-09-02 18:07:02 +05:30
def build_commit_note ( commit )
2015-04-26 12:48:37 +05:30
notes . new ( commit_id : commit . id , noteable_type : 'Commit' )
2014-09-02 18:07:02 +05:30
end
def last_activity
last_event
end
def last_activity_date
2018-03-27 19:54:05 +05:30
[ last_activity_at , last_repository_updated_at , updated_at ] . compact . max
2014-09-02 18:07:02 +05:30
end
def project_id
self . id
end
2017-01-15 13:20:01 +05:30
def get_issue ( issue_id , current_user )
2017-09-10 17:25:29 +05:30
issue = IssuesFinder . new ( current_user , project_id : id ) . find_by ( iid : issue_id ) if issues_enabled?
if issue
issue
elsif external_issue_tracker
2015-09-11 14:41:01 +05:30
ExternalIssue . new ( issue_id , self )
2014-09-02 18:07:02 +05:30
end
end
2015-09-11 14:41:01 +05:30
def issue_exists? ( issue_id )
get_issue ( issue_id )
end
2017-08-17 22:00:37 +05:30
def external_issue_reference_pattern
2017-09-10 17:25:29 +05:30
external_issue_tracker . class . reference_pattern ( only_long : issues_enabled? )
2016-11-03 12:29:30 +05:30
end
2015-04-26 12:48:37 +05:30
def default_issues_tracker?
2015-09-11 14:41:01 +05:30
! external_issue_tracker
2015-04-26 12:48:37 +05:30
end
def external_issue_tracker
2021-03-11 19:13:27 +05:30
cache_has_external_issue_tracker if has_external_issue_tracker . nil?
2016-06-16 23:09:34 +05:30
2021-03-11 19:13:27 +05:30
return unless has_external_issue_tracker?
2014-09-02 18:07:02 +05:30
2021-06-08 01:23:25 +05:30
@external_issue_tracker || = integrations . external_issue_trackers . first
2014-09-02 18:07:02 +05:30
end
2020-04-08 14:13:33 +05:30
def external_references_supported?
external_issue_tracker & . support_cross_reference?
end
2016-09-29 09:46:39 +05:30
def has_wiki?
wiki_enabled? || has_external_wiki?
end
2016-08-24 12:49:21 +05:30
def external_wiki
2021-03-08 18:12:59 +05:30
cache_has_external_wiki if has_external_wiki . nil?
2016-08-24 12:49:21 +05:30
2021-03-08 18:12:59 +05:30
return unless has_external_wiki?
2016-08-24 12:49:21 +05:30
2021-06-08 01:23:25 +05:30
@external_wiki || = integrations . external_wikis . first
2016-08-24 12:49:21 +05:30
end
2020-04-22 19:07:51 +05:30
def find_or_initialize_services
2021-06-08 01:23:25 +05:30
available_services_names = Integration . available_services_names - disabled_services
2017-09-10 17:25:29 +05:30
2020-04-22 19:07:51 +05:30
available_services_names . map do | service_name |
2018-12-05 23:21:45 +05:30
find_or_initialize_service ( service_name )
2020-06-23 00:09:42 +05:30
end . sort_by ( & :title )
2018-11-08 19:23:39 +05:30
end
def disabled_services
2021-04-29 21:17:54 +05:30
return %w[ datadog hipchat ] unless Feature . enabled? ( :datadog_ci_integration , self )
2021-02-22 17:27:13 +05:30
2021-04-29 21:17:54 +05:30
%w[ hipchat ]
2014-09-02 18:07:02 +05:30
end
2017-08-17 22:00:37 +05:30
def find_or_initialize_service ( name )
2018-12-05 23:21:45 +05:30
return if disabled_services . include? ( name )
2021-06-08 01:23:25 +05:30
find_service ( integrations , name ) || build_from_instance_or_template ( name ) || build_service ( name )
2017-08-17 22:00:37 +05:30
end
2018-12-05 23:21:45 +05:30
# rubocop: disable CodeReuse/ServiceClass
2015-09-25 12:07:36 +05:30
def create_labels
Label . templates . each do | label |
2019-03-02 22:35:43 +05:30
params = label . attributes . except ( 'id' , 'template' , 'created_at' , 'updated_at' , 'type' )
2016-11-03 12:29:30 +05:30
Labels :: FindOrCreateService . new ( nil , self , params ) . execute ( skip_authorization : true )
2015-09-25 12:07:36 +05:30
end
end
2018-12-05 23:21:45 +05:30
# rubocop: enable CodeReuse/ServiceClass
2015-09-25 12:07:36 +05:30
2014-09-02 18:07:02 +05:30
def ci_services
2021-06-08 01:23:25 +05:30
integrations . where ( category : :ci )
2014-09-02 18:07:02 +05:30
end
def ci_service
2016-06-02 11:05:42 +05:30
@ci_service || = ci_services . reorder ( nil ) . find_by ( active : true )
2015-12-23 02:04:40 +05:30
end
2017-08-17 22:00:37 +05:30
def monitoring_services
2021-06-08 01:23:25 +05:30
integrations . where ( category : :monitoring )
2017-08-17 22:00:37 +05:30
end
def monitoring_service
@monitoring_service || = monitoring_services . reorder ( nil ) . find_by ( active : true )
end
2015-04-26 12:48:37 +05:30
def avatar_in_git
2016-06-02 11:05:42 +05:30
repository . avatar
2015-04-26 12:48:37 +05:30
end
2017-09-10 17:25:29 +05:30
def avatar_url ( ** args )
2018-03-17 18:26:18 +05:30
Gitlab :: Routing . url_helpers . project_avatar_url ( self ) if avatar_in_git
2015-04-26 12:48:37 +05:30
end
2014-09-02 18:07:02 +05:30
# For compatibility with old code
def code
path
end
2019-02-15 15:39:39 +05:30
def all_clusters
group_clusters = Clusters :: Cluster . joins ( :groups ) . where ( cluster_groups : { group_id : ancestors_upto } )
2020-01-01 13:55:28 +05:30
instance_clusters = Clusters :: Cluster . instance_type
2019-02-15 15:39:39 +05:30
2020-01-01 13:55:28 +05:30
Clusters :: Cluster . from_union ( [ clusters , group_clusters , instance_clusters ] )
2019-02-15 15:39:39 +05:30
end
2015-04-26 12:48:37 +05:30
def items_for ( entity )
2014-09-02 18:07:02 +05:30
case entity
when 'issue' then
issues
when 'merge_request' then
merge_requests
end
end
2018-12-05 23:21:45 +05:30
# rubocop: disable CodeReuse/ServiceClass
2015-10-24 18:46:33 +05:30
def send_move_instructions ( old_path_with_namespace )
2016-01-14 18:37:52 +05:30
# New project path needs to be committed to the DB or notification will
# retrieve stale information
2018-03-17 18:26:18 +05:30
run_after_commit do
NotificationService . new . project_was_moved ( self , old_path_with_namespace )
end
2014-09-02 18:07:02 +05:30
end
2018-12-05 23:21:45 +05:30
# rubocop: enable CodeReuse/ServiceClass
2014-09-02 18:07:02 +05:30
def owner
2020-05-24 23:13:21 +05:30
group || namespace . try ( :owner )
2014-09-02 18:07:02 +05:30
end
2020-10-24 23:57:45 +05:30
def default_owner
obj = owner
if obj . respond_to? ( :default_owner )
obj . default_owner
else
obj
end
end
2019-10-31 01:37:42 +05:30
def to_ability_name
model_name . singular
end
2018-12-05 23:21:45 +05:30
# rubocop: disable CodeReuse/ServiceClass
2014-09-02 18:07:02 +05:30
def execute_hooks ( data , hooks_scope = :push_hooks )
2018-03-17 18:26:18 +05:30
run_after_commit_or_now do
2018-11-20 20:47:30 +05:30
hooks . hooks_for ( hooks_scope ) . select_active ( hooks_scope , data ) . each do | hook |
2018-03-17 18:26:18 +05:30
hook . async_execute ( data , hooks_scope . to_s )
end
SystemHooksService . new . execute_hooks ( data , hooks_scope )
2014-09-02 18:07:02 +05:30
end
end
2018-12-05 23:21:45 +05:30
# rubocop: enable CodeReuse/ServiceClass
2014-09-02 18:07:02 +05:30
2015-04-26 12:48:37 +05:30
def execute_services ( data , hooks_scope = :push_hooks )
# Call only service hooks that are active for this scope
2018-03-17 18:26:18 +05:30
run_after_commit_or_now do
2021-06-08 01:23:25 +05:30
integrations . public_send ( hooks_scope ) . each do | integration | # rubocop:disable GitlabSecurity/PublicSend
integration . async_execute ( data )
2018-03-17 18:26:18 +05:30
end
2014-09-02 18:07:02 +05:30
end
end
2019-10-12 21:52:04 +05:30
def has_active_hooks? ( hooks_scope = :push_hooks )
2020-03-13 15:44:24 +05:30
hooks . hooks_for ( hooks_scope ) . any? || SystemHook . hooks_for ( hooks_scope ) . any? || Gitlab :: FileHook . any?
2019-10-12 21:52:04 +05:30
end
def has_active_services? ( hooks_scope = :push_hooks )
2021-06-08 01:23:25 +05:30
integrations . public_send ( hooks_scope ) . any? # rubocop:disable GitlabSecurity/PublicSend
2019-10-12 21:52:04 +05:30
end
2020-11-24 15:15:51 +05:30
def feature_usage
super . presence || build_feature_usage
end
2014-09-02 18:07:02 +05:30
def forked?
2018-12-13 13:39:08 +05:30
fork_network && fork_network . root_project != self
2014-09-02 18:07:02 +05:30
end
2018-03-17 18:26:18 +05:30
def fork_source
2019-07-07 11:18:12 +05:30
return unless forked?
2018-03-17 18:26:18 +05:30
forked_from_project || fork_network & . root_project
end
2020-11-24 15:15:51 +05:30
def lfs_objects_for_repository_types ( * types )
2020-03-13 15:44:24 +05:30
LfsObject
. joins ( :lfs_objects_projects )
2020-11-24 15:15:51 +05:30
. where ( lfs_objects_projects : { project : self , repository_type : types } )
2020-04-08 14:13:33 +05:30
end
def lfs_objects_oids ( oids : [ ] )
oids ( lfs_objects , oids : oids )
2018-05-09 12:01:36 +05:30
end
2014-09-02 18:07:02 +05:30
def personal?
! group
end
2016-04-02 18:10:28 +05:30
# Expires various caches before a project is renamed.
def expire_caches_before_rename ( old_path )
2020-05-24 23:13:21 +05:30
project_repo = Repository . new ( old_path , self , shard : repository_storage )
wiki_repo = Repository . new ( " #{ old_path } #{ Gitlab :: GlRepository :: WIKI . path_suffix } " , self , shard : repository_storage , repo_type : Gitlab :: GlRepository :: WIKI )
design_repo = Repository . new ( " #{ old_path } #{ Gitlab :: GlRepository :: DESIGN . path_suffix } " , self , shard : repository_storage , repo_type : Gitlab :: GlRepository :: DESIGN )
2016-04-02 18:10:28 +05:30
2020-05-24 23:13:21 +05:30
[ project_repo , wiki_repo , design_repo ] . each do | repo |
repo . before_delete if repo . exists?
2016-04-02 18:10:28 +05:30
end
end
2017-09-10 17:25:29 +05:30
# Check if repository already exists on disk
2018-03-17 18:26:18 +05:30
def check_repository_path_availability
return true if skip_disk_validation
2018-10-15 14:42:47 +05:30
return false unless repository_storage
2017-09-10 17:25:29 +05:30
2018-03-17 18:26:18 +05:30
# Check if repository with same path already exists on disk we can
# skip this for the hashed storage because the path does not change
if legacy_storage? && repository_with_same_path_already_exists?
2019-07-31 22:56:46 +05:30
errors . add ( :base , _ ( 'There is already a repository with that name on disk' ) )
2017-09-10 17:25:29 +05:30
return false
end
true
2018-03-17 18:26:18 +05:30
rescue GRPC :: Internal # if the path is too long
false
2017-09-10 17:25:29 +05:30
end
2019-02-15 15:39:39 +05:30
def track_project_repository
repository = project_repository || build_project_repository
repository . update! ( shard_name : repository_storage , disk_path : disk_path )
end
2018-03-17 18:26:18 +05:30
def create_repository ( force : false )
# Forked import is handled asynchronously
return if forked? && ! force
2020-04-08 14:13:33 +05:30
repository . create_repository
repository . after_create
true
2021-06-08 01:23:25 +05:30
rescue StandardError = > err
2020-04-08 14:13:33 +05:30
Gitlab :: ErrorTracking . track_exception ( err , project : { id : id , full_path : full_path , disk_path : disk_path } )
errors . add ( :base , _ ( 'Failed to create repository' ) )
false
2017-09-10 17:25:29 +05:30
end
2016-06-02 11:05:42 +05:30
def hook_attrs ( backward : true )
attrs = {
2018-03-17 18:26:18 +05:30
id : id ,
2015-04-26 12:48:37 +05:30
name : name ,
2016-04-02 18:10:28 +05:30
description : description ,
2015-09-25 12:07:36 +05:30
web_url : web_url ,
2018-03-17 18:26:18 +05:30
avatar_url : avatar_url ( only_path : false ) ,
2016-04-02 18:10:28 +05:30
git_ssh_url : ssh_url_to_repo ,
git_http_url : http_url_to_repo ,
2015-04-26 12:48:37 +05:30
namespace : namespace . name ,
2016-04-02 18:10:28 +05:30
visibility_level : visibility_level ,
2017-09-10 17:25:29 +05:30
path_with_namespace : full_path ,
2016-04-02 18:10:28 +05:30
default_branch : default_branch ,
2017-09-10 17:25:29 +05:30
ci_config_path : ci_config_path
2015-04-26 12:48:37 +05:30
}
2016-06-02 11:05:42 +05:30
# Backward compatibility
if backward
attrs . merge! ( {
homepage : web_url ,
url : url_to_repo ,
ssh_url : ssh_url_to_repo ,
http_url : http_url_to_repo
} )
end
attrs
2015-04-26 12:48:37 +05:30
end
2014-09-02 18:07:02 +05:30
def project_member ( user )
2018-03-17 18:26:18 +05:30
if project_members . loaded?
project_members . find { | member | member . user_id == user . id }
else
project_members . find_by ( user_id : user )
end
2014-09-02 18:07:02 +05:30
end
2020-05-24 23:13:21 +05:30
def bots
users . project_bot
end
2018-12-05 23:21:45 +05:30
# Filters `users` to return only authorized users of the project
def members_among ( users )
if users . is_a? ( ActiveRecord :: Relation ) && ! users . loaded?
authorized_users . merge ( users )
else
return [ ] if users . empty?
user_ids = authorized_users . where ( users : { id : users . map ( & :id ) } ) . pluck ( :id )
users . select { | user | user_ids . include? ( user . id ) }
end
end
2014-09-02 18:07:02 +05:30
def visibility_level_field
2017-08-17 22:00:37 +05:30
:visibility_level
2014-09-02 18:07:02 +05:30
end
def change_head ( branch )
2018-03-17 18:26:18 +05:30
if repository . branch_exists? ( branch )
repository . before_change_head
2019-02-15 15:39:39 +05:30
repository . raw_repository . write_ref ( 'HEAD' , " refs/heads/ #{ branch } " )
2018-03-17 18:26:18 +05:30
repository . copy_gitattributes ( branch )
repository . after_change_head
2019-07-07 11:18:12 +05:30
ProjectCacheWorker . perform_async ( self . id , [ ] , [ :commit_count ] )
2018-03-17 18:26:18 +05:30
reload_default_branch
else
2019-07-31 22:56:46 +05:30
errors . add ( :base , _ ( " Could not change HEAD: branch '%{branch}' does not exist " ) % { branch : branch } )
2018-03-17 18:26:18 +05:30
false
end
2014-09-02 18:07:02 +05:30
end
2018-03-17 18:26:18 +05:30
def forked_from? ( other_project )
forked? && forked_from_project == other_project
end
def in_fork_network_of? ( other_project )
return false if fork_network . nil? || other_project . fork_network . nil?
fork_network == other_project . fork_network
2014-09-02 18:07:02 +05:30
end
2015-04-26 12:48:37 +05:30
def origin_merge_requests
merge_requests . where ( source_project_id : self . id )
end
2017-09-10 17:25:29 +05:30
def ensure_repository
create_repository ( force : true ) unless repository_exists?
2015-04-26 12:48:37 +05:30
end
2018-03-17 18:26:18 +05:30
# update visibility_level of forks
2017-09-10 17:25:29 +05:30
def update_forks_visibility_level
2020-01-01 13:55:28 +05:30
return if unlink_forks_upon_visibility_decrease_enabled?
2019-07-31 22:56:46 +05:30
return unless visibility_level < visibility_level_before_last_save
2017-09-10 17:25:29 +05:30
forks . each do | forked_project |
if forked_project . visibility_level > visibility_level
forked_project . visibility_level = visibility_level
forked_project . save!
end
end
end
2016-06-02 11:05:42 +05:30
def allowed_to_share_with_group?
! namespace . share_with_group_lock
2015-10-24 18:46:33 +05:30
end
2018-03-17 18:26:18 +05:30
def latest_successful_pipeline_for_default_branch
if defined? ( @latest_successful_pipeline_for_default_branch )
return @latest_successful_pipeline_for_default_branch
end
@latest_successful_pipeline_for_default_branch =
2019-10-12 21:52:04 +05:30
ci_pipelines . latest_successful_for_ref ( default_branch )
2018-03-17 18:26:18 +05:30
end
def latest_successful_pipeline_for ( ref = nil )
if ref && ref != default_branch
2019-10-12 21:52:04 +05:30
ci_pipelines . latest_successful_for_ref ( ref )
2018-03-17 18:26:18 +05:30
else
latest_successful_pipeline_for_default_branch
end
end
2016-06-02 11:05:42 +05:30
def enable_ci
2016-09-29 09:46:39 +05:30
project_feature . update_attribute ( :builds_access_level , ProjectFeature :: ENABLED )
2015-11-26 14:37:03 +05:30
end
2015-12-23 02:04:40 +05:30
2017-08-17 22:00:37 +05:30
def shared_runners_available?
shared_runners_enabled?
end
2015-12-23 02:04:40 +05:30
2017-08-17 22:00:37 +05:30
def shared_runners
2018-11-08 19:23:39 +05:30
@shared_runners || = shared_runners_available? ? Ci :: Runner . instance_type : Ci :: Runner . none
2015-12-23 02:04:40 +05:30
end
2018-10-15 14:42:47 +05:30
def group_runners
@group_runners || = group_runners_enabled? ? Ci :: Runner . belonging_to_parent_group_of_project ( self . id ) : Ci :: Runner . none
end
def all_runners
2018-12-05 23:21:45 +05:30
Ci :: Runner . from_union ( [ runners , group_runners , shared_runners ] )
2015-12-23 02:04:40 +05:30
end
2018-11-08 19:23:39 +05:30
def active_runners
strong_memoize ( :active_runners ) do
all_runners . active
end
end
2021-04-29 21:17:54 +05:30
# Deprecated: https://gitlab.com/gitlab-org/gitlab/-/issues/326989
2021-04-17 20:07:23 +05:30
def any_active_runners? ( & block )
active_runners_with_tags . any? ( & block )
2017-08-17 22:00:37 +05:30
end
2021-04-29 21:17:54 +05:30
def any_online_runners? ( & block )
online_runners_with_tags . any? ( & block )
end
2017-08-17 22:00:37 +05:30
def valid_runners_token? ( token )
2019-09-30 21:07:59 +05:30
self . runners_token && ActiveSupport :: SecurityUtils . secure_compare ( token , self . runners_token )
2015-12-23 02:04:40 +05:30
end
2018-12-05 23:21:45 +05:30
# rubocop: disable CodeReuse/ServiceClass
2018-11-08 19:23:39 +05:30
def open_issues_count ( current_user = nil )
Projects :: OpenIssuesCountService . new ( self , current_user ) . count
2018-03-17 18:26:18 +05:30
end
2018-12-05 23:21:45 +05:30
# rubocop: enable CodeReuse/ServiceClass
2018-03-17 18:26:18 +05:30
2018-12-05 23:21:45 +05:30
# rubocop: disable CodeReuse/ServiceClass
2018-03-17 18:26:18 +05:30
def open_merge_requests_count
Projects :: OpenMergeRequestsCountService . new ( self ) . count
2015-12-23 02:04:40 +05:30
end
2018-12-05 23:21:45 +05:30
# rubocop: enable CodeReuse/ServiceClass
2016-01-14 18:37:52 +05:30
2016-06-02 11:05:42 +05:30
def visibility_level_allowed_as_fork? ( level = self . visibility_level )
2016-01-14 18:37:52 +05:30
return true unless forked?
2016-06-02 11:05:42 +05:30
2018-12-13 13:39:08 +05:30
original_project = fork_source
2016-06-02 11:05:42 +05:30
return true unless original_project
level < = original_project . visibility_level
end
def visibility_level_allowed_by_group? ( level = self . visibility_level )
return true unless group
level < = group . visibility_level
end
def visibility_level_allowed? ( level = self . visibility_level )
visibility_level_allowed_as_fork? ( level ) && visibility_level_allowed_by_group? ( level )
2016-01-14 18:37:52 +05:30
end
def runners_token
ensure_runners_token!
end
2016-04-02 18:10:28 +05:30
2017-08-17 22:00:37 +05:30
def pages_deployed?
2020-10-24 23:57:45 +05:30
pages_metadatum & . deployed?
2017-08-17 22:00:37 +05:30
end
2018-05-09 12:01:36 +05:30
def pages_group_url
2017-08-17 22:00:37 +05:30
# The host in URL always needs to be downcased
2018-05-09 12:01:36 +05:30
Gitlab . config . pages . url . sub ( %r{ ^https?:// } ) do | prefix |
" #{ prefix } #{ pages_subdomain } . "
2017-08-17 22:00:37 +05:30
end . downcase
2018-05-09 12:01:36 +05:30
end
def pages_url
url = pages_group_url
url_path = full_path . partition ( '/' ) . last
2017-08-17 22:00:37 +05:30
# If the project path is the same as host, we serve it as group page
2020-07-28 23:09:34 +05:30
return url if url == " #{ Settings . pages . protocol } :// #{ url_path } " . downcase
2017-08-17 22:00:37 +05:30
" #{ url } / #{ url_path } "
end
2019-12-21 20:55:43 +05:30
def pages_group_root?
pages_group_url == pages_url
end
2017-08-17 22:00:37 +05:30
def pages_subdomain
full_path . partition ( '/' ) . first
end
def pages_path
2018-03-17 18:26:18 +05:30
# TODO: when we migrate Pages to work with new storage types, change here to use disk_path
File . join ( Settings . pages . path , full_path )
2017-08-17 22:00:37 +05:30
end
2018-03-17 18:26:18 +05:30
def pages_available?
2019-03-02 22:35:43 +05:30
Gitlab . config . pages . enabled
2018-03-17 18:26:18 +05:30
end
2017-09-10 17:25:29 +05:30
def remove_private_deploy_keys
exclude_keys_linked_to_other_projects = <<-SQL
NOT EXISTS (
SELECT 1
FROM deploy_keys_projects dkp2
WHERE dkp2 . deploy_key_id = deploy_keys_projects . deploy_key_id
AND dkp2 . project_id != deploy_keys_projects . project_id
)
SQL
deploy_keys . where ( public : false )
. where ( exclude_keys_linked_to_other_projects )
. delete_all
end
2021-03-11 19:13:27 +05:30
# TODO: remove this method https://gitlab.com/gitlab-org/gitlab/-/issues/320775
2018-12-05 23:21:45 +05:30
# rubocop: disable CodeReuse/ServiceClass
2021-03-11 19:13:27 +05:30
def legacy_remove_pages
2021-04-29 21:17:54 +05:30
return unless :: Settings . pages . local_store . enabled
2021-03-11 19:13:27 +05:30
2017-09-10 17:25:29 +05:30
# Projects with a missing namespace cannot have their pages removed
return unless namespace
2019-12-21 20:55:43 +05:30
mark_pages_as_not_deployed unless destroyed?
2017-09-10 17:25:29 +05:30
2017-08-17 22:00:37 +05:30
# 1. We rename pages to temporary directory
# 2. We wait 5 minutes, due to NFS caching
# 3. We asynchronously remove pages with force
temp_path = " #{ path } . #{ SecureRandom . hex } .deleted "
if Gitlab :: PagesTransfer . new . rename_project ( path , temp_path , namespace . full_path )
PagesWorker . perform_in ( 5 . minutes , :remove , namespace . full_path , temp_path )
end
end
2018-12-05 23:21:45 +05:30
# rubocop: enable CodeReuse/ServiceClass
2017-08-17 22:00:37 +05:30
2020-11-24 15:15:51 +05:30
def mark_pages_as_deployed ( artifacts_archive : nil )
ensure_pages_metadatum . update! ( deployed : true , artifacts_archive : artifacts_archive )
2019-12-21 20:55:43 +05:30
end
def mark_pages_as_not_deployed
2021-01-29 00:20:46 +05:30
ensure_pages_metadatum . update! ( deployed : false , artifacts_archive : nil , pages_deployment : nil )
end
def update_pages_deployment! ( deployment )
ensure_pages_metadatum . update! ( pages_deployment : deployment )
2019-12-21 20:55:43 +05:30
end
2021-03-08 18:12:59 +05:30
def set_first_pages_deployment! ( deployment )
ensure_pages_metadatum
# where().update_all to perform update in the single transaction with check for null
ProjectPagesMetadatum
. where ( project_id : id , pages_deployment_id : nil )
2021-04-29 21:17:54 +05:30
. update_all ( deployed : deployment . present? , pages_deployment_id : deployment & . id )
2021-03-08 18:12:59 +05:30
end
2018-03-17 18:26:18 +05:30
def write_repository_config ( gl_full_path : full_path )
# We'd need to keep track of project full path otherwise directory tree
# created with hashed storage enabled cannot be usefully imported using
# the import rake task.
repository . raw_repository . write_config ( full_path : gl_full_path )
rescue Gitlab :: Git :: Repository :: NoRepository = > e
2020-06-23 00:09:42 +05:30
Gitlab :: AppLogger . error ( " Error writing to .git/config for project #{ full_path } ( #{ id } ): #{ e . message } . " )
2018-03-17 18:26:18 +05:30
nil
end
def after_import
2020-04-08 14:13:33 +05:30
repository . expire_content_cache
wiki . repository . expire_content_cache
DetectRepositoryLanguagesWorker . perform_async ( id )
2021-02-22 17:27:13 +05:30
ProjectCacheWorker . perform_async ( self . id , [ ] , [ :repository_size ] )
2019-03-02 22:35:43 +05:30
# The import assigns iid values on its own, e.g. by re-using GitHub ids.
# Flush existing InternalId records for this project for consistency reasons.
# Those records are going to be recreated with the next normal creation
# of a model instance (e.g. an Issue).
InternalId . flush_records! ( project : self )
2019-02-15 15:39:39 +05:30
import_state . finish
2018-03-17 18:26:18 +05:30
update_project_counter_caches
after_create_default_branch
2019-02-15 15:39:39 +05:30
join_pool_repository
2018-05-09 12:01:36 +05:30
refresh_markdown_cache!
2020-07-28 23:09:34 +05:30
write_repository_config
2018-03-17 18:26:18 +05:30
end
def update_project_counter_caches
classes = [
Projects :: OpenIssuesCountService ,
Projects :: OpenMergeRequestsCountService
]
classes . each do | klass |
klass . new ( self ) . refresh_cache
end
end
2018-12-05 23:21:45 +05:30
# rubocop: disable CodeReuse/ServiceClass
2018-03-17 18:26:18 +05:30
def after_create_default_branch
2019-03-02 22:35:43 +05:30
Projects :: ProtectDefaultBranchService . new ( self ) . execute
2018-03-17 18:26:18 +05:30
end
2018-12-05 23:21:45 +05:30
# rubocop: enable CodeReuse/ServiceClass
2018-03-17 18:26:18 +05:30
2017-08-17 22:00:37 +05:30
# Lazy loading of the `pipeline_status` attribute
def pipeline_status
@pipeline_status || = Gitlab :: Cache :: Ci :: ProjectPipelineStatus . load_for_project ( self )
end
2018-05-09 12:01:36 +05:30
def add_export_job ( current_user : , after_export_strategy : nil , params : { } )
job_id = ProjectExportWorker . perform_async ( current_user . id , self . id , after_export_strategy , params )
2016-06-22 15:30:34 +05:30
if job_id
2020-06-23 00:09:42 +05:30
Gitlab :: AppLogger . info " Export job started for project ID #{ self . id } with job ID #{ job_id } "
2016-06-22 15:30:34 +05:30
else
2020-06-23 00:09:42 +05:30
Gitlab :: AppLogger . error " Export job failed to start for project ID #{ self . id } "
2016-06-22 15:30:34 +05:30
end
end
2018-03-27 19:54:05 +05:30
def import_export_shared
@import_export_shared || = Gitlab :: ImportExport :: Shared . new ( self )
end
2016-06-22 15:30:34 +05:30
def export_path
2019-07-07 11:18:12 +05:30
return unless namespace . present? || hashed_storage? ( :repository )
2018-03-17 18:26:18 +05:30
2018-03-27 19:54:05 +05:30
import_export_shared . archive_path
2016-06-22 15:30:34 +05:30
end
2018-03-27 19:54:05 +05:30
def export_status
2020-04-08 14:13:33 +05:30
if regeneration_in_progress?
:regeneration_in_progress
elsif export_enqueued?
:queued
elsif export_in_progress?
2018-03-27 19:54:05 +05:30
:started
2018-11-20 20:47:30 +05:30
elsif export_file_exists?
2018-03-27 19:54:05 +05:30
:finished
else
:none
end
end
def export_in_progress?
2020-04-08 14:13:33 +05:30
strong_memoize ( :export_in_progress ) do
:: Projects :: ExportJobFinder . new ( self , { status : :started } ) . execute . present?
end
end
def export_enqueued?
strong_memoize ( :export_enqueued ) do
:: Projects :: ExportJobFinder . new ( self , { status : :queued } ) . execute . present?
end
2018-03-27 19:54:05 +05:30
end
2020-04-08 14:13:33 +05:30
def regeneration_in_progress?
( export_enqueued? || export_in_progress? ) && export_file_exists?
2018-05-09 12:01:36 +05:30
end
2018-11-20 20:47:30 +05:30
def remove_exports
return unless export_file_exists?
import_export_upload . remove_export_file!
import_export_upload . save unless import_export_upload . destroyed?
2018-03-17 18:26:18 +05:30
end
2018-11-20 20:47:30 +05:30
def export_file_exists?
export_file & . file
2018-11-08 19:23:39 +05:30
end
2018-05-09 12:01:36 +05:30
2018-11-20 20:47:30 +05:30
def export_file
import_export_upload & . export_file
2018-05-09 12:01:36 +05:30
end
2018-03-17 18:26:18 +05:30
def full_path_slug
Gitlab :: Utils . slugify ( full_path . to_s )
end
def has_ci?
repository . gitlab_ci_yml || auto_devops_enabled?
2016-06-22 15:30:34 +05:30
end
2016-08-24 12:49:21 +05:30
def predefined_variables
2020-01-01 13:55:28 +05:30
Gitlab :: Ci :: Variables :: Collection . new
. concat ( predefined_ci_server_variables )
. concat ( predefined_project_variables )
. concat ( pages_variables )
. concat ( container_registry_variables )
2021-02-22 17:27:13 +05:30
. concat ( dependency_proxy_variables )
2020-01-01 13:55:28 +05:30
. concat ( auto_devops_variables )
. concat ( api_variables )
end
2018-05-09 12:01:36 +05:30
2020-01-01 13:55:28 +05:30
def predefined_project_variables
2018-05-09 12:01:36 +05:30
Gitlab :: Ci :: Variables :: Collection . new
2020-01-01 13:55:28 +05:30
. append ( key : 'GITLAB_FEATURES' , value : licensed_features . join ( ',' ) )
2018-05-09 12:01:36 +05:30
. append ( key : 'CI_PROJECT_ID' , value : id . to_s )
. append ( key : 'CI_PROJECT_NAME' , value : path )
2019-12-21 20:55:43 +05:30
. append ( key : 'CI_PROJECT_TITLE' , value : title )
2018-05-09 12:01:36 +05:30
. append ( key : 'CI_PROJECT_PATH' , value : full_path )
. append ( key : 'CI_PROJECT_PATH_SLUG' , value : full_path_slug )
. append ( key : 'CI_PROJECT_NAMESPACE' , value : namespace . full_path )
2020-07-28 23:09:34 +05:30
. append ( key : 'CI_PROJECT_ROOT_NAMESPACE' , value : namespace . root_ancestor . path )
2018-05-09 12:01:36 +05:30
. append ( key : 'CI_PROJECT_URL' , value : web_url )
2020-01-01 13:55:28 +05:30
. append ( key : 'CI_PROJECT_VISIBILITY' , value : Gitlab :: VisibilityLevel . string_level ( visibility_level ) )
2019-12-04 20:38:33 +05:30
. append ( key : 'CI_PROJECT_REPOSITORY_LANGUAGES' , value : repository_languages . map ( & :name ) . join ( ',' ) . downcase )
2020-01-01 13:55:28 +05:30
. append ( key : 'CI_DEFAULT_BRANCH' , value : default_branch )
2021-03-08 18:12:59 +05:30
. append ( key : 'CI_PROJECT_CONFIG_PATH' , value : ci_config_path_or_default )
2021-04-17 20:07:23 +05:30
. append ( key : 'CI_CONFIG_PATH' , value : ci_config_path_or_default )
2020-01-01 13:55:28 +05:30
end
def predefined_ci_server_variables
Gitlab :: Ci :: Variables :: Collection . new
. append ( key : 'CI' , value : 'true' )
. append ( key : 'GITLAB_CI' , value : 'true' )
2020-03-13 15:44:24 +05:30
. append ( key : 'CI_SERVER_URL' , value : Gitlab . config . gitlab . url )
2020-01-01 13:55:28 +05:30
. append ( key : 'CI_SERVER_HOST' , value : Gitlab . config . gitlab . host )
2020-03-13 15:44:24 +05:30
. append ( key : 'CI_SERVER_PORT' , value : Gitlab . config . gitlab . port . to_s )
. append ( key : 'CI_SERVER_PROTOCOL' , value : Gitlab . config . gitlab . protocol )
2020-01-01 13:55:28 +05:30
. append ( key : 'CI_SERVER_NAME' , value : 'GitLab' )
. append ( key : 'CI_SERVER_VERSION' , value : Gitlab :: VERSION )
. append ( key : 'CI_SERVER_VERSION_MAJOR' , value : Gitlab . version_info . major . to_s )
. append ( key : 'CI_SERVER_VERSION_MINOR' , value : Gitlab . version_info . minor . to_s )
. append ( key : 'CI_SERVER_VERSION_PATCH' , value : Gitlab . version_info . patch . to_s )
. append ( key : 'CI_SERVER_REVISION' , value : Gitlab . revision )
2019-02-15 15:39:39 +05:30
end
2019-03-02 22:35:43 +05:30
def pages_variables
Gitlab :: Ci :: Variables :: Collection . new . tap do | variables |
2020-01-01 13:55:28 +05:30
break unless pages_enabled?
2019-03-02 22:35:43 +05:30
variables . append ( key : 'CI_PAGES_DOMAIN' , value : Gitlab . config . pages . host )
variables . append ( key : 'CI_PAGES_URL' , value : pages_url )
end
end
2019-02-15 15:39:39 +05:30
def api_variables
Gitlab :: Ci :: Variables :: Collection . new . tap do | variables |
variables . append ( key : 'CI_API_V4_URL' , value : API :: Helpers :: Version . new ( 'v4' ) . root_url )
end
2016-08-24 12:49:21 +05:30
end
2021-02-22 17:27:13 +05:30
def dependency_proxy_variables
Gitlab :: Ci :: Variables :: Collection . new . tap do | variables |
break variables unless Gitlab . config . dependency_proxy . enabled
2021-04-17 20:07:23 +05:30
variables . append ( key : 'CI_DEPENDENCY_PROXY_SERVER' , value : Gitlab . host_with_port )
2021-02-22 17:27:13 +05:30
variables . append (
key : 'CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX' ,
2021-04-17 20:07:23 +05:30
# The namespace path can include uppercase letters, which
# Docker doesn't allow. The proxy expects it to be downcased.
value : " #{ Gitlab . host_with_port } / #{ namespace . root_ancestor . path . downcase } #{ DependencyProxy :: URL_SUFFIX } "
2021-02-22 17:27:13 +05:30
)
end
end
2016-08-24 12:49:21 +05:30
def container_registry_variables
2018-05-09 12:01:36 +05:30
Gitlab :: Ci :: Variables :: Collection . new . tap do | variables |
2018-10-15 14:42:47 +05:30
break variables unless Gitlab . config . registry . enabled
2016-08-24 12:49:21 +05:30
2018-05-09 12:01:36 +05:30
variables . append ( key : 'CI_REGISTRY' , value : Gitlab . config . registry . host_port )
2016-08-24 12:49:21 +05:30
2018-05-09 12:01:36 +05:30
if container_registry_enabled?
variables . append ( key : 'CI_REGISTRY_IMAGE' , value : container_registry_url )
end
2016-08-24 12:49:21 +05:30
end
end
2018-11-08 19:23:39 +05:30
def default_environment
2019-12-26 22:10:19 +05:30
production_first = Arel . sql ( " (CASE WHEN name = 'production' THEN 0 ELSE 1 END), id ASC " )
2018-11-08 19:23:39 +05:30
environments
. with_state ( :available )
. reorder ( production_first )
. first
end
2018-12-13 13:39:08 +05:30
def ci_variables_for ( ref : , environment : nil )
2020-04-08 14:13:33 +05:30
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
def uncached_ci_variables_for ( ref : , environment : nil )
2019-10-12 21:52:04 +05:30
result = if protected_for? ( ref )
variables
else
variables . unprotected
end
if environment
result . on_environment ( environment )
2017-09-10 17:25:29 +05:30
else
2019-10-12 21:52:04 +05:30
result . where ( environment_scope : '*' )
2016-08-24 12:49:21 +05:30
end
end
2020-05-24 23:13:21 +05:30
def ci_instance_variables_for ( ref : )
if protected_for? ( ref )
Ci :: InstanceVariable . all_cached
else
Ci :: InstanceVariable . unprotected_cached
end
end
2017-09-10 17:25:29 +05:30
def protected_for? ( ref )
2019-01-03 12:48:30 +05:30
raise Repository :: AmbiguousRefError if repository . ambiguous_ref? ( ref )
resolved_ref = repository . expand_ref ( ref ) || ref
return false unless Gitlab :: Git . tag_ref? ( resolved_ref ) || Gitlab :: Git . branch_ref? ( resolved_ref )
ref_name = if resolved_ref == ref
Gitlab :: Git . ref_name ( resolved_ref )
else
ref
end
if Gitlab :: Git . branch_ref? ( resolved_ref )
ProtectedBranch . protected? ( self , ref_name )
elsif Gitlab :: Git . tag_ref? ( resolved_ref )
ProtectedTag . protected? ( self , ref_name )
2018-03-17 18:26:18 +05:30
end
2017-09-10 17:25:29 +05:30
end
2020-01-01 13:55:28 +05:30
def deployment_variables ( environment : , kubernetes_namespace : nil )
2019-10-12 21:52:04 +05:30
platform = deployment_platform ( environment : environment )
return [ ] unless platform . present?
2020-01-01 13:55:28 +05:30
platform . predefined_variables (
project : self ,
environment_name : environment ,
kubernetes_namespace : kubernetes_namespace
)
2018-03-17 18:26:18 +05:30
end
2016-08-24 12:49:21 +05:30
2018-03-17 18:26:18 +05:30
def auto_devops_variables
return [ ] unless auto_devops_enabled?
2018-05-09 12:01:36 +05:30
( auto_devops || build_auto_devops ) & . predefined_variables
2016-08-24 12:49:21 +05:30
end
2017-08-17 22:00:37 +05:30
def route_map_for ( commit_sha )
@route_maps_by_commit || = Hash . new do | h , sha |
h [ sha ] = begin
data = repository . route_map_for ( sha )
2016-09-29 09:46:39 +05:30
2019-09-30 21:07:59 +05:30
Gitlab :: RouteMap . new ( data ) if data
2021-04-29 21:17:54 +05:30
rescue Gitlab :: RouteMap :: FormatError
nil
2016-09-29 09:46:39 +05:30
end
2017-08-17 22:00:37 +05:30
end
@route_maps_by_commit [ commit_sha ]
end
2016-09-29 09:46:39 +05:30
2017-08-17 22:00:37 +05:30
def public_path_for_source_path ( path , commit_sha )
map = route_map_for ( commit_sha )
return unless map
map . public_path_for_source_path ( path )
end
def parent_changed?
namespace_id_changed?
end
def default_merge_request_target
2021-04-29 21:17:54 +05:30
return self if project_setting . mr_default_target_self
return self unless mr_can_target_upstream?
forked_from_project
end
def mr_can_target_upstream?
# When our current visibility is more restrictive than the upstream project,
# (e.g., the fork is `private` but the parent is `public`), don't allow target upstream
forked_from_project &&
forked_from_project . merge_requests_enabled? &&
forked_from_project . visibility_level_value < = visibility_level_value
2016-09-29 09:46:39 +05:30
end
2018-03-27 19:54:05 +05:30
def multiple_issue_boards_available?
2019-09-30 21:07:59 +05:30
true
2018-03-17 18:26:18 +05:30
end
2019-07-31 22:56:46 +05:30
def full_path_before_last_save
File . join ( namespace . full_path , path_before_last_save )
2018-03-17 18:26:18 +05:30
end
2017-08-17 22:00:37 +05:30
alias_method :name_with_namespace , :full_name
alias_method :human_name , :full_name
2017-09-10 17:25:29 +05:30
# @deprecated cannot remove yet because it has an index with its name in elasticsearch
2017-08-17 22:00:37 +05:30
alias_method :path_with_namespace , :full_path
2018-12-05 23:21:45 +05:30
# rubocop: disable CodeReuse/ServiceClass
2017-09-10 17:25:29 +05:30
def forks_count
2020-07-28 23:09:34 +05:30
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
2017-09-10 17:25:29 +05:30
end
2018-12-05 23:21:45 +05:30
# rubocop: enable CodeReuse/ServiceClass
2017-09-10 17:25:29 +05:30
def legacy_storage?
2018-03-17 18:26:18 +05:30
[ nil , 0 ] . include? ( self . storage_version )
end
# Check if Hashed Storage is enabled for the project with at least informed feature rolled out
#
# @param [Symbol] feature that needs to be rolled out for the project (:repository, :attachments)
def hashed_storage? ( feature )
2019-07-31 22:56:46 +05:30
raise ArgumentError , _ ( " Invalid feature " ) unless HASHED_STORAGE_FEATURES . include? ( feature )
2018-03-17 18:26:18 +05:30
self . storage_version && self . storage_version > = HASHED_STORAGE_FEATURES [ feature ]
end
def renamed?
persisted? && path_changed?
end
2019-07-07 11:18:12 +05:30
def human_merge_method
if merge_method == :ff
'Fast-forward'
else
merge_method . to_s . humanize
end
end
2018-03-17 18:26:18 +05:30
def merge_method
if self . merge_requests_ff_only_enabled
:ff
elsif self . merge_requests_rebase_enabled
:rebase_merge
else
:merge
end
end
def merge_method = ( method )
case method . to_s
when " ff "
self . merge_requests_ff_only_enabled = true
self . merge_requests_rebase_enabled = true
when " rebase_merge "
self . merge_requests_ff_only_enabled = false
self . merge_requests_rebase_enabled = true
when " merge "
self . merge_requests_ff_only_enabled = false
self . merge_requests_rebase_enabled = false
end
end
def ff_merge_must_be_possible?
self . merge_requests_ff_only_enabled || self . merge_requests_rebase_enabled
end
def migrate_to_hashed_storage!
2019-02-15 15:39:39 +05:30
return unless storage_upgradable?
2018-03-17 18:26:18 +05:30
2019-02-15 15:39:39 +05:30
if git_transfer_in_progress?
2019-07-07 11:18:12 +05:30
HashedStorage :: ProjectMigrateWorker . perform_in ( Gitlab :: ReferenceCounter :: REFERENCE_EXPIRE_TIME , id )
else
HashedStorage :: ProjectMigrateWorker . perform_async ( id )
end
end
def rollback_to_legacy_storage!
return if legacy_storage?
if git_transfer_in_progress?
HashedStorage :: ProjectRollbackWorker . perform_in ( Gitlab :: ReferenceCounter :: REFERENCE_EXPIRE_TIME , id )
2018-03-17 18:26:18 +05:30
else
2019-07-07 11:18:12 +05:30
HashedStorage :: ProjectRollbackWorker . perform_async ( id )
2018-03-17 18:26:18 +05:30
end
end
2021-02-22 17:27:13 +05:30
override :git_transfer_in_progress?
2019-02-15 15:39:39 +05:30
def git_transfer_in_progress?
2021-02-22 17:27:13 +05:30
GL_REPOSITORY_TYPES . any? do | type |
reference_counter ( type : type ) . value > 0
end
2019-02-15 15:39:39 +05:30
end
2018-03-17 18:26:18 +05:30
def storage_version = ( value )
super
@storage = nil if storage_version_changed?
end
2018-03-27 19:54:05 +05:30
def badges
return project_badges unless group
2018-12-05 23:21:45 +05:30
Badge . from_union ( [
project_badges ,
GroupBadge . where ( group : group . self_and_ancestors )
] )
2018-03-27 19:54:05 +05:30
end
def merge_requests_allowing_push_to_user ( user )
return MergeRequest . none unless user
developer_access_exists = user . project_authorizations
. where ( 'access_level >= ? ' , Gitlab :: Access :: DEVELOPER )
. where ( 'project_authorizations.project_id = merge_requests.target_project_id' )
. limit ( 1 )
. select ( 1 )
2019-02-15 15:39:39 +05:30
merge_requests_allowing_collaboration . where ( 'EXISTS (?)' , developer_access_exists )
2018-03-27 19:54:05 +05:30
end
2019-02-15 15:39:39 +05:30
def any_branch_allows_collaboration? ( user )
fetch_branch_allows_collaboration ( user )
end
2018-03-27 19:54:05 +05:30
2019-02-15 15:39:39 +05:30
def branch_allows_collaboration? ( user , branch_name )
fetch_branch_allows_collaboration ( user , branch_name )
2018-03-27 19:54:05 +05:30
end
2019-07-07 11:18:12 +05:30
def external_authorization_classification_label
super || :: Gitlab :: CurrentSettings . current_application_settings
. external_authorization_service_default_label
end
2021-04-29 21:17:54 +05:30
# Overridden in EE::Project
def licensed_feature_available? ( _feature )
false
end
2018-10-15 14:42:47 +05:30
def licensed_features
[ ]
end
2021-01-03 14:25:43 +05:30
def mark_primary_write_location
# Overriden in EE
end
2018-10-15 14:42:47 +05:30
def toggle_ci_cd_settings! ( settings_attribute )
ci_cd_settings . toggle! ( settings_attribute )
end
def gitlab_deploy_token
@gitlab_deploy_token || = deploy_tokens . gitlab_deploy_token
end
2018-11-08 19:23:39 +05:30
def any_lfs_file_locks?
lfs_file_locks . any?
end
request_cache ( :any_lfs_file_locks? ) { self . id }
def auto_cancel_pending_pipelines?
auto_cancel_pending_pipelines == 'enabled'
end
2018-03-17 18:26:18 +05:30
def storage
@storage || =
if hashed_storage? ( :repository )
2020-03-13 15:44:24 +05:30
Storage :: Hashed . new ( self )
2018-03-17 18:26:18 +05:30
else
Storage :: LegacyProject . new ( self )
end
end
2018-12-13 13:39:08 +05:30
def storage_upgradable?
storage_version != LATEST_STORAGE_VERSION
end
def snippets_visible? ( user = nil )
2020-03-13 15:44:24 +05:30
Ability . allowed? ( user , :read_snippet , self )
2018-12-13 13:39:08 +05:30
end
2019-02-15 15:39:39 +05:30
def max_attachment_size
Gitlab :: CurrentSettings . max_attachment_size . megabytes . to_i
end
def object_pool_params
return { } unless ! forked? && git_objects_poolable?
{
repository_storage : repository_storage ,
pool_repository : pool_repository || create_new_pool_repository
}
end
# Git objects are only poolable when the project is or has:
# - Hashed storage -> The object pool will have a remote to its members, using relative paths.
# If the repository path changes we would have to update the remote.
2020-01-01 13:55:28 +05:30
# - not private -> The visibility level or repository access level has to be greater than private
# to prevent fetching objects that might not exist
2019-02-15 15:39:39 +05:30
# - Repository -> Else the disk path will be empty, and there's nothing to pool
def git_objects_poolable?
hashed_storage? ( :repository ) &&
2020-01-01 13:55:28 +05:30
visibility_level > Gitlab :: VisibilityLevel :: PRIVATE &&
repository_access_level > ProjectFeature :: PRIVATE &&
2019-02-15 15:39:39 +05:30
repository_exists? &&
2019-12-04 20:38:33 +05:30
Gitlab :: CurrentSettings . hashed_storage_enabled
2019-02-15 15:39:39 +05:30
end
def leave_pool_repository
2019-07-07 11:18:12 +05:30
pool_repository & . mark_obsolete_if_last ( repository ) && update_column ( :pool_repository_id , nil )
2019-03-02 22:35:43 +05:30
end
def link_pool_repository
pool_repository & . link_repository ( repository )
2019-02-15 15:39:39 +05:30
end
2019-07-07 11:18:12 +05:30
def has_pool_repository?
pool_repository . present?
end
2019-12-04 20:38:33 +05:30
def access_request_approvers_to_be_notified
2021-06-08 01:23:25 +05:30
members . maintainers . connected_to_user . order_recent_sign_in . limit ( ACCESS_REQUEST_APPROVERS_TO_BE_NOTIFIED_LIMIT )
2019-12-04 20:38:33 +05:30
end
2019-12-21 20:55:43 +05:30
def pages_lookup_path ( trim_prefix : nil , domain : nil )
Pages :: LookupPath . new ( self , trim_prefix : trim_prefix , domain : domain )
end
def closest_setting ( name )
setting = read_attribute ( name )
setting = closest_namespace_setting ( name ) if setting . nil?
setting = app_settings_for ( name ) if setting . nil?
setting
end
def drop_visibility_level!
if group && group . visibility_level < visibility_level
self . visibility_level = group . visibility_level
end
if Gitlab :: CurrentSettings . restricted_visibility_levels . include? ( visibility_level )
self . visibility_level = Gitlab :: VisibilityLevel :: PRIVATE
end
2019-12-04 20:38:33 +05:30
end
2020-02-01 01:16:34 +05:30
def template_source?
false
end
2020-11-24 15:15:51 +05:30
def jira_subscription_exists?
JiraConnectSubscription . for_project ( self ) . exists?
end
2020-03-13 15:44:24 +05:30
def uses_default_ci_config?
ci_config_path . blank? || ci_config_path == Gitlab :: FileDetector :: PATTERNS [ :gitlab_ci ]
end
def limited_protected_branches ( limit )
protected_branches . limit ( limit )
end
def self_monitoring?
Gitlab :: CurrentSettings . self_monitoring_project_id == id
end
2020-04-08 14:13:33 +05:30
def deploy_token_create_url ( opts = { } )
2020-04-25 10:58:03 +05:30
Gitlab :: Routing . url_helpers . create_deploy_token_project_settings_repository_path ( self , opts )
2020-04-08 14:13:33 +05:30
end
def deploy_token_revoke_url_for ( token )
Gitlab :: Routing . url_helpers . revoke_project_deploy_token_path ( self , token )
end
def default_branch_protected?
branch_protection = Gitlab :: Access :: BranchProtection . new ( self . namespace . default_branch_protection )
branch_protection . fully_protected? || branch_protection . developer_can_merge?
end
2020-04-22 19:07:51 +05:30
def environments_for_scope ( scope )
quoted_scope = :: Gitlab :: SQL :: Glob . q ( scope )
environments . where ( " name LIKE ( #{ :: Gitlab :: SQL :: Glob . to_like ( quoted_scope ) } ) " ) # rubocop:disable GitlabSecurity/SqlInjection
end
def latest_jira_import
jira_imports . last
end
2020-06-23 00:09:42 +05:30
def metrics_setting
super || build_metrics_setting
end
2020-07-28 23:09:34 +05:30
def service_desk_enabled
Gitlab :: ServiceDesk . enabled? ( project : self )
end
alias_method :service_desk_enabled? , :service_desk_enabled
def service_desk_address
2020-10-24 23:57:45 +05:30
service_desk_custom_address || service_desk_incoming_address
end
def service_desk_incoming_address
2020-07-28 23:09:34 +05:30
return unless service_desk_enabled?
config = Gitlab . config . incoming_email
wildcard = Gitlab :: IncomingEmail :: WILDCARD_PLACEHOLDER
config . address & . gsub ( wildcard , " #{ full_path_slug } - #{ id } -issue- " )
end
2020-10-24 23:57:45 +05:30
def service_desk_custom_address
2021-03-08 18:12:59 +05:30
return unless Gitlab :: ServiceDeskEmail . enabled?
2020-10-24 23:57:45 +05:30
key = service_desk_setting & . project_key
return unless key . present?
2021-03-08 18:12:59 +05:30
Gitlab :: ServiceDeskEmail . address_for_key ( " #{ full_path_slug } - #{ key } " )
2021-02-22 17:27:13 +05:30
end
2020-07-28 23:09:34 +05:30
def root_namespace
if namespace . has_parent?
namespace . root_ancestor
else
namespace
end
end
def package_already_taken? ( package_name )
namespace . root_ancestor . all_projects
. joins ( :packages )
. where . not ( id : id )
2021-06-08 01:23:25 +05:30
. merge ( Packages :: Package . default_scoped . with_name ( package_name ) )
2020-07-28 23:09:34 +05:30
. exists?
end
2021-06-08 01:23:25 +05:30
def default_branch_or_main
return default_branch if default_branch
Gitlab :: DefaultBranch . value ( object : self )
2020-11-24 15:15:51 +05:30
end
def ci_config_path_or_default
ci_config_path . presence || Ci :: Pipeline :: DEFAULT_CONFIG_PATH
end
2021-01-03 14:25:43 +05:30
def ci_config_for ( sha )
repository . gitlab_ci_yml_for ( sha , ci_config_path_or_default )
end
2020-11-24 15:15:51 +05:30
def enabled_group_deploy_keys
return GroupDeployKey . none unless group
GroupDeployKey . for_groups ( group . self_and_ancestors_ids )
end
2021-01-03 14:25:43 +05:30
def feature_flags_client_token
instance = operations_feature_flags_client || create_operations_feature_flags_client!
instance . token
end
def tracing_external_url
tracing_setting & . external_url
end
2021-03-11 19:13:27 +05:30
override :git_garbage_collect_worker_klass
def git_garbage_collect_worker_klass
Projects :: GitGarbageCollectWorker
end
2021-04-17 20:07:23 +05:30
def inherited_issuable_templates_enabled?
Feature . enabled? ( :inherited_issuable_templates , self , default_enabled : :yaml )
end
2021-06-08 01:23:25 +05:30
def activity_path
Gitlab :: Routing . url_helpers . activity_project_path ( self )
end
def increment_statistic_value ( statistic , delta )
return if pending_delete?
ProjectStatistics . increment_statistic ( self , statistic , delta )
end
2018-12-13 13:39:08 +05:30
private
2021-04-17 20:07:23 +05:30
def set_container_registry_access_level
# changes_to_save = { 'container_registry_enabled' => [value_before_update, value_after_update] }
value = changes_to_save [ 'container_registry_enabled' ] [ 1 ]
access_level =
if value
ProjectFeature :: ENABLED
else
ProjectFeature :: DISABLED
end
project_feature . update! ( container_registry_access_level : access_level )
end
2020-04-22 19:07:51 +05:30
def find_service ( services , name )
services . find { | service | service . to_param == name }
end
2020-06-23 00:09:42 +05:30
def build_from_instance_or_template ( name )
instance = find_service ( services_instances , name )
2021-06-08 01:23:25 +05:30
return Integration . build_from_integration ( instance , project_id : id ) if instance
2020-06-23 00:09:42 +05:30
template = find_service ( services_templates , name )
2021-06-08 01:23:25 +05:30
return Integration . build_from_integration ( template , project_id : id ) if template
2020-06-23 00:09:42 +05:30
end
2021-04-29 21:17:54 +05:30
def build_service ( name )
2021-06-08 01:23:25 +05:30
Integration . service_name_to_model ( name ) . new ( project_id : id )
2021-04-29 21:17:54 +05:30
end
2020-06-23 00:09:42 +05:30
def services_templates
2021-06-08 01:23:25 +05:30
@services_templates || = Integration . for_template
2020-06-23 00:09:42 +05:30
end
def services_instances
2021-06-08 01:23:25 +05:30
@services_instances || = Integration . for_instance
2020-06-23 00:09:42 +05:30
end
2019-12-21 20:55:43 +05:30
def closest_namespace_setting ( name )
namespace . closest_setting ( name )
end
def app_settings_for ( name )
Gitlab :: CurrentSettings . send ( name ) # rubocop:disable GitlabSecurity/PublicSend
end
2019-02-15 15:39:39 +05:30
def merge_requests_allowing_collaboration ( source_branch = nil )
relation = source_of_merge_requests . opened . where ( allow_collaboration : true )
relation = relation . where ( source_branch : source_branch ) if source_branch
relation
end
def create_new_pool_repository
2019-07-31 22:56:46 +05:30
pool = PoolRepository . safe_find_or_create_by! ( shard : Shard . by_name ( repository_storage ) , source_project : self )
update! ( pool_repository : pool )
2019-02-15 15:39:39 +05:30
pool . schedule unless pool . scheduled?
2019-07-31 22:56:46 +05:30
2019-02-15 15:39:39 +05:30
pool
end
def join_pool_repository
return unless pool_repository
ObjectPool :: JoinWorker . perform_async ( pool_repository . id , self . id )
end
2018-03-17 18:26:18 +05:30
def use_hashed_storage
if self . new_record? && Gitlab :: CurrentSettings . hashed_storage_enabled
self . storage_version = LATEST_STORAGE_VERSION
end
end
def check_repository_absence!
return if skip_disk_validation
2018-10-15 14:42:47 +05:30
if repository_storage . blank? || repository_with_same_path_already_exists?
2019-07-31 22:56:46 +05:30
errors . add ( :base , _ ( 'There is already a repository with that name on disk' ) )
2020-04-08 14:13:33 +05:30
throw :abort # rubocop:disable Cop/BanCatchThrow
2018-03-17 18:26:18 +05:30
end
end
def repository_with_same_path_already_exists?
2019-12-21 20:55:43 +05:30
gitlab_shell . repository_exists? ( repository_storage , " #{ disk_path } .git " )
2018-03-17 18:26:18 +05:30
end
2018-12-13 13:39:08 +05:30
def set_timestamps_for_create
update_columns ( last_activity_at : self . created_at , last_repository_updated_at : self . created_at )
2018-03-17 18:26:18 +05:30
end
2017-08-17 22:00:37 +05:30
def cross_namespace_reference? ( from )
case from
when Project
2021-06-08 01:23:25 +05:30
namespace_id != from . namespace_id
2017-08-17 22:00:37 +05:30
when Namespace
namespace != from
2020-10-24 23:57:45 +05:30
when User
true
2017-08-17 22:00:37 +05:30
end
end
# Check if a reference is being done cross-project
def cross_project_reference? ( from )
return true if from . is_a? ( Namespace )
from && self != from
end
def update_project_statistics
stats = statistics || build_statistics
stats . update ( namespace_id : namespace_id )
2016-08-24 12:49:21 +05:30
end
2017-08-17 22:00:37 +05:30
def check_pending_delete
return if valid_attribute? ( :name ) && valid_attribute? ( :path )
return unless pending_delete_twin
% i [ route route . path name path ] . each do | error |
errors . delete ( error )
end
2016-08-24 12:49:21 +05:30
2019-07-31 22:56:46 +05:30
errors . add ( :base , _ ( " The project is still being deleted. Please try again later. " ) )
2016-08-24 12:49:21 +05:30
end
2017-08-17 22:00:37 +05:30
def pending_delete_twin
return false unless path
2016-08-24 12:49:21 +05:30
2017-09-10 17:25:29 +05:30
Project . pending_delete . find_by_full_path ( full_path )
2017-08-17 22:00:37 +05:30
end
2016-08-24 12:49:21 +05:30
2017-08-17 22:00:37 +05:30
##
# This method is here because of support for legacy container repository
# which has exactly the same path like project does, but which might not be
# persisted in `container_repositories` table.
#
def has_root_container_repository_tags?
return false unless Gitlab . config . registry . enabled
ContainerRepository . build_root_repository ( self ) . has_tags?
2016-08-24 12:49:21 +05:30
end
2016-11-03 12:29:30 +05:30
2019-02-15 15:39:39 +05:30
def fetch_branch_allows_collaboration ( user , branch_name = nil )
return false unless user
2018-05-09 12:01:36 +05:30
2019-02-15 15:39:39 +05:30
Gitlab :: SafeRequestStore . fetch ( " project- #{ id } :branch- #{ branch_name } :user- #{ user . id } :branch_allows_collaboration " ) do
next false if empty_repo?
2018-11-08 19:23:39 +05:30
2019-12-04 20:38:33 +05:30
# Issue for N+1: https://gitlab.com/gitlab-org/gitlab-foss/issues/49322
2018-11-18 11:00:15 +05:30
Gitlab :: GitalyClient . allow_n_plus_1_calls do
2019-02-15 15:39:39 +05:30
merge_requests_allowing_collaboration ( branch_name ) . any? do | merge_request |
2021-04-01 16:36:13 +05:30
merge_request . can_be_merged_by? ( user , skip_collaboration_check : true )
2018-11-18 11:00:15 +05:30
end
2018-11-08 19:23:39 +05:30
end
2018-03-27 19:54:05 +05:30
end
end
2018-12-05 23:21:45 +05:30
2019-12-21 20:55:43 +05:30
def ensure_pages_metadatum
pages_metadatum || create_pages_metadatum!
rescue ActiveRecord :: RecordNotUnique
reset
retry
end
2020-04-08 14:13:33 +05:30
def oids ( objects , oids : [ ] )
2020-11-24 15:15:51 +05:30
objects = objects . where ( oid : oids ) if oids . any?
2020-04-08 14:13:33 +05:30
2020-11-24 15:15:51 +05:30
[ ] . tap do | out |
objects . each_batch { | relation | out . concat ( relation . pluck ( :oid ) ) }
end
2020-04-08 14:13:33 +05:30
end
2021-03-08 18:12:59 +05:30
def cache_has_external_wiki
2021-06-08 01:23:25 +05:30
update_column ( :has_external_wiki , integrations . external_wikis . any? ) if Gitlab :: Database . read_write?
2021-03-08 18:12:59 +05:30
end
2021-03-11 19:13:27 +05:30
def cache_has_external_issue_tracker
2021-06-08 01:23:25 +05:30
update_column ( :has_external_issue_tracker , integrations . external_issue_trackers . any? ) if Gitlab :: Database . read_write?
2021-03-11 19:13:27 +05:30
end
2021-04-17 20:07:23 +05:30
def active_runners_with_tags
2021-04-29 21:17:54 +05:30
@active_runners_with_tags || = active_runners . with_tags
end
def online_runners_with_tags
@online_runners_with_tags || = active_runners_with_tags . online
2021-04-17 20:07:23 +05:30
end
2014-09-02 18:07:02 +05:30
end
2019-12-04 20:38:33 +05:30
2021-06-08 01:23:25 +05:30
Project . prepend_mod_with ( 'Project' )