summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>2016-12-27 19:01:45 +0000
committerDouglas Barbosa Alexandre <dbalexandre@gmail.com>2017-01-05 15:45:29 -0200
commit4b19c1ac665fb58108e6c6057be4187cf8f40ac8 (patch)
treeeda6d0fb9408730fff8adbca0fd2fae05e480617
parent8fcb955923a64e32820a6fdde2a80a0a8ec4fbea (diff)
downloadgitlab-ce-4b19c1ac665fb58108e6c6057be4187cf8f40ac8.tar.gz
Merge branch 'dz-rename-reserved-project-names' into 'master'
Rename projects with reserved path names See merge request !8234
-rw-r--r--app/models/project.rb4
-rw-r--r--changelogs/unreleased/dz-rename-reserved-project-names.yml4
-rw-r--r--db/post_migrate/20161221153951_rename_reserved_project_names.rb135
-rw-r--r--db/schema.rb2
-rw-r--r--spec/migrations/rename_reserved_project_names_spec.rb47
5 files changed, 189 insertions, 3 deletions
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..7f7c2424a5c
--- /dev/null
+++ b/db/post_migrate/20161221153951_rename_reserved_project_names.rb
@@ -0,0 +1,135 @@
+require 'thread'
+
+class RenameReservedProjectNames < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+ include Gitlab::ShellAdapter
+
+ DOWNTIME = false
+
+ THREAD_COUNT = 8
+
+ KNOWN_PATHS = %w(.well-known
+ all
+ assets
+ blame
+ blob
+ commits
+ create
+ create_dir
+ edit
+ files
+ files
+ find_file
+ groups
+ hooks
+ issues
+ logs_tree
+ merge_requests
+ new
+ new
+ preview
+ profile
+ projects
+ public
+ raw
+ repository
+ robots.txt
+ s
+ snippets
+ teams
+ tree
+ u
+ unsubscribes
+ update
+ users
+ wikis)
+
+ def up
+ queues = Array.new(THREAD_COUNT) { Queue.new }
+ start = false
+
+ threads = Array.new(THREAD_COUNT) do |index|
+ Thread.new do
+ queue = queues[index]
+
+ # Wait until we have input to process.
+ until start; end
+
+ rename_projects(queue.pop) until queue.empty?
+ end
+ end
+
+ enum = queues.each
+
+ reserved_projects.each_slice(100) do |slice|
+ begin
+ queue = enum.next
+ rescue StopIteration
+ enum.rewind
+ retry
+ end
+
+ queue << slice
+ end
+
+ start = true
+
+ threads.each(&:join)
+ end
+
+ def down
+ # nothing to do here
+ end
+
+ private
+
+ def reserved_projects
+ Project.unscoped.
+ includes(:namespace).
+ where('EXISTS (SELECT 1 FROM namespaces WHERE projects.namespace_id = namespaces.id)').
+ where('projects.path' => KNOWN_PATHS)
+ end
+
+ def route_exists?(full_path)
+ quoted_path = ActiveRecord::Base.connection.quote_string(full_path)
+
+ ActiveRecord::Base.connection.
+ select_all("SELECT id, path FROM routes WHERE path = '#{quoted_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 |project|
+ id = project.id
+ path_was = project.path
+ namespace_path = project.namespace.path
+ path = rename_path(namespace_path, path_was)
+
+ 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
+ project.rename_repo if rename_project_row(project, path)
+ rescue Exception => e # rubocop: disable Lint/RescueException
+ Rails.logger.error "Exception when renaming project #{id}: #{e.message}"
+ end
+ end
+ end
+
+ def rename_project_row(project, path)
+ project.respond_to?(:update_attributes) &&
+ project.update_attributes(path: path) &&
+ project.respond_to?(:rename_repo)
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 05b6c807660..eab6d7b3800 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -1293,4 +1293,4 @@ ActiveRecord::Schema.define(version: 20161221140236) do
add_foreign_key "subscriptions", "projects", on_delete: :cascade
add_foreign_key "trending_projects", "projects", on_delete: :cascade
add_foreign_key "u2f_registrations", "users"
-end
+end \ No newline at end of file
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..4fb7ed36884
--- /dev/null
+++ b/spec/migrations/rename_reserved_project_names_spec.rb
@@ -0,0 +1,47 @@
+# encoding: utf-8
+
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20161221153951_rename_reserved_project_names.rb')
+
+# This migration uses multiple threads, and thus different transactions. This
+# means data created in this spec may not be visible to some threads. To work
+# around this we use the TRUNCATE cleaning strategy.
+describe RenameReservedProjectNames, truncate: true do
+ let(:migration) { described_class.new }
+ let!(:project) { create(:empty_project) }
+
+ before do
+ project.path = 'projects'
+ project.save!(validate: false)
+ 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