Skip to content

Commit ffc505d

Browse files
committed
Merge branch 'bvl-remove-forked-project-link' into 'master'
Remove ForkedProjectLink model Closes #38883 See merge request gitlab-org/gitlab-ce!22226
2 parents e2b8337 + f3fba17 commit ffc505d

34 files changed

+217
-251
lines changed

app/controllers/admin/dashboard_controller.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
class Admin::DashboardController < Admin::ApplicationController
44
include CountHelper
55

6-
COUNTED_ITEMS = [Project, User, Group, ForkedProjectLink, Issue, MergeRequest,
7-
Note, Snippet, Key, Milestone].freeze
6+
COUNTED_ITEMS = [Project, User, Group, ForkNetworkMember, ForkNetwork, Issue,
7+
MergeRequest, Note, Snippet, Key, Milestone].freeze
88

99
# rubocop: disable CodeReuse/ActiveRecord
1010
def index

app/helpers/count_helper.rb

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,18 @@ def approximate_count_with_delimiters(count_data, model)
88

99
number_with_delimiter(count)
1010
end
11+
12+
# This will approximate the fork count by checking all counting all fork network
13+
# memberships, and deducting 1 for each root of the fork network.
14+
# This might be inacurate as the root of the fork network might have been deleted.
15+
#
16+
# This makes querying this information a lot more effecient and it should be
17+
# accurate enough for the instance wide statistics
18+
def approximate_fork_count_with_delimiters(count_data)
19+
fork_network_count = count_data[ForkNetwork]
20+
fork_network_member_count = count_data[ForkNetworkMember]
21+
approximate_fork_count = fork_network_member_count - fork_network_count
22+
23+
number_with_delimiter(approximate_fork_count)
24+
end
1125
end

app/models/forked_project_link.rb

Lines changed: 0 additions & 6 deletions
This file was deleted.

app/models/project.rb

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -167,20 +167,15 @@ class Project < ActiveRecord::Base
167167
has_one :packagist_service
168168
has_one :hangouts_chat_service
169169

170-
# TODO: replace these relations with the fork network versions
171-
has_one :forked_project_link, foreign_key: "forked_to_project_id"
172-
has_one :forked_from_project, through: :forked_project_link
173-
174-
has_many :forked_project_links, foreign_key: "forked_from_project_id"
175-
has_many :forks, through: :forked_project_links, source: :forked_to_project
176-
# TODO: replace these relations with the fork network versions
177-
178170
has_one :root_of_fork_network,
179171
foreign_key: 'root_project_id',
180172
inverse_of: :root_project,
181173
class_name: 'ForkNetwork'
182174
has_one :fork_network_member
183175
has_one :fork_network, through: :fork_network_member
176+
has_one :forked_from_project, through: :fork_network_member
177+
has_many :forked_to_members, class_name: 'ForkNetworkMember', foreign_key: 'forked_from_project_id'
178+
has_many :forks, through: :forked_to_members, source: :project, inverse_of: :forked_from_project
184179

185180
has_one :import_state, autosave: true, class_name: 'ProjectImportState', inverse_of: :project
186181
has_one :import_export_upload, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
@@ -1250,12 +1245,7 @@ def http_url_to_repo
12501245
end
12511246

12521247
def forked?
1253-
return true if fork_network && fork_network.root_project != self
1254-
1255-
# TODO: Use only the above conditional using the `fork_network`
1256-
# This is the old conditional that looks at the `forked_project_link`, we
1257-
# fall back to this while we're migrating the new models
1258-
!(forked_project_link.nil? || forked_project_link.forked_from_project.nil?)
1248+
fork_network && fork_network.root_project != self
12591249
end
12601250

12611251
def fork_source
@@ -1546,9 +1536,7 @@ def open_merge_requests_count
15461536
def visibility_level_allowed_as_fork?(level = self.visibility_level)
15471537
return true unless forked?
15481538

1549-
# self.forked_from_project will be nil before the project is saved, so
1550-
# we need to go through the relation
1551-
original_project = forked_project_link&.forked_from_project
1539+
original_project = fork_source
15521540
return true unless original_project
15531541

15541542
level <= original_project.visibility_level

app/services/projects/create_service.rb

Lines changed: 2 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ def execute
1313
return ::Projects::CreateFromTemplateService.new(current_user, params).execute
1414
end
1515

16-
forked_from_project_id = params.delete(:forked_from_project_id)
1716
import_data = params.delete(:import_data)
17+
relations_block = params.delete(:relations_block)
1818

1919
@project = Project.new(params)
2020

@@ -24,11 +24,6 @@ def execute
2424
return @project
2525
end
2626

27-
unless allowed_fork?(forked_from_project_id)
28-
@project.errors.add(:forked_from_project_id, 'is forbidden')
29-
return @project
30-
end
31-
3227
set_project_name_from_path
3328

3429
# get namespace id
@@ -47,17 +42,14 @@ def execute
4742
@project.namespace_id = current_user.namespace_id
4843
end
4944

45+
relations_block&.call(@project)
5046
yield(@project) if block_given?
5147

