gitlab-org--gitlab-foss/db/post_migrate/20161221153951_rename_reserved_project_names.rb
Yorick Peterse 4b9c17f196
Move Project#rename_repo to a service class
This moves the logic of Project#rename_repo and all methods _only_ used
by this method into a new service class: Projects::AfterRenameService.
By moving this code into a separate service class we can more easily
refactor it, and we also get rid of some RuboCop "disable" statements
automatically.

During the refactoring of this code, I removed most of the explicit
logging using Gitlab::AppLogger. The data that was logged would not be
useful when debugging renaming issues, as it does not add any value on
top of data provided by users.

I also removed a variety of comments that either mentioned something the
code does in literal form, or contained various grammatical errors.
Instead we now resort to more clearly named methods, removing the need
for code comments.

This method was chosen based on analysis in
https://gitlab.com/gitlab-org/release/framework/issues/28. In this issue
we determined this method has seen a total of 293 lines being changed in
it. We also noticed that RuboCop determined the ABC size
(https://www.softwarerenovation.com/ABCMetric.pdf) was too great.
2018-10-22 15:12:46 +02:00

130 lines
3.2 KiB
Ruby

class RenameReservedProjectNames < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
include Gitlab::ShellAdapter
DOWNTIME = false
THREAD_COUNT = 8
KNOWN_PATHS = %w(.well-known
all
blame
blob
commits
create
create_dir
edit
files
find_file
groups
hooks
issues
logs_tree
merge_requests
new
preview
projects
raw
repository
robots.txt
s
snippets
teams
tree
u
unsubscribes
update
users
wikis).freeze
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
if rename_project_row(project, path)
Projects::AfterRenameService.new(project).execute
end
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(path: path) &&
defined?(Projects::AfterRenameService)
end
end