Move project repository storage classes to namespace

In this commit we move some project related classes to the
`Projects` namespace. This is an effort to have everything
in the codebase aligned.
parent 5cf5298a
...@@ -345,7 +345,7 @@ class Project < ApplicationRecord ...@@ -345,7 +345,7 @@ class Project < ApplicationRecord
has_many :daily_build_group_report_results, class_name: 'Ci::DailyBuildGroupReportResult' has_many :daily_build_group_report_results, class_name: 'Ci::DailyBuildGroupReportResult'
has_many :repository_storage_moves, class_name: 'ProjectRepositoryStorageMove', inverse_of: :container has_many :repository_storage_moves, class_name: 'Projects::RepositoryStorageMove', inverse_of: :container
has_many :webide_pipelines, -> { webide_source }, class_name: 'Ci::Pipeline', inverse_of: :project has_many :webide_pipelines, -> { webide_source }, class_name: 'Ci::Pipeline', inverse_of: :project
has_many :reviews, inverse_of: :project has_many :reviews, inverse_of: :project
......
# frozen_string_literal: true # frozen_string_literal: true
# ProjectRepositoryStorageMove are details of repository storage moves for a # This is a compatibility class to avoid calling a non-existent
# project. For example, moving a project to another gitaly node to help # class from sidekiq during deployment.
# balance storage capacity. #
class ProjectRepositoryStorageMove < ApplicationRecord # This class was moved to a namespace in https://gitlab.com/gitlab-org/gitlab/-/issues/299853.
extend ::Gitlab::Utils::Override # we cannot remove this class entirely because there can be jobs
include RepositoryStorageMovable # referencing it.
#
belongs_to :container, class_name: 'Project', inverse_of: :repository_storage_moves, foreign_key: :project_id # We can get rid of this class in 14.0
alias_attribute :project, :container # https://gitlab.com/gitlab-org/gitlab/-/issues/322393
scope :with_projects, -> { includes(container: :route) } class ProjectRepositoryStorageMove < Projects::RepositoryStorageMove
override :update_repository_storage
def update_repository_storage(new_storage)
container.update_column(:repository_storage, new_storage)
end
override :schedule_repository_storage_update_worker
def schedule_repository_storage_update_worker
ProjectUpdateRepositoryStorageWorker.perform_async(
project_id,
destination_storage_name,
id
)
end
private
override :error_key
def error_key
:project
end
end end
# frozen_string_literal: true
# Projects::RepositoryStorageMove are details of repository storage moves for a
# project. For example, moving a project to another gitaly node to help
# balance storage capacity.
module Projects
class RepositoryStorageMove < ApplicationRecord
extend ::Gitlab::Utils::Override
include RepositoryStorageMovable
self.table_name = 'project_repository_storage_moves'
belongs_to :container, class_name: 'Project', inverse_of: :repository_storage_moves, foreign_key: :project_id
alias_attribute :project, :container
scope :with_projects, -> { includes(container: :route) }
override :update_repository_storage
def update_repository_storage(new_storage)
container.update_column(:repository_storage, new_storage)
end
override :schedule_repository_storage_update_worker
def schedule_repository_storage_update_worker
Projects::UpdateRepositoryStorageWorker.perform_async(
project_id,
destination_storage_name,
id
)
end
private
override :error_key
def error_key
:project
end
end
end
...@@ -25,7 +25,7 @@ module Projects ...@@ -25,7 +25,7 @@ module Projects
override :schedule_bulk_worker_klass override :schedule_bulk_worker_klass
def self.schedule_bulk_worker_klass def self.schedule_bulk_worker_klass
::ProjectScheduleBulkRepositoryShardMovesWorker ::Projects::ScheduleBulkRepositoryShardMovesWorker
end end
end end
end end
...@@ -2020,6 +2020,22 @@ ...@@ -2020,6 +2020,22 @@
:weight: 1 :weight: 1
:idempotent: :idempotent:
:tags: [] :tags: []
- :name: projects_schedule_bulk_repository_shard_moves
:feature_category: :gitaly
:has_external_dependencies:
:urgency: :throttled
:resource_boundary: :unknown
:weight: 1
:idempotent: true
:tags: []
- :name: projects_update_repository_storage
:feature_category: :gitaly
:has_external_dependencies:
:urgency: :throttled
:resource_boundary: :unknown
:weight: 1
:idempotent: true
:tags: []
- :name: prometheus_create_default_alerts - :name: prometheus_create_default_alerts
:feature_category: :incident_management :feature_category: :incident_management
:has_external_dependencies: :has_external_dependencies:
......
# frozen_string_literal: true # frozen_string_literal: true
class ProjectScheduleBulkRepositoryShardMovesWorker # This is a compatibility class to avoid calling a non-existent
include ApplicationWorker # class from sidekiq during deployment.
#
# This class was moved to a namespace in https://gitlab.com/gitlab-org/gitlab/-/issues/299853.
# we cannot remove this class entirely because there can be jobs
# referencing it.
#
# We can get rid of this class in 14.0
# https://gitlab.com/gitlab-org/gitlab/-/issues/322393
class ProjectScheduleBulkRepositoryShardMovesWorker < Projects::ScheduleBulkRepositoryShardMovesWorker
idempotent! idempotent!
feature_category :gitaly
urgency :throttled urgency :throttled
def perform(source_storage_name, destination_storage_name = nil)
Projects::ScheduleBulkRepositoryShardMovesService.new.execute(source_storage_name, destination_storage_name)
end
end end
# frozen_string_literal: true # frozen_string_literal: true
class ProjectUpdateRepositoryStorageWorker # rubocop:disable Scalability/IdempotentWorker # This is a compatibility class to avoid calling a non-existent
extend ::Gitlab::Utils::Override # class from sidekiq during deployment.
include UpdateRepositoryStorageWorker #
# This class was moved to a namespace in https://gitlab.com/gitlab-org/gitlab/-/issues/299853.
private # we cannot remove this class entirely because there can be jobs
# referencing it.
override :find_repository_storage_move #
def find_repository_storage_move(repository_storage_move_id) # We can get rid of this class in 14.0
ProjectRepositoryStorageMove.find(repository_storage_move_id) # https://gitlab.com/gitlab-org/gitlab/-/issues/322393
end class ProjectUpdateRepositoryStorageWorker < Projects::UpdateRepositoryStorageWorker
idempotent!
override :find_container urgency :throttled
def find_container(container_id)
Project.find(container_id)
end
override :update_repository_storage
def update_repository_storage(repository_storage_move)
::Projects::UpdateRepositoryStorageService.new(repository_storage_move).execute
end
end end
# frozen_string_literal: true
module Projects
class ScheduleBulkRepositoryShardMovesWorker
include ApplicationWorker
idempotent!
feature_category :gitaly
urgency :throttled
def perform(source_storage_name, destination_storage_name = nil)
Projects::ScheduleBulkRepositoryShardMovesService.new.execute(source_storage_name, destination_storage_name)
end
end
end
# frozen_string_literal: true
module Projects
class UpdateRepositoryStorageWorker # rubocop:disable Scalability/IdempotentWorker
extend ::Gitlab::Utils::Override
include ::UpdateRepositoryStorageWorker
private
override :find_repository_storage_move
def find_repository_storage_move(repository_storage_move_id)
::Projects::RepositoryStorageMove.find(repository_storage_move_id)
end
override :find_container
def find_container(container_id)
Project.find(container_id)
end
override :update_repository_storage
def update_repository_storage(repository_storage_move)
::Projects::UpdateRepositoryStorageService.new(repository_storage_move).execute
end
end
end
...@@ -282,6 +282,10 @@ ...@@ -282,6 +282,10 @@
- 1 - 1
- - projects_git_garbage_collect - - projects_git_garbage_collect
- 1 - 1
- - projects_schedule_bulk_repository_shard_moves
- 1
- - projects_update_repository_storage
- 1
- - prometheus_create_default_alerts - - prometheus_create_default_alerts
- 1 - 1
- - propagate_integration - - propagate_integration
......
...@@ -10,16 +10,16 @@ class BackfillUpdatedAtAfterRepositoryStorageMove < ActiveRecord::Migration[6.0] ...@@ -10,16 +10,16 @@ class BackfillUpdatedAtAfterRepositoryStorageMove < ActiveRecord::Migration[6.0]
disable_ddl_transaction! disable_ddl_transaction!
class ProjectRepositoryStorageMove < ActiveRecord::Base class RepositoryStorageMove < ActiveRecord::Base
include EachBatch include EachBatch
self.table_name = 'project_repository_storage_moves' self.table_name = 'project_repository_storage_moves'
end end
def up def up
ProjectRepositoryStorageMove.reset_column_information RepositoryStorageMove.reset_column_information
ProjectRepositoryStorageMove.select(:project_id).distinct.each_batch(of: BATCH_SIZE, column: :project_id) do |batch, index| RepositoryStorageMove.select(:project_id).distinct.each_batch(of: BATCH_SIZE, column: :project_id) do |batch, index|
migrate_in( migrate_in(
INTERVAL * index, INTERVAL * index,
MIGRATION_CLASS, MIGRATION_CLASS,
......
...@@ -2,8 +2,10 @@ ...@@ -2,8 +2,10 @@
module API module API
module Entities module Entities
class ProjectRepositoryStorageMove < BasicRepositoryStorageMove module Projects
class RepositoryStorageMove < BasicRepositoryStorageMove
expose :project, using: Entities::ProjectIdentity expose :project, using: Entities::ProjectIdentity
end end
end end
end
end end
...@@ -11,28 +11,28 @@ module API ...@@ -11,28 +11,28 @@ module API
resource :project_repository_storage_moves do resource :project_repository_storage_moves do
desc 'Get a list of all project repository storage moves' do desc 'Get a list of all project repository storage moves' do
detail 'This feature was introduced in GitLab 13.0.' detail 'This feature was introduced in GitLab 13.0.'
success Entities::ProjectRepositoryStorageMove success Entities::Projects::RepositoryStorageMove
end end
params do params do
use :pagination use :pagination
end end
get do get do
storage_moves = ProjectRepositoryStorageMove.with_projects.order_created_at_desc storage_moves = ::Projects::RepositoryStorageMove.with_projects.order_created_at_desc
present paginate(storage_moves), with: Entities::ProjectRepositoryStorageMove, current_user: current_user present paginate(storage_moves), with: Entities::Projects::RepositoryStorageMove, current_user: current_user
end end
desc 'Get a project repository storage move' do desc 'Get a project repository storage move' do
detail 'This feature was introduced in GitLab 13.0.' detail 'This feature was introduced in GitLab 13.0.'
success Entities::ProjectRepositoryStorageMove success Entities::Projects::RepositoryStorageMove
end end
params do params do
requires :repository_storage_move_id, type: Integer, desc: 'The ID of a project repository storage move' requires :repository_storage_move_id, type: Integer, desc: 'The ID of a project repository storage move'
end end
get ':repository_storage_move_id' do get ':repository_storage_move_id' do
storage_move = ProjectRepositoryStorageMove.find(params[:repository_storage_move_id]) storage_move = ::Projects::RepositoryStorageMove.find(params[:repository_storage_move_id])
present storage_move, with: Entities::ProjectRepositoryStorageMove, current_user: current_user present storage_move, with: Entities::Projects::RepositoryStorageMove, current_user: current_user
end end
desc 'Schedule bulk project repository storage moves' do desc 'Schedule bulk project repository storage moves' do
...@@ -58,7 +58,7 @@ module API ...@@ -58,7 +58,7 @@ module API
resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
desc 'Get a list of all project repository storage moves' do desc 'Get a list of all project repository storage moves' do
detail 'This feature was introduced in GitLab 13.1.' detail 'This feature was introduced in GitLab 13.1.'
success Entities::ProjectRepositoryStorageMove success Entities::Projects::RepositoryStorageMove
end end
params do params do
use :pagination use :pagination
...@@ -66,12 +66,12 @@ module API ...@@ -66,12 +66,12 @@ module API
get ':id/repository_storage_moves' do get ':id/repository_storage_moves' do
storage_moves = user_project.repository_storage_moves.with_projects.order_created_at_desc storage_moves = user_project.repository_storage_moves.with_projects.order_created_at_desc
present paginate(storage_moves), with: Entities::ProjectRepositoryStorageMove, current_user: current_user present paginate(storage_moves), with: Entities::Projects::RepositoryStorageMove, current_user: current_user
end end
desc 'Get a project repository storage move' do desc 'Get a project repository storage move' do
detail 'This feature was introduced in GitLab 13.1.' detail 'This feature was introduced in GitLab 13.1.'
success Entities::ProjectRepositoryStorageMove success Entities::Projects::RepositoryStorageMove
end end
params do params do
requires :repository_storage_move_id, type: Integer, desc: 'The ID of a project repository storage move' requires :repository_storage_move_id, type: Integer, desc: 'The ID of a project repository storage move'
...@@ -79,12 +79,12 @@ module API ...@@ -79,12 +79,12 @@ module API
get ':id/repository_storage_moves/:repository_storage_move_id' do get ':id/repository_storage_moves/:repository_storage_move_id' do
storage_move = user_project.repository_storage_moves.find(params[:repository_storage_move_id]) storage_move = user_project.repository_storage_moves.find(params[:repository_storage_move_id])
present storage_move, with: Entities::ProjectRepositoryStorageMove, current_user: current_user present storage_move, with: Entities::Projects::RepositoryStorageMove, current_user: current_user
end end
desc 'Schedule a project repository storage move' do desc 'Schedule a project repository storage move' do
detail 'This feature was introduced in GitLab 13.1.' detail 'This feature was introduced in GitLab 13.1.'
success Entities::ProjectRepositoryStorageMove success Entities::Projects::RepositoryStorageMove
end end
params do params do
optional :destination_storage_name, type: String, desc: 'The destination storage shard' optional :destination_storage_name, type: String, desc: 'The destination storage shard'
...@@ -95,7 +95,7 @@ module API ...@@ -95,7 +95,7 @@ module API
) )
if storage_move.schedule if storage_move.schedule
present storage_move, with: Entities::ProjectRepositoryStorageMove, current_user: current_user present storage_move, with: Entities::Projects::RepositoryStorageMove, current_user: current_user
else else
render_validation_error!(storage_move) render_validation_error!(storage_move)
end end
......
...@@ -5,7 +5,7 @@ module Gitlab ...@@ -5,7 +5,7 @@ module Gitlab
# Update existent project update_at column after their repository storage was moved # Update existent project update_at column after their repository storage was moved
class BackfillProjectUpdatedAtAfterRepositoryStorageMove class BackfillProjectUpdatedAtAfterRepositoryStorageMove
def perform(*project_ids) def perform(*project_ids)
updated_repository_storages = ProjectRepositoryStorageMove.select("project_id, MAX(updated_at) as updated_at").where(project_id: project_ids).group(:project_id) updated_repository_storages = Projects::RepositoryStorageMove.select("project_id, MAX(updated_at) as updated_at").where(project_id: project_ids).group(:project_id)
Project.connection.execute <<-SQL Project.connection.execute <<-SQL
WITH repository_storage_cte as ( WITH repository_storage_cte as (
......
# frozen_string_literal: true # frozen_string_literal: true
FactoryBot.define do FactoryBot.define do
factory :project_repository_storage_move, class: 'ProjectRepositoryStorageMove' do factory :project_repository_storage_move, class: 'Projects::RepositoryStorageMove' do
container { association(:project) } container { association(:project) }
source_storage_name { 'default' } source_storage_name { 'default' }
trait :scheduled do trait :scheduled do
state { ProjectRepositoryStorageMove.state_machines[:state].states[:scheduled].value } state { Projects::RepositoryStorageMove.state_machines[:state].states[:scheduled].value }
end end
trait :started do trait :started do
state { ProjectRepositoryStorageMove.state_machines[:state].states[:started].value } state { Projects::RepositoryStorageMove.state_machines[:state].states[:started].value }
end end
trait :replicated do trait :replicated do
state { ProjectRepositoryStorageMove.state_machines[:state].states[:replicated].value } state { Projects::RepositoryStorageMove.state_machines[:state].states[:replicated].value }
end end
trait :finished do trait :finished do
state { ProjectRepositoryStorageMove.state_machines[:state].states[:finished].value } state { Projects::RepositoryStorageMove.state_machines[:state].states[:finished].value }
end end
trait :failed do trait :failed do
state { ProjectRepositoryStorageMove.state_machines[:state].states[:failed].value } state { Projects::RepositoryStorageMove.state_machines[:state].states[:failed].value }
end end
end end
end end
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe API::Entities::ProjectRepositoryStorageMove do RSpec.describe API::Entities::Projects::RepositoryStorageMove do
describe '#as_json' do describe '#as_json' do
subject { entity.as_json } subject { entity.as_json }
......
...@@ -9,7 +9,7 @@ RSpec.describe ProjectRepositoryStorageMove, type: :model do ...@@ -9,7 +9,7 @@ RSpec.describe ProjectRepositoryStorageMove, type: :model do
let(:container) { project } let(:container) { project }
let(:repository_storage_factory_key) { :project_repository_storage_move } let(:repository_storage_factory_key) { :project_repository_storage_move }
let(:error_key) { :project } let(:error_key) { :project }
let(:repository_storage_worker) { ProjectUpdateRepositoryStorageWorker } let(:repository_storage_worker) { Projects::UpdateRepositoryStorageWorker }
end end
describe 'state transitions' do describe 'state transitions' do
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Projects::RepositoryStorageMove, type: :model do
let_it_be_with_refind(:project) { create(:project) }
it_behaves_like 'handles repository moves' do
let(:container) { project }
let(:repository_storage_factory_key) { :project_repository_storage_move }
let(:error_key) { :project }
let(:repository_storage_worker) { Projects::UpdateRepositoryStorageWorker }
end
describe 'state transitions' do
let(:storage) { 'test_second_storage' }
before do
stub_storage_settings(storage => { 'path' => 'tmp/tests/extra_storage' })
end
context 'when started' do
subject(:storage_move) { create(:project_repository_storage_move, :started, container: project, destination_storage_name: storage) }
context 'and transits to replicated' do
it 'sets the repository storage and marks the container as writable' do
storage_move.finish_replication!
expect(project.repository_storage).to eq(storage)
expect(project).not_to be_repository_read_only
end
end
end
end
end
...@@ -7,6 +7,6 @@ RSpec.describe API::ProjectRepositoryStorageMoves do ...@@ -7,6 +7,6 @@ RSpec.describe API::ProjectRepositoryStorageMoves do
let_it_be(:container) { create(:project, :repository).tap { |project| project.track_project_repository } } let_it_be(:container) { create(:project, :repository).tap { |project| project.track_project_repository } }
let_it_be(:storage_move) { create(:project_repository_storage_move, :scheduled, container: container) } let_it_be(:storage_move) { create(:project_repository_storage_move, :scheduled, container: container) }
let(:repository_storage_move_factory) { :project_repository_storage_move } let(:repository_storage_move_factory) { :project_repository_storage_move }
let(:bulk_worker_klass) { ProjectScheduleBulkRepositoryShardMovesWorker } let(:bulk_worker_klass) { Projects::ScheduleBulkRepositoryShardMovesWorker }
end end
end end
...@@ -2818,7 +2818,7 @@ RSpec.describe API::Projects do ...@@ -2818,7 +2818,7 @@ RSpec.describe API::Projects do
Sidekiq::Testing.fake! do Sidekiq::Testing.fake! do
put(api("/projects/#{new_project.id}", user), params: { repository_storage: unknown_storage, issues_enabled: false }) put(api("/projects/#{new_project.id}", user), params: { repository_storage: unknown_storage, issues_enabled: false })
end end
end.not_to change(ProjectUpdateRepositoryStorageWorker.jobs, :size) end.not_to change(Projects::UpdateRepositoryStorageWorker.jobs, :size)
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
expect(json_response['issues_enabled']).to eq(false) expect(json_response['issues_enabled']).to eq(false)
...@@ -2845,7 +2845,7 @@ RSpec.describe API::Projects do ...@@ -2845,7 +2845,7 @@ RSpec.describe API::Projects do
Sidekiq::Testing.fake! do Sidekiq::Testing.fake! do
put(api("/projects/#{new_project.id}", admin), params: { repository_storage: 'test_second_storage' }) put(api("/projects/#{new_project.id}", admin), params: { repository_storage: 'test_second_storage' })
end end
end.to change(ProjectUpdateRepositoryStorageWorker.jobs, :size).by(1) end.to change(Projects::UpdateRepositoryStorageWorker.jobs, :size).by(1)
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
end end
......
...@@ -6,7 +6,7 @@ RSpec.describe Projects::ScheduleBulkRepositoryShardMovesService do ...@@ -6,7 +6,7 @@ RSpec.describe Projects::ScheduleBulkRepositoryShardMovesService do
it_behaves_like 'moves repository shard in bulk' do it_behaves_like 'moves repository shard in bulk' do
let_it_be_with_reload(:container) { create(:project, :repository).tap { |project| project.track_project_repository } } let_it_be_with_reload(:container) { create(:project, :repository).tap { |project| project.track_project_repository } }
let(:move_service_klass) { ProjectRepositoryStorageMove } let(:move_service_klass) { Projects::RepositoryStorageMove }
let(:bulk_worker_klass) { ::ProjectScheduleBulkRepositoryShardMovesWorker } let(:bulk_worker_klass) { ::Projects::ScheduleBulkRepositoryShardMovesWorker }
end end
end end
...@@ -551,7 +551,7 @@ RSpec.describe Projects::UpdateService do ...@@ -551,7 +551,7 @@ RSpec.describe Projects::UpdateService do
expect(project).to be_repository_read_only expect(project).to be_repository_read_only
expect(project.repository_storage_moves.last).to have_attributes( expect(project.repository_storage_moves.last).to have_attributes(
state: ::ProjectRepositoryStorageMove.state_machines[:state].states[:scheduled].value, state: ::Projects::RepositoryStorageMove.state_machines[:state].states[:scheduled].value,
source_storage_name: 'default', source_storage_name: 'default',
destination_storage_name: 'test_second_storage' destination_storage_name: 'test_second_storage'
) )
......
...@@ -6,7 +6,7 @@ RSpec.describe ProjectScheduleBulkRepositoryShardMovesWorker do ...@@ -6,7 +6,7 @@ RSpec.describe ProjectScheduleBulkRepositoryShardMovesWorker do
it_behaves_like 'schedules bulk repository shard moves' do it_behaves_like 'schedules bulk repository shard moves' do
let_it_be_with_reload(:container) { create(:project, :repository).tap { |project| project.track_project_repository } } let_it_be_with_reload(:container) { create(:project, :repository).tap { |project| project.track_project_repository } }
let(:move_service_klass) { ProjectRepositoryStorageMove } let(:move_service_klass) { Projects::RepositoryStorageMove }
let(:worker_klass) { ProjectUpdateRepositoryStorageWorker } let(:worker_klass) { Projects::UpdateRepositoryStorageWorker }
end end
end end
...@@ -10,6 +10,6 @@ RSpec.describe ProjectUpdateRepositoryStorageWorker do ...@@ -10,6 +10,6 @@ RSpec.describe ProjectUpdateRepositoryStorageWorker do
let_it_be(:repository_storage_move) { create(:project_repository_storage_move) } let_it_be(:repository_storage_move) { create(:project_repository_storage_move) }
let(:service_klass) { Projects::UpdateRepositoryStorageService } let(:service_klass) { Projects::UpdateRepositoryStorageService }
let(:repository_storage_move_klass) { ProjectRepositoryStorageMove } let(:repository_storage_move_klass) { Projects::RepositoryStorageMove }
end end
end end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Projects::ScheduleBulkRepositoryShardMovesWorker do
it_behaves_like 'schedules bulk repository shard moves' do
let_it_be_with_reload(:container) { create(:project, :repository).tap { |project| project.track_project_repository } }
let(:move_service_klass) { Projects::RepositoryStorageMove }
let(:worker_klass) { Projects::UpdateRepositoryStorageWorker }
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Projects::UpdateRepositoryStorageWorker do
subject { described_class.new }
it_behaves_like 'an update storage move worker' do
let_it_be_with_refind(:container) { create(:project, :repository) }
let_it_be(:repository_storage_move) { create(:project_repository_storage_move) }
let(:service_klass) { Projects::UpdateRepositoryStorageService }
let(:repository_storage_move_klass) { Projects::RepositoryStorageMove }
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment