From df5c3f364a3d415e35a2da462f044b08b854285b Mon Sep 17 00:00:00 2001
From: Bob Van Landuyt <bob@gitlab.com>
Date: Thu, 18 May 2017 23:31:03 +0200
Subject: [PATCH 1/5] Rename users that had their namespace renamed

---
 ...835_rename_users_with_renamed_namespace.rb | 43 +++++++++++++++++++
 db/schema.rb                                  |  2 +-
 ...ename_users_with_renamed_namespace_spec.rb | 19 ++++++++
 3 files changed, 63 insertions(+), 1 deletion(-)
 create mode 100644 db/post_migrate/20170518200835_rename_users_with_renamed_namespace.rb
 create mode 100644 spec/migrations/rename_users_with_renamed_namespace_spec.rb

diff --git a/db/post_migrate/20170518200835_rename_users_with_renamed_namespace.rb b/db/post_migrate/20170518200835_rename_users_with_renamed_namespace.rb
new file mode 100644
index 00000000000..a0444f4cc62
--- /dev/null
+++ b/db/post_migrate/20170518200835_rename_users_with_renamed_namespace.rb
@@ -0,0 +1,43 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class RenameUsersWithRenamedNamespace < ActiveRecord::Migration
+  include Gitlab::Database::MigrationHelpers
+
+  DOWNTIME = false
+  DISALLOWED_ROOT_PATHS = %w[
+    -
+    abuse_reports
+    api
+    autocomplete
+    explore
+    health_check
+    import
+    invites
+    jwt
+    koding
+    member
+    notification_settings
+    oauth
+    sent_notifications
+    unicorn_test
+    uploads
+    users
+  ]
+
+  def up
+    namespace_table = Arel::Table.new('namespaces')
+    users_table = Arel::Table.new('users')
+    matching_path = namespace_table.project(namespace_table[:path])
+                      .join(users_table).on(users_table[:id].eq(namespace_table[:owner_id]))
+                      .where(users_table[:username].not_eq(namespace_table[:path]))
+    path_name = Arel::Nodes::SqlLiteral.new("matching_path.path FROM (#{matching_path.to_sql}) as matching_path")
+
+    update_column_in_batches(:users, :username, path_name) do |table, query|
+      query.where(table[:username].matches_any(DISALLOWED_ROOT_PATHS))
+    end
+  end
+
+  def down
+  end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 294e0b531eb..8c7da682807 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: 20170516183131) do
+ActiveRecord::Schema.define(version: 20170518200835) do
 
   # These are extensions that must be enabled in order to support this database
   enable_extension "plpgsql"
diff --git a/spec/migrations/rename_users_with_renamed_namespace_spec.rb b/spec/migrations/rename_users_with_renamed_namespace_spec.rb
new file mode 100644
index 00000000000..aefa539094f
--- /dev/null
+++ b/spec/migrations/rename_users_with_renamed_namespace_spec.rb
@@ -0,0 +1,19 @@
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20170518200835_rename_users_with_renamed_namespace.rb')
+
+describe RenameUsersWithRenamedNamespace, truncate: true do
+  it 'renames a user that had his namespace renamed to the namespace path' do
+    other_user = create(:user, username: 'kodingu')
+
+    user = create(:user, username: "Users0")
+    user.update_attribute(:username, 'Users')
+    user1 = create(:user, username: "import0")
+    user1.update_attribute(:username, 'import')
+
+    described_class.new.up
+
+    expect(user.reload.username).to eq('Users0')
+    expect(user1.reload.username).to eq('import0')
+    expect(other_user.reload.username).to eq('kodingu')
+  end
+end
-- 
GitLab


From 84f8cd1718857b4ee75fa6bd4b2fdb4879846bcf Mon Sep 17 00:00:00 2001
From: Bob Van Landuyt <bob@gitlab.com>
Date: Fri, 19 May 2017 12:17:13 +0200
Subject: [PATCH 2/5] Fix incorrectly renamed routes

---
 ...170518231126_fix_wrongly_renamed_routes.rb | 104 ++++++++++++++++++
 db/schema.rb                                  |   2 +-
 .../fix_wrongly_renamed_routes_spec.rb        |  73 ++++++++++++
 3 files changed, 178 insertions(+), 1 deletion(-)
 create mode 100644 db/post_migrate/20170518231126_fix_wrongly_renamed_routes.rb
 create mode 100644 spec/migrations/fix_wrongly_renamed_routes_spec.rb

