From 27f54bebb29a1e56251ac2d669c2883aeaf1cb1c Mon Sep 17 00:00:00 2001 From: Bob Van Landuyt Date: Fri, 14 Apr 2017 19:29:22 +0200 Subject: Use objects for renaming namespaces and projects --- .../database/rename_reserved_paths_migration.rb | 97 +------------ .../migration_classes.rb | 1 + .../rename_reserved_paths_migration/namespaces.rb | 70 ---------- .../rename_reserved_paths_migration/projects.rb | 38 ----- .../rename_reserved_paths_migration/rename_base.rb | 103 ++++++++++++++ .../rename_namespaces.rb | 70 ++++++++++ .../rename_projects.rb | 38 +++++ .../namespaces_spec.rb | 153 --------------------- .../projects_spec.rb | 78 ----------- .../rename_base_spec.rb | 147 ++++++++++++++++++++ .../rename_namespaces_spec.rb | 152 ++++++++++++++++++++ .../rename_projects_spec.rb | 77 +++++++++++ .../rename_reserved_paths_migration_spec.rb | 147 +++----------------- spec/support/fake_migration_classes.rb | 3 + 14 files changed, 616 insertions(+), 558 deletions(-) delete mode 100644 lib/gitlab/database/rename_reserved_paths_migration/namespaces.rb delete mode 100644 lib/gitlab/database/rename_reserved_paths_migration/projects.rb create mode 100644 lib/gitlab/database/rename_reserved_paths_migration/rename_base.rb create mode 100644 lib/gitlab/database/rename_reserved_paths_migration/rename_namespaces.rb create mode 100644 lib/gitlab/database/rename_reserved_paths_migration/rename_projects.rb delete mode 100644 spec/lib/gitlab/database/rename_reserved_paths_migration/namespaces_spec.rb delete mode 100644 spec/lib/gitlab/database/rename_reserved_paths_migration/projects_spec.rb create mode 100644 spec/lib/gitlab/database/rename_reserved_paths_migration/rename_base_spec.rb create mode 100644 spec/lib/gitlab/database/rename_reserved_paths_migration/rename_namespaces_spec.rb create mode 100644 spec/lib/gitlab/database/rename_reserved_paths_migration/rename_projects_spec.rb create mode 100644 spec/support/fake_migration_classes.rb diff --git a/lib/gitlab/database/rename_reserved_paths_migration.rb b/lib/gitlab/database/rename_reserved_paths_migration.rb index 5314f933435..0507ae4da51 100644 --- a/lib/gitlab/database/rename_reserved_paths_migration.rb +++ b/lib/gitlab/database/rename_reserved_paths_migration.rb @@ -1,104 +1,19 @@ module Gitlab module Database module RenameReservedPathsMigration - include MigrationHelpers - include Namespaces - include Projects + def self.included(kls) + kls.include(MigrationHelpers) + end def rename_wildcard_paths(one_or_more_paths) paths = Array(one_or_more_paths) - rename_namespaces(paths, type: :wildcard) - rename_projects(paths) + RenameNamespaces.new(paths, self).rename_namespaces(type: :wildcard) + RenameProjects.new(paths, self).rename_projects end def rename_root_paths(paths) paths = Array(paths) - rename_namespaces(paths, type: :top_level) - end - - def rename_path_for_routable(routable) - old_path = routable.path - old_full_path = routable.full_path - # Only remove the last occurrence of the path name to get the parent namespace path - namespace_path = remove_last_occurrence(old_full_path, old_path) - new_path = rename_path(namespace_path, old_path) - new_full_path = join_routable_path(namespace_path, new_path) - - # skips callbacks & validations - routable.class.where(id: routable). - update_all(path: new_path) - - rename_routes(old_full_path, new_full_path) - - [old_full_path, new_full_path] - end - - def rename_routes(old_full_path, new_full_path) - replace_statement = replace_sql(Route.arel_table[:path], - old_full_path, - new_full_path) - - update_column_in_batches(:routes, :path, replace_statement) do |table, query| - query.where(MigrationClasses::Route.arel_table[:path].matches("#{old_full_path}%")) - end - end - - def rename_path(namespace_path, path_was) - counter = 0 - path = "#{path_was}#{counter}" - - while route_exists?(join_routable_path(namespace_path, path)) - counter += 1 - path = "#{path_was}#{counter}" - end - - path - end - - def remove_last_occurrence(string, pattern) - string.reverse.sub(pattern.reverse, "").reverse - end - - def join_routable_path(namespace_path, top_level) - if namespace_path.present? - File.join(namespace_path, top_level) - else - top_level - end - end - - def route_exists?(full_path) - MigrationClasses::Route.where(Route.arel_table[:path].matches(full_path)).any? - end - - def move_pages(old_path, new_path) - move_folders(pages_dir, old_path, new_path) - end - - def move_uploads(old_path, new_path) - return unless file_storage? - - move_folders(uploads_dir, old_path, new_path) - end - - def move_folders(directory, old_relative_path, new_relative_path) - old_path = File.join(directory, old_relative_path) - return unless File.directory?(old_path) - - new_path = File.join(directory, new_relative_path) - FileUtils.mv(old_path, new_path) - end - - def file_storage? - CarrierWave::Uploader::Base.storage == CarrierWave::Storage::File - end - - def uploads_dir - File.join(CarrierWave.root, "uploads") - end - - def pages_dir - Settings.pages.path + RenameNamespaces.new(paths, self).rename_namespaces(type: :top_level) end end end diff --git a/lib/gitlab/database/rename_reserved_paths_migration/migration_classes.rb b/lib/gitlab/database/rename_reserved_paths_migration/migration_classes.rb index d725402ace4..9133b97d239 100644 --- a/lib/gitlab/database/rename_reserved_paths_migration/migration_classes.rb +++ b/lib/gitlab/database/rename_reserved_paths_migration/migration_classes.rb @@ -76,6 +76,7 @@ module Gitlab class Project < ActiveRecord::Base include MigrationClasses::Routable + has_one :route, as: :source self.table_name = 'projects' def repository_storage_path diff --git a/lib/gitlab/database/rename_reserved_paths_migration/namespaces.rb b/lib/gitlab/database/rename_reserved_paths_migration/namespaces.rb deleted file mode 100644 index 2ef2629f4c2..00000000000 --- a/lib/gitlab/database/rename_reserved_paths_migration/namespaces.rb +++ /dev/null @@ -1,70 +0,0 @@ -module Gitlab - module Database - module RenameReservedPathsMigration - module Namespaces - include Gitlab::ShellAdapter - - def rename_namespaces(paths, type:) - namespaces_for_paths(paths, type: type).each do |namespace| - rename_namespace(namespace) - end - end - - def namespaces_for_paths(paths, type:) - namespaces = if type == :wildcard - MigrationClasses::Namespace.where.not(parent_id: nil) - elsif type == :top_level - MigrationClasses::Namespace.where(parent_id: nil) - end - with_paths = MigrationClasses::Namespace.arel_table[:path]. - matches_any(paths) - namespaces.where(with_paths) - end - - def rename_namespace(namespace) - old_full_path, new_full_path = rename_path_for_routable(namespace) - - move_repositories(namespace, old_full_path, new_full_path) - move_uploads(old_full_path, new_full_path) - move_pages(old_full_path, new_full_path) - end - - def move_repositories(namespace, old_full_path, new_full_path) - repo_paths_for_namespace(namespace).each do |repository_storage_path| - # Ensure old directory exists before moving it - gitlab_shell.add_namespace(repository_storage_path, old_full_path) - - unless gitlab_shell.mv_namespace(repository_storage_path, old_full_path, new_full_path) - message = "Exception moving path #{repository_storage_path} \ - from #{old_full_path} to #{new_full_path}" - Rails.logger.error message - end - end - end - - def repo_paths_for_namespace(namespace) - projects_for_namespace(namespace). - select('distinct(repository_storage)').map(&:repository_storage_path) - end - - def projects_for_namespace(namespace) - namespace_ids = child_ids_for_parent(namespace, ids: [namespace.id]) - namespace_or_children = MigrationClasses::Project. - arel_table[:namespace_id]. - in(namespace_ids) - MigrationClasses::Project.unscoped.where(namespace_or_children) - end - - # This won't scale to huge trees, but it should do for a handful of - # namespaces called `system`. - def child_ids_for_parent(namespace, ids: []) - namespace.children.each do |child| - ids << child.id - child_ids_for_parent(child, ids: ids) if child.children.any? - end - ids - end - end - end - end -end diff --git a/lib/gitlab/database/rename_reserved_paths_migration/projects.rb b/lib/gitlab/database/rename_reserved_paths_migration/projects.rb deleted file mode 100644 index a2c9354e430..00000000000 --- a/lib/gitlab/database/rename_reserved_paths_migration/projects.rb +++ /dev/null @@ -1,38 +0,0 @@ -module Gitlab - module Database - module RenameReservedPathsMigration - module Projects - include Gitlab::ShellAdapter - - def rename_projects(paths) - projects_for_paths(paths).each do |project| - rename_project(project) - end - end - - def rename_project(project) - old_full_path, new_full_path = rename_path_for_routable(project) - - move_repository(project, old_full_path, new_full_path) - move_repository(project, "#{old_full_path}.wiki", "#{new_full_path}.wiki") - move_uploads(old_full_path, new_full_path) - move_pages(old_full_path, new_full_path) - end - - def move_repository(project, old_path, new_path) - unless gitlab_shell.mv_repository(project.repository_storage_path, - old_path, - new_path) - Rails.logger.error "Error moving #{old_path} to #{new_path}" - end - end - - def projects_for_paths(paths) - with_paths = MigrationClasses::Project.arel_table[:path] - .matches_any(paths) - MigrationClasses::Project.where(with_paths) - end - end - end - end -end diff --git a/lib/gitlab/database/rename_reserved_paths_migration/rename_base.rb b/lib/gitlab/database/rename_reserved_paths_migration/rename_base.rb new file mode 100644 index 00000000000..367348a9a42 --- /dev/null +++ b/lib/gitlab/database/rename_reserved_paths_migration/rename_base.rb @@ -0,0 +1,103 @@ +module Gitlab + module Database + module RenameReservedPathsMigration + class RenameBase + attr_reader :paths, :migration + + delegate :update_column_in_batches, + :replace_sql, + to: :migration + + def initialize(paths, migration) + @paths = paths + @migration = migration + end + + def rename_path_for_routable(routable) + old_path = routable.path + old_full_path = routable.full_path + # Only remove the last occurrence of the path name to get the parent namespace path + namespace_path = remove_last_occurrence(old_full_path, old_path) + new_path = rename_path(namespace_path, old_path) + new_full_path = join_routable_path(namespace_path, new_path) + + # skips callbacks & validations + routable.class.where(id: routable). + update_all(path: new_path) + + rename_routes(old_full_path, new_full_path) + + [old_full_path, new_full_path] + end + + def rename_routes(old_full_path, new_full_path) + replace_statement = replace_sql(Route.arel_table[:path], + old_full_path, + new_full_path) + + update_column_in_batches(:routes, :path, replace_statement) do |table, query| + query.where(MigrationClasses::Route.arel_table[:path].matches("#{old_full_path}%")) + end + end + + def rename_path(namespace_path, path_was) + counter = 0 + path = "#{path_was}#{counter}" + + while route_exists?(join_routable_path(namespace_path, path)) + counter += 1 + path = "#{path_was}#{counter}" + end + + path + end + + def remove_last_occurrence(string, pattern) + string.reverse.sub(pattern.reverse, "").reverse + end + + def join_routable_path(namespace_path, top_level) + if namespace_path.present? + File.join(namespace_path, top_level) + else + top_level + end + end + + def route_exists?(full_path) + MigrationClasses::Route.where(Route.arel_table[:path].matches(full_path)).any? + end + + def move_pages(old_path, new_path) + move_folders(pages_dir, old_path, new_path) + end + + def move_uploads(old_path, new_path) + return unless file_storage? + + move_folders(uploads_dir, old_path, new_path) + end + + def move_folders(directory, old_relative_path, new_relative_path) + old_path = File.join(directory, old_relative_path) + return unless File.directory?(old_path) + + new_path = File.join(directory, new_relative_path) + FileUtils.mv(old_path, new_path) + end + + def file_storage? + CarrierWave::Uploader::Base.storage == CarrierWave::Storage::File + end + + def uploads_dir + File.join(CarrierWave.root, "uploads") + end + + def pages_dir + Settings.pages.path + end + end + end + end +end diff --git a/lib/gitlab/database/rename_reserved_paths_migration/rename_namespaces.rb b/lib/gitlab/database/rename_reserved_paths_migration/rename_namespaces.rb new file mode 100644 index 00000000000..80e8135ea93 --- /dev/null +++ b/lib/gitlab/database/rename_reserved_paths_migration/rename_namespaces.rb @@ -0,0 +1,70 @@ +module Gitlab + module Database + module RenameReservedPathsMigration + class RenameNamespaces < RenameBase + include Gitlab::ShellAdapter + + def rename_namespaces(type:) + namespaces_for_paths(type: type).each do |namespace| + rename_namespace(namespace) + end + end + + def namespaces_for_paths(type:) + namespaces = if type == :wildcard + MigrationClasses::Namespace.where.not(parent_id: nil) + elsif type == :top_level + MigrationClasses::Namespace.where(parent_id: nil) + end + with_paths = MigrationClasses::Namespace.arel_table[:path]. + matches_any(paths) + namespaces.where(with_paths) + end + + def rename_namespace(namespace) + old_full_path, new_full_path = rename_path_for_routable(namespace) + + move_repositories(namespace, old_full_path, new_full_path) + move_uploads(old_full_path, new_full_path) + move_pages(old_full_path, new_full_path) + end + + def move_repositories(namespace, old_full_path, new_full_path) + repo_paths_for_namespace(namespace).each do |repository_storage_path| + # Ensure old directory exists before moving it + gitlab_shell.add_namespace(repository_storage_path, old_full_path) + + unless gitlab_shell.mv_namespace(repository_storage_path, old_full_path, new_full_path) + message = "Exception moving path #{repository_storage_path} \ + from #{old_full_path} to #{new_full_path}" + Rails.logger.error message + end + end + end + + def repo_paths_for_namespace(namespace) + projects_for_namespace(namespace). + select('distinct(repository_storage)').map(&:repository_storage_path) + end + + def projects_for_namespace(namespace) + namespace_ids = child_ids_for_parent(namespace, ids: [namespace.id]) + namespace_or_children = MigrationClasses::Project. + arel_table[:namespace_id]. + in(namespace_ids) + MigrationClasses::Project.where(namespace_or_children) + end + + # This won't scale to huge trees, but it should do for a handful of + # namespaces called `system`. + def child_ids_for_parent(namespace, ids: []) + namespace.children.each do |child| + ids << child.id + child_ids_for_parent(child, ids: ids) if child.children.any? + end + ids + end + end + end + end +end diff --git a/lib/gitlab/database/rename_reserved_paths_migration/rename_projects.rb b/lib/gitlab/database/rename_reserved_paths_migration/rename_projects.rb new file mode 100644 index 00000000000..02f10d8e951 --- /dev/null +++ b/lib/gitlab/database/rename_reserved_paths_migration/rename_projects.rb @@ -0,0 +1,38 @@ +module Gitlab + module Database + module RenameReservedPathsMigration + class RenameProjects < RenameBase + include Gitlab::ShellAdapter + + def rename_projects + projects_for_paths.each do |project| + rename_project(project) + end + end + + def rename_project(project) + old_full_path, new_full_path = rename_path_for_routable(project) + + move_repository(project, old_full_path, new_full_path) + move_repository(project, "#{old_full_path}.wiki", "#{new_full_path}.wiki") + move_uploads(old_full_path, new_full_path) + move_pages(old_full_path, new_full_path) + end + + def move_repository(project, old_path, new_path) + unless gitlab_shell.mv_repository(project.repository_storage_path, + old_path, + new_path) + Rails.logger.error "Error moving #{old_path} to #{new_path}" + end + end + + def projects_for_paths + with_paths = MigrationClasses::Project.arel_table[:path] + .matches_any(paths) + MigrationClasses::Project.where(with_paths) + end + end + end + end +end diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/namespaces_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/namespaces_spec.rb deleted file mode 100644 index fb24800e1f1..00000000000 --- a/spec/lib/gitlab/database/rename_reserved_paths_migration/namespaces_spec.rb +++ /dev/null @@ -1,153 +0,0 @@ -require 'spec_helper' - -describe Gitlab::Database::RenameReservedPathsMigration::Namespaces, :truncate do - let(:subject) do - ActiveRecord::Migration.new.extend( - Gitlab::Database::RenameReservedPathsMigration - ) - end - - before do - allow(subject).to receive(:say) - end - - def migration_namespace(namespace) - Gitlab::Database::RenameReservedPathsMigration::MigrationClasses:: - Namespace.find(namespace.id) - end - - describe '#namespaces_for_paths' do - context 'for wildcard namespaces' do - it 'only returns child namespaces with the correct path' do - _root_namespace = create(:namespace, path: 'THE-path') - _other_path = create(:namespace, - path: 'other', - parent: create(:namespace)) - namespace = create(:namespace, - path: 'the-path', - parent: create(:namespace)) - - found_ids = subject.namespaces_for_paths(['the-PATH'], type: :wildcard). - map(&:id) - expect(found_ids).to contain_exactly(namespace.id) - end - end - - context 'for top levelnamespaces' do - it 'only returns child namespaces with the correct path' do - root_namespace = create(:namespace, path: 'the-path') - _other_path = create(:namespace, path: 'other') - _child_namespace = create(:namespace, - path: 'the-path', - parent: create(:namespace)) - - found_ids = subject.namespaces_for_paths(['the-path'], type: :top_level). - map(&:id) - expect(found_ids).to contain_exactly(root_namespace.id) - end - end - end - - describe '#move_repositories' do - let(:namespace) { create(:group, name: 'hello-group') } - it 'moves a project for a namespace' do - create(:project, namespace: namespace, path: 'hello-project') - expected_path = File.join(TestEnv.repos_path, 'bye-group', 'hello-project.git') - - subject.move_repositories(namespace, 'hello-group', 'bye-group') - - expect(File.directory?(expected_path)).to be(true) - end - - it 'moves a namespace in a subdirectory correctly' do - child_namespace = create(:group, name: 'sub-group', parent: namespace) - create(:project, namespace: child_namespace, path: 'hello-project') - - expected_path = File.join(TestEnv.repos_path, 'hello-group', 'renamed-sub-group', 'hello-project.git') - - subject.move_repositories(child_namespace, 'hello-group/sub-group', 'hello-group/renamed-sub-group') - - expect(File.directory?(expected_path)).to be(true) - end - - it 'moves a parent namespace with subdirectories' do - child_namespace = create(:group, name: 'sub-group', parent: namespace) - create(:project, namespace: child_namespace, path: 'hello-project') - expected_path = File.join(TestEnv.repos_path, 'renamed-group', 'sub-group', 'hello-project.git') - - subject.move_repositories(child_namespace, 'hello-group', 'renamed-group') - - expect(File.directory?(expected_path)).to be(true) - end - end - - describe "#child_ids_for_parent" do - it "collects child ids for all levels" do - parent = create(:namespace) - first_child = create(:namespace, parent: parent) - second_child = create(:namespace, parent: parent) - third_child = create(:namespace, parent: second_child) - all_ids = [parent.id, first_child.id, second_child.id, third_child.id] - - collected_ids = subject.child_ids_for_parent(parent, ids: [parent.id]) - - expect(collected_ids).to contain_exactly(*all_ids) - end - end - - describe "#rename_namespace" do - let(:namespace) { create(:namespace, path: 'the-path') } - - it 'renames paths & routes for the namesapce' do - expect(subject).to receive(:rename_path_for_routable). - with(namespace). - and_call_original - - subject.rename_namespace(namespace) - end - - it "moves the the repository for a project in the namespace" do - create(:project, namespace: namespace, path: "the-path-project") - expected_repo = File.join(TestEnv.repos_path, "the-path0", "the-path-project.git") - - subject.rename_namespace(namespace) - - expect(File.directory?(expected_repo)).to be(true) - end - - it "moves the uploads for the namespace" do - expect(subject).to receive(:move_uploads).with("the-path", "the-path0") - - subject.rename_namespace(namespace) - end - - it "moves the pages for the namespace" do - expect(subject).to receive(:move_pages).with("the-path", "the-path0") - - subject.rename_namespace(namespace) - end - - it 'invalidates the markdown cache of related projects' - end - - describe '#rename_namespaces' do - let!(:top_level_namespace) { create(:namespace, path: 'the-path') } - let!(:child_namespace) do - create(:namespace, path: 'the-path', parent: create(:namespace)) - end - - it 'renames top level namespaces the namespace' do - expect(subject).to receive(:rename_namespace). - with(migration_namespace(top_level_namespace)) - - subject.rename_namespaces(['the-path'], type: :top_level) - end - - it 'renames child namespaces' do - expect(subject).to receive(:rename_namespace). - with(migration_namespace(child_namespace)) - - subject.rename_namespaces(['the-path'], type: :wildcard) - end - end -end diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/projects_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/projects_spec.rb deleted file mode 100644 index 1812e3ea7f4..00000000000 --- a/spec/lib/gitlab/database/rename_reserved_paths_migration/projects_spec.rb +++ /dev/null @@ -1,78 +0,0 @@ -require 'spec_helper' - -describe Gitlab::Database::RenameReservedPathsMigration::Projects, :truncate do - let(:subject) do - ActiveRecord::Migration.new.extend( - Gitlab::Database::RenameReservedPathsMigration - ) - end - - before do - allow(subject).to receive(:say) - end - - describe '#projects_for_paths' do - it 'includes the correct projects' do - project = create(:empty_project, path: 'THE-path') - _other_project = create(:empty_project) - - result_ids = subject.projects_for_paths(['the-PATH']).map(&:id) - - expect(result_ids).to contain_exactly(project.id) - end - end - - describe '#rename_project' do - let(:project) do - create(:empty_project, - path: 'the-path', - namespace: create(:namespace, path: 'known-parent' )) - end - - it 'renames path & route for the project' do - expect(subject).to receive(:rename_path_for_routable). - with(project). - and_call_original - - subject.rename_project(project) - end - - it 'moves the wiki & the repo' do - expect(subject).to receive(:move_repository). - with(project, 'known-parent/the-path.wiki', 'known-parent/the-path0.wiki') - expect(subject).to receive(:move_repository). - with(project, 'known-parent/the-path', 'known-parent/the-path0') - - subject.rename_project(project) - end - - it 'moves uploads' do - expect(subject).to receive(:move_uploads). - with('known-parent/the-path', 'known-parent/the-path0') - - subject.rename_project(project) - end - - it 'moves pages' do - expect(subject).to receive(:move_pages). - with('known-parent/the-path', 'known-parent/the-path0') - - subject.rename_project(project) - end - - it 'invalidates the markdown cache of related projects' - end - - describe '#move_repository' do - let(:known_parent) { create(:namespace, path: 'known-parent') } - let(:project) { create(:project, path: 'the-path', namespace: known_parent) } - - it 'moves the repository for a project' do - expected_path = File.join(TestEnv.repos_path, 'known-parent', 'new-repo.git') - - subject.move_repository(project, 'known-parent/the-path', 'known-parent/new-repo') - - expect(File.directory?(expected_path)).to be(true) - end - end -end diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/rename_base_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/rename_base_spec.rb new file mode 100644 index 00000000000..48234170d31 --- /dev/null +++ b/spec/lib/gitlab/database/rename_reserved_paths_migration/rename_base_spec.rb @@ -0,0 +1,147 @@ +require 'spec_helper' + +describe Gitlab::Database::RenameReservedPathsMigration::RenameBase do + let(:migration) { FakeRenameReservedPathMigration.new } + let(:subject) { described_class.new(['the-path'], migration) } + + before do + allow(migration).to receive(:say) + end + + def migration_namespace(namespace) + Gitlab::Database::RenameReservedPathsMigration::MigrationClasses:: + Namespace.find(namespace.id) + end + + def migration_project(project) + Gitlab::Database::RenameReservedPathsMigration::MigrationClasses:: + Project.find(project.id) + end + + describe "#remove_last_ocurrence" do + it "removes only the last occurance of a string" do + input = "this/is/a-word-to-replace/namespace/with/a-word-to-replace" + + expect(subject.remove_last_occurrence(input, "a-word-to-replace")) + .to eq("this/is/a-word-to-replace/namespace/with/") + end + end + + describe '#rename_path_for_routable' do + context 'for namespaces' do + let(:namespace) { create(:namespace, path: 'the-path') } + it "renames namespaces called the-path" do + subject.rename_path_for_routable(migration_namespace(namespace)) + + expect(namespace.reload.path).to eq("the-path0") + end + + it "renames the route to the namespace" do + subject.rename_path_for_routable(migration_namespace(namespace)) + + expect(Namespace.find(namespace.id).full_path).to eq("the-path0") + end + + it "renames the route for projects of the namespace" do + project = create(:project, path: "project-path", namespace: namespace) + + subject.rename_path_for_routable(migration_namespace(namespace)) + + expect(project.route.reload.path).to eq("the-path0/project-path") + end + + it 'returns the old & the new path' do + old_path, new_path = subject.rename_path_for_routable(migration_namespace(namespace)) + + expect(old_path).to eq('the-path') + expect(new_path).to eq('the-path0') + end + + context "the-path namespace -> subgroup -> the-path0 project" do + it "updates the route of the project correctly" do + subgroup = create(:group, path: "subgroup", parent: namespace) + project = create(:project, path: "the-path0", namespace: subgroup) + + subject.rename_path_for_routable(migration_namespace(namespace)) + + expect(project.route.reload.path).to eq("the-path0/subgroup/the-path0") + end + end + end + + context 'for projects' do + let(:parent) { create(:namespace, path: 'the-parent') } + let(:project) { create(:empty_project, path: 'the-path', namespace: parent) } + + it 'renames the project called `the-path`' do + subject.rename_path_for_routable(migration_project(project)) + + expect(project.reload.path).to eq('the-path0') + end + + it 'renames the route for the project' do + subject.rename_path_for_routable(project) + + expect(project.reload.route.path).to eq('the-parent/the-path0') + end + + it 'returns the old & new path' do + old_path, new_path = subject.rename_path_for_routable(migration_project(project)) + + expect(old_path).to eq('the-parent/the-path') + expect(new_path).to eq('the-parent/the-path0') + end + end + end + + describe '#move_pages' do + it 'moves the pages directory' do + expect(subject).to receive(:move_folders) + .with(TestEnv.pages_path, 'old-path', 'new-path') + + subject.move_pages('old-path', 'new-path') + end + end + + describe "#move_uploads" do + let(:test_dir) { File.join(Rails.root, 'tmp', 'tests', 'rename_reserved_paths') } + let(:uploads_dir) { File.join(test_dir, 'public', 'uploads') } + + it 'moves subdirectories in the uploads folder' do + expect(subject).to receive(:uploads_dir).and_return(uploads_dir) + expect(subject).to receive(:move_folders).with(uploads_dir, 'old_path', 'new_path') + + subject.move_uploads('old_path', 'new_path') + end + + it "doesn't move uploads when they are stored in object storage" do + expect(subject).to receive(:file_storage?).and_return(false) + expect(subject).not_to receive(:move_folders) + + subject.move_uploads('old_path', 'new_path') + end + end + + describe '#move_folders' do + let(:test_dir) { File.join(Rails.root, 'tmp', 'tests', 'rename_reserved_paths') } + let(:uploads_dir) { File.join(test_dir, 'public', 'uploads') } + + before do + FileUtils.remove_dir(test_dir) if File.directory?(test_dir) + FileUtils.mkdir_p(uploads_dir) + allow(subject).to receive(:uploads_dir).and_return(uploads_dir) + end + + it 'moves a folder with files' do + source = File.join(uploads_dir, 'parent-group', 'sub-group') + FileUtils.mkdir_p(source) + destination = File.join(uploads_dir, 'parent-group', 'moved-group') + FileUtils.touch(File.join(source, 'test.txt')) + expected_file = File.join(destination, 'test.txt') + + subject.move_folders(uploads_dir, File.join('parent-group', 'sub-group'), File.join('parent-group', 'moved-group')) + + expect(File.exist?(expected_file)).to be(true) + end + end +end diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/rename_namespaces_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/rename_namespaces_spec.rb new file mode 100644 index 00000000000..ee481e4610c --- /dev/null +++ b/spec/lib/gitlab/database/rename_reserved_paths_migration/rename_namespaces_spec.rb @@ -0,0 +1,152 @@ +require 'spec_helper' + +describe Gitlab::Database::RenameReservedPathsMigration::RenameNamespaces do + let(:migration) { FakeRenameReservedPathMigration.new } + let(:subject) { described_class.new(['the-path'], migration) } + + before do + allow(migration).to receive(:say) + end + + def migration_namespace(namespace) + Gitlab::Database::RenameReservedPathsMigration::MigrationClasses:: + Namespace.find(namespace.id) + end + + describe '#namespaces_for_paths' do + context 'for wildcard namespaces' do + it 'only returns child namespaces with the correct path' do + _root_namespace = create(:namespace, path: 'THE-path') + _other_path = create(:namespace, + path: 'other', + parent: create(:namespace)) + namespace = create(:namespace, + path: 'the-path', + parent: create(:namespace)) + + found_ids = subject.namespaces_for_paths(type: :wildcard). + map(&:id) + expect(found_ids).to contain_exactly(namespace.id) + end + end + + context 'for top levelnamespaces' do + it 'only returns child namespaces with the correct path' do + root_namespace = create(:namespace, path: 'the-path') + _other_path = create(:namespace, path: 'other') + _child_namespace = create(:namespace, + path: 'the-path', + parent: create(:namespace)) + + found_ids = subject.namespaces_for_paths(type: :top_level). + map(&:id) + expect(found_ids).to contain_exactly(root_namespace.id) + end + end + end + + describe '#move_repositories' do + let(:namespace) { create(:group, name: 'hello-group') } + it 'moves a project for a namespace' do + create(:project, namespace: namespace, path: 'hello-project') + expected_path = File.join(TestEnv.repos_path, 'bye-group', 'hello-project.git') + + subject.move_repositories(namespace, 'hello-group', 'bye-group') + + expect(File.directory?(expected_path)).to be(true) + end + + it 'moves a namespace in a subdirectory correctly' do + child_namespace = create(:group, name: 'sub-group', parent: namespace) + create(:project, namespace: child_namespace, path: 'hello-project') + + expected_path = File.join(TestEnv.repos_path, 'hello-group', 'renamed-sub-group', 'hello-project.git') + + subject.move_repositories(child_namespace, 'hello-group/sub-group', 'hello-group/renamed-sub-group') + + expect(File.directory?(expected_path)).to be(true) + end + + it 'moves a parent namespace with subdirectories' do + child_namespace = create(:group, name: 'sub-group', parent: namespace) + create(:project, namespace: child_namespace, path: 'hello-project') + expected_path = File.join(TestEnv.repos_path, 'renamed-group', 'sub-group', 'hello-project.git') + + subject.move_repositories(child_namespace, 'hello-group', 'renamed-group') + + expect(File.directory?(expected_path)).to be(true) + end + end + + describe "#child_ids_for_parent" do + it "collects child ids for all levels" do + parent = create(:namespace) + first_child = create(:namespace, parent: parent) + second_child = create(:namespace, parent: parent) + third_child = create(:namespace, parent: second_child) + all_ids = [parent.id, first_child.id, second_child.id, third_child.id] + + collected_ids = subject.child_ids_for_parent(parent, ids: [parent.id]) + + expect(collected_ids).to contain_exactly(*all_ids) + end + end + + describe "#rename_namespace" do + let(:namespace) { create(:namespace, path: 'the-path') } + + it 'renames paths & routes for the namespace' do + expect(subject).to receive(:rename_path_for_routable). + with(namespace). + and_call_original + + subject.rename_namespace(namespace) + + expect(namespace.reload.path).to eq('the-path0') + end + + it "moves the the repository for a project in the namespace" do + create(:project, namespace: namespace, path: "the-path-project") + expected_repo = File.join(TestEnv.repos_path, "the-path0", "the-path-project.git") + + subject.rename_namespace(namespace) + + expect(File.directory?(expected_repo)).to be(true) + end + + it "moves the uploads for the namespace" do + expect(subject).to receive(:move_uploads).with("the-path", "the-path0") + + subject.rename_namespace(namespace) + end + + it "moves the pages for the namespace" do + expect(subject).to receive(:move_pages).with("the-path", "the-path0") + + subject.rename_namespace(namespace) + end + + it 'invalidates the markdown cache of related projects' + end + + describe '#rename_namespaces' do + let!(:top_level_namespace) { create(:namespace, path: 'the-path') } + let!(:child_namespace) do + create(:namespace, path: 'the-path', parent: create(:namespace)) + end + + it 'renames top level namespaces the namespace' do + expect(subject).to receive(:rename_namespace). + with(migration_namespace(top_level_namespace)) + + subject.rename_namespaces(type: :top_level) + end + + it 'renames child namespaces' do + expect(subject).to receive(:rename_namespace). + with(migration_namespace(child_namespace)) + + subject.rename_namespaces(type: :wildcard) + end + end +end diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/rename_projects_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/rename_projects_spec.rb new file mode 100644 index 00000000000..4a572133b69 --- /dev/null +++ b/spec/lib/gitlab/database/rename_reserved_paths_migration/rename_projects_spec.rb @@ -0,0 +1,77 @@ +require 'spec_helper' + +describe Gitlab::Database::RenameReservedPathsMigration::RenameProjects do + let(:migration) { FakeRenameReservedPathMigration.new } + let(:subject) { described_class.new(['the-path'], migration) } + + before do + allow(migration).to receive(:say) + end + + describe '#projects_for_paths' do + it 'includes the correct projects' do + project = create(:empty_project, path: 'THE-path') + _other_project = create(:empty_project) + + result_ids = subject.projects_for_paths.map(&:id) + + expect(result_ids).to contain_exactly(project.id) + end + end + + describe '#rename_project' do + let(:project) do + create(:empty_project, + path: 'the-path', + namespace: create(:namespace, path: 'known-parent' )) + end + + it 'renames path & route for the project' do + expect(subject).to receive(:rename_path_for_routable). + with(project). + and_call_original + + subject.rename_project(project) + + expect(project.reload.path).to eq('the-path0') + end + + it 'moves the wiki & the repo' do + expect(subject).to receive(:move_repository). + with(project, 'known-parent/the-path.wiki', 'known-parent/the-path0.wiki') + expect(subject).to receive(:move_repository). + with(project, 'known-parent/the-path', 'known-parent/the-path0') + + subject.rename_project(project) + end + + it 'moves uploads' do + expect(subject).to receive(:move_uploads). + with('known-parent/the-path', 'known-parent/the-path0') + + subject.rename_project(project) + end + + it 'moves pages' do + expect(subject).to receive(:move_pages). + with('known-parent/the-path', 'known-parent/the-path0') + + subject.rename_project(project) + end + + it 'invalidates the markdown cache of related projects' + end + + describe '#move_repository' do + let(:known_parent) { create(:namespace, path: 'known-parent') } + let(:project) { create(:project, path: 'the-path', namespace: known_parent) } + + it 'moves the repository for a project' do + expected_path = File.join(TestEnv.repos_path, 'known-parent', 'new-repo.git') + + subject.move_repository(project, 'known-parent/the-path', 'known-parent/new-repo') + + expect(File.directory?(expected_path)).to be(true) + end + end +end diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration_spec.rb index d3021d330be..b46595ba628 100644 --- a/spec/lib/gitlab/database/rename_reserved_paths_migration_spec.rb +++ b/spec/lib/gitlab/database/rename_reserved_paths_migration_spec.rb @@ -1,11 +1,7 @@ require 'spec_helper' describe Gitlab::Database::RenameReservedPathsMigration do - let(:subject) do - ActiveRecord::Migration.new.extend( - Gitlab::Database::RenameReservedPathsMigration - ) - end + let(:subject) { FakeRenameReservedPathMigration.new } before do allow(subject).to receive(:say) @@ -13,14 +9,23 @@ describe Gitlab::Database::RenameReservedPathsMigration do describe '#rename_wildcard_paths' do it 'should rename namespaces' do - expect(subject).to receive(:rename_namespaces). - with(['first-path', 'second-path'], type: :wildcard) + rename_namespaces = double + expect(Gitlab::Database::RenameReservedPathsMigration::RenameNamespaces). + to receive(:new).with(['first-path', 'second-path'], subject). + and_return(rename_namespaces) + expect(rename_namespaces).to receive(:rename_namespaces). + with(type: :wildcard) subject.rename_wildcard_paths(['first-path', 'second-path']) end it 'should rename projects' do - expect(subject).to receive(:rename_projects).with(['the-path']) + rename_projects = double + expect(Gitlab::Database::RenameReservedPathsMigration::RenameProjects). + to receive(:new).with(['the-path'], subject). + and_return(rename_projects) + + expect(rename_projects).to receive(:rename_projects) subject.rename_wildcard_paths(['the-path']) end @@ -28,128 +33,14 @@ describe Gitlab::Database::RenameReservedPathsMigration do describe '#rename_root_paths' do it 'should rename namespaces' do - expect(subject).to receive(:rename_namespaces). - with(['the-path'], type: :top_level) + rename_namespaces = double + expect(Gitlab::Database::RenameReservedPathsMigration::RenameNamespaces). + to receive(:new).with(['the-path'], subject). + and_return(rename_namespaces) + expect(rename_namespaces).to receive(:rename_namespaces). + with(type: :top_level) subject.rename_root_paths('the-path') end end - - describe "#remove_last_ocurrence" do - it "removes only the last occurance of a string" do - input = "this/is/a-word-to-replace/namespace/with/a-word-to-replace" - - expect(subject.remove_last_occurrence(input, "a-word-to-replace")) - .to eq("this/is/a-word-to-replace/namespace/with/") - end - end - - describe '#rename_path_for_routable' do - context 'for namespaces' do - let(:namespace) { create(:namespace, path: 'the-path') } - it "renames namespaces called the-path" do - subject.rename_path_for_routable(namespace) - - expect(namespace.reload.path).to eq("the-path0") - end - - it "renames the route to the namespace" do - subject.rename_path_for_routable(namespace) - - expect(Namespace.find(namespace.id).full_path).to eq("the-path0") - end - - it "renames the route for projects of the namespace" do - project = create(:project, path: "project-path", namespace: namespace) - - subject.rename_path_for_routable(namespace) - - expect(project.route.reload.path).to eq("the-path0/project-path") - end - - it 'returns the old & the new path' do - old_path, new_path = subject.rename_path_for_routable(namespace) - - expect(old_path).to eq('the-path') - expect(new_path).to eq('the-path0') - end - - context "the-path namespace -> subgroup -> the-path0 project" do - it "updates the route of the project correctly" do - subgroup = create(:group, path: "subgroup", parent: namespace) - project = create(:project, path: "the-path0", namespace: subgroup) - - subject.rename_path_for_routable(namespace) - - expect(project.route.reload.path).to eq("the-path0/subgroup/the-path0") - end - end - end - - context 'for projects' do - let(:parent) { create(:namespace, path: 'the-parent') } - let(:project) { create(:empty_project, path: 'the-path', namespace: parent) } - - it 'renames the project called `the-path`' do - subject.rename_path_for_routable(project) - - expect(project.reload.path).to eq('the-path0') - end - - it 'renames the route for the project' do - subject.rename_path_for_routable(project) - - expect(project.reload.route.path).to eq('the-parent/the-path0') - end - - it 'returns the old & new path' do - old_path, new_path = subject.rename_path_for_routable(project) - - expect(old_path).to eq('the-parent/the-path') - expect(new_path).to eq('the-parent/the-path0') - end - end - end - - describe "#move_uploads" do - let(:test_dir) { File.join(Rails.root, 'tmp', 'tests', 'rename_reserved_paths') } - let(:uploads_dir) { File.join(test_dir, 'public', 'uploads') } - - it 'moves subdirectories in the uploads folder' do - expect(subject).to receive(:uploads_dir).and_return(uploads_dir) - expect(subject).to receive(:move_folders).with(uploads_dir, 'old_path', 'new_path') - - subject.move_uploads('old_path', 'new_path') - end - - it "doesn't move uploads when they are stored in object storage" do - expect(subject).to receive(:file_storage?).and_return(false) - expect(subject).not_to receive(:move_folders) - - subject.move_uploads('old_path', 'new_path') - end - end - - describe '#move_folders' do - let(:test_dir) { File.join(Rails.root, 'tmp', 'tests', 'rename_reserved_paths') } - let(:uploads_dir) { File.join(test_dir, 'public', 'uploads') } - - before do - FileUtils.remove_dir(test_dir) if File.directory?(test_dir) - FileUtils.mkdir_p(uploads_dir) - allow(subject).to receive(:uploads_dir).and_return(uploads_dir) - end - - it 'moves a folder with files' do - source = File.join(uploads_dir, 'parent-group', 'sub-group') - FileUtils.mkdir_p(source) - destination = File.join(uploads_dir, 'parent-group', 'moved-group') - FileUtils.touch(File.join(source, 'test.txt')) - expected_file = File.join(destination, 'test.txt') - - subject.move_folders(uploads_dir, File.join('parent-group', 'sub-group'), File.join('parent-group', 'moved-group')) - - expect(File.exist?(expected_file)).to be(true) - end - end end diff --git a/spec/support/fake_migration_classes.rb b/spec/support/fake_migration_classes.rb new file mode 100644 index 00000000000..7ff36fe434c --- /dev/null +++ b/spec/support/fake_migration_classes.rb @@ -0,0 +1,3 @@ +class FakeRenameReservedPathMigration < ActiveRecord::Migration + include Gitlab::Database::RenameReservedPathsMigration +end -- cgit v1.2.1