Commit 7508ee56 authored by Bob Van Landuyt's avatar Bob Van Landuyt

Make renaming records in the database reusable

So we can use it for projects
parent e3d69578
...@@ -15,11 +15,38 @@ module Gitlab ...@@ -15,11 +15,38 @@ module Gitlab
rename_namespaces(paths, type: :top_level) rename_namespaces(paths, type: :top_level)
end 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) def rename_path(namespace_path, path_was)
counter = 0 counter = 0
path = "#{path_was}#{counter}" path = "#{path_was}#{counter}"
while route_exists?(File.join(namespace_path, path)) while route_exists?(join_routable_path(namespace_path, path))
counter += 1 counter += 1
path = "#{path_was}#{counter}" path = "#{path_was}#{counter}"
end end
...@@ -27,6 +54,18 @@ module Gitlab ...@@ -27,6 +54,18 @@ module Gitlab
path path
end 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) def route_exists?(full_path)
MigrationClasses::Route.where(Route.arel_table[:path].matches(full_path)).any? MigrationClasses::Route.where(Route.arel_table[:path].matches(full_path)).any?
end end
......
...@@ -2,26 +2,7 @@ module Gitlab ...@@ -2,26 +2,7 @@ module Gitlab
module Database module Database
module RenameReservedPathsMigration module RenameReservedPathsMigration
module MigrationClasses module MigrationClasses
class User < ActiveRecord::Base module Routable
self.table_name = 'users'
end
class Namespace < ActiveRecord::Base
self.table_name = 'namespaces'
belongs_to :parent,
class_name: "#{MigrationClasses.name}::Namespace"
has_one :route, as: :source
has_many :children,
class_name: "#{MigrationClasses.name}::Namespace",
foreign_key: :parent_id
belongs_to :owner,
class_name: "#{MigrationClasses.name}::User"
# Overridden to have the correct `source_type` for the `route` relation
def self.name
'Namespace'
end
def full_path def full_path
if route && route.path.present? if route && route.path.present?
@full_path ||= route.path @full_path ||= route.path
...@@ -66,17 +47,45 @@ module Gitlab ...@@ -66,17 +47,45 @@ module Gitlab
end end
end end
class User < ActiveRecord::Base
self.table_name = 'users'
end
class Namespace < ActiveRecord::Base
include MigrationClasses::Routable
self.table_name = 'namespaces'
belongs_to :parent,
class_name: "#{MigrationClasses.name}::Namespace"
has_one :route, as: :source
has_many :children,
class_name: "#{MigrationClasses.name}::Namespace",
foreign_key: :parent_id
belongs_to :owner,
class_name: "#{MigrationClasses.name}::User"
# Overridden to have the correct `source_type` for the `route` relation
def self.name
'Namespace'
end
end
class Route < ActiveRecord::Base class Route < ActiveRecord::Base
self.table_name = 'routes' self.table_name = 'routes'
belongs_to :source, polymorphic: true belongs_to :source, polymorphic: true
end end
class Project < ActiveRecord::Base class Project < ActiveRecord::Base
include MigrationClasses::Routable
self.table_name = 'projects' self.table_name = 'projects'
def repository_storage_path def repository_storage_path
Gitlab.config.repositories.storages[repository_storage]['path'] Gitlab.config.repositories.storages[repository_storage]['path']
end end
# Overridden to have the correct `source_type` for the `route` relation
def self.name
'Project'
end
end end
end end
end end
......
...@@ -16,32 +16,11 @@ module Gitlab ...@@ -16,32 +16,11 @@ module Gitlab
elsif type == :top_level elsif type == :top_level
MigrationClasses::Namespace.where(parent_id: nil) MigrationClasses::Namespace.where(parent_id: nil)
end end
namespaces.where(path: paths.map(&:downcase)) namespaces.where('lower(path) in (?)', paths.map(&:downcase))
end end
def rename_namespace(namespace) def rename_namespace(namespace)
old_path = namespace.path old_full_path, new_full_path = rename_path_for_routable(namespace)
old_full_path = namespace.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 = if namespace_path.present?
File.join(namespace_path, new_path)
else
new_path
end
# skips callbacks & validations
MigrationClasses::Namespace.where(id: namespace).
update_all(path: new_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
move_repositories(namespace, old_full_path, new_full_path) move_repositories(namespace, old_full_path, new_full_path)
move_namespace_folders(uploads_dir, old_full_path, new_full_path) if file_storage? move_namespace_folders(uploads_dir, old_full_path, new_full_path) if file_storage?
...@@ -92,10 +71,6 @@ module Gitlab ...@@ -92,10 +71,6 @@ module Gitlab
ids ids
end end
def remove_last_occurrence(string, pattern)
string.reverse.sub(pattern.reverse, "").reverse
end
def file_storage? def file_storage?
CarrierWave::Uploader::Base.storage == CarrierWave::Storage::File CarrierWave::Uploader::Base.storage == CarrierWave::Storage::File
end end
......
...@@ -25,7 +25,7 @@ describe Gitlab::Database::RenameReservedPathsMigration::Namespaces, :truncate d ...@@ -25,7 +25,7 @@ describe Gitlab::Database::RenameReservedPathsMigration::Namespaces, :truncate d
describe '#namespaces_for_paths' do describe '#namespaces_for_paths' do
context 'for wildcard namespaces' do context 'for wildcard namespaces' do
it 'only returns child namespaces with the correct path' do it 'only returns child namespaces with the correct path' do
_root_namespace = create(:namespace, path: 'the-path') _root_namespace = create(:namespace, path: 'THE-path')
_other_path = create(:namespace, _other_path = create(:namespace,
path: 'other', path: 'other',
parent: create(:namespace)) parent: create(:namespace))
...@@ -33,13 +33,13 @@ describe Gitlab::Database::RenameReservedPathsMigration::Namespaces, :truncate d ...@@ -33,13 +33,13 @@ describe Gitlab::Database::RenameReservedPathsMigration::Namespaces, :truncate d
path: 'the-path', path: 'the-path',
parent: create(:namespace)) parent: create(:namespace))
found_ids = subject.namespaces_for_paths(['the-path'], type: :wildcard). found_ids = subject.namespaces_for_paths(['the-PATH'], type: :wildcard).
pluck(:id) map(&:id)
expect(found_ids).to contain_exactly(namespace.id) expect(found_ids).to contain_exactly(namespace.id)
end end
end end
context 'for top level namespaces' do context 'for top levelnamespaces' do
it 'only returns child namespaces with the correct path' do it 'only returns child namespaces with the correct path' do
root_namespace = create(:namespace, path: 'the-path') root_namespace = create(:namespace, path: 'the-path')
_other_path = create(:namespace, path: 'other') _other_path = create(:namespace, path: 'other')
...@@ -48,7 +48,7 @@ describe Gitlab::Database::RenameReservedPathsMigration::Namespaces, :truncate d ...@@ -48,7 +48,7 @@ describe Gitlab::Database::RenameReservedPathsMigration::Namespaces, :truncate d
parent: create(:namespace)) parent: create(:namespace))
found_ids = subject.namespaces_for_paths(['the-path'], type: :top_level). found_ids = subject.namespaces_for_paths(['the-path'], type: :top_level).
pluck(:id) map(&:id)
expect(found_ids).to contain_exactly(root_namespace.id) expect(found_ids).to contain_exactly(root_namespace.id)
end end
end end
...@@ -127,35 +127,15 @@ describe Gitlab::Database::RenameReservedPathsMigration::Namespaces, :truncate d ...@@ -127,35 +127,15 @@ describe Gitlab::Database::RenameReservedPathsMigration::Namespaces, :truncate d
end end
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_namespace" do describe "#rename_namespace" do
let(:namespace) { create(:namespace, path: 'the-path') } let(:namespace) { create(:namespace, path: 'the-path') }
it "renames namespaces called the-path" do
subject.rename_namespace(namespace)
expect(namespace.reload.path).to eq("the-path0")
end
it "renames the route to the namespace" do
subject.rename_namespace(namespace)
expect(Namespace.find(namespace.id).full_path).to eq("the-path0")
end
it "renames the route for projects of the namespace" do it 'renames paths & routes for the namesapce' do
project = create(:project, path: "project-path", namespace: namespace) expect(subject).to receive(:rename_path_for_routable).
with(namespace).
and_call_original
subject.rename_namespace(namespace) subject.rename_namespace(namespace)
expect(project.route.reload.path).to eq("the-path0/project-path")
end end
it "moves the the repository for a project in the namespace" do it "moves the the repository for a project in the namespace" do
...@@ -180,29 +160,26 @@ describe Gitlab::Database::RenameReservedPathsMigration::Namespaces, :truncate d ...@@ -180,29 +160,26 @@ describe Gitlab::Database::RenameReservedPathsMigration::Namespaces, :truncate d
subject.rename_namespace(namespace) subject.rename_namespace(namespace)
end end
end
context "the-path namespace -> subgroup -> the-path0 project" do describe '#rename_namespaces' do
it "updates the route of the project correctly" do let!(:top_level_namespace) { create(:namespace, path: 'the-path') }
subgroup = create(:group, path: "subgroup", parent: namespace) let!(:child_namespace) do
project = create(:project, path: "the-path0", namespace: subgroup) create(:namespace, path: 'the-path', parent: create(:namespace))
end
subject.rename_namespace(namespace) it 'renames top level namespaces the namespace' do
expect(subject).to receive(:rename_namespace).
with(migration_namespace(top_level_namespace))
expect(project.route.reload.path).to eq("the-path0/subgroup/the-path0") subject.rename_namespaces(['the-path'], type: :top_level)
end
end end
end
describe '#rename_namespaces' do it 'renames child namespaces' do
context 'top level namespaces' do expect(subject).to receive(:rename_namespace).
let!(:namespace) { create(:namespace, path: 'the-path') } with(migration_namespace(child_namespace))
it 'should rename the namespace' do subject.rename_namespaces(['the-path'], type: :wildcard)
expect(subject).to receive(:rename_namespace).
with(migration_namespace(namespace))
subject.rename_namespaces(['the-path'], type: :top_level)
end
end end
end end
end end
...@@ -7,6 +7,10 @@ describe Gitlab::Database::RenameReservedPathsMigration do ...@@ -7,6 +7,10 @@ describe Gitlab::Database::RenameReservedPathsMigration do
) )
end end
before do
allow(subject).to receive(:say)
end
describe '#rename_wildcard_paths' do describe '#rename_wildcard_paths' do
it 'should rename namespaces' do it 'should rename namespaces' do
expect(subject).to receive(:rename_namespaces). expect(subject).to receive(:rename_namespaces).
...@@ -26,4 +30,80 @@ describe Gitlab::Database::RenameReservedPathsMigration do ...@@ -26,4 +30,80 @@ describe Gitlab::Database::RenameReservedPathsMigration do
subject.rename_root_paths('the-path') subject.rename_root_paths('the-path')
end end
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
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