diff --git a/db/post_migrate/20170518231126_fix_wrongly_renamed_routes.rb b/db/post_migrate/20170518231126_fix_wrongly_renamed_routes.rb
new file mode 100644
index 00000000000..c78beda9d21
--- /dev/null
+++ b/db/post_migrate/20170518231126_fix_wrongly_renamed_routes.rb
@@ -0,0 +1,104 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class FixWronglyRenamedRoutes < ActiveRecord::Migration
+  include Gitlab::Database::RenameReservedPathsMigration::V1
+
+  DOWNTIME = false
+
+  disable_ddl_transaction!
+
+  DISALLOWED_ROOT_PATHS = %w[
+    -
+    abuse_reports
+    api
+    autocomplete
+    explore
+    health_check
+    import
+    invites
+    jwt
+    koding
+    member
+    notification_settings
+    oauth
+    sent_notifications
+    unicorn_test
+    uploads
+    users
+  ]
+
+  FIXED_PATHS = DISALLOWED_ROOT_PATHS.map { |p| "#{p}0" }
+
+  class Route < Gitlab::Database::RenameReservedPathsMigration::V1::MigrationClasses::Route
+    self.table_name = 'routes'
+  end
+
+  def routes
+    @routes ||= Route.arel_table
+  end
+
+  def namespaces
+    @namespaces ||= Arel::Table.new(:namespaces)
+  end
+
+  def wildcard_collection(collection)
+    collection.map { |word| "#{word}%" }
+  end
+
+  # The routes that got incorrectly renamed before, still have a namespace that
+  # contains the correct path.
+  # This query fetches all rows from the `routes` table that meet the following
+  # conditions using `api` as an example:
+  # - route.path ILIKE `api0%`
+  # - route.source_type = `Namespace`
+  # - namespace.parent_id IS NULL
+  # - namespace.path ILIKE `api%`
+  # - NOT(namespace.path ILIKE `api0%`)
+  # This gives us all root-routes, that were renamed, but their namespace was not.
+  #
+  def wrongly_renamed
+    Route.joins("INNER JOIN namespaces ON routes.source_id = namespaces.id")
+      .where(
+        routes[:source_type].eq('Namespace')
+          .and(namespaces[:parent_id].eq(nil))
+      )
+      .where(namespaces[:path].matches_any(wildcard_collection(DISALLOWED_ROOT_PATHS)))
+      .where.not(namespaces[:path].matches_any(wildcard_collection(FIXED_PATHS)))
+      .where(routes[:path].matches_any(wildcard_collection(FIXED_PATHS)))
+  end
+
+  # Using the query above, we just fetch the `route.path` & the `namespace.path`
+  # `route.path` is the part of the route that is now incorrect
+  # `namespace.path` is what it should be
+  # We can use `route.path` to find all the namespaces that need to be fixed
+  # And we can use `namespace.path` to apply the correct name.
+  #
+  def paths_and_corrections
+    connection.select_all(
+      wrongly_renamed.select(routes[:path], namespaces[:path].as('namespace_path')).to_sql
+    )
+  end
+
+  # This can be used to limit the `update_in_batches` call to all routes for a
+  # single namespace, note the `/` that's what went wrong in the initial migration.
+  #
+  def routes_in_namespace_query(namespace)
+    routes[:path].matches_any([namespace, "#{namespace}/%"])
+  end
+
+  def up
+    paths_and_corrections.each do |root_namespace|
+      wrong_path = root_namespace['path']
+      correct_path = root_namespace['namespace_path']
+      replace_statement = replace_sql(Route.arel_table[:path], wrong_path, correct_path)
+
+      update_column_in_batches(:routes, :path, replace_statement) do |table, query|
+        query.where(routes_in_namespace_query(wrong_path))
+      end
+    end
+  end
+
+  def down
+  end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 8c7da682807..d14126401c9 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: 20170518200835) do
+ActiveRecord::Schema.define(version: 20170518231126) do
 
   # These are extensions that must be enabled in order to support this database
   enable_extension "plpgsql"
