diff options
Diffstat (limited to 'app/models/project.rb')
-rw-r--r-- | app/models/project.rb | 138 |
1 files changed, 95 insertions, 43 deletions
diff --git a/app/models/project.rb b/app/models/project.rb index 4c394646787..3a5a01db518 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -186,6 +186,11 @@ class Project < ActiveRecord::Base # Validations validates :creator, presence: true, on: :create validates :description, length: { maximum: 2000 }, allow_blank: true + validates :ci_config_path, + format: { without: /\.{2}/, + message: 'cannot include directory traversal.' }, + length: { maximum: 255 }, + allow_blank: true validates :name, presence: true, length: { maximum: 255 }, @@ -222,9 +227,8 @@ class Project < ActiveRecord::Base has_many :uploads, as: :model, dependent: :destroy # Scopes - default_scope { where(pending_delete: false) } - - scope :with_deleted, -> { unscope(where: :pending_delete) } + scope :pending_delete, -> { where(pending_delete: true) } + scope :without_deleted, -> { where(pending_delete: false) } scope :sorted_by_activity, -> { reorder(last_activity_at: :desc) } scope :sorted_by_stars, -> { reorder('projects.star_count DESC') } @@ -244,8 +248,8 @@ class Project < ActiveRecord::Base 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. - joins("INNER JOIN routes rs ON rs.source_id = projects.id AND rs.source_type = 'Project'"). - where('rs.path LIKE ?', "#{sanitize_sql_like(path)}/%") + joins("INNER JOIN routes rs ON rs.source_id = projects.id AND rs.source_type = 'Project'") + .where('rs.path LIKE ?', "#{sanitize_sql_like(path)}/%") end # "enabled" here means "not disabled". It includes private features! @@ -266,20 +270,49 @@ class Project < ActiveRecord::Base enum auto_cancel_pending_pipelines: { disabled: 0, enabled: 1 } + # 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 + authorized = user + .project_authorizations + .select(1) + .where('project_authorizations.project_id = projects.id') + + levels = Gitlab::VisibilityLevel.levels_for_user(user) + + where('EXISTS (?) OR projects.visibility_level IN (?)', authorized, levels) + else + public_to_user + end + end + # project features may be "disabled", "internal" or "enabled". If "internal", # they are only available to team members. This scope returns projects where # the feature is either enabled, or internal with permission for the user. + # + # 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. def self.with_feature_available_for_user(feature, user) - return with_feature_enabled(feature) if user.try(:admin?) + visible = [nil, ProjectFeature::ENABLED] - unconditional = with_feature_access_level(feature, [nil, ProjectFeature::ENABLED]) - return unconditional if user.nil? + if user&.admin? + with_feature_enabled(feature) + elsif user + column = ProjectFeature.quoted_access_level_column(feature) - conditional = with_feature_access_level(feature, ProjectFeature::PRIVATE) - authorized = user.authorized_projects.merge(conditional.reorder(nil)) + authorized = user.project_authorizations.select(1) + .where('project_authorizations.project_id = projects.id') - union = Gitlab::SQL::Union.new([unconditional.select(:id), authorized.select(:id)]) - where(arel_table[:id].in(Arel::Nodes::SqlLiteral.new(union.to_sql))) + with_project_feature + .where("#{column} IN (?) OR (#{column} = ? AND EXISTS (?))", + visible, + ProjectFeature::PRIVATE, + authorized) + else + with_feature_access_level(feature, visible) + end end scope :active, -> { joins(:issues, :notes, :merge_requests).order('issues.created_at, notes.created_at, merge_requests.created_at DESC') } @@ -321,7 +354,19 @@ class Project < ActiveRecord::Base project.run_after_commit { add_import_job } end - after_transition started: :finished, do: :reset_cache_and_import_attrs + after_transition started: :finished do |project, _| + project.reset_cache_and_import_attrs + + if Gitlab::ImportSources.importer_names.include?(project.import_type) && project.repo_exists? + project.run_after_commit do + begin + Projects::HousekeepingService.new(project).execute + rescue Projects::HousekeepingService::LeaseTaken => e + Rails.logger.info("Could not perform housekeeping for project #{project.path_with_namespace} (#{project.id}): #{e}") + end + end + end + end end class << self @@ -340,14 +385,14 @@ class Project < ActiveRecord::Base # unscoping unnecessary conditions that'll be applied # when executing `where("projects.id IN (#{union.to_sql})")` projects = unscoped.select(:id).where( - ptable[:path].matches(pattern). - or(ptable[:name].matches(pattern)). - or(ptable[:description].matches(pattern)) + ptable[:path].matches(pattern) + .or(ptable[:name].matches(pattern)) + .or(ptable[:description].matches(pattern)) ) - namespaces = unscoped.select(:id). - joins(:namespace). - where(ntable[:name].matches(pattern)) + namespaces = unscoped.select(:id) + .joins(:namespace) + .where(ntable[:name].matches(pattern)) union = Gitlab::SQL::Union.new([projects, namespaces]) @@ -388,8 +433,8 @@ class Project < ActiveRecord::Base end def trending - joins('INNER JOIN trending_projects ON projects.id = trending_projects.project_id'). - reorder('trending_projects.id ASC') + joins('INNER JOIN trending_projects ON projects.id = trending_projects.project_id') + .reorder('trending_projects.id ASC') end def cached_count @@ -478,11 +523,12 @@ class Project < ActiveRecord::Base ProjectCacheWorker.perform_async(self.id) end - remove_import_data + import_data&.destroy end - def remove_import_data - import_data&.destroy + def ci_config_path=(value) + # Strip all leading slashes so that //foo -> foo + super(value&.sub(%r{\A/+}, '')&.delete("\0")) end def import_url=(value) @@ -639,7 +685,7 @@ class Project < ActiveRecord::Base end def web_url - Gitlab::Routing.url_helpers.namespace_project_url(self.namespace, self) + Gitlab::Routing.url_helpers.project_url(self) end def new_issue_address(author) @@ -660,7 +706,7 @@ class Project < ActiveRecord::Base end def last_activity_date - last_activity_at || updated_at + last_repository_updated_at || last_activity_at || updated_at end def project_id @@ -691,8 +737,8 @@ class Project < ActiveRecord::Base end end - def issue_reference_pattern - issues_tracker.reference_pattern + def external_issue_reference_pattern + external_issue_tracker.class.reference_pattern end def default_issues_tracker? @@ -779,7 +825,7 @@ class Project < ActiveRecord::Base end def ci_service - @ci_service ||= ci_services.reorder(nil).find_by(active: true) + @ci_service ||= ci_services.find_by(active: true) end def deployment_services @@ -787,7 +833,7 @@ class Project < ActiveRecord::Base end def deployment_service - @deployment_service ||= deployment_services.reorder(nil).find_by(active: true) + @deployment_service ||= deployment_services.find_by(active: true) end def monitoring_services @@ -795,7 +841,7 @@ class Project < ActiveRecord::Base end def monitoring_service - @monitoring_service ||= monitoring_services.reorder(nil).find_by(active: true) + @monitoring_service ||= monitoring_services.find_by(active: true) end def jira_tracker? @@ -815,7 +861,7 @@ class Project < ActiveRecord::Base def avatar_url(**args) # We use avatar_path instead of overriding avatar_url because of carrierwave. # See https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/11001/diffs#note_28659864 - avatar_path(args) || (Gitlab::Routing.url_helpers.namespace_project_avatar_url(namespace, self) if avatar_in_git) + avatar_path(args) || (Gitlab::Routing.url_helpers.project_avatar_url(self) if avatar_in_git) end # For compatibility with old code @@ -927,6 +973,7 @@ class Project < ActiveRecord::Base begin gitlab_shell.mv_repository(repository_storage_path, "#{old_path_with_namespace}.wiki", "#{new_path_with_namespace}.wiki") send_move_instructions(old_path_with_namespace) + expires_full_path_cache @old_path_with_namespace = old_path_with_namespace @@ -978,7 +1025,8 @@ class Project < ActiveRecord::Base namespace: namespace.name, visibility_level: visibility_level, path_with_namespace: path_with_namespace, - default_branch: default_branch + default_branch: default_branch, + ci_config_path: ci_config_path } # Backward compatibility @@ -1037,19 +1085,23 @@ class Project < ActiveRecord::Base merge_requests.where(source_project_id: self.id) end - def create_repository + def create_repository(force: false) # Forked import is handled asynchronously - unless forked? - if gitlab_shell.add_repository(repository_storage_path, path_with_namespace) - repository.after_create - true - else - errors.add(:base, 'Failed to create repository via gitlab-shell') - false - end + return if forked? && !force + + if gitlab_shell.add_repository(repository_storage_path, path_with_namespace) + repository.after_create + true + else + errors.add(:base, 'Failed to create repository via gitlab-shell') + false end end + def ensure_repository + create_repository(force: true) unless repository_exists? + end + def repository_exists? !!repository.exists? end @@ -1412,7 +1464,7 @@ class Project < ActiveRecord::Base def pending_delete_twin return false unless path - Project.unscoped.where(pending_delete: true).find_by_full_path(path_with_namespace) + Project.pending_delete.find_by_full_path(path_with_namespace) end ## |