2018-03-17 18:26:18 +05:30
|
|
|
# frozen_string_literal: true
|
|
|
|
|
|
|
|
module Gitlab
|
|
|
|
module BackgroundMigration
|
|
|
|
# This background migration is going to create all `fork_networks` and
|
|
|
|
# the `fork_network_members` for the roots of fork networks based on the
|
|
|
|
# existing `forked_project_links`.
|
|
|
|
#
|
|
|
|
# When the source of a fork is deleted, we will create the fork with the
|
|
|
|
# target project as the root. This way, when there are forks of the target
|
|
|
|
# project, they will be joined into the same fork network.
|
|
|
|
#
|
|
|
|
# When the `fork_networks` and memberships for the root projects are created
|
|
|
|
# the `CreateForkNetworkMembershipsRange` migration is scheduled. This
|
|
|
|
# migration will create the memberships for all remaining forks-of-forks
|
|
|
|
class PopulateForkNetworksRange
|
|
|
|
def perform(start_id, end_id)
|
|
|
|
create_fork_networks_for_existing_projects(start_id, end_id)
|
|
|
|
create_fork_networks_for_missing_projects(start_id, end_id)
|
|
|
|
create_fork_networks_memberships_for_root_projects(start_id, end_id)
|
|
|
|
|
2018-11-18 11:00:15 +05:30
|
|
|
delay = BackgroundMigration::CreateForkNetworkMembershipsRange::RESCHEDULE_DELAY
|
2018-03-17 18:26:18 +05:30
|
|
|
BackgroundMigrationWorker.perform_in(
|
|
|
|
delay, "CreateForkNetworkMembershipsRange", [start_id, end_id]
|
|
|
|
)
|
|
|
|
end
|
|
|
|
|
|
|
|
def create_fork_networks_for_existing_projects(start_id, end_id)
|
|
|
|
log("Creating fork networks: #{start_id} - #{end_id}")
|
|
|
|
ActiveRecord::Base.connection.execute <<~INSERT_NETWORKS
|
|
|
|
INSERT INTO fork_networks (root_project_id)
|
|
|
|
SELECT DISTINCT forked_project_links.forked_from_project_id
|
|
|
|
|
|
|
|
FROM forked_project_links
|
|
|
|
|
|
|
|
-- Exclude the forks that are not the first level fork of a project
|
|
|
|
WHERE NOT EXISTS (
|
|
|
|
SELECT true
|
|
|
|
FROM forked_project_links inner_links
|
|
|
|
WHERE inner_links.forked_to_project_id = forked_project_links.forked_from_project_id
|
|
|
|
)
|
|
|
|
|
|
|
|
/* Exclude the ones that are already created, in case the fork network
|
|
|
|
was already created for another fork of the project.
|
|
|
|
*/
|
|
|
|
AND NOT EXISTS (
|
|
|
|
SELECT true
|
|
|
|
FROM fork_networks
|
|
|
|
WHERE forked_project_links.forked_from_project_id = fork_networks.root_project_id
|
|
|
|
)
|
|
|
|
|
|
|
|
-- Only create a fork network for a root project that still exists
|
|
|
|
AND EXISTS (
|
|
|
|
SELECT true
|
|
|
|
FROM projects
|
|
|
|
WHERE projects.id = forked_project_links.forked_from_project_id
|
|
|
|
)
|
|
|
|
AND forked_project_links.id BETWEEN #{start_id} AND #{end_id}
|
|
|
|
INSERT_NETWORKS
|
|
|
|
end
|
|
|
|
|
|
|
|
def create_fork_networks_for_missing_projects(start_id, end_id)
|
|
|
|
log("Creating fork networks with missing root: #{start_id} - #{end_id}")
|
|
|
|
ActiveRecord::Base.connection.execute <<~INSERT_NETWORKS
|
|
|
|
INSERT INTO fork_networks (root_project_id)
|
|
|
|
SELECT DISTINCT forked_project_links.forked_to_project_id
|
|
|
|
|
|
|
|
FROM forked_project_links
|
|
|
|
|
|
|
|
-- Exclude forks that are not the root forks
|
|
|
|
WHERE NOT EXISTS (
|
|
|
|
SELECT true
|
|
|
|
FROM forked_project_links inner_links
|
|
|
|
WHERE inner_links.forked_to_project_id = forked_project_links.forked_from_project_id
|
|
|
|
)
|
|
|
|
|
|
|
|
/* Exclude the ones that are already created, in case this migration is
|
|
|
|
re-run
|
|
|
|
*/
|
|
|
|
AND NOT EXISTS (
|
|
|
|
SELECT true
|
|
|
|
FROM fork_networks
|
|
|
|
WHERE forked_project_links.forked_to_project_id = fork_networks.root_project_id
|
|
|
|
)
|
|
|
|
|
|
|
|
/* Exclude projects for which the project still exists, those are
|
|
|
|
Processed in the previous step of this migration
|
|
|
|
*/
|
|
|
|
AND NOT EXISTS (
|
|
|
|
SELECT true
|
|
|
|
FROM projects
|
|
|
|
WHERE projects.id = forked_project_links.forked_from_project_id
|
|
|
|
)
|
|
|
|
AND forked_project_links.id BETWEEN #{start_id} AND #{end_id}
|
|
|
|
INSERT_NETWORKS
|
|
|
|
end
|
|
|
|
|
|
|
|
def create_fork_networks_memberships_for_root_projects(start_id, end_id)
|
|
|
|
log("Creating memberships for root projects: #{start_id} - #{end_id}")
|
|
|
|
|
|
|
|
ActiveRecord::Base.connection.execute <<~INSERT_ROOT
|
|
|
|
INSERT INTO fork_network_members (fork_network_id, project_id)
|
|
|
|
SELECT DISTINCT fork_networks.id, fork_networks.root_project_id
|
|
|
|
|
|
|
|
FROM fork_networks
|
|
|
|
|
|
|
|
/* Joining both on forked_from- and forked_to- so we could create the
|
|
|
|
memberships for forks for which the source was deleted
|
|
|
|
*/
|
|
|
|
INNER JOIN forked_project_links
|
|
|
|
ON forked_project_links.forked_from_project_id = fork_networks.root_project_id
|
|
|
|
OR forked_project_links.forked_to_project_id = fork_networks.root_project_id
|
|
|
|
|
|
|
|
WHERE NOT EXISTS (
|
|
|
|
SELECT true
|
|
|
|
FROM fork_network_members
|
|
|
|
WHERE fork_network_members.project_id = fork_networks.root_project_id
|
|
|
|
)
|
|
|
|
AND forked_project_links.id BETWEEN #{start_id} AND #{end_id}
|
|
|
|
INSERT_ROOT
|
|
|
|
end
|
|
|
|
|
|
|
|
def log(message)
|
|
|
|
Rails.logger.info("#{self.class.name} - #{message}")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|