5248
# If the block added errors, don't try to save the project
5349
return @project if @project.errors.any?
5450

5551
@project.creator = current_user
5652

57-
if forked_from_project_id
58-
@project.build_forked_project_link(forked_from_project_id: forked_from_project_id)
59-
end
60-
6153
save_project_and_import_data(import_data)
6254

6355
after_create_actions if @project.persisted?
@@ -79,15 +71,6 @@ def deny_namespace
7971
@project.errors.add(:namespace, "is not valid")
8072
end
8173

82-
# rubocop: disable CodeReuse/ActiveRecord
83-
def allowed_fork?(source_project_id)
84-
return true if source_project_id.nil?
85-
86-
source_project = Project.find_by(id: source_project_id)
87-
current_user.can?(:fork_project, source_project)
88-
end
89-
# rubocop: enable CodeReuse/ActiveRecord
90-
9174
# rubocop: disable CodeReuse/ActiveRecord
9275
def allowed_namespace?(user, namespace_id)
9376
namespace = Namespace.find_by(id: namespace_id)

app/services/projects/fork_service.rb

Lines changed: 38 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -12,31 +12,42 @@ def execute(fork_to_project = nil)
1212

1313
private
1414

15+
def allowed_fork?
16+
current_user.can?(:fork_project, @project)
17+
end
18+
1519
def link_existing_project(fork_to_project)
1620
return if fork_to_project.forked?
1721

18-
link_fork_network(fork_to_project)
22+
build_fork_network_member(fork_to_project)
1923

20-
# A forked project stores its LFS objects in the `forked_from_project`.
21-
# So the LFS objects become inaccessible, and therefore delete them from
22-
# the database so they'll get cleaned up.
23-
#
24-
# TODO: refactor this to get the correct lfs objects when implementing
25-
# https://gitlab.com/gitlab-org/gitlab-ce/issues/39769
26-
fork_to_project.lfs_objects_projects.delete_all
24+
if link_fork_network(fork_to_project)
25+
# A forked project stores its LFS objects in the `forked_from_project`.
26+
# So the LFS objects become inaccessible, and therefore delete them from
27+
# the database so they'll get cleaned up.
28+
#
29+
# TODO: refactor this to get the correct lfs objects when implementing
30+
# https://gitlab.com/gitlab-org/gitlab-ce/issues/39769
31+
fork_to_project.lfs_objects_projects.delete_all
2732

28-
fork_to_project
33+
fork_to_project
34+
end
2935
end
3036

3137
def fork_new_project
3238
new_params = {
33-
forked_from_project_id: @project.id,
3439
visibility_level: allowed_visibility_level,
3540
description: @project.description,
3641
name: @project.name,
3742
path: @project.path,
3843
shared_runners_enabled: @project.shared_runners_enabled,
39-
namespace_id: target_namespace.id
44+
namespace_id: target_namespace.id,
45+
fork_network: fork_network,
46+
# We need to assign the fork network membership after the project has
47+
# been instantiated to avoid ActiveRecord trying to create it when
48+
# initializing the project, as that would cause a foreign key constraint
49+
# exception.
50+
relations_block: -> (project) { build_fork_network_member(project) }
4051
}
4152

4253
if @project.avatar.present? && @project.avatar.image?
@@ -46,38 +57,35 @@ def fork_new_project
4657
new_project = CreateService.new(current_user, new_params).execute
4758
return new_project unless new_project.persisted?
4859

60+
# Set the forked_from_project relation after saving to avoid having to
61+
# reload the project to reset the association information and cause an
62+
# extra query.
63+
new_project.forked_from_project = @project
64+
4965
builds_access_level = @project.project_feature.builds_access_level
5066
new_project.project_feature.update(builds_access_level: builds_access_level)
5167

52-
link_fork_network(new_project)
53-
5468
new_project
5569
end
5670

5771
def fork_network
58-
if @project.fork_network
59-
@project.fork_network
60-
elsif forked_from_project = @project.forked_from_project
61-
# TODO: remove this case when all background migrations have completed
62-
# this only happens when a project had a `forked_project_link` that was
63-
# not migrated to the `fork_network` relation
64-
forked_from_project.fork_network || forked_from_project.create_root_of_fork_network
72+
@fork_network ||= @project.fork_network || @project.build_root_of_fork_network
73+
end
74+
75+
def build_fork_network_member(fork_to_project)
76+
if allowed_fork?
77+
fork_to_project.build_fork_network_member(forked_from_project: @project,
78+
fork_network: fork_network)
6579
else
66-
@project.create_root_of_fork_network
80+
fork_to_project.errors.add(:forked_from_project_id, 'is forbidden')
6781
end
6882
end
6983

7084
def link_fork_network(fork_to_project)
71-
fork_network.fork_network_members.create(project: fork_to_project,
72-
forked_from_project: @project)
73-
74-
# TODO: remove this when ForkedProjectLink model is removed
75-
unless fork_to_project.forked_project_link
76-
fork_to_project.create_forked_project_link(forked_to_project: fork_to_project,
77-
forked_from_project: @project)
78-
end
85+
return if fork_to_project.errors.any?
7986

