148 lines
4.2 KiB
Ruby
148 lines
4.2 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
module Projects
|
|
class UpdateRepositoryStorageService
|
|
Error = Class.new(StandardError)
|
|
SameFilesystemError = Class.new(Error)
|
|
|
|
attr_reader :repository_storage_move
|
|
delegate :project, :source_storage_name, :destination_storage_name, to: :repository_storage_move
|
|
|
|
def initialize(repository_storage_move)
|
|
@repository_storage_move = repository_storage_move
|
|
end
|
|
|
|
def execute
|
|
repository_storage_move.with_lock do
|
|
return ServiceResponse.success unless repository_storage_move.scheduled? # rubocop:disable Cop/AvoidReturnFromBlocks
|
|
|
|
repository_storage_move.start!
|
|
end
|
|
|
|
raise SameFilesystemError if same_filesystem?(source_storage_name, destination_storage_name)
|
|
|
|
mirror_repositories
|
|
|
|
repository_storage_move.transaction do
|
|
repository_storage_move.finish_replication!
|
|
|
|
project.leave_pool_repository
|
|
project.track_project_repository
|
|
end
|
|
|
|
remove_old_paths
|
|
enqueue_housekeeping
|
|
|
|
repository_storage_move.finish_cleanup!
|
|
|
|
ServiceResponse.success
|
|
|
|
rescue StandardError => e
|
|
repository_storage_move.do_fail!
|
|
|
|
Gitlab::ErrorTracking.track_exception(e, project_path: project.full_path)
|
|
|
|
ServiceResponse.error(
|
|
message: s_("UpdateRepositoryStorage|Error moving repository storage for %{project_full_path} - %{message}") % { project_full_path: project.full_path, message: e.message }
|
|
)
|
|
end
|
|
|
|
private
|
|
|
|
def same_filesystem?(old_storage, new_storage)
|
|
Gitlab::GitalyClient.filesystem_id(old_storage) == Gitlab::GitalyClient.filesystem_id(new_storage)
|
|
end
|
|
|
|
def mirror_repositories
|
|
mirror_repository
|
|
|
|
if project.wiki.repository_exists?
|
|
mirror_repository(type: Gitlab::GlRepository::WIKI)
|
|
end
|
|
|
|
if project.design_repository.exists?
|
|
mirror_repository(type: ::Gitlab::GlRepository::DESIGN)
|
|
end
|
|
end
|
|
|
|
def mirror_repository(type: Gitlab::GlRepository::PROJECT)
|
|
unless wait_for_pushes(type)
|
|
raise Error, s_('UpdateRepositoryStorage|Timeout waiting for %{type} repository pushes') % { type: type.name }
|
|
end
|
|
|
|
repository = type.repository_for(project)
|
|
full_path = repository.full_path
|
|
raw_repository = repository.raw
|
|
checksum = repository.checksum
|
|
|
|
# Initialize a git repository on the target path
|
|
new_repository = Gitlab::Git::Repository.new(
|
|
destination_storage_name,
|
|
raw_repository.relative_path,
|
|
raw_repository.gl_repository,
|
|
full_path
|
|
)
|
|
|
|
new_repository.replicate(raw_repository)
|
|
new_checksum = new_repository.checksum
|
|
|
|
if checksum != new_checksum
|
|
raise Error, s_('UpdateRepositoryStorage|Failed to verify %{type} repository checksum from %{old} to %{new}') % { type: type.name, old: checksum, new: new_checksum }
|
|
end
|
|
end
|
|
|
|
def remove_old_paths
|
|
Gitlab::Git::Repository.new(
|
|
source_storage_name,
|
|
"#{project.disk_path}.git",
|
|
nil,
|
|
nil
|
|
).remove
|
|
|
|
if project.wiki.repository_exists?
|
|
Gitlab::Git::Repository.new(
|
|
source_storage_name,
|
|
"#{project.wiki.disk_path}.git",
|
|
nil,
|
|
nil
|
|
).remove
|
|
end
|
|
|
|
if project.design_repository.exists?
|
|
Gitlab::Git::Repository.new(
|
|
source_storage_name,
|
|
"#{project.design_repository.disk_path}.git",
|
|
nil,
|
|
nil
|
|
).remove
|
|
end
|
|
end
|
|
|
|
# The underlying FetchInternalRemote call uses a `git fetch` to move data
|
|
# to the new repository, which leaves it in a less-well-packed state,
|
|
# lacking bitmaps and commit graphs. Housekeeping will boost performance
|
|
# significantly.
|
|
def enqueue_housekeeping
|
|
return unless Gitlab::CurrentSettings.housekeeping_enabled?
|
|
return unless Feature.enabled?(:repack_after_shard_migration, project)
|
|
|
|
Projects::HousekeepingService.new(project, :gc).execute
|
|
rescue Projects::HousekeepingService::LeaseTaken
|
|
# No action required
|
|
end
|
|
|
|
def wait_for_pushes(type)
|
|
reference_counter = project.reference_counter(type: type)
|
|
|
|
# Try for 30 seconds, polling every 10
|
|
3.times do
|
|
return true if reference_counter.value == 0
|
|
|
|
sleep 10
|
|
end
|
|
|
|
false
|
|
end
|
|
end
|
|
end
|