2018-11-18 11:00:15 +05:30
# frozen_string_literal: true
2015-04-26 12:48:37 +05:30
require 'carrierwave/orm/activerecord'
2014-09-02 18:07:02 +05:30
class Project < ActiveRecord :: Base
2015-09-11 14:41:01 +05:30
include Gitlab :: ConfigHelper
2014-09-02 18:07:02 +05:30
include Gitlab :: ShellAdapter
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 Referable
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
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
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
2019-02-13 22:33:31 +05:30
include IgnorableColumn
2018-11-08 19:23:39 +05:30
extend Gitlab :: Cache :: RequestCache
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
2018-11-18 11:00:15 +05:30
STATISTICS_ATTRIBUTE = 'repositories_count' . freeze
2016-11-03 12:29:30 +05:30
NUMBER_OF_PERMITTED_BOARDS = 1
2017-08-17 22:00:37 +05:30
UNKNOWN_IMPORT_URL = 'http://unknown.git' . freeze
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-02-13 22:33:31 +05:30
ignore_column :import_status , :import_jid , :import_error
2016-11-03 12:29:30 +05:30
cache_markdown_field :description , pipeline : :description
2017-08-17 22:00:37 +05:30
delegate :feature_available? , :builds_enabled? , :wiki_enabled? ,
2018-12-05 23:21:45 +05:30
:merge_requests_enabled? , :issues_enabled? , :pages_enabled? , :public_pages? ,
to : :project_feature , allow_nil : true
2016-09-29 09:46:39 +05:30
2018-03-17 18:26:18 +05:30
delegate :base_dir , :disk_path , :ensure_storage_path_exists , to : :storage
2019-02-13 22:33:31 +05:30
delegate :scheduled? , :started? , :in_progress? ,
:failed? , :finished? ,
prefix : :import , to : :import_state , allow_nil : true
delegate :no_import? , to : :import_state , allow_nil : true
2014-09-02 18:07:02 +05:30
default_value_for :archived , false
default_value_for :visibility_level , gitlab_config_features . visibility_level
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
2018-03-17 18:26:18 +05:30
default_value_for ( :repository_storage ) { Gitlab :: CurrentSettings . pick_repository_storage }
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
2014-09-02 18:07:02 +05:30
2019-02-13 22:33:31 +05:30
add_authentication_token_field :runners_token , encrypted : true , migrating : true
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
2018-03-17 18:26:18 +05:30
after_save :update_project_statistics , if : :namespace_id_changed?
2018-10-15 14:42:47 +05:30
after_save :create_import_state , if : - > ( project ) { project . import? && project . import_state . nil? }
2018-03-17 18:26:18 +05:30
after_create :create_project_feature , unless : :project_feature
2018-10-15 14:42:47 +05:30
after_create :create_ci_cd_settings ,
unless : :ci_cd_settings ,
if : proc { ProjectCiCdSetting . available? }
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
2017-09-10 17:25:29 +05:30
after_destroy - > { run_after_commit { 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!
after_create :ensure_storage_path_exists
after_save :ensure_storage_path_exists , if : :namespace_id_changed?
2017-09-10 17:25:29 +05:30
2018-12-05 23:21:45 +05:30
acts_as_ordered_taggable
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'
2017-09-10 17:25:29 +05:30
has_many :boards , before_add : :validate_board_limit
2016-09-29 09:46:39 +05:30
2014-09-02 18:07:02 +05:30
# Project services
2017-09-10 17:25:29 +05:30
has_one :campfire_service
2019-02-13 22:33:31 +05:30
has_one :discord_service
2017-09-10 17:25:29 +05:30
has_one :drone_ci_service
has_one :emails_on_push_service
has_one :pipelines_email_service
has_one :irker_service
has_one :pivotaltracker_service
has_one :hipchat_service
has_one :flowdock_service
has_one :assembla_service
has_one :asana_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 :bamboo_service
has_one :teamcity_service
has_one :pushover_service
has_one :jira_service
has_one :redmine_service
has_one :custom_issue_tracker_service
has_one :bugzilla_service
has_one :gitlab_issue_tracker_service , inverse_of : :project
has_one :external_wiki_service
has_one :kubernetes_service , inverse_of : :project
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
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
2015-04-26 12:48:37 +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
2019-02-13 22:33:31 +05:30
has_one :project_repository , inverse_of : :project
has_one :error_tracking_setting , inverse_of : :project , class_name : 'ErrorTracking::ProjectErrorTrackingSetting'
2018-10-15 14:42:47 +05:30
2014-09-02 18:07:02 +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
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'
has_many :services
has_many :events
has_many :milestones
has_many :notes
has_many :snippets , class_name : 'ProjectSnippet'
has_many :hooks , class_name : 'ProjectHook'
has_many :protected_branches
has_many :protected_tags
2018-11-18 11:00:15 +05:30
has_many :repository_languages , - > { order " share DESC " }
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
2017-09-10 17:25:29 +05:30
has_many :deploy_keys_projects
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
2015-11-26 14:37:03 +05:30
has_many :lfs_objects , 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 :pages_domains
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'
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'
2018-11-08 19:23:39 +05:30
has_many :cluster_ingresses , through : :clusters , source : :application_ingress , class_name : 'Clusters::Applications::Ingress'
2019-02-13 22:33:31 +05:30
has_many :kubernetes_namespaces , class_name : 'Clusters::KubernetesNamespace'
2018-03-17 18:26:18 +05:30
2018-11-20 20:47:30 +05:30
has_many :prometheus_metrics
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
has_many :commit_statuses
2019-02-13 22:33:31 +05:30
# The relation :all_pipelines is intented to be used when we want to get the
# whole list of pipelines associated to the project
has_many :all_pipelines , class_name : 'Ci::Pipeline' , inverse_of : :project
# The relation :ci_pipelines is intented to be used when we want to get only
# those pipeline which are directly related to CI. There are
# other pipelines, like webide ones, that we won't retrieve
# if we use this relation.
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
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
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
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
2018-12-13 13:39:08 +05:30
has_many :deployments , - > { success }
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
2017-08-17 22:00:37 +05:30
2018-03-17 18:26:18 +05:30
has_one :auto_devops , class_name : 'ProjectAutoDevops'
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
2018-03-27 19:54:05 +05:30
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
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
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? }
2019-02-13 22:33:31 +05:30
accepts_nested_attributes_for :error_tracking_setting , update_only : 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
delegate :add_master , to : :team # @deprecated
2018-10-15 14:42:47 +05:30
delegate :group_runners_enabled , :group_runners_enabled = , :group_runners_enabled? , to : :ci_cd_settings
2019-02-13 22:33:31 +05:30
delegate :group_clusters_enabled? , to : :group , allow_nil : true
delegate :root_ancestor , to : :namespace , allow_nil : true
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/) } ,
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
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-01-03 12:48:30 +05:30
validates :import_url , public_url : { protocols : - > ( project ) { project . persisted? ? VALID_MIRROR_PROTOCOLS : VALID_IMPORT_PROTOCOLS } ,
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 }
validate :check_limit , on : :create
2018-03-17 18:26:18 +05:30
validate :check_repository_path_availability , on : :update , if : - > ( project ) { project . renamed? }
2019-02-13 22:33:31 +05:30
validate :visibility_level_allowed_by_group , if : - > { changes . has_key? ( :visibility_level ) }
validate :visibility_level_allowed_as_fork , if : - > { changes . has_key? ( :visibility_level ) }
2016-08-24 12:49:21 +05:30
validate :check_wiki_path_conflict
2018-05-09 12:01:36 +05:30
validate :validate_pages_https_only , if : - > { changes . has_key? ( :pages_https_only ) }
2016-08-24 12:49:21 +05:30
validates :repository_storage ,
presence : true ,
inclusion : { in : - > ( _object ) { Gitlab . config . repositories . storages . keys } }
2018-03-17 18:26:18 +05:30
validates :variables , variable_duplicates : { scope : :environment_scope }
2019-02-13 22:33:31 +05:30
validates :bfg_object_map , file_size : { maximum : :max_attachment_size }
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
2018-03-17 18:26:18 +05:30
scope :with_storage_feature , - > ( feature ) { where ( 'storage_version >= :version' , version : HASHED_STORAGE_FEATURES [ feature ] ) }
scope :without_storage_feature , - > ( feature ) { where ( 'storage_version < :version OR storage_version IS NULL' , version : HASHED_STORAGE_FEATURES [ feature ] ) }
scope :with_unmigrated_storage , - > { where ( 'storage_version < :version OR storage_version IS NULL' , version : LATEST_STORAGE_VERSION ) }
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
scope :sorted_by_activity , - > { reorder ( " GREATEST(COALESCE(last_activity_at, '1970-01-01'), COALESCE(last_repository_updated_at, '1970-01-01')) DESC " ) }
2018-12-05 23:21:45 +05:30
scope :sorted_by_stars , - > { reorder ( star_count : :desc ) }
2015-04-26 12:48:37 +05:30
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 ) }
2015-04-26 12:48:37 +05:30
scope :joined , - > ( user ) { where ( '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 }
scope :with_push , - > { joins ( :events ) . where ( 'events.action = ?' , Event :: PUSHED ) }
2016-11-24 13:41:30 +05:30
scope :with_project_feature , - > { joins ( 'LEFT JOIN project_features ON projects.id = project_features.project_id' ) }
2017-08-17 22:00:37 +05:30
scope :with_statistics , - > { includes ( :statistics ) }
scope :with_shared_runners , - > { where ( shared_runners_enabled : true ) }
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-13 22:33:31 +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 } )
}
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 ) }
2017-09-10 17:25:29 +05:30
scope :with_merge_requests_enabled , - > { with_feature_enabled ( :merge_requests ) }
2018-10-15 14:42:47 +05:30
scope :with_remote_mirrors , - > { joins ( :remote_mirrors ) . where ( remote_mirrors : { enabled : true } ) . distinct }
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-02-13 22:33:31 +05:30
scope :missing_kubernetes_namespace , - > ( kubernetes_namespaces ) do
subquery = kubernetes_namespaces . select ( '1' ) . where ( 'clusters_kubernetes_namespaces.project_id = projects.id' )
where ( 'NOT EXISTS (?)' , subquery )
end
2017-08-17 22:00:37 +05:30
enum auto_cancel_pending_pipelines : { disabled : 0 , enabled : 1 }
2019-02-13 22:33:31 +05:30
chronic_duration_attr :build_timeout_human_readable , :build_timeout ,
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 ,
less_than : 1 . month ,
only_integer : true ,
message : 'needs to be beetween 10 minutes and 1 month' }
2018-05-09 12:01:36 +05:30
2019-02-13 22:33:31 +05:30
# Used by Projects::CleanupService to hold a map of rewritten object IDs
mount_uploader :bfg_object_map , AttachmentUploader
2018-12-13 13:39:08 +05:30
# Returns a project, if it is not about to be removed.
#
# id - The ID of the project to retrieve.
def self . find_without_deleted ( id )
without_deleted . find_by_id ( id )
end
2018-11-20 20:47:30 +05:30
# Paginates a collection using a `WHERE id < ?` condition.
#
# before - A project ID to use for filtering out projects with an equal or
# greater ID. If no ID is given, all projects are included.
#
# limit - The maximum number of rows to include.
def self . paginate_in_descending_order_using_id (
before : nil ,
limit : Kaminari . config . default_per_page
)
relation = order_id_desc . limit ( limit )
relation = relation . where ( 'projects.id < ?' , before ) if before
relation
end
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.
def self . public_or_visible_to_user ( user = nil )
if user
2018-03-17 18:26:18 +05:30
where ( 'EXISTS (?) OR projects.visibility_level IN (?)' ,
user . authorizations_for_projects ,
Gitlab :: VisibilityLevel . levels_for_user ( user ) )
2017-09-10 17:25:29 +05:30
else
public_to_user
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.
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-13 22:33:31 +05:30
visible = [ ProjectFeature :: ENABLED , ProjectFeature :: PUBLIC ]
min_access_level = ProjectFeature . required_minimum_access_level ( feature )
2016-11-24 13:41:30 +05:30
2017-09-10 17:25:29 +05:30
if user & . admin?
with_feature_enabled ( feature )
elsif user
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-02-13 22:33:31 +05:30
. where (
" (projects.visibility_level > :private AND ( #{ column } IS NULL OR #{ column } >= (:public_visible) OR ( #{ column } = :private_visible AND EXISTS(:authorizations)))) " \
" OR (projects.visibility_level = :private AND ( #{ column } IS NULL OR #{ column } >= :private_visible) AND EXISTS(:authorizations)) " ,
{
private : Gitlab :: VisibilityLevel :: PRIVATE ,
public_visible : ProjectFeature :: ENABLED ,
private_visible : ProjectFeature :: PRIVATE ,
authorizations : user . authorizations_for_projects ( min_access_level : min_access_level )
} )
2017-09-10 17:25:29 +05:30
else
with_feature_access_level ( feature , visible )
end
2016-11-24 13:41:30 +05:30
end
2016-09-29 09:46:39 +05:30
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-13 22:33:31 +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 ) }
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
# search. On MySQL a regular "LIKE" is used as it's already
# case-insensitive.
#
# query - The search query as a String.
def search ( query )
2018-03-17 18:26:18 +05:30
fuzzy_search ( query , [ :path , :name , :description ] )
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'
reorder ( last_activity_at : :desc )
when 'latest_activity_asc'
reorder ( last_activity_at : :asc )
2018-12-05 23:21:45 +05:30
when 'stars_desc'
sorted_by_stars
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-13 22:33:31 +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
%r{
#{reference_pattern}
( #{reference_postfix}|#{reference_postfix_escaped})
} x
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
2014-09-02 18:07:02 +05:30
end
2019-02-13 22:33:31 +05:30
def all_pipelines
if builds_enabled?
super
else
super . external
end
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-13 22:33:31 +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-13 22:33:31 +05:30
alias_method :ancestors , :ancestors_upto
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?
2018-11-20 20:47:30 +05:30
auto_devops & . enabled . nil? &&
( Gitlab :: CurrentSettings . auto_devops_enabled? || Feature . enabled? ( :force_autodevops_on_by_default , self ) )
2018-11-18 11:00:15 +05:30
end
2018-03-17 18:26:18 +05:30
def has_auto_devops_implicitly_disabled?
2018-11-20 20:47:30 +05:30
auto_devops & . enabled . nil? && ! ( Gitlab :: CurrentSettings . auto_devops_enabled? || Feature . enabled? ( :force_autodevops_on_by_default , self ) )
2018-03-17 18:26:18 +05:30
end
def empty_repo?
repository . empty?
end
2014-09-02 18:07:02 +05:30
def team
@team || = ProjectTeam . new ( self )
end
def repository
2017-09-10 17:25:29 +05:30
@repository || = Repository . new ( full_path , self , disk_path : disk_path )
2015-09-11 14:41:01 +05:30
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
2016-08-24 12:49:21 +05:30
def commit ( ref = 'HEAD' )
repository . commit ( ref )
end
2018-03-17 18:26:18 +05:30
def commit_by ( oid : )
repository . commit_by ( oid : oid )
end
2018-11-18 11:00:15 +05:30
def commits_by ( oids : )
repository . commits_by ( oids : oids )
end
2016-08-24 12:49:21 +05:30
# ref can't be HEAD, can only be branch/tag name or SHA
def latest_successful_builds_for ( ref = default_branch )
2019-02-13 22:33:31 +05:30
latest_pipeline = ci_pipelines . latest_successful_for ( ref )
2016-08-24 12:49:21 +05:30
if latest_pipeline
2018-03-27 19:54:05 +05:30
latest_pipeline . builds . latest . with_artifacts_archive
2016-08-24 12:49:21 +05:30
else
builds . none
end
2014-09-02 18:07:02 +05:30
end
2019-02-13 22:33:31 +05:30
def latest_successful_build_for ( job_name , ref = default_branch )
builds = latest_successful_builds_for ( ref )
builds . find_by! ( name : job_name )
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-13 22:33:31 +05:30
def import_status
import_state & . status || 'none'
end
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 )
2018-03-17 18:26:18 +05:30
elsif gitlab_project_import?
# Do not retry on Import/Export until https://gitlab.com/gitlab-org/gitlab-ce/issues/26189 is solved.
RepositoryImportWorker . set ( retry : false ) . perform_async ( self . 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
2018-03-17 18:26:18 +05:30
Rails . logger . info ( " #{ job_type } job scheduled for #{ full_path } with job ID #{ job_id } . " )
2015-09-25 12:07:36 +05:30
else
2018-03-17 18:26:18 +05:30
Rails . logger . 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-13 22:33:31 +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 )
2016-08-24 12:49:21 +05:30
return super ( value ) unless Gitlab :: UrlSanitizer . valid? ( value )
2016-06-02 11:05:42 +05:30
import_url = Gitlab :: UrlSanitizer . new ( value )
super ( import_url . sanitized_url )
2016-08-24 12:49:21 +05:30
create_or_update_import_data ( credentials : import_url . credentials )
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
2018-12-13 13:39:08 +05:30
rescue
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-13 22:33:31 +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?
2018-03-17 18:26:18 +05:30
external_import? || forked? || gitlab_project_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
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 ,
last_error : 'The remote mirror took to long to complete.' ,
last_update_at : Time . now
)
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
2014-09-02 18:07:02 +05:30
def check_limit
2017-08-17 22:00:37 +05:30
unless creator . can_create_project? || namespace . kind == 'group'
2016-06-16 23:09:34 +05:30
projects_limit = creator . projects_limit
if projects_limit == 0
self . errors . add ( :limit_reached , " Personal project creation is not allowed. Please contact your administrator with questions " )
else
self . errors . add ( :limit_reached , " Your project limit is #{ projects_limit } projects! Please contact your administrator to increase it " )
end
2014-09-02 18:07:02 +05:30
end
rescue
2016-06-02 11:05:42 +05:30
self . errors . add ( :base , " Can't check your ability to create project " )
end
def visibility_level_allowed_by_group
return if visibility_level_allowed_by_group?
level_name = Gitlab :: VisibilityLevel . level_name ( self . visibility_level ) . downcase
group_level_name = Gitlab :: VisibilityLevel . level_name ( self . group . visibility_level ) . downcase
self . errors . add ( :visibility_level , " #{ level_name } is not allowed in a #{ group_level_name } group. " )
end
def visibility_level_allowed_as_fork
return if visibility_level_allowed_as_fork?
level_name = Gitlab :: VisibilityLevel . level_name ( self . visibility_level ) . downcase
self . errors . add ( :visibility_level , " #{ level_name } is not allowed since the fork source project has lower visibility. " )
2014-09-02 18:07:02 +05:30
end
2016-08-24 12:49:21 +05:30
def check_wiki_path_conflict
return if path . blank?
path_to_check = path . ends_with? ( '.wiki' ) ? path . chomp ( '.wiki' ) : " #{ path } .wiki "
if Project . where ( namespace_id : namespace_id , path : path_to_check ) . exists?
errors . add ( :name , 'has already been taken' )
end
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? )
errors . add ( :pages_https_only , " cannot be enabled unless all domains have TLS certificates " )
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
2018-11-20 20:47:30 +05:30
def to_reference_with_postfix
" #{ to_reference ( full : true ) } #{ self . class . reference_postfix } "
end
2017-08-17 22:00:37 +05:30
# `from` argument can be a Namespace or Project.
def to_reference ( from = nil , full : false )
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
2017-08-17 22:00:37 +05:30
def web_url
2017-09-10 17:25:29 +05:30
Gitlab :: Routing . url_helpers . project_url ( self )
2014-09-02 18:07:02 +05:30
end
2018-11-08 19:23:39 +05:30
def readme_url
2019-02-13 22:33:31 +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-13 22:33:31 +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-13 22:33:31 +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
2015-04-26 12:48:37 +05:30
def default_issue_tracker
gitlab_issue_tracker_service || create_gitlab_issue_tracker_service
end
def issues_tracker
if external_issue_tracker
external_issue_tracker
else
default_issue_tracker
end
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
2016-06-16 23:09:34 +05:30
if has_external_issue_tracker . nil? # To populate existing projects
cache_has_external_issue_tracker
end
if has_external_issue_tracker?
return @external_issue_tracker if defined? ( @external_issue_tracker )
@external_issue_tracker = services . external_issue_trackers . first
else
nil
end
2014-09-02 18:07:02 +05:30
end
2016-06-16 23:09:34 +05:30
def cache_has_external_issue_tracker
2018-03-17 18:26:18 +05:30
update_column ( :has_external_issue_tracker , services . external_issue_trackers . any? ) if Gitlab :: Database . read_write?
2014-09-02 18:07:02 +05:30
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
if has_external_wiki . nil?
cache_has_external_wiki # Populate
end
if has_external_wiki
@external_wiki || = services . external_wikis . first
else
nil
end
end
def cache_has_external_wiki
2018-03-17 18:26:18 +05:30
update_column ( :has_external_wiki , services . external_wikis . any? ) if Gitlab :: Database . read_write?
2016-08-24 12:49:21 +05:30
end
2017-09-10 17:25:29 +05:30
def find_or_initialize_services ( exceptions : [ ] )
available_services_names = Service . available_services_names - exceptions
2018-11-08 19:23:39 +05:30
available_services = available_services_names . map do | service_name |
2018-12-05 23:21:45 +05:30
find_or_initialize_service ( service_name )
2014-09-02 18:07:02 +05:30
end
2018-11-08 19:23:39 +05:30
2018-12-05 23:21:45 +05:30
available_services . compact
2018-11-08 19:23:39 +05:30
end
def disabled_services
[ ]
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 )
service = find_service ( services , name )
return service if service
# We should check if template for the service exists
template = find_service ( services_templates , name )
if template
Service . build_from_template ( id , template )
else
# If no template, we should create an instance. Ex `build_gitlab_ci_service`
public_send ( " build_ #{ name } _service " ) # rubocop:disable GitlabSecurity/PublicSend
end
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 |
2016-11-03 12:29:30 +05:30
params = label . attributes . except ( 'id' , 'template' , 'created_at' , 'updated_at' )
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
2015-04-26 12:48:37 +05:30
def find_service ( list , name )
list . find { | service | service . to_param == name }
2014-09-02 18:07:02 +05:30
end
def ci_services
2016-06-02 11:05:42 +05:30
services . 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
services . where ( category : :monitoring )
end
def monitoring_service
@monitoring_service || = monitoring_services . reorder ( nil ) . find_by ( active : true )
end
2015-12-23 02:04:40 +05:30
def jira_tracker?
issues_tracker . to_param == 'jira'
2014-09-02 18:07:02 +05:30
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-13 22:33:31 +05:30
def all_clusters
group_clusters = Clusters :: Cluster . joins ( :groups ) . where ( cluster_groups : { group_id : ancestors_upto } )
Clusters :: Cluster . from_union ( [ clusters , group_clusters ] )
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
if group
group
else
namespace . try ( :owner )
end
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
services . public_send ( hooks_scope ) . each do | service | # rubocop:disable GitlabSecurity/PublicSend
service . async_execute ( data )
end
2014-09-02 18:07:02 +05:30
end
end
def valid_repo?
repository . exists?
rescue
2015-04-26 12:48:37 +05:30
errors . add ( :path , 'Invalid repository path' )
2014-09-02 18:07:02 +05:30
false
end
def url_to_repo
2017-09-10 17:25:29 +05:30
gitlab_shell . url_to_repo ( full_path )
2014-09-02 18:07:02 +05:30
end
def repo_exists?
2018-03-17 18:26:18 +05:30
strong_memoize ( :repo_exists ) do
begin
repository . exists?
rescue
false
end
end
2014-09-02 18:07:02 +05:30
end
def root_ref? ( branch )
repository . root_ref == branch
end
def ssh_url_to_repo
url_to_repo
end
2017-09-10 17:25:29 +05:30
def http_url_to_repo
" #{ web_url } .git "
2014-09-02 18:07:02 +05:30
end
2019-02-13 22:33:31 +05:30
# Is overriden in EE
def lfs_http_url_to_repo ( _ )
http_url_to_repo
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
return nil unless forked?
forked_from_project || fork_network & . root_project
end
def lfs_storage_project
@lfs_storage_project || = begin
result = self
# TODO: Make this go to the fork_network root immeadiatly
# dependant on the discussion in: https://gitlab.com/gitlab-org/gitlab-ce/issues/39769
result = result . fork_source while result & . forked?
result || self
end
end
2018-05-09 12:01:36 +05:30
# This will return all `lfs_objects` that are accessible to the project.
# So this might be `self.lfs_objects` if the project is not part of a fork
# network, or it is the base of the fork network.
#
# TODO: refactor this to get the correct lfs objects when implementing
# https://gitlab.com/gitlab-org/gitlab-ce/issues/39769
def all_lfs_objects
lfs_storage_project . lfs_objects
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 )
repo = Repository . new ( old_path , self )
wiki = Repository . new ( " #{ old_path } .wiki " , self )
if repo . exists?
2016-06-02 11:05:42 +05:30
repo . before_delete
2016-04-02 18:10:28 +05:30
end
if wiki . exists?
2016-06-02 11:05:42 +05:30
wiki . before_delete
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?
2017-09-10 17:25:29 +05:30
errors . add ( :base , 'There is already a repository with that name on disk' )
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-13 22:33:31 +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
2018-05-09 12:01:36 +05:30
if gitlab_shell . create_repository ( repository_storage , disk_path )
2018-03-17 18:26:18 +05:30
repository . after_create
true
else
errors . add ( :base , 'Failed to create repository via gitlab-shell' )
false
end
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
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 default_branch
@default_branch || = repository . root_ref if repository . exists?
end
def reload_default_branch
@default_branch = nil
default_branch
end
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-13 22:33:31 +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
reload_default_branch
else
errors . add ( :base , " Could not change HEAD: branch ' #{ branch } ' does not exist " )
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 )
# TODO: Remove this in a next release when all fork_networks are populated
# This makes sure all MergeRequests remain valid while the projects don't
# have a fork_network yet.
return true if forked_from? ( 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
def repository_exists?
! ! repository . exists?
end
2018-03-17 18:26:18 +05:30
def wiki_repository_exists?
wiki . repository_exists?
end
# update visibility_level of forks
2017-09-10 17:25:29 +05:30
def update_forks_visibility_level
return unless visibility_level < visibility_level_was
forks . each do | forked_project |
if forked_project . visibility_level > visibility_level
forked_project . visibility_level = visibility_level
forked_project . save!
end
end
end
2015-04-26 12:48:37 +05:30
def create_wiki
ProjectWiki . new ( self , self . owner ) . wiki
true
2015-10-24 18:46:33 +05:30
rescue ProjectWiki :: CouldNotCreateWikiError
2015-04-26 12:48:37 +05:30
errors . add ( :base , 'Failed create wiki' )
false
end
2015-10-24 18:46:33 +05:30
2017-09-10 17:25:29 +05:30
def wiki
@wiki || = ProjectWiki . new ( self , self . owner )
end
2015-12-23 02:04:40 +05:30
def jira_tracker_active?
jira_tracker? && jira_service . active
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
2016-09-29 09:46:39 +05:30
def pipeline_for ( ref , sha = nil )
sha || = commit ( ref ) . try ( :sha )
return unless sha
2019-02-13 22:33:31 +05:30
ci_pipelines . order ( id : :desc ) . find_by ( sha : sha , ref : ref )
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-02-13 22:33:31 +05:30
ci_pipelines . latest_successful_for ( default_branch )
2018-03-17 18:26:18 +05:30
end
def latest_successful_pipeline_for ( ref = nil )
if ref && ref != default_branch
2019-02-13 22:33:31 +05:30
ci_pipelines . latest_successful_for ( 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
2017-08-17 22:00:37 +05:30
def any_runners? ( & block )
2018-11-08 19:23:39 +05:30
active_runners . any? ( & block )
2017-08-17 22:00:37 +05:30
end
def valid_runners_token? ( token )
self . runners_token && ActiveSupport :: SecurityUtils . variable_size_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?
Dir . exist? ( public_pages_path )
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
2018-05-09 12:01:36 +05:30
return url if url == " #{ Settings . pages . protocol } :// #{ url_path } "
2017-08-17 22:00:37 +05:30
" #{ url } / #{ url_path } "
end
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
def public_pages_path
File . join ( pages_path , 'public' )
end
2018-03-17 18:26:18 +05:30
def pages_available?
Gitlab . config . pages . enabled && ! namespace . subgroup?
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
# TODO: what to do here when not using Legacy Storage? Do we still need to rename and delay removal?
2018-12-05 23:21:45 +05:30
# rubocop: disable CodeReuse/ServiceClass
2017-08-17 22:00:37 +05:30
def remove_pages
2017-09-10 17:25:29 +05:30
# Projects with a missing namespace cannot have their pages removed
return unless namespace
:: Projects :: UpdatePagesConfigurationService . new ( self ) . execute
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
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
Rails . logger . error ( " Error writing to .git/config for project #{ full_path } ( #{ id } ): #{ e . message } . " )
nil
end
def after_import
repository . after_import
2018-11-08 19:23:39 +05:30
wiki . repository . after_import
2019-02-13 22:33:31 +05:30
import_state . finish
import_state . remove_jid
2018-03-17 18:26:18 +05:30
update_project_counter_caches
after_create_default_branch
2019-02-13 22:33:31 +05:30
join_pool_repository
2018-05-09 12:01:36 +05:30
refresh_markdown_cache!
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
return unless default_branch
# Ensure HEAD points to the default branch in case it is not master
change_head ( default_branch )
if Gitlab :: CurrentSettings . default_branch_protection != Gitlab :: Access :: PROTECTION_NONE && ! ProtectedBranch . protected? ( self , default_branch )
params = {
name : default_branch ,
push_access_levels_attributes : [ {
2018-11-18 11:00:15 +05:30
access_level : Gitlab :: CurrentSettings . default_branch_protection == Gitlab :: Access :: PROTECTION_DEV_CAN_PUSH ? Gitlab :: Access :: DEVELOPER : Gitlab :: Access :: MAINTAINER
2018-03-17 18:26:18 +05:30
} ] ,
merge_access_levels_attributes : [ {
2018-11-18 11:00:15 +05:30
access_level : Gitlab :: CurrentSettings . default_branch_protection == Gitlab :: Access :: PROTECTION_DEV_CAN_MERGE ? Gitlab :: Access :: DEVELOPER : Gitlab :: Access :: MAINTAINER
2018-03-17 18:26:18 +05:30
} ]
}
ProtectedBranches :: CreateService . new ( self , creator , params ) . execute ( skip_authorization : true )
end
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
Rails . logger . info " Export job started for project ID #{ self . id } with job ID #{ job_id } "
else
Rails . logger . error " Export job failed to start for project ID #{ self . id } "
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
2018-03-17 18:26:18 +05:30
return nil unless namespace . present? || hashed_storage? ( :repository )
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
if export_in_progress?
:started
2018-05-09 12:01:36 +05:30
elsif after_export_in_progress?
:after_export_action
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?
import_export_shared . active_export_count > 0
end
2018-05-09 12:01:36 +05:30
def after_export_in_progress?
import_export_shared . after_export_in_progress?
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
2018-05-09 12:01:36 +05:30
visibility = Gitlab :: VisibilityLevel . string_level ( visibility_level )
Gitlab :: Ci :: Variables :: Collection . new
. append ( key : 'CI_PROJECT_ID' , value : id . to_s )
. append ( key : 'CI_PROJECT_NAME' , value : path )
. append ( key : 'CI_PROJECT_PATH' , value : full_path )
. append ( key : 'CI_PROJECT_PATH_SLUG' , value : full_path_slug )
. append ( key : 'CI_PROJECT_NAMESPACE' , value : namespace . full_path )
. append ( key : 'CI_PROJECT_URL' , value : web_url )
. append ( key : 'CI_PROJECT_VISIBILITY' , value : visibility )
. concat ( container_registry_variables )
. concat ( auto_devops_variables )
2019-02-13 22:33:31 +05:30
. concat ( api_variables )
end
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
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
production_first = " (CASE WHEN name = 'production' THEN 0 ELSE 1 END), id ASC "
environments
. with_state ( :available )
. reorder ( production_first )
. first
end
2018-12-13 13:39:08 +05:30
def ci_variables_for ( ref : , environment : nil )
2017-09-10 17:25:29 +05:30
# EE would use the environment
if protected_for? ( ref )
variables
else
variables . unprotected
2016-08-24 12:49:21 +05:30
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
2018-05-09 12:01:36 +05:30
def deployment_variables ( environment : nil )
2018-12-13 13:39:08 +05:30
deployment_platform ( environment : environment ) & . predefined_variables ( project : self ) || [ ]
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
def append_or_update_attribute ( name , value )
2018-03-17 18:26:18 +05:30
old_values = public_send ( name . to_s ) # rubocop:disable GitlabSecurity/PublicSend
2016-08-24 12:49:21 +05:30
if Project . reflect_on_association ( name ) . try ( :macro ) == :has_many && old_values . any?
update_attribute ( name , old_values + value )
else
update_attribute ( name , value )
end
2017-08-17 22:00:37 +05:30
rescue ActiveRecord :: RecordNotSaved = > e
handle_update_attribute_error ( e , value )
2016-08-24 12:49:21 +05:30
end
2019-02-13 22:33:31 +05:30
# Tries to set repository as read_only, checking for existing Git transfers in progress beforehand
#
# @return [Boolean] true when set to read_only or false when an existing git transfer is in progress
def set_repository_read_only!
with_lock do
break false if git_transfer_in_progress?
update_column ( :repository_read_only , true )
end
end
# Set repository as writable again
def set_repository_writable!
with_lock do
update_column ( repository_read_only , false )
end
end
2016-09-29 09:46:39 +05:30
def pushes_since_gc
2017-09-10 17:25:29 +05:30
Gitlab :: Redis :: SharedState . with { | redis | redis . get ( pushes_since_gc_redis_shared_state_key ) . to_i }
2016-09-29 09:46:39 +05:30
end
def increment_pushes_since_gc
2017-09-10 17:25:29 +05:30
Gitlab :: Redis :: SharedState . with { | redis | redis . incr ( pushes_since_gc_redis_shared_state_key ) }
2016-09-29 09:46:39 +05:30
end
def reset_pushes_since_gc
2017-09-10 17:25:29 +05:30
Gitlab :: Redis :: SharedState . with { | redis | redis . del ( pushes_since_gc_redis_shared_state_key ) }
2016-09-29 09:46:39 +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 )
next unless data
2016-09-29 09:46:39 +05:30
2017-08-17 22:00:37 +05:30
Gitlab :: RouteMap . new ( data )
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
if forked_from_project & . merge_requests_enabled?
forked_from_project
else
self
2016-09-29 09:46:39 +05:30
end
end
2018-03-27 19:54:05 +05:30
# Overridden on EE module
def multiple_issue_boards_available?
false
2018-03-17 18:26:18 +05:30
end
def full_path_was
File . join ( namespace . full_path , previous_changes [ 'path' ] . first )
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
Projects :: ForksCountService . new ( self ) . count
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 )
raise ArgumentError , " Invalid feature " unless HASHED_STORAGE_FEATURES . include? ( feature )
self . storage_version && self . storage_version > = HASHED_STORAGE_FEATURES [ feature ]
end
def renamed?
persisted? && path_changed?
end
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-13 22:33:31 +05:30
return unless storage_upgradable?
2018-03-17 18:26:18 +05:30
2019-02-13 22:33:31 +05:30
if git_transfer_in_progress?
2018-03-17 18:26:18 +05:30
ProjectMigrateHashedStorageWorker . perform_in ( Gitlab :: ReferenceCounter :: REFERENCE_EXPIRE_TIME , id )
else
ProjectMigrateHashedStorageWorker . perform_async ( id )
end
end
2019-02-13 22:33:31 +05:30
def git_transfer_in_progress?
repo_reference_count > 0 || wiki_reference_count > 0
end
2018-03-17 18:26:18 +05:30
def storage_version = ( value )
super
@storage = nil if storage_version_changed?
end
def gl_repository ( is_wiki : )
Gitlab :: GlRepository . gl_repository ( self , is_wiki )
end
def reference_counter ( wiki : false )
Gitlab :: ReferenceCounter . new ( gl_repository ( is_wiki : wiki ) )
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-13 22:33:31 +05:30
merge_requests_allowing_collaboration . where ( 'EXISTS (?)' , developer_access_exists )
2018-03-27 19:54:05 +05:30
end
2019-02-13 22:33:31 +05:30
def any_branch_allows_collaboration? ( user )
fetch_branch_allows_collaboration ( user )
end
2018-03-27 19:54:05 +05:30
2019-02-13 22:33:31 +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
2018-10-15 14:42:47 +05:30
def licensed_features
[ ]
end
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 )
Storage :: HashedProject . new ( self )
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 )
Ability . allowed? ( user , :read_project_snippet , self )
end
2019-02-13 22:33:31 +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.
# - Public -> User will be able to fetch Git objects that might not exist
# in their own repository.
# - Repository -> Else the disk path will be empty, and there's nothing to pool
def git_objects_poolable?
hashed_storage? ( :repository ) &&
public ? &&
repository_exists? &&
Gitlab :: CurrentSettings . hashed_storage_enabled &&
Feature . enabled? ( :object_pools , self )
end
def leave_pool_repository
pool_repository & . unlink_repository ( repository )
end
2018-12-13 13:39:08 +05:30
private
2019-02-13 22:33:31 +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
pool = begin
create_pool_repository! ( shard : Shard . by_name ( repository_storage ) , source_project : self )
rescue ActiveRecord :: RecordNotUnique
pool_repository ( true )
end
pool . schedule unless pool . scheduled?
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 repo_reference_count
reference_counter . value
end
def wiki_reference_count
reference_counter ( wiki : true ) . value
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?
2018-03-17 18:26:18 +05:30
errors . add ( :base , 'There is already a repository with that name on disk' )
throw :abort
end
end
def repository_with_same_path_already_exists?
2018-10-15 14:42:47 +05:30
gitlab_shell . 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
namespace != from . namespace
when Namespace
namespace != from
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
2017-09-10 17:25:29 +05:30
def pushes_since_gc_redis_shared_state_key
2016-09-29 09:46:39 +05:30
" projects/ #{ id } /pushes_since_gc "
end
2017-08-17 22:00:37 +05:30
# Similar to the normal callbacks that hook into the life cycle of an
# Active Record object, you can also define callbacks that get triggered
# when you add an object to an association collection. If any of these
# callbacks throw an exception, the object will not be added to the
# collection. Before you add a new board to the boards collection if you
# already have 1, 2, or n it will fail, but it if you have 0 that is lower
# than the number of permitted boards per project it won't fail.
def validate_board_limit ( board )
raise BoardLimitExceeded , 'Number of permitted boards exceeded' if boards . size > = NUMBER_OF_PERMITTED_BOARDS
2016-09-13 17:45:13 +05:30
end
2017-08-17 22:00:37 +05:30
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
2017-08-17 22:00:37 +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
2017-08-17 22:00:37 +05:30
def handle_update_attribute_error ( ex , value )
if ex . message . start_with? ( 'Failed to replace' )
if value . respond_to? ( :each )
invalid = value . detect ( & :invalid? )
raise ex , ( [ ex . message ] + invalid . errors . full_messages ) . join ( ' ' ) if invalid
end
end
raise ex
2016-11-03 12:29:30 +05:30
end
2018-03-27 19:54:05 +05:30
2019-02-13 22:33:31 +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-13 22:33:31 +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
2018-11-18 11:00:15 +05:30
# Issue for N+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/49322
Gitlab :: GitalyClient . allow_n_plus_1_calls do
2019-02-13 22:33:31 +05:30
merge_requests_allowing_collaboration ( branch_name ) . any? do | merge_request |
merge_request . can_be_merged_by? ( user )
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
def services_templates
@services_templates || = Service . where ( template : true )
end
2014-09-02 18:07:02 +05:30
end