diff --git a/spec/migrations/fix_wrongly_renamed_routes_spec.rb b/spec/migrations/fix_wrongly_renamed_routes_spec.rb
new file mode 100644
index 00000000000..148290b0e7d
--- /dev/null
+++ b/spec/migrations/fix_wrongly_renamed_routes_spec.rb
@@ -0,0 +1,73 @@
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20170518231126_fix_wrongly_renamed_routes.rb')
+
+describe FixWronglyRenamedRoutes, truncate: true do
+  let(:subject) { described_class.new }
+  let(:broken_namespace) do
+    namespace = create(:group, name: 'apiis')
+    namespace.route.update_attribute(:path, 'api0is')
+    namespace
+  end
+
+  describe '#wrongly_renamed' do
+    it "includes routes that have names that don't match their namespace" do
+      broken_namespace
+      _other_namespace = create(:group, name: 'api0')
+
+      expect(subject.wrongly_renamed.map(&:id))
+        .to contain_exactly(broken_namespace.route.id)
+    end
+  end
+
+  describe "#paths_and_corrections" do
+    it 'finds the wrong path and gets the correction from the namespace' do
+      broken_namespace
+      namespace = create(:group, name: 'uploads-test')
+      namespace.route.update_attribute(:path, 'uploads0-test')
+
+      expected_result = [
+        { 'namespace_path' => 'apiis', 'path' => 'api0is' },
+        { 'namespace_path' => 'uploads-test', 'path' => 'uploads0-test' }
+      ]
+
+      expect(subject.paths_and_corrections).to include(*expected_result)
+    end
+  end
+
+  describe '#routes_in_namespace_query' do
+    it 'includes only the required routes' do
+      namespace = create(:group, path: 'hello')
+      project = create(:empty_project, namespace: namespace)
+      _other_namespace = create(:group, path: 'hello0')
+
+      result = Route.where(subject.routes_in_namespace_query('hello'))
+
+      expect(result).to contain_exactly(namespace.route, project.route)
+    end
+  end
+
+  describe '#up' do
+    let(:broken_project) do
+      project = create(:empty_project, namespace: broken_namespace, path: 'broken-project')
+      project.route.update_attribute(:path, 'api0is/broken-project')
+      project
+    end
+
+    it 'renames incorrectly named routes' do
+      broken_project
+
+      subject.up
+
+      expect(broken_project.route.reload.path).to eq('apiis/broken-project')
+      expect(broken_namespace.route.reload.path).to eq('apiis')
+    end
+
+    it "doesn't touch namespaces that look like something that should be renamed" do
+      namespace = create(:group, path: 'api0')
+
+      subject.up
+
+      expect(namespace.route.reload.path).to eq('api0')
+    end
+  end
+end
-- 
GitLab


From 51913c32bab12ec366cc3c529f2ed3d6699ee25e Mon Sep 17 00:00:00 2001
From: Bob Van Landuyt <bob@gitlab.com>
Date: Fri, 19 May 2017 17:22:45 +0200
Subject: [PATCH 3/5] Simpler way of renaming users

---
 ...8200835_rename_users_with_renamed_namespace.rb | 15 ++++++---------
 .../rename_users_with_renamed_namespace_spec.rb   |  5 ++++-
 2 files changed, 10 insertions(+), 10 deletions(-)

diff --git a/db/post_migrate/20170518200835_rename_users_with_renamed_namespace.rb b/db/post_migrate/20170518200835_rename_users_with_renamed_namespace.rb
index a0444f4cc62..dfb8a020119 100644
--- a/db/post_migrate/20170518200835_rename_users_with_renamed_namespace.rb
+++ b/db/post_migrate/20170518200835_rename_users_with_renamed_namespace.rb
@@ -26,15 +26,12 @@ class RenameUsersWithRenamedNamespace < ActiveRecord::Migration
   ]
 
   def up
-    namespace_table = Arel::Table.new('namespaces')
-    users_table = Arel::Table.new('users')
-    matching_path = namespace_table.project(namespace_table[:path])
-                      .join(users_table).on(users_table[:id].eq(namespace_table[:owner_id]))
-                      .where(users_table[:username].not_eq(namespace_table[:path]))
-    path_name = Arel::Nodes::SqlLiteral.new("matching_path.path FROM (#{matching_path.to_sql}) as matching_path")
-
-    update_column_in_batches(:users, :username, path_name) do |table, query|
-      query.where(table[:username].matches_any(DISALLOWED_ROOT_PATHS))
+    DISALLOWED_ROOT_PATHS.each do |path|
+      update_sql = "UPDATE users SET username = namespaces.path "\
+                   "FROM namespaces WHERE namespaces.owner_id = users.id "\
+                   "AND namespaces.type IS NULL "\
+                   "AND users.username ILIKE '#{path}'"
+      connection.execute(update_sql)
     end
   end
 
