summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBob Van Landuyt <bob@vanlanduyt.co>2017-09-28 16:38:12 +0200
committerBob Van Landuyt <bob@vanlanduyt.co>2017-10-07 11:46:23 +0200
commitd328007214786c7137c31d2c73e9ee76b025e6ed (patch)
treefe4ec118148b7397890f86c6b2982ce667086c38
parent20727db1702849b78e6714197f16f602f68cecf8 (diff)
downloadgitlab-ce-d328007214786c7137c31d2c73e9ee76b025e6ed.tar.gz
Create a fork network when forking a project
When no fork network exists for the source projects, we create a new one with the correct source
-rw-r--r--app/models/fork_network.rb10
-rw-r--r--app/models/fork_network_member.rb7
-rw-r--r--app/models/project.rb13
-rw-r--r--app/services/projects/fork_service.rb20
-rw-r--r--db/migrate/20170928133643_create_fork_network_members.rb26
-rw-r--r--db/schema.rb12
-rw-r--r--spec/models/fork_network_member_spec.rb8
-rw-r--r--spec/models/fork_network_spec.rb43
-rw-r--r--spec/services/projects/fork_service_spec.rb27
9 files changed, 160 insertions, 6 deletions
diff --git a/app/models/fork_network.rb b/app/models/fork_network.rb
index 60c8c167b21..fd2510d0a4c 100644
--- a/app/models/fork_network.rb
+++ b/app/models/fork_network.rb
@@ -1,3 +1,11 @@
class ForkNetwork < ActiveRecord::Base
- belongs_to :root_project
+ belongs_to :root_project, class_name: 'Project'
+ has_many :fork_network_members
+ has_many :projects, through: :fork_network_members
+
+ after_create :add_root_as_member, if: :root_project
+
+ def add_root_as_member
+ projects << root_project
+ end
end
diff --git a/app/models/fork_network_member.rb b/app/models/fork_network_member.rb
new file mode 100644
index 00000000000..6a9b52a1ef8
--- /dev/null
+++ b/app/models/fork_network_member.rb
@@ -0,0 +1,7 @@
+class ForkNetworkMember < ActiveRecord::Base
+ belongs_to :fork_network
+ belongs_to :project
+ belongs_to :forked_from_project, class_name: 'Project'
+
+ validates :fork_network, :project, presence: true
+end
diff --git a/app/models/project.rb b/app/models/project.rb
index 608b545f99f..aff8fd768f5 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -118,11 +118,20 @@ class Project < ActiveRecord::Base
has_one :mock_monitoring_service
has_one :microsoft_teams_service
+ # TODO: replace these relations with the fork network versions
has_one :forked_project_link, foreign_key: "forked_to_project_id"
has_one :forked_from_project, through: :forked_project_link
has_many :forked_project_links, foreign_key: "forked_from_project_id"
has_many :forks, through: :forked_project_links, source: :forked_to_project
+ # TODO: replace these relations with the fork network versions
+
+ has_one :root_of_fork_network,
+ foreign_key: 'root_project_id',
+ inverse_of: :root_project,
+ class_name: 'ForkNetwork'
+ has_one :fork_network_member
+ has_one :fork_network, through: :fork_network_member
# Merge Requests for target project should be removed with it
has_many :merge_requests, foreign_key: 'target_project_id'
@@ -1119,8 +1128,8 @@ class Project < ActiveRecord::Base
end
end
- def forked_from?(project)
- forked? && project == forked_from_project
+ def in_fork_network_of?(project)
+ forked? && project.fork_network == fork_network
end
def origin_merge_requests
diff --git a/app/services/projects/fork_service.rb b/app/services/projects/fork_service.rb
index ad67e68a86a..eb5cce5ab98 100644
--- a/app/services/projects/fork_service.rb
+++ b/app/services/projects/fork_service.rb
@@ -23,11 +23,31 @@ module Projects
refresh_forks_count
+ link_fork_network(new_project)
+
new_project
end
private
+ def fork_network
+ if @project.fork_network
+ @project.fork_network
+ elsif forked_from_project = @project.forked_from_project
+ # TODO: remove this case when all background migrations have completed
+ # this only happens when a project had a `forked_project_link` that was
+ # not migrated to the `fork_network` relation
+ forked_from_project.fork_network || forked_from_project.create_root_of_fork_network
+ else
+ @project.create_root_of_fork_network
+ end
+ end
+
+ def link_fork_network(new_project)
+ fork_network.fork_network_members.create(project: new_project,
+ forked_from_project: @project)
+ end
+
def refresh_forks_count
Projects::ForksCountService.new(@project).refresh_cache
end
diff --git a/db/migrate/20170928133643_create_fork_network_members.rb b/db/migrate/20170928133643_create_fork_network_members.rb
new file mode 100644
index 00000000000..c1d94e7d52e
--- /dev/null
+++ b/db/migrate/20170928133643_create_fork_network_members.rb
@@ -0,0 +1,26 @@
+class CreateForkNetworkMembers < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ create_table :fork_network_members do |t|
+ t.references :fork_network, null: false, index: true, foreign_key: { on_delete: :cascade }
+ t.references :project, null: false, index: { unique: true }, foreign_key: { on_delete: :cascade }
+ t.references :forked_from_project, references: :projects
+ end
+
+ add_concurrent_foreign_key :fork_network_members, :projects,
+ column: :forked_from_project_id,
+ on_delete: :nullify
+ end
+
+ def down
+ if foreign_key_exists?(:fork_network_members, column: :forked_from_project_id)
+ remove_foreign_key :fork_network_members, column: :forked_from_project_id
+ end
+ drop_table :fork_network_members
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index d7e1a2c94fa..aac37b6b455 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -591,6 +591,15 @@ ActiveRecord::Schema.define(version: 20171006091000) do
add_index "features", ["key"], name: "index_features_on_key", unique: true, using: :btree
+ create_table "fork_network_members", force: :cascade do |t|
+ t.integer "fork_network_id", null: false
+ t.integer "project_id", null: false
+ t.integer "forked_from_project_id"
+ end
+
+ add_index "fork_network_members", ["fork_network_id"], name: "index_fork_network_members_on_fork_network_id", using: :btree
+ add_index "fork_network_members", ["project_id"], name: "index_fork_network_members_on_project_id", unique: true, using: :btree
+
create_table "fork_networks", force: :cascade do |t|
t.integer "root_project_id"
t.string "deleted_root_project_name"
@@ -1800,6 +1809,9 @@ ActiveRecord::Schema.define(version: 20171006091000) do
add_foreign_key "environments", "projects", name: "fk_d1c8c1da6a", on_delete: :cascade
add_foreign_key "events", "projects", on_delete: :cascade
add_foreign_key "events", "users", column: "author_id", name: "fk_edfd187b6f", on_delete: :cascade
+ add_foreign_key "fork_network_members", "fork_networks", on_delete: :cascade
+ add_foreign_key "fork_network_members", "projects", column: "forked_from_project_id", name: "fk_b01280dae4", on_delete: :nullify
+ add_foreign_key "fork_network_members", "projects", on_delete: :cascade
add_foreign_key "fork_networks", "projects", column: "root_project_id", name: "fk_e7b436b2b5", on_delete: :nullify
add_foreign_key "forked_project_links", "projects", column: "forked_to_project_id", name: "fk_434510edb0", on_delete: :cascade
add_foreign_key "gcp_clusters", "projects", on_delete: :cascade
diff --git a/spec/models/fork_network_member_spec.rb b/spec/models/fork_network_member_spec.rb
new file mode 100644
index 00000000000..532ca1fca8c
--- /dev/null
+++ b/spec/models/fork_network_member_spec.rb
@@ -0,0 +1,8 @@
+require 'spec_helper'
+
+describe ForkNetworkMember do
+ describe 'validations' do
+ it { is_expected.to validate_presence_of(:project) }
+ it { is_expected.to validate_presence_of(:fork_network) }
+ end
+end
diff --git a/spec/models/fork_network_spec.rb b/spec/models/fork_network_spec.rb
index b7758a0fbb5..4781a959846 100644
--- a/spec/models/fork_network_spec.rb
+++ b/spec/models/fork_network_spec.rb
@@ -1,5 +1,42 @@
-require 'rails_helper'
+require 'spec_helper'
-RSpec.describe ForkNetwork, type: :model do
- pending "add some examples to (or delete) #{__FILE__}"
+describe ForkNetwork do
+ include ProjectForksHelper
+
+ describe '#add_root_as_member' do
+ it 'adds the root project as a member when creating a new root network' do
+ project = create(:project)
+ fork_network = described_class.create(root_project: project)
+
+ expect(fork_network.projects).to include(project)
+ end
+ end
+
+ context 'for a deleted project' do
+ it 'keeps the fork network' do
+ project = create(:project, :public)
+ forked = fork_project(project)
+ project.destroy!
+
+ fork_network = forked.reload.fork_network
+
+ expect(fork_network.projects).to contain_exactly(forked)
+ expect(fork_network.root_project).to be_nil
+ end
+
+ it 'allows multiple fork networks where the root project is deleted' do
+ first_project = create(:project)
+ second_project = create(:project)
+ first_fork = fork_project(first_project)
+ second_fork = fork_project(second_project)
+
+ first_project.destroy
+ second_project.destroy
+
+ expect(first_fork.fork_network).not_to be_nil
+ expect(first_fork.fork_network.root_project).to be_nil
+ expect(second_fork.fork_network).not_to be_nil
+ expect(second_fork.fork_network.root_project).to be_nil
+ end
+ end
end
diff --git a/spec/services/projects/fork_service_spec.rb b/spec/services/projects/fork_service_spec.rb
index fa9d6969830..a5ec4111b70 100644
--- a/spec/services/projects/fork_service_spec.rb
+++ b/spec/services/projects/fork_service_spec.rb
@@ -60,6 +60,33 @@ describe Projects::ForkService do
expect(@from_project.forks_count).to eq(1)
end
+
+ it 'creates a fork network with the new project and the root project set' do
+ to_project
+ fork_network = @from_project.reload.fork_network
+
+ expect(fork_network).not_to be_nil
+ expect(fork_network.root_project).to eq(@from_project)
+ expect(fork_network.projects).to contain_exactly(@from_project, to_project)
+ end
+ end
+
+ context 'creating a fork of a fork' do
+ let(:from_forked_project) { fork_project(@from_project, @to_user) }
+ let(:other_namespace) do
+ group = create(:group)
+ group.add_owner(@to_user)
+ group
+ end
+ let(:to_project) { fork_project(from_forked_project, @to_user, namespace: other_namespace) }
+
+ it 'sets the root of the network to the root project' do
+ expect(to_project.fork_network.root_project).to eq(@from_project)
+ end
+
+ it 'sets the forked_from_project on the membership' do
+ expect(to_project.fork_network_member.forked_from_project).to eq(from_forked_project)
+ end
end
end