1# frozen_string_literal: true 2 3module Projects 4 class OverwriteProjectService < BaseService 5 def execute(source_project) 6 return unless source_project && source_project.namespace_id == @project.namespace_id 7 8 start_time = ::Gitlab::Metrics::System.monotonic_time 9 10 Project.transaction do 11 move_before_destroy_relationships(source_project) 12 # Reset is required in order to get the proper 13 # uncached fork network method calls value. 14 destroy_old_project(source_project.reset) 15 rename_project(source_project.name, source_project.path) 16 17 @project 18 end 19 # Projects::DestroyService can raise Exceptions, but we don't want 20 # to pass that kind of exception to the caller. Instead, we change it 21 # for a StandardError exception 22 rescue Exception => e # rubocop:disable Lint/RescueException 23 attempt_restore_repositories(source_project) 24 25 if e.instance_of?(Exception) 26 raise StandardError, e.message 27 else 28 raise 29 end 30 31 ensure 32 track_service(start_time, source_project, e) 33 end 34 35 private 36 37 def track_service(start_time, source_project, exception) 38 return if ::Feature.disabled?(:project_overwrite_service_tracking, source_project, default_enabled: :yaml) 39 40 duration = ::Gitlab::Metrics::System.monotonic_time - start_time 41 42 Gitlab::AppJsonLogger.info(class: self.class.name, 43 namespace_id: source_project.namespace_id, 44 project_id: source_project.id, 45 duration_s: duration.to_f, 46 error: exception.class.name) 47 end 48 49 def move_before_destroy_relationships(source_project) 50 options = { remove_remaining_elements: false } 51 52 ::Projects::MoveUsersStarProjectsService.new(@project, @current_user).execute(source_project, **options) 53 ::Projects::MoveAccessService.new(@project, @current_user).execute(source_project, **options) 54 ::Projects::MoveDeployKeysProjectsService.new(@project, @current_user).execute(source_project, **options) 55 ::Projects::MoveNotificationSettingsService.new(@project, @current_user).execute(source_project, **options) 56 ::Projects::MoveForksService.new(@project, @current_user).execute(source_project, **options) 57 ::Projects::MoveLfsObjectsProjectsService.new(@project, @current_user).execute(source_project, **options) 58 add_source_project_to_fork_network(source_project) 59 end 60 61 def destroy_old_project(source_project) 62 # Delete previous project (synchronously) and unlink relations 63 ::Projects::DestroyService.new(source_project, @current_user).execute 64 end 65 66 def rename_project(name, path) 67 # Update de project's name and path to the original name/path 68 ::Projects::UpdateService.new(@project, 69 @current_user, 70 { name: name, path: path }) 71 .execute 72 end 73 74 def attempt_restore_repositories(project) 75 ::Projects::DestroyRollbackService.new(project, @current_user).execute 76 end 77 78 def add_source_project_to_fork_network(source_project) 79 return unless @project.fork_network 80 81 # Because they have moved all references in the fork network from the source_project 82 # we won't be able to query the database (only through its cached data), 83 # for its former relationships. That's why we're adding it to the network 84 # as a fork of the target project 85 ForkNetworkMember.create!(fork_network: @project.fork_network, 86 project: source_project, 87 forked_from_project: @project) 88 end 89 end 90end 91