diff --git a/changelogs/unreleased/api-wiki-dot-slug.yml b/changelogs/unreleased/api-wiki-dot-slug.yml
new file mode 100644
index 0000000000000000000000000000000000000000..82c76fa745092a404d9f5606c74886d6c663cb4a
--- /dev/null
+++ b/changelogs/unreleased/api-wiki-dot-slug.yml
@@ -0,0 +1,5 @@
+---
+title: 'API: Support dots in wiki slugs'
+merge_request: 24383
+author: Robert Schilling
+type: fixed
diff --git a/changelogs/unreleased/sh-fix-backfill-project-repo-migration.yml b/changelogs/unreleased/sh-fix-backfill-project-repo-migration.yml
new file mode 100644
index 0000000000000000000000000000000000000000..d1d4412eb500422e4a926bff6935c2e4c180641b
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-backfill-project-repo-migration.yml
@@ -0,0 +1,5 @@
+---
+title: Fix duplicate project disk path in BackfillLegacyProjectRepositories
+merge_request: 24213
+author:
+type: changed
diff --git a/lib/api/wikis.rb b/lib/api/wikis.rb
index 302b2797a3462766edeb51e5e41ac5625421cca6..ef0e3decc2c7776e5454eb3266b28f04608d733e 100644
--- a/lib/api/wikis.rb
+++ b/lib/api/wikis.rb
@@ -22,7 +22,9 @@ module API
       end
     end
 
-    resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
+    WIKI_ENDPOINT_REQUIREMENTS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS.merge(slug: API::NO_SLASH_URL_PART_REGEX)
+
+    resource :projects, requirements: WIKI_ENDPOINT_REQUIREMENTS do
       desc 'Get a list of wiki pages' do
         success Entities::WikiPageBasic
       end
@@ -103,7 +105,7 @@ module API
         requires :file, type: ::API::Validations::Types::SafeFile, desc: 'The attachment file to be uploaded'
         optional :branch, type: String, desc: 'The name of the branch'
       end
