2020-03-02 21:08:01 +00:00
# frozen_string_literal: true
module Projects
class UpdateRepositoryStorageService < BaseService
include Gitlab :: ShellAdapter
2020-03-06 15:08:05 +00:00
Error = Class . new ( StandardError )
2020-04-14 00:09:57 +00:00
SameFilesystemError = Class . new ( Error )
2020-03-02 21:08:01 +00:00
def initialize ( project )
@project = project
end
def execute ( new_repository_storage_key )
2020-04-14 00:09:57 +00:00
raise SameFilesystemError if same_filesystem? ( project . repository . storage , new_repository_storage_key )
2020-03-06 15:08:05 +00:00
mirror_repositories ( new_repository_storage_key )
2020-03-02 21:08:01 +00:00
2020-03-06 15:08:05 +00:00
mark_old_paths_for_archive
2020-03-02 21:08:01 +00:00
2020-04-22 15:09:27 +00:00
project . update! ( repository_storage : new_repository_storage_key , repository_read_only : false )
2020-03-06 15:08:05 +00:00
project . leave_pool_repository
project . track_project_repository
enqueue_housekeeping
success
2020-04-22 15:09:27 +00:00
rescue StandardError = > e
project . update! ( repository_read_only : false )
2020-03-06 15:08:05 +00:00
Gitlab :: ErrorTracking . track_exception ( e , project_path : project . full_path )
error ( s_ ( " UpdateRepositoryStorage|Error moving repository storage for %{project_full_path} - %{message} " ) % { project_full_path : project . full_path , message : e . message } )
2020-03-02 21:08:01 +00:00
end
private
2020-04-14 00:09:57 +00:00
def same_filesystem? ( old_storage , new_storage )
Gitlab :: GitalyClient . filesystem_id ( old_storage ) == Gitlab :: GitalyClient . filesystem_id ( new_storage )
end
2020-03-02 21:08:01 +00:00
def mirror_repositories ( new_repository_storage_key )
2020-03-06 15:08:05 +00:00
mirror_repository ( new_repository_storage_key )
2020-03-02 21:08:01 +00:00
if project . wiki . repository_exists?
2020-03-06 15:08:05 +00:00
mirror_repository ( new_repository_storage_key , type : Gitlab :: GlRepository :: WIKI )
2020-03-02 21:08:01 +00:00
end
end
def mirror_repository ( new_storage_key , type : Gitlab :: GlRepository :: PROJECT )
2020-03-06 15:08:05 +00:00
unless wait_for_pushes ( type )
raise Error , s_ ( 'UpdateRepositoryStorage|Timeout waiting for %{type} repository pushes' ) % { type : type . name }
end
2020-03-02 21:08:01 +00:00
repository = type . repository_for ( project )
full_path = repository . full_path
raw_repository = repository . raw
2020-03-06 15:08:05 +00:00
checksum = repository . checksum
2020-03-02 21:08:01 +00:00
# Initialize a git repository on the target path
2020-03-16 12:09:12 +00:00
new_repository = Gitlab :: Git :: Repository . new (
new_storage_key ,
raw_repository . relative_path ,
raw_repository . gl_repository ,
full_path
)
new_repository . create_repository
2020-03-02 21:08:01 +00:00
2020-03-13 00:09:34 +00:00
new_repository . replicate ( raw_repository )
2020-03-06 15:08:05 +00:00
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
2020-03-02 21:08:01 +00:00
end
def mark_old_paths_for_archive
old_repository_storage = project . repository_storage
new_project_path = moved_path ( project . disk_path )
# Notice that the block passed to `run_after_commit` will run with `project`
# as its context
project . run_after_commit do
GitlabShellWorker . perform_async ( :mv_repository ,
old_repository_storage ,
disk_path ,
new_project_path )
if wiki . repository_exists?
GitlabShellWorker . perform_async ( :mv_repository ,
old_repository_storage ,
wiki . disk_path ,
" #{ new_project_path } .wiki " )
end
end
end
def moved_path ( path )
" #{ path } + #{ project . id } +moved+ #{ Time . now . to_i } "
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
Projects :: UpdateRepositoryStorageService . prepend_if_ee ( 'EE::Projects::UpdateRepositoryStorageService' )