summaryrefslogtreecommitdiff
path: root/app/services/projects/update_repository_storage_service.rb
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-03-02 21:08:01 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-03-02 21:08:01 +0000
commit561e1b470f0a99fe6304c8f197348c47a637d594 (patch)
tree6b361b6b0b412b70450aca167079c50a13bd88d8 /app/services/projects/update_repository_storage_service.rb
parent7b52c7cb634ef7047d30b0337fe477bcdcedf41d (diff)
downloadgitlab-ce-561e1b470f0a99fe6304c8f197348c47a637d594.tar.gz
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/services/projects/update_repository_storage_service.rb')
-rw-r--r--app/services/projects/update_repository_storage_service.rb115
1 files changed, 115 insertions, 0 deletions
diff --git a/app/services/projects/update_repository_storage_service.rb b/app/services/projects/update_repository_storage_service.rb
new file mode 100644
index 00000000000..30b99e85304
--- /dev/null
+++ b/app/services/projects/update_repository_storage_service.rb
@@ -0,0 +1,115 @@
+# frozen_string_literal: true
+
+module Projects
+ class UpdateRepositoryStorageService < BaseService
+ include Gitlab::ShellAdapter
+
+ RepositoryAlreadyMoved = Class.new(StandardError)
+
+ def initialize(project)
+ @project = project
+ end
+
+ def execute(new_repository_storage_key)
+ # Raising an exception is a little heavy handed but this behavior (doing
+ # nothing if the repo is already on the right storage) prevents data
+ # loss, so it is valuable for us to be able to observe it via the
+ # exception.
+ raise RepositoryAlreadyMoved if project.repository_storage == new_repository_storage_key
+
+ if mirror_repositories(new_repository_storage_key)
+ mark_old_paths_for_archive
+
+ project.update(repository_storage: new_repository_storage_key, repository_read_only: false)
+ project.leave_pool_repository
+ project.track_project_repository
+
+ enqueue_housekeeping
+ else
+ project.update(repository_read_only: false)
+ end
+ end
+
+ private
+
+ def mirror_repositories(new_repository_storage_key)
+ result = mirror_repository(new_repository_storage_key)
+
+ if project.wiki.repository_exists?
+ result &&= mirror_repository(new_repository_storage_key, type: Gitlab::GlRepository::WIKI)
+ end
+
+ result
+ end
+
+ def mirror_repository(new_storage_key, type: Gitlab::GlRepository::PROJECT)
+ return false unless wait_for_pushes(type)
+
+ repository = type.repository_for(project)
+ full_path = repository.full_path
+ raw_repository = repository.raw
+
+ # Initialize a git repository on the target path
+ gitlab_shell.create_repository(new_storage_key, raw_repository.relative_path, full_path)
+ new_repository = Gitlab::Git::Repository.new(new_storage_key,
+ raw_repository.relative_path,
+ raw_repository.gl_repository,
+ full_path)
+
+ new_repository.fetch_repository_as_mirror(raw_repository)
+ 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')