-      post ":id/wikis/attachments", requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
+      post ":id/wikis/attachments" do
         authorize! :create_wiki, user_project
 
         result = ::Wikis::CreateAttachmentService.new(user_project,
diff --git a/lib/gitlab/background_migration/backfill_project_repositories.rb b/lib/gitlab/background_migration/backfill_project_repositories.rb
index aaf520d70f69e6fbf9b09165efdf77a1adc736df..c8d83cc1803def1b89390ccf050a7a334cc73f06 100644
--- a/lib/gitlab/background_migration/backfill_project_repositories.rb
+++ b/lib/gitlab/background_migration/backfill_project_repositories.rb
@@ -83,7 +83,7 @@ module Gitlab
         extend ActiveSupport::Concern
 
         def full_path
-          @full_path ||= build_full_path
+          route&.path || build_full_path
         end
 
         def build_full_path
@@ -99,7 +99,12 @@ module Gitlab
         end
       end
 
-      # Namespace model.
+      # Route model
+      class Route < ActiveRecord::Base
+        belongs_to :source, inverse_of: :route, polymorphic: true
+      end
+
+      # Namespace model
       class Namespace < ActiveRecord::Base
         self.table_name = 'namespaces'
         self.inheritance_column = nil
@@ -108,6 +113,8 @@ module Gitlab
 
         belongs_to :parent, class_name: 'Namespace', inverse_of: 'namespaces'
 
+        has_one :route, -> { where(source_type: 'Namespace') }, inverse_of: :source, foreign_key: :source_id
+
         has_many :projects, inverse_of: :parent
         has_many :namespaces, inverse_of: :parent
       end
@@ -134,6 +141,7 @@ module Gitlab
 
         belongs_to :parent, class_name: 'Namespace', foreign_key: :namespace_id, inverse_of: 'projects'
 
+        has_one :route, -> { where(source_type: 'Project') }, inverse_of: :source, foreign_key: :source_id
         has_one :project_repository, inverse_of: :project
 
         delegate :disk_path, to: :storage
@@ -194,6 +202,8 @@ module Gitlab
       def project_repositories(start_id, stop_id)
         projects
           .without_project_repository
+          .includes(:route, parent: [:route]).references(:routes)
+          .includes(:parent).references(:namespaces)
           .where(id: start_id..stop_id)
           .map { |project| build_attributes_for_project(project) }
           .compact
diff --git a/spec/factories/wiki_pages.rb b/spec/factories/wiki_pages.rb
index 2335b5118ddf474c89323bf2cb357319044242b9..ae257d769e894380286209e51098747817459cde 100644
--- a/spec/factories/wiki_pages.rb
+++ b/spec/factories/wiki_pages.rb
@@ -5,7 +5,7 @@ FactoryBot.define do
     transient do
       attrs do
         {
-          title: 'Title',
+          title: 'Title.with.dot',
           content: 'Content for wiki page',
           format: 'markdown'
         }
diff --git a/spec/lib/gitlab/background_migration/backfill_project_repositories_spec.rb b/spec/lib/gitlab/background_migration/backfill_project_repositories_spec.rb
index 53c071f026885156715b039f653d8f6729a74748..f4a6f4be754f4bc6449e6ed24e9165f358cf06df 100644
--- a/spec/lib/gitlab/background_migration/backfill_project_repositories_spec.rb
+++ b/spec/lib/gitlab/background_migration/backfill_project_repositories_spec.rb
@@ -34,6 +34,7 @@ describe Gitlab::BackgroundMigration::BackfillProjectRepositories do
     let!(:project_hashed_storage_2) { create(:project, name: 'bar', path: 'bar', namespace: group, storage_version: 2) }
     let!(:project_legacy_storage_3) { create(:project, name: 'baz', path: 'baz', namespace: group, storage_version: 0) }
     let!(:project_legacy_storage_4) { create(:project, name: 'zoo', path: 'zoo', namespace: group, storage_version: nil) }
+    let!(:project_legacy_storage_5) { create(:project, name: 'test', path: 'test', namespace: group, storage_version: nil) }
 
     describe '.on_hashed_storage' do
       it 'finds projects with repository on hashed storage' do
@@ -47,7 +48,7 @@ describe Gitlab::BackgroundMigration::BackfillProjectRepositories do
       it 'finds projects with repository on legacy storage' do
         projects = described_class.on_legacy_storage.pluck(:id)
 
-        expect(projects).to match_array([project_legacy_storage_3.id, project_legacy_storage_4.id])
+        expect(projects).to match_array([project_legacy_storage_3.id, project_legacy_storage_4.id, project_legacy_storage_5.id])
       end
     end
 
@@ -58,7 +59,7 @@ describe Gitlab::BackgroundMigration::BackfillProjectRepositories do
 
         projects = described_class.without_project_repository.pluck(:id)
 
-        expect(projects).to contain_exactly(project_hashed_storage_2.id, project_legacy_storage_4.id)
+        expect(projects).to contain_exactly(project_hashed_storage_2.id, project_legacy_storage_4.id, project_legacy_storage_5.id)
       end
     end
 
@@ -78,14 +79,23 @@ describe Gitlab::BackgroundMigration::BackfillProjectRepositories do
           expect(project.disk_path).to eq(project_legacy_storage_3.disk_path)
         end
 
+        it 'returns the correct disk_path using the route entry' do
+          project_legacy_storage_5.route.update(path: 'zoo/new-test')
+          project = described_class.find(project_legacy_storage_5.id)
+
+          expect(project.disk_path).to eq('zoo/new-test')
+        end
+
         it 'raises OrphanedNamespaceError when any parent namespace does not exist' do
           subgroup = create(:group, parent: group)
           project_orphaned_namespace = create(:project, name: 'baz', path: 'baz', namespace: subgroup, storage_version: nil)
           subgroup.update_column(:parent_id, Namespace.maximum(:id).succ)
 
           project = described_class.find(project_orphaned_namespace.id)
+          project.route.destroy
+          subgroup.route.destroy
 
-          expect { project.disk_path }
+          expect { project.reload.disk_path }
             .to raise_error(Gitlab::BackgroundMigration::BackfillProjectRepositories::OrphanedNamespaceError)
         end
       end
diff --git a/spec/requests/api/wikis_spec.rb b/spec/requests/api/wikis_spec.rb
index f5092e8e2b5475ed28e6403e1990fd17a9b11bb4..6109829aad185a2d8232d9994569a805f901afe2 100644
--- a/spec/requests/api/wikis_spec.rb
+++ b/spec/requests/api/wikis_spec.rb
@@ -22,7 +22,7 @@ describe API::Wikis do
     context 'when wiki has pages' do
       let!(:pages) do
         [create(:wiki_page, wiki: project_wiki, attrs: { title: 'page1', content: 'content of page1' }),
-         create(:wiki_page, wiki: project_wiki, attrs: { title: 'page2', content: 'content of page2' })]
+         create(:wiki_page, wiki: project_wiki, attrs: { title: 'page2.with.dot', content: 'content of page2' })]
       end
 
       it 'returns the list of wiki pages without content' do
diff --git a/spec/support/shared_examples/lib/gitlab/background_migration/backfill_project_repositories_examples.rb b/spec/support/shared_examples/lib/gitlab/background_migration/backfill_project_repositories_examples.rb
index 1f688c0f9d3e6e88dd5a1c7914c4eabe92d9fcd3..dcf7c1a90c25d6217045179cd7fe57074fb88b58 100644
--- a/spec/support/shared_examples/lib/gitlab/background_migration/backfill_project_repositories_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/background_migration/backfill_project_repositories_examples.rb
@@ -32,11 +32,13 @@ shared_examples 'backfill migration for project repositories' do |storage|
 
     it 'inserts rows in a single query' do
       projects.create!(name: 'foo', path: 'foo', namespace_id: group.id, storage_version: storage_version, repository_storage: shard.name)
+      group2 = namespaces.create!(name: 'gro', path: 'gro')
 
       control_count = ActiveRecord::QueryRecorder.new { described_class.new.perform(1, projects.last.id) }
 
       projects.create!(name: 'bar', path: 'bar', namespace_id: group.id, storage_version: storage_version, repository_storage: shard.name)
-      projects.create!(name: 'zoo', path: 'zoo', namespace_id: group.id, storage_version: storage_version, repository_storage: shard.name)
+      projects.create!(name: 'top', path: 'top', namespace_id: group.id, storage_version: storage_version, repository_storage: shard.name)
+      projects.create!(name: 'zoo', path: 'zoo', namespace_id: group2.id, storage_version: storage_version, repository_storage: shard.name)
 
       expect { described_class.new.perform(1, projects.last.id) }.not_to exceed_query_limit(control_count)
     end