diff options
author | Bob Van Landuyt <bob@gitlab.com> | 2017-04-12 20:14:22 +0200 |
---|---|---|
committer | Bob Van Landuyt <bob@gitlab.com> | 2017-05-01 11:14:24 +0200 |
commit | 58bc628d3051d6c97b9592985b43aa741a87d086 (patch) | |
tree | 314c65b0efb0defaf165e83cdbff74cf48422bd1 /lib | |
parent | 9fb9414ec0787a0414c912bb7b62103f96c48d34 (diff) | |
download | gitlab-ce-58bc628d3051d6c97b9592985b43aa741a87d086.tar.gz |
Rename namespace-paths in a migration helper
Diffstat (limited to 'lib')
3 files changed, 232 insertions, 0 deletions
diff --git a/lib/gitlab/database/rename_reserved_paths_migration.rb b/lib/gitlab/database/rename_reserved_paths_migration.rb new file mode 100644 index 00000000000..ad5570b4c72 --- /dev/null +++ b/lib/gitlab/database/rename_reserved_paths_migration.rb @@ -0,0 +1,35 @@ +module Gitlab + module Database + module RenameReservedPathsMigration + include MigrationHelpers + include Namespaces + include Projects + + def rename_wildcard_paths(one_or_more_paths) + paths = Array(one_or_more_paths) + rename_namespaces(paths, type: :wildcard) + end + + def rename_root_paths(paths) + paths = Array(paths) + rename_namespaces(paths, type: :top_level) + end + + def rename_path(namespace_path, path_was) + counter = 0 + path = "#{path_was}#{counter}" + + while route_exists?(File.join(namespace_path, path)) + counter += 1 + path = "#{path_was}#{counter}" + end + + path + end + + def route_exists?(full_path) + MigrationClasses::Route.where(Route.arel_table[:path].matches(full_path)).any? + end + 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 new file mode 100644 index 00000000000..a919d250541 --- /dev/null +++ b/lib/gitlab/database/rename_reserved_paths_migration/migration_classes.rb @@ -0,0 +1,84 @@ +module Gitlab + module Database + module RenameReservedPathsMigration + module MigrationClasses + class User < ActiveRecord::Base + 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 + if route && route.path.present? + @full_path ||= route.path + else + update_route if persisted? + + build_full_path + end + end + + def build_full_path + if parent && path + parent.full_path + '/' + path + else + path + end + end + + def update_route + prepare_route + route.save + end + + def prepare_route + route || build_route(source: self) + route.path = build_full_path + route.name = build_full_name + @full_path = nil + @full_name = nil + end + + def build_full_name + if parent && name + parent.human_name + ' / ' + name + else + name + end + end + + def human_name + owner&.name + end + end + + class Route < ActiveRecord::Base + self.table_name = 'routes' + belongs_to :source, polymorphic: true + end + + class Project < ActiveRecord::Base + self.table_name = 'projects' + + def repository_storage_path + Gitlab.config.repositories.storages[repository_storage]['path'] + end + end + end + end + end +end diff --git a/lib/gitlab/database/rename_reserved_paths_migration/namespaces.rb b/lib/gitlab/database/rename_reserved_paths_migration/namespaces.rb new file mode 100644 index 00000000000..f8fbeaa990a --- /dev/null +++ b/lib/gitlab/database/rename_reserved_paths_migration/namespaces.rb @@ -0,0 +1,113 @@ +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 + namespaces.where(path: paths.map(&:downcase)) + end + + def rename_namespace(namespace) + old_path = namespace.path + 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_namespace_folders(uploads_dir, old_full_path, new_full_path) if file_storage? + move_namespace_folders(pages_dir, old_full_path, new_full_path) + end + + def move_namespace_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 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 + + def remove_last_occurrence(string, pattern) + string.reverse.sub(pattern.reverse, "").reverse + 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 |