89a407dc3b
Pool repositories are persisted in the database, and when the DB is restored, the data need to be restored on disk. This is done by resetting the state machine and rescheduling the object pool creation. This is not an exact replica of the state like at the time of the creation of the backup. However, the data is consistent again. Dumping isn't required as internally GitLab uses git bundles which bundle all refs and include all objects in the bundle that they require, reduplicating as more repositories get backed up. This does require more data to be stored. Fixes https://gitlab.com/gitlab-org/gitaly/issues/1355
177 lines
5.1 KiB
Ruby
177 lines
5.1 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require 'yaml'
|
|
|
|
module Backup
|
|
class Repository
|
|
include Gitlab::ShellAdapter
|
|
attr_reader :progress
|
|
|
|
def initialize(progress)
|
|
@progress = progress
|
|
end
|
|
|
|
def dump
|
|
prepare
|
|
|
|
Project.find_each(batch_size: 1000) do |project|
|
|
progress.print " * #{display_repo_path(project)} ... "
|
|
|
|
if project.hashed_storage?(:repository)
|
|
FileUtils.mkdir_p(File.dirname(File.join(backup_repos_path, project.disk_path)))
|
|
else
|
|
FileUtils.mkdir_p(File.join(backup_repos_path, project.namespace.full_path)) if project.namespace
|
|
end
|
|
|
|
if !empty_repo?(project)
|
|
backup_project(project)
|
|
progress.puts "[DONE]".color(:green)
|
|
else
|
|
progress.puts "[SKIPPED]".color(:cyan)
|
|
end
|
|
|
|
wiki = ProjectWiki.new(project)
|
|
|
|
if !empty_repo?(wiki)
|
|
backup_project(wiki)
|
|
progress.puts "[DONE] Wiki".color(:green)
|
|
else
|
|
progress.puts "[SKIPPED] Wiki".color(:cyan)
|
|
end
|
|
end
|
|
end
|
|
|
|
def prepare_directories
|
|
Gitlab.config.repositories.storages.each do |name, _repository_storage|
|
|
Gitlab::GitalyClient::StorageService.new(name).delete_all_repositories
|
|
end
|
|
end
|
|
|
|
def backup_project(project)
|
|
path_to_project_bundle = path_to_bundle(project)
|
|
Gitlab::GitalyClient::RepositoryService.new(project.repository)
|
|
.create_bundle(path_to_project_bundle)
|
|
|
|
backup_custom_hooks(project)
|
|
rescue => e
|
|
progress_warn(project, e, 'Failed to backup repo')
|
|
end
|
|
|
|
def backup_custom_hooks(project)
|
|
FileUtils.mkdir_p(project_backup_path(project))
|
|
|
|
custom_hooks_path = custom_hooks_tar(project)
|
|
Gitlab::GitalyClient::RepositoryService.new(project.repository)
|
|
.backup_custom_hooks(custom_hooks_path)
|
|
end
|
|
|
|
def restore_custom_hooks(project)
|
|
return unless Dir.exist?(project_backup_path(project))
|
|
return if Dir.glob("#{project_backup_path(project)}/custom_hooks*").none?
|
|
|
|
custom_hooks_path = custom_hooks_tar(project)
|
|
Gitlab::GitalyClient::RepositoryService.new(project.repository)
|
|
.restore_custom_hooks(custom_hooks_path)
|
|
end
|
|
|
|
def restore
|
|
prepare_directories
|
|
|
|
Project.find_each(batch_size: 1000) do |project|
|
|
progress.print " * #{project.full_path} ... "
|
|
path_to_project_bundle = path_to_bundle(project)
|
|
project.ensure_storage_path_exists
|
|
|
|
restore_repo_success = nil
|
|
if File.exist?(path_to_project_bundle)
|
|
begin
|
|
project.repository.create_from_bundle(path_to_project_bundle)
|
|
restore_custom_hooks(project)
|
|
restore_repo_success = true
|
|
rescue => e
|
|
restore_repo_success = false
|
|
progress.puts "Error: #{e}".color(:red)
|
|
end
|
|
else
|
|
restore_repo_success = gitlab_shell.create_repository(project.repository_storage, project.disk_path)
|
|
end
|
|
|
|
if restore_repo_success
|
|
progress.puts "[DONE]".color(:green)
|
|
else
|
|
progress.puts "[Failed] restoring #{project.full_path} repository".color(:red)
|
|
end
|
|
|
|
wiki = ProjectWiki.new(project)
|
|
path_to_wiki_bundle = path_to_bundle(wiki)
|
|
|
|
if File.exist?(path_to_wiki_bundle)
|
|
progress.print " * #{wiki.full_path} ... "
|
|
begin
|
|
wiki.repository.create_from_bundle(path_to_wiki_bundle)
|
|
restore_custom_hooks(wiki)
|
|
|
|
progress.puts "[DONE]".color(:green)
|
|
rescue => e
|
|
progress.puts "[Failed] restoring #{wiki.full_path} wiki".color(:red)
|
|
progress.puts "Error #{e}".color(:red)
|
|
end
|
|
end
|
|
end
|
|
|
|
restore_object_pools
|
|
end
|
|
|
|
protected
|
|
|
|
def path_to_bundle(project)
|
|
File.join(backup_repos_path, project.disk_path + '.bundle')
|
|
end
|
|
|
|
def project_backup_path(project)
|
|
File.join(backup_repos_path, project.disk_path)
|
|
end
|
|
|
|
def custom_hooks_tar(project)
|
|
File.join(project_backup_path(project), "custom_hooks.tar")
|
|
end
|
|
|
|
def backup_repos_path
|
|
File.join(Gitlab.config.backup.path, 'repositories')
|
|
end
|
|
|
|
def prepare
|
|
FileUtils.rm_rf(backup_repos_path)
|
|
FileUtils.mkdir_p(Gitlab.config.backup.path)
|
|
FileUtils.mkdir(backup_repos_path, mode: 0700)
|
|
end
|
|
|
|
private
|
|
|
|
def progress_warn(project, cmd, output)
|
|
progress.puts "[WARNING] Executing #{cmd}".color(:orange)
|
|
progress.puts "Ignoring error on #{display_repo_path(project)} - #{output}".color(:orange)
|
|
end
|
|
|
|
def empty_repo?(project_or_wiki)
|
|
project_or_wiki.repository.expire_emptiness_caches
|
|
project_or_wiki.repository.empty?
|
|
end
|
|
|
|
def display_repo_path(project)
|
|
project.hashed_storage?(:repository) ? "#{project.full_path} (#{project.disk_path})" : project.full_path
|
|
end
|
|
|
|
def restore_object_pools
|
|
PoolRepository.includes(:source_project).find_each do |pool|
|
|
progress.puts " - Object pool #{pool.disk_path}..."
|
|
|
|
pool.source_project ||= pool.member_projects.first.root_of_fork_network
|
|
pool.state = 'none'
|
|
pool.save
|
|
|
|
pool.schedule
|
|
end
|
|
end
|
|
end
|
|
end
|