summaryrefslogtreecommitdiff
path: root/db/post_migrate/20161221153951_rename_reserved_project_names.rb
blob: 49a6bc884a8320f1fc4e9c335c91ec9161e80952 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
require 'thread'

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
        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