80-
refresh_forks_count
87+
fork_to_project.fork_network_member.save &&
88+
refresh_forks_count
8189
end
8290

8391
def refresh_forks_count

app/services/projects/forks_count_service.rb

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,7 @@ def cache_key_name
99

1010
# rubocop: disable CodeReuse/ActiveRecord
1111
def self.query(project_ids)
12-
# We can't directly change ForkedProjectLink to ForkNetworkMember here
13-
# Nowadays, when a call using v3 to projects/:id/fork is made,
14-
# the relationship to ForkNetworkMember is not updated
15-
ForkedProjectLink.where(forked_from_project: project_ids)
12+
ForkNetworkMember.where(forked_from_project: project_ids)
1613
end
1714
# rubocop: enable CodeReuse/ActiveRecord
1815
end

app/services/projects/move_forks_service.rb

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ def execute(source_project, remove_remaining_elements: true)
66
return unless super && source_project.fork_network
77

88
Project.transaction(requires_new: true) do
9-
move_forked_project_links
109
move_fork_network_members
1110
update_root_project
1211
refresh_forks_count
@@ -17,18 +16,6 @@ def execute(source_project, remove_remaining_elements: true)
1716

1817
private
1918

20-
# rubocop: disable CodeReuse/ActiveRecord
21-
def move_forked_project_links
22-
# Update ancestor
23-
ForkedProjectLink.where(forked_to_project: source_project)
24-
.update_all(forked_to_project_id: @project.id)
25-
26-
# Update the descendants
27-
ForkedProjectLink.where(forked_from_project: source_project)
28-
.update_all(forked_from_project_id: @project.id)
29-
end
30-
# rubocop: enable CodeReuse/ActiveRecord
31-
3219
# rubocop: disable CodeReuse/ActiveRecord
3320
def move_fork_network_members
3421
ForkNetworkMember.where(project: source_project).update_all(project_id: @project.id)

app/services/projects/unlink_fork_service.rb

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ def execute
2525
end
2626

2727
@project.fork_network_member.destroy
28-
@project.forked_project_link.destroy
2928
end
3029
# rubocop: enable CodeReuse/ActiveRecord
3130

app/views/admin/dashboard/index.html.haml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
%p
4343
Forks
4444
%span.light.float-right
45-
= approximate_count_with_delimiters(@counts, ForkedProjectLink)
45+
= approximate_fork_count_with_delimiters(@counts)
4646
%p
4747
Issues
4848
%span.light.float-right

app/workers/namespaceless_project_destroy_worker.rb

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,5 @@ def unlink_fork(project)
3232
merge_requests = project.forked_from_project.merge_requests.opened.from_project(project)
3333

3434
merge_requests.update_all(state: 'closed')
35-
36-
project.forked_project_link.destroy
3735
end
3836
end

lib/api/entities.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,7 @@ def self.preload_relation(projects_relation, options = {})
260260
super(projects_relation).preload(:group)
261261
.preload(project_group_links: :group,
262262
fork_network: :root_project,
263-
forked_project_link: :forked_from_project,
263+
fork_network_member: :forked_from_project,
264264
forked_from_project: [:route, :forks, :tags, namespace: :route])
265265
end
266266
# rubocop: enable CodeReuse/ActiveRecord

spec/controllers/projects/merge_requests_controller_spec.rb

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -838,12 +838,14 @@ def expect_rebase_worker_for(user)
838838
end
839839

840840
context 'with a forked project' do
841-
let(:fork_project) { create(:project, :repository, forked_from_project: project) }
842-
let(:fork_owner) { fork_project.owner }
841+
let(:forked_project) { fork_project(project, fork_owner, repository: true) }
842+
let(:fork_owner) { create(:user) }
843843

844844
before do
845-
merge_request.update!(source_project: fork_project)
846-
fork_project.add_reporter(user)
845+
project.add_developer(fork_owner)
846+
847+
merge_request.update!(source_project: forked_project)
848+
forked_project.add_reporter(user)
847849
end
848850

849851
context 'user cannot push to source branch' do

spec/factories/forked_project_links.rb

Lines changed: 0 additions & 15 deletions
This file was deleted.

spec/features/admin/dashboard_spec.rb

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
require 'spec_helper'
2+
3+
describe 'admin visits dashboard' do
4+
include ProjectForksHelper
5+
6+
before do
7+
sign_in(create(:admin))
8+
end
9+
10+
context 'counting forks' do
11+
it 'correctly counts 2 forks of a project' do
12+
project = create(:project)
13+
project_fork = fork_project(project)
14+
fork_project(project_fork)
15+
16+
# Make sure the fork_networks & fork_networks reltuples have been updated
17+
# to get a correct count on postgresql
18+
if Gitlab::Database.postgresql?
19+
ActiveRecord::Base.connection.execute('ANALYZE fork_networks')
20+
ActiveRecord::Base.connection.execute('ANALYZE fork_network_members')
21+
end
22+
23+
visit admin_root_path
24+
25+
expect(page).to have_content('Forks 2')
26+
end
27+
end
28+
end

0 commit comments

Comments
 (0)