diff options
Diffstat (limited to 'app/models/project.rb')
-rw-r--r-- | app/models/project.rb | 168 |
1 files changed, 103 insertions, 65 deletions
diff --git a/app/models/project.rb b/app/models/project.rb index cf740de9405..7735f23cb9e 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'carrierwave/orm/activerecord' class Project < ActiveRecord::Base @@ -31,6 +33,7 @@ class Project < ActiveRecord::Base BoardLimitExceeded = Class.new(StandardError) + STATISTICS_ATTRIBUTE = 'repositories_count'.freeze NUMBER_OF_PERMITTED_BOARDS = 1 UNKNOWN_IMPORT_URL = 'http://unknown.git'.freeze # Hashed Storage versions handle rolling out new storage to project and dependents models: @@ -79,6 +82,10 @@ class Project < ActiveRecord::Base after_create :create_project_feature, unless: :project_feature + after_create -> { SiteStatistic.track(STATISTICS_ATTRIBUTE) } + before_destroy ->(project) { project.project_feature.untrack_statistics_for_deletion! } + after_destroy -> { SiteStatistic.untrack(STATISTICS_ATTRIBUTE) } + after_create :create_ci_cd_settings, unless: :ci_cd_settings, if: proc { ProjectCiCdSetting.available? } @@ -154,6 +161,7 @@ class Project < ActiveRecord::Base has_one :mock_monitoring_service has_one :microsoft_teams_service has_one :packagist_service + has_one :hangouts_chat_service # TODO: replace these relations with the fork network versions has_one :forked_project_link, foreign_key: "forked_to_project_id" @@ -171,6 +179,7 @@ class Project < ActiveRecord::Base has_one :fork_network, through: :fork_network_member has_one :import_state, autosave: true, class_name: 'ProjectImportState', inverse_of: :project + has_one :import_export_upload, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent # Merge Requests for target project should be removed with it has_many :merge_requests, foreign_key: 'target_project_id' @@ -185,6 +194,7 @@ class Project < ActiveRecord::Base has_many :hooks, class_name: 'ProjectHook' has_many :protected_branches has_many :protected_tags + has_many :repository_languages, -> { order "share DESC" } has_many :project_authorizations has_many :authorized_users, through: :project_authorizations, source: :user, class_name: 'User' @@ -268,7 +278,8 @@ class Project < ActiveRecord::Base delegate :name, to: :owner, allow_nil: true, prefix: true delegate :members, to: :team, prefix: true delegate :add_user, :add_users, to: :team - delegate :add_guest, :add_reporter, :add_developer, :add_master, :add_role, to: :team + delegate :add_guest, :add_reporter, :add_developer, :add_maintainer, :add_role, to: :team + delegate :add_master, to: :team # @deprecated delegate :group_runners_enabled, :group_runners_enabled=, :group_runners_enabled?, to: :ci_cd_settings # Validations @@ -324,6 +335,7 @@ class Project < ActiveRecord::Base scope :joined, ->(user) { where('namespace_id != ?', user.namespace_id) } scope :starred_by, ->(user) { joins(:users_star_projects).where('users_star_projects.user_id': user.id) } scope :visible_to_user, ->(user) { where(id: user.authorized_projects.select(:id).reorder(nil)) } + 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)) } scope :archived, -> { where(archived: true) } scope :non_archived, -> { where(archived: false) } scope :for_milestones, ->(ids) { joins(:milestones).where('milestones.id' => ids).distinct } @@ -366,8 +378,10 @@ class Project < ActiveRecord::Base chronic_duration_attr :build_timeout_human_readable, :build_timeout, default: 3600 validates :build_timeout, allow_nil: true, - numericality: { greater_than_or_equal_to: 600, - message: 'needs to be at least 10 minutes' } + 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' } # Returns a collection of projects that is either public or visible to the # logged in user. @@ -511,6 +525,10 @@ class Project < ActiveRecord::Base end end + def has_auto_devops_implicitly_enabled? + auto_devops&.enabled.nil? && Gitlab::CurrentSettings.auto_devops_enabled? + end + def has_auto_devops_implicitly_disabled? auto_devops&.enabled.nil? && !Gitlab::CurrentSettings.auto_devops_enabled? end @@ -555,6 +573,10 @@ class Project < ActiveRecord::Base repository.commit_by(oid: oid) end + def commits_by(oids:) + repository.commits_by(oids: oids) + end + # ref can't be HEAD, can only be branch/tag name or SHA def latest_successful_builds_for(ref = default_branch) latest_pipeline = pipelines.latest_successful_for(ref) @@ -654,6 +676,8 @@ class Project < ActiveRecord::Base project_import_data.credentials ||= {} project_import_data.credentials = project_import_data.credentials.merge(credentials) end + + project_import_data end def import? @@ -1249,8 +1273,6 @@ class Project < ActiveRecord::Base return true if skip_disk_validation return false unless repository_storage - expires_full_path_cache # we need to clear cache to validate renames correctly - # 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? @@ -1326,14 +1348,6 @@ class Project < ActiveRecord::Base :visibility_level end - def archive! - update_attribute(:archived, true) - end - - def unarchive! - update_attribute(:archived, false) - end - def change_head(branch) if repository.branch_exists?(branch) repository.before_change_head @@ -1444,7 +1458,7 @@ class Project < ActiveRecord::Base end def shared_runners - @shared_runners ||= shared_runners_available? ? Ci::Runner.shared : Ci::Runner.none + @shared_runners ||= shared_runners_available? ? Ci::Runner.instance_type : Ci::Runner.none end def group_runners @@ -1574,47 +1588,33 @@ class Project < ActiveRecord::Base end def rename_repo - new_full_path = build_full_path + path_before = previous_changes['path'].first + full_path_before = full_path_was + full_path_after = build_full_path - Rails.logger.error "Attempting to rename #{full_path_was} -> #{new_full_path}" + Gitlab::AppLogger.info("Attempting to rename #{full_path_was} -> #{full_path_after}") if has_container_registry_tags? - Rails.logger.error "Project #{full_path_was} cannot be renamed because container registry tags are present!" + Gitlab::AppLogger.info("Project #{full_path_was} cannot be renamed because container registry tags are present!") - # we currently doesn't support renaming repository if it contains images in container registry + # we currently don't support renaming repository if it contains images in container registry raise StandardError.new('Project cannot be renamed, because images are present in its container registry') end - expire_caches_before_rename(full_path_was) + expire_caches_before_rename(full_path_before) - if storage.rename_repo - Gitlab::AppLogger.info "Project was renamed: #{full_path_was} -> #{new_full_path}" - rename_repo_notify! - after_rename_repo + if rename_or_migrate_repository! + Gitlab::AppLogger.info("Project was renamed: #{full_path_before} -> #{full_path_after}") + after_rename_repository(full_path_before, path_before) else - Rails.logger.error "Repository could not be renamed: #{full_path_was} -> #{new_full_path}" + Gitlab::AppLogger.info("Repository could not be renamed: #{full_path_before} -> #{full_path_after}") # if we cannot move namespace directory we should rollback # db changes in order to prevent out of sync between db and fs - raise StandardError.new('repository cannot be renamed') + raise StandardError.new('Repository cannot be renamed') end end - def after_rename_repo - write_repository_config - - path_before_change = previous_changes['path'].first - - # We need to check if project had been rolled out to move resource to hashed storage or not and decide - # if we need execute any take action or no-op. - - unless hashed_storage?(:attachments) - Gitlab::UploadsTransfer.new.rename_project(path_before_change, self.path, namespace.full_path) - end - - Gitlab::PagesTransfer.new.rename_project(path_before_change, self.path, namespace.full_path) - end - 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 @@ -1625,18 +1625,6 @@ class Project < ActiveRecord::Base nil end - def rename_repo_notify! - # When we import a project overwriting the original project, there - # is a move operation. In that case we don't want to send the instructions. - send_move_instructions(full_path_was) unless import_started? - expires_full_path_cache - - self.old_path_with_namespace = full_path_was - SystemHooksService.new.execute_hooks_for(self, :rename) - - reload_repository! - end - def after_import repository.after_import wiki.repository.after_import @@ -1668,10 +1656,10 @@ class Project < ActiveRecord::Base params = { name: default_branch, push_access_levels_attributes: [{ - access_level: Gitlab::CurrentSettings.default_branch_protection == Gitlab::Access::PROTECTION_DEV_CAN_PUSH ? Gitlab::Access::DEVELOPER : Gitlab::Access::MASTER + access_level: Gitlab::CurrentSettings.default_branch_protection == Gitlab::Access::PROTECTION_DEV_CAN_PUSH ? Gitlab::Access::DEVELOPER : Gitlab::Access::MAINTAINER }], merge_access_levels_attributes: [{ - access_level: Gitlab::CurrentSettings.default_branch_protection == Gitlab::Access::PROTECTION_DEV_CAN_MERGE ? Gitlab::Access::DEVELOPER : Gitlab::Access::MASTER + access_level: Gitlab::CurrentSettings.default_branch_protection == Gitlab::Access::PROTECTION_DEV_CAN_MERGE ? Gitlab::Access::DEVELOPER : Gitlab::Access::MAINTAINER }] } @@ -1734,7 +1722,7 @@ class Project < ActiveRecord::Base :started elsif after_export_in_progress? :after_export_action - elsif export_project_path + elsif export_project_path || export_project_object_exists? :finished else :none @@ -1749,16 +1737,21 @@ class Project < ActiveRecord::Base import_export_shared.after_export_in_progress? end - def remove_exports - return nil unless export_path.present? - - FileUtils.rm_rf(export_path) + def remove_exports(path = export_path) + if path.present? + FileUtils.rm_rf(path) + elsif export_project_object_exists? + import_export_upload.remove_export_file! + import_export_upload.save + end end def remove_exported_project_file - return unless export_project_path.present? + remove_exports(export_project_path) + end - FileUtils.rm_f(export_project_path) + def export_project_object_exists? + Gitlab::ImportExport.object_storage? && import_export_upload&.export_file&.file end def full_path_slug @@ -1796,6 +1789,15 @@ class Project < ActiveRecord::Base end end + 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 + def secret_variables_for(ref:, environment: nil) # EE would use the environment if protected_for?(ref) @@ -2047,6 +2049,39 @@ class Project < ActiveRecord::Base private + def rename_or_migrate_repository! + if Gitlab::CurrentSettings.hashed_storage_enabled? && storage_version != LATEST_STORAGE_VERSION + ::Projects::HashedStorageMigrationService.new(self, full_path_was).execute + else + storage.rename_repo + end + end + + def after_rename_repository(full_path_before, path_before) + execute_rename_repository_hooks!(full_path_before) + + write_repository_config + + # We need to check if project had been rolled out to move resource to hashed storage or not and decide + # if we need execute any take action or no-op. + unless hashed_storage?(:attachments) + Gitlab::UploadsTransfer.new.rename_project(path_before, self.path, namespace.full_path) + end + + Gitlab::PagesTransfer.new.rename_project(path_before, self.path, namespace.full_path) + end + + def execute_rename_repository_hooks!(full_path_before) + # When we import a project overwriting the original project, there + # is a move operation. In that case we don't want to send the instructions. + send_move_instructions(full_path_before) unless import_started? + + self.old_path_with_namespace = full_path_before + SystemHooksService.new.execute_hooks_for(self, :rename) + + reload_repository! + end + def storage @storage ||= if hashed_storage?(:repository) @@ -2175,10 +2210,13 @@ class Project < ActiveRecord::Base merge_requests = source_of_merge_requests.opened .where(allow_collaboration: true) - if branch_name - merge_requests.find_by(source_branch: branch_name)&.can_be_merged_by?(user) - else - merge_requests.any? { |merge_request| merge_request.can_be_merged_by?(user) } + # Issue for N+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/49322 + Gitlab::GitalyClient.allow_n_plus_1_calls do + if branch_name + merge_requests.find_by(source_branch: branch_name)&.can_be_merged_by?(user) + else + merge_requests.any? { |merge_request| merge_request.can_be_merged_by?(user) } + end end end |