diff --git a/spec/migrations/rename_users_with_renamed_namespace_spec.rb b/spec/migrations/rename_users_with_renamed_namespace_spec.rb
index aefa539094f..1e9aab3d9a1 100644
--- a/spec/migrations/rename_users_with_renamed_namespace_spec.rb
+++ b/spec/migrations/rename_users_with_renamed_namespace_spec.rb
@@ -2,8 +2,9 @@ require 'spec_helper'
 require Rails.root.join('db', 'post_migrate', '20170518200835_rename_users_with_renamed_namespace.rb')
 
 describe RenameUsersWithRenamedNamespace, truncate: true do
-  it 'renames a user that had his namespace renamed to the namespace path' do
+  it 'renames a user that had their namespace renamed to the namespace path' do
     other_user = create(:user, username: 'kodingu')
+    other_user1 = create(:user, username: 'api0')
 
     user = create(:user, username: "Users0")
     user.update_attribute(:username, 'Users')
@@ -14,6 +15,8 @@ describe RenameUsersWithRenamedNamespace, truncate: true do
 
     expect(user.reload.username).to eq('Users0')
     expect(user1.reload.username).to eq('import0')
+
     expect(other_user.reload.username).to eq('kodingu')
+    expect(other_user1.reload.username).to eq('api0')
   end
 end
-- 
GitLab


From bc58f51d14aff86789dd5356245f1a8105aa8995 Mon Sep 17 00:00:00 2001
From: Bob Van Landuyt <bob@gitlab.com>
Date: Fri, 19 May 2017 17:39:13 +0200
Subject: [PATCH 4/5] Don't try to rename `-`

---
 .../20170518200835_rename_users_with_renamed_namespace.rb        | 1 -
 1 file changed, 1 deletion(-)

diff --git a/db/post_migrate/20170518200835_rename_users_with_renamed_namespace.rb b/db/post_migrate/20170518200835_rename_users_with_renamed_namespace.rb
index dfb8a020119..45ead3d6235 100644
--- a/db/post_migrate/20170518200835_rename_users_with_renamed_namespace.rb
+++ b/db/post_migrate/20170518200835_rename_users_with_renamed_namespace.rb
@@ -6,7 +6,6 @@ class RenameUsersWithRenamedNamespace < ActiveRecord::Migration
 
   DOWNTIME = false
   DISALLOWED_ROOT_PATHS = %w[
-    -
     abuse_reports
     api
     autocomplete
-- 
GitLab


From 537b45b7fbe28efe529555a5a9ac25fad0cb7667 Mon Sep 17 00:00:00 2001
From: Bob Van Landuyt <bob@gitlab.com>
Date: Fri, 19 May 2017 20:11:03 +0200
Subject: [PATCH 5/5] Rename users on mysql

---
 ...835_rename_users_with_renamed_namespace.rb | 19 +++++++++++++++----
 1 file changed, 15 insertions(+), 4 deletions(-)

diff --git a/db/post_migrate/20170518200835_rename_users_with_renamed_namespace.rb b/db/post_migrate/20170518200835_rename_users_with_renamed_namespace.rb
index 45ead3d6235..da0fcda87a6 100644
--- a/db/post_migrate/20170518200835_rename_users_with_renamed_namespace.rb
+++ b/db/post_migrate/20170518200835_rename_users_with_renamed_namespace.rb
@@ -26,10 +26,21 @@ class RenameUsersWithRenamedNamespace < ActiveRecord::Migration
 
   def up
     DISALLOWED_ROOT_PATHS.each do |path|
-      update_sql = "UPDATE users SET username = namespaces.path "\
-                   "FROM namespaces WHERE namespaces.owner_id = users.id "\
-                   "AND namespaces.type IS NULL "\
-                   "AND users.username ILIKE '#{path}'"
+      users = Arel::Table.new(:users)
+      namespaces = Arel::Table.new(:namespaces)
+      predicate = namespaces[:owner_id].eq(users[:id])
+                    .and(namespaces[:type].eq(nil))
+                    .and(users[:username].matches(path))
+      update_sql = if Gitlab::Database.postgresql?
+                     "UPDATE users SET username = namespaces.path "\
+                     "FROM namespaces WHERE #{predicate.to_sql}"
+                   else
+                     "UPDATE users INNER JOIN namespaces "\
+                     "ON namespaces.owner_id = users.id "\
+                     "SET username = namespaces.path "\
+                     "WHERE #{predicate.to_sql}"
+                   end
+
       connection.execute(update_sql)
     end
   end
-- 
GitLab