232 lines
6.4 KiB
Ruby
232 lines
6.4 KiB
Ruby
|
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
|
||
|
# for more information on how to write migrations for GitLab.
|
||
|
class RenameSystemNamespaces < ActiveRecord::Migration
|
||
|
include Gitlab::Database::MigrationHelpers
|
||
|
include Gitlab::ShellAdapter
|
||
|
disable_ddl_transaction!
|
||
|
|
||
|
class User < ActiveRecord::Base
|
||
|
self.table_name = 'users'
|
||
|
end
|
||
|
|
||
|
class Namespace < ActiveRecord::Base
|
||
|
self.table_name = 'namespaces'
|
||
|
belongs_to :parent, class_name: 'RenameSystemNamespaces::Namespace'
|
||
|
has_one :route, as: :source
|
||
|
has_many :children, class_name: 'RenameSystemNamespaces::Namespace', foreign_key: :parent_id
|
||
|
belongs_to :owner, class_name: 'RenameSystemNamespaces::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
|
||
|
|
||
|
DOWNTIME = false
|
||
|
|
||
|
def up
|
||
|
return unless system_namespace
|
||
|
|
||
|
old_path = system_namespace.path
|
||
|
old_full_path = system_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 = join_namespace_path(namespace_path, new_path)
|
||
|
|
||
|
Namespace.where(id: system_namespace).update_all(path: new_path) # skips callbacks & validations
|
||
|
|
||
|
replace_statement = replace_sql(Route.arel_table[:path], old_full_path, new_full_path)
|
||
|
route_matches = [old_full_path, "#{old_full_path}/%"]
|
||
|
|
||
|
update_column_in_batches(:routes, :path, replace_statement) do |table, query|
|
||
|
query.where(Route.arel_table[:path].matches_any(route_matches))
|
||
|
end
|
||
|
|
||
|
clear_cache_for_namespace(system_namespace)
|
||
|
|
||
|
# tasks here are based on `Namespace#move_dir`
|
||
|
move_repositories(system_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 down
|
||
|
# nothing to do
|
||
|
end
|
||
|
|
||
|
def remove_last_occurrence(string, pattern)
|
||
|
string.reverse.sub(pattern.reverse, "").reverse
|
||
|
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)
|
||
|
say "Exception moving path #{repository_storage_path} from #{old_full_path} to #{new_full_path}"
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def rename_path(namespace_path, path_was)
|
||
|
counter = 0
|
||
|
path = "#{path_was}#{counter}"
|
||
|
|
||
|
while route_exists?(join_namespace_path(namespace_path, path))
|
||
|
counter += 1
|
||
|
path = "#{path_was}#{counter}"
|
||
|
end
|
||
|
|
||
|
path
|
||
|
end
|
||
|
|
||
|
def route_exists?(full_path)
|
||
|
Route.where(Route.arel_table[:path].matches(full_path)).any?
|
||
|
end
|
||
|
|
||
|
def join_namespace_path(namespace_path, path)
|
||
|
if namespace_path.present?
|
||
|
File.join(namespace_path, path)
|
||
|
else
|
||
|
path
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def system_namespace
|
||
|
@system_namespace ||= Namespace.where(parent_id: nil).
|
||
|
where(arel_table[:path].matches(system_namespace_path)).
|
||
|
first
|
||
|
end
|
||
|
|
||
|
def system_namespace_path
|
||
|
"system"
|
||
|
end
|
||
|
|
||
|
def clear_cache_for_namespace(namespace)
|
||
|
project_ids = projects_for_namespace(namespace).pluck(:id)
|
||
|
|
||
|
update_column_in_batches(:projects, :description_html, nil) do |table, query|
|
||
|
query.where(table[:id].in(project_ids))
|
||
|
end
|
||
|
|
||
|
update_column_in_batches(:issues, :description_html, nil) do |table, query|
|
||
|
query.where(table[:project_id].in(project_ids))
|
||
|
end
|
||
|
|
||
|
update_column_in_batches(:merge_requests, :description_html, nil) do |table, query|
|
||
|
query.where(table[:target_project_id].in(project_ids))
|
||
|
end
|
||
|
|
||
|
update_column_in_batches(:notes, :note_html, nil) do |table, query|
|
||
|
query.where(table[:project_id].in(project_ids))
|
||
|
end
|
||
|
|
||
|
update_column_in_batches(:milestones, :description_html, nil) do |table, query|
|
||
|
query.where(table[:project_id].in(project_ids))
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def projects_for_namespace(namespace)
|
||
|
namespace_ids = child_ids_for_parent(namespace, ids: [namespace.id])
|
||
|
namespace_or_children = Project.arel_table[:namespace_id].in(namespace_ids)
|
||
|
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 repo_paths_for_namespace(namespace)
|
||
|
projects_for_namespace(namespace).distinct.
|
||
|
select(:repository_storage).map(&:repository_storage_path)
|
||
|
end
|
||
|
|
||
|
def uploads_dir
|
||
|
File.join(Rails.root, "public", "uploads")
|
||
|
end
|
||
|
|
||
|
def pages_dir
|
||
|
Settings.pages.path
|
||
|
end
|
||
|
|
||
|
def file_storage?
|
||
|
CarrierWave::Uploader::Base.storage == CarrierWave::Storage::File
|
||
|
end
|
||
|
|
||
|
def arel_table
|
||
|
Namespace.arel_table
|
||
|
end
|
||
|
end
|