From d72b40423c6736fd24ef4371604897871a1b3acc Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 21 Dec 2016 19:31:30 +0200 Subject: Rename projects with reserved path names We cant have project with name 'project' or 'tree' anymore. This merge request containts a migration that will find and rename all projects using reserved names by adding N digit to the end of the name. Signed-off-by: Dmitriy Zaporozhets --- app/models/project.rb | 4 +- .../dz-rename-reserved-project-names.yml | 4 ++ ...20161221153951_rename_reserved_project_names.rb | 76 ++++++++++++++++++++++ db/schema.rb | 2 +- .../rename_reserved_project_names_spec.rb | 45 +++++++++++++ 5 files changed, 128 insertions(+), 3 deletions(-) create mode 100644 changelogs/unreleased/dz-rename-reserved-project-names.yml create mode 100644 db/post_migrate/20161221153951_rename_reserved_project_names.rb create mode 100644 spec/migrations/rename_reserved_project_names_spec.rb diff --git a/app/models/project.rb b/app/models/project.rb index 26fa20f856d..5fd6c86fd70 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -921,7 +921,7 @@ class Project < ActiveRecord::Base Rails.logger.error "Project #{old_path_with_namespace} cannot be renamed because container registry tags are present" # we currently doesn't support renaming repository if it contains tags in container registry - raise Exception.new('Project cannot be renamed, because tags are present in its container registry') + raise StandardError.new('Project cannot be renamed, because tags are present in its container registry') end if gitlab_shell.mv_repository(repository_storage_path, old_path_with_namespace, new_path_with_namespace) @@ -948,7 +948,7 @@ class Project < ActiveRecord::Base # if we cannot move namespace directory we should rollback # db changes in order to prevent out of sync between db and fs - raise Exception.new('repository cannot be renamed') + raise StandardError.new('repository cannot be renamed') end Gitlab::AppLogger.info "Project was renamed: #{old_path_with_namespace} -> #{new_path_with_namespace}" diff --git a/changelogs/unreleased/dz-rename-reserved-project-names.yml b/changelogs/unreleased/dz-rename-reserved-project-names.yml new file mode 100644 index 00000000000..30bcc1a0396 --- /dev/null +++ b/changelogs/unreleased/dz-rename-reserved-project-names.yml @@ -0,0 +1,4 @@ +--- +title: Rename projects wth reserved names +merge_request: 8234 +author: diff --git a/db/post_migrate/20161221153951_rename_reserved_project_names.rb b/db/post_migrate/20161221153951_rename_reserved_project_names.rb new file mode 100644 index 00000000000..7e7342d80f9 --- /dev/null +++ b/db/post_migrate/20161221153951_rename_reserved_project_names.rb @@ -0,0 +1,76 @@ +class RenameReservedProjectNames < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + include Gitlab::ShellAdapter + + DOWNTIME = false + + class Project < ActiveRecord::Base; end + + def up + threads = reserved_projects.each_slice(100).map do |slice| + Thread.new do + rename_projects(slice) + end + end + + threads.each(&:join) + end + + def down + # nothing to do here + end + + private + + def reserved_projects + select_all("SELECT p.id, p.path, p.repository_storage, n.path AS namespace_path, n.id AS namespace_id FROM projects p + INNER JOIN namespaces n ON n.id = p.namespace_id + WHERE p.path IN ( + '.well-known', 'all', 'assets', 'files', 'groups', 'hooks', 'issues', + 'merge_requests', 'new', 'profile', 'projects', 'public', 'repository', + 'robots.txt', 's', 'snippets', 'teams', 'u', 'unsubscribes', 'users', + 'tree', 'commits', 'wikis', 'new', 'edit', 'create', 'update', 'logs_tree', + 'preview', 'blob', 'blame', 'raw', 'files', 'create_dir', 'find_file')") + end + + def route_exists?(full_path) + select_all("SELECT id, path FROM routes WHERE path = '#{quote_string(full_path)}'").present? + end + + # Adds number to the end of the path that is not taken by other route + def rename_path(namespace_path, path_was) + counter = 0 + path = "#{path_was}#{counter}" + + while route_exists?("#{namespace_path}/#{path}") + counter += 1 + path = "#{path_was}#{counter}" + end + + path + end + + def rename_projects(projects) + projects.each do |row| + id = row['id'] + path_was = row['path'] + namespace_path = row['namespace_path'] + path = rename_path(namespace_path, path_was) + project = Project.find_by(id: id) + + begin + # Because project path update is quite complex operation we can't safely + # copy-paste all code from GitLab. As exception we use Rails code here + if project && + project.respond_to?(:update_attributes) && + project.update_attributes(path: path) && + project.respond_to?(:rename_repo) + + project.rename_repo + end + rescue => e + Rails.logger.error "Exception when rename project #{id}: #{e.message}" + end + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 05b6c807660..e48ce9f4ca5 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20161221140236) do +ActiveRecord::Schema.define(version: 20161221153951) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" diff --git a/spec/migrations/rename_reserved_project_names_spec.rb b/spec/migrations/rename_reserved_project_names_spec.rb new file mode 100644 index 00000000000..66f68570b50 --- /dev/null +++ b/spec/migrations/rename_reserved_project_names_spec.rb @@ -0,0 +1,45 @@ +# encoding: utf-8 + +require 'spec_helper' +require Rails.root.join('db', 'post_migrate', '20161221153951_rename_reserved_project_names.rb') + +describe RenameReservedProjectNames do + let(:migration) { described_class.new } + let!(:project) { create(:project) } + + before do + project.path = 'projects' + project.save!(validate: false) + allow(Thread).to receive(:new).and_yield + end + + describe '#up' do + context 'when project repository exists' do + before { project.create_repository } + + context 'when no exception is raised' do + it 'renames project with reserved names' do + migration.up + + expect(project.reload.path).to eq('projects0') + end + end + + context 'when exception is raised during rename' do + before do + allow(project).to receive(:rename_repo).and_raise(StandardError) + end + + it 'captures exception from project rename' do + expect { migration.up }.not_to raise_error + end + end + end + + context 'when project repository does not exist' do + it 'does not raise error' do + expect { migration.up }.not_to raise_error + end + end + end +end -- cgit v1.2.1