Commit 3cb79835 authored by Douwe Maan's avatar Douwe Maan

Merge branch 'bvl-fork-network-schema' into 'master'

Create merge requests across a fork network

Closes #20097

See merge request gitlab-org/gitlab-ce!14422
parents 8eec69ef fc51bde9
...@@ -120,10 +120,13 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap ...@@ -120,10 +120,13 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap
end end
def selected_target_project def selected_target_project
if @project.id.to_s == params[:target_project_id] || @project.forked_project_link.nil? if @project.id.to_s == params[:target_project_id] || !@project.forked?
@project @project
elsif params[:target_project_id].present?
MergeRequestTargetProjectFinder.new(current_user: current_user, source_project: @project)
.execute.find(params[:target_project_id])
else else
@project.forked_project_link.forked_from_project @project.forked_from_project
end end
end end
end end
class MergeRequestTargetProjectFinder
attr_reader :current_user, :source_project
def initialize(current_user: nil, source_project:)
@current_user = current_user
@source_project = source_project
end
def execute
if @source_project.fork_network
@source_project.fork_network.projects
.public_or_visible_to_user(current_user)
.with_feature_available_for_user(:merge_requests, current_user)
else
Project.where(id: source_project)
end
end
end
...@@ -73,7 +73,8 @@ module MergeRequestsHelper ...@@ -73,7 +73,8 @@ module MergeRequestsHelper
end end
def target_projects(project) def target_projects(project)
[project, project.default_merge_request_target].uniq MergeRequestTargetProjectFinder.new(current_user: current_user, source_project: project)
.execute
end end
def merge_request_button_visibility(merge_request, closed) def merge_request_button_visibility(merge_request, closed)
......
class ForkNetwork < ActiveRecord::Base
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
def find_forks_in(other_projects)
projects.where(id: other_projects)
end
end
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
...@@ -403,7 +403,7 @@ class MergeRequest < ActiveRecord::Base ...@@ -403,7 +403,7 @@ class MergeRequest < ActiveRecord::Base
return false unless for_fork? return false unless for_fork?
return true unless source_project return true unless source_project
!source_project.forked_from?(target_project) !source_project.in_fork_network_of?(target_project)
end end
def reopenable? def reopenable?
......
...@@ -139,7 +139,9 @@ class Namespace < ActiveRecord::Base ...@@ -139,7 +139,9 @@ class Namespace < ActiveRecord::Base
end end
def find_fork_of(project) def find_fork_of(project)
projects.joins(:forked_project_link).find_by('forked_project_links.forked_from_project_id = ?', project.id) return nil unless project.fork_network
project.fork_network.find_forks_in(projects).first
end end
def lfs_enabled? def lfs_enabled?
......
...@@ -118,11 +118,20 @@ class Project < ActiveRecord::Base ...@@ -118,11 +118,20 @@ class Project < ActiveRecord::Base
has_one :mock_monitoring_service has_one :mock_monitoring_service
has_one :microsoft_teams_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_project_link, foreign_key: "forked_to_project_id"
has_one :forked_from_project, through: :forked_project_link has_one :forked_from_project, through: :forked_project_link
has_many :forked_project_links, foreign_key: "forked_from_project_id" has_many :forked_project_links, foreign_key: "forked_from_project_id"
has_many :forks, through: :forked_project_links, source: :forked_to_project 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 # Merge Requests for target project should be removed with it
has_many :merge_requests, foreign_key: 'target_project_id' has_many :merge_requests, foreign_key: 'target_project_id'
...@@ -1000,6 +1009,11 @@ class Project < ActiveRecord::Base ...@@ -1000,6 +1009,11 @@ class Project < ActiveRecord::Base
end end
def forked? def forked?
return true if fork_network && fork_network.root_project != self
# TODO: Use only the above conditional using the `fork_network`
# This is the old conditional that looks at the `forked_project_link`, we
# fall back to this while we're migrating the new models
!(forked_project_link.nil? || forked_project_link.forked_from_project.nil?) !(forked_project_link.nil? || forked_project_link.forked_from_project.nil?)
end end
...@@ -1119,8 +1133,19 @@ class Project < ActiveRecord::Base ...@@ -1119,8 +1133,19 @@ class Project < ActiveRecord::Base
end end
end end
def forked_from?(project) def forked_from?(other_project)
forked? && project == forked_from_project forked? && forked_from_project == other_project
end
def in_fork_network_of?(other_project)
# TODO: Remove this in a next release when all fork_networks are populated
# This makes sure all MergeRequests remain valid while the projects don't
# have a fork_network yet.
return true if forked_from?(other_project)
return false if fork_network.nil? || other_project.fork_network.nil?
fork_network == other_project.fork_network
end end
def origin_merge_requests def origin_merge_requests
......
...@@ -697,15 +697,7 @@ class User < ActiveRecord::Base ...@@ -697,15 +697,7 @@ class User < ActiveRecord::Base
end end
def fork_of(project) def fork_of(project)
links = ForkedProjectLink.where( namespace.find_fork_of(project)
forked_from_project_id: project,
forked_to_project_id: personal_projects.unscope(:order)
)
if links.any?
links.first.forked_to_project
else
nil
end
end end
def ldap_user? def ldap_user?
......
...@@ -22,6 +22,13 @@ module Projects ...@@ -22,6 +22,13 @@ module Projects
Projects::UnlinkForkService.new(project, current_user).execute Projects::UnlinkForkService.new(project, current_user).execute
# The project is not necessarily a fork, so update the fork network originating
# from this project
if fork_network = project.root_of_fork_network
fork_network.update(root_project: nil,
deleted_root_project_name: project.full_name)
end
attempt_destroy_transaction(project) attempt_destroy_transaction(project)
system_hook_service.execute_hooks_for(project, :destroy) system_hook_service.execute_hooks_for(project, :destroy)
......
...@@ -23,11 +23,31 @@ module Projects ...@@ -23,11 +23,31 @@ module Projects
refresh_forks_count refresh_forks_count
link_fork_network(new_project)
new_project new_project
end end
private 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 def refresh_forks_count
Projects::ForksCountService.new(@project).refresh_cache Projects::ForksCountService.new(@project).refresh_cache
end end
......
...@@ -16,6 +16,7 @@ module Projects ...@@ -16,6 +16,7 @@ module Projects
refresh_forks_count(@project.forked_from_project) refresh_forks_count(@project.forked_from_project)
@project.forked_project_link.destroy @project.forked_project_link.destroy
@project.fork_network_member.destroy
end end
def refresh_forks_count(project) def refresh_forks_count(project)
......
- empty_repo = @project.empty_repo? - empty_repo = @project.empty_repo?
- fork_network = @project.fork_network
- forked_from_project = @project.forked_from_project || fork_network&.root_project
.project-home-panel.text-center{ class: ("empty-project" if empty_repo) } .project-home-panel.text-center{ class: ("empty-project" if empty_repo) }
.limit-container-width{ class: container_class } .limit-container-width{ class: container_class }
.avatar-container.s70.project-avatar .avatar-container.s70.project-avatar
...@@ -12,11 +14,15 @@ ...@@ -12,11 +14,15 @@
- if @project.description.present? - if @project.description.present?
= markdown_field(@project, :description) = markdown_field(@project, :description)
- if forked_from_project = @project.forked_from_project - if @project.forked?
%p %p
#{ s_('ForkedFromProjectPath|Forked from') } - if forked_from_project
= link_to project_path(forked_from_project) do #{ s_('ForkedFromProjectPath|Forked from') }
= forked_from_project.namespace.try(:name) = link_to project_path(forked_from_project) do
= forked_from_project.full_name
- else
- deleted_message = s_('ForkedFromProjectPath|Forked from %{project_name} (deleted)')
= deleted_message % { project_name: fork_network.deleted_root_project_name }
.project-repo-buttons .project-repo-buttons
.count-buttons .count-buttons
......
---
title: Allow creating merge requests across a fork network
merge_request: 14422
author:
type: changed
class CreateForkNetworks < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
create_table :fork_networks do |t|
t.references :root_project,
references: :projects,
index: { unique: true }
t.string :deleted_root_project_name
end
add_concurrent_foreign_key :fork_networks, :projects,
column: :root_project_id,
on_delete: :nullify
end
def down
if foreign_keys_for(:fork_networks, :root_project_id).any?
remove_foreign_key :fork_networks, column: :root_project_id
end
drop_table :fork_networks
end
end
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_keys_for(:fork_network_members, :forked_from_project_id).any?
remove_foreign_key :fork_network_members, column: :forked_from_project_id
end
drop_table :fork_network_members
end
end
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class PopulateForkNetworks < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
MIGRATION = 'PopulateForkNetworksRange'.freeze
BATCH_SIZE = 100
DELAY_INTERVAL = 15.seconds
disable_ddl_transaction!
class ForkedProjectLink < ActiveRecord::Base
include EachBatch
self.table_name = 'forked_project_links'
end
def up
say 'Populating the `fork_networks` based on existing `forked_project_links`'
queue_background_migration_jobs_by_range_at_intervals(ForkedProjectLink, MIGRATION, DELAY_INTERVAL, batch_size: BATCH_SIZE)
end
def down
# nothing
end
end
...@@ -591,6 +591,22 @@ ActiveRecord::Schema.define(version: 20171006091000) do ...@@ -591,6 +591,22 @@ ActiveRecord::Schema.define(version: 20171006091000) do
add_index "features", ["key"], name: "index_features_on_key", unique: true, using: :btree 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"
end
add_index "fork_networks", ["root_project_id"], name: "index_fork_networks_on_root_project_id", unique: true, using: :btree
create_table "forked_project_links", force: :cascade do |t| create_table "forked_project_links", force: :cascade do |t|
t.integer "forked_to_project_id", null: false t.integer "forked_to_project_id", null: false
t.integer "forked_from_project_id", null: false t.integer "forked_from_project_id", null: false
...@@ -1793,6 +1809,10 @@ ActiveRecord::Schema.define(version: 20171006091000) do ...@@ -1793,6 +1809,10 @@ ActiveRecord::Schema.define(version: 20171006091000) do
add_foreign_key "environments", "projects", name: "fk_d1c8c1da6a", on_delete: :cascade add_foreign_key "environments", "projects", name: "fk_d1c8c1da6a", on_delete: :cascade
add_foreign_key "events", "projects", 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 "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 "forked_project_links", "projects", column: "forked_to_project_id", name: "fk_434510edb0", on_delete: :cascade
add_foreign_key "gcp_clusters", "projects", on_delete: :cascade add_foreign_key "gcp_clusters", "projects", on_delete: :cascade
add_foreign_key "gcp_clusters", "services", on_delete: :nullify add_foreign_key "gcp_clusters", "services", on_delete: :nullify
......
...@@ -58,13 +58,13 @@ class Spinach::Features::ProjectFork < Spinach::FeatureSteps ...@@ -58,13 +58,13 @@ class Spinach::Features::ProjectFork < Spinach::FeatureSteps
step 'I should see my fork on the list' do step 'I should see my fork on the list' do
page.within('.js-projects-list-holder') do page.within('.js-projects-list-holder') do
project = @user.fork_of(@project) project = @user.fork_of(@project.reload)
expect(page).to have_content("#{project.namespace.human_name} / #{project.name}") expect(page).to have_content("#{project.namespace.human_name} / #{project.name}")
end end
end end
step 'I make forked repo invalid' do step 'I make forked repo invalid' do
project = @user.fork_of(@project) project = @user.fork_of(@project.reload)
project.path = 'test-crappy-path' project.path = 'test-crappy-path'
project.save! project.save!
end end
......
...@@ -5,6 +5,7 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps ...@@ -5,6 +5,7 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps
include SharedPaths include SharedPaths
include Select2Helper include Select2Helper
include WaitForRequests include WaitForRequests
include ProjectForksHelper
step 'I am a member of project "Shop"' do step 'I am a member of project "Shop"' do
@project = ::Project.find_by(name: "Shop") @project = ::Project.find_by(name: "Shop")
...@@ -13,7 +14,9 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps ...@@ -13,7 +14,9 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps
end end
step 'I have a project forked off of "Shop" called "Forked Shop"' do step 'I have a project forked off of "Shop" called "Forked Shop"' do
@forked_project = Projects::ForkService.new(@project, @user).execute @forked_project = fork_project(@project, @user,
namespace: @user.namespace,
repository: true)
end end
step 'I click link "New Merge Request"' do step 'I click link "New Merge Request"' do
......
...@@ -10,7 +10,7 @@ if ENV['CI'] ...@@ -10,7 +10,7 @@ if ENV['CI']
Knapsack::Adapters::SpinachAdapter.bind Knapsack::Adapters::SpinachAdapter.bind
end end
%w(select2_helper test_env repo_helpers wait_for_requests sidekiq).each do |f| %w(select2_helper test_env repo_helpers wait_for_requests sidekiq project_forks_helper).each do |f|
require Rails.root.join('spec', 'support', f) require Rails.root.join('spec', 'support', f)
end end
......
module Gitlab
module BackgroundMigration
class CreateForkNetworkMembershipsRange
RESCHEDULE_DELAY = 15
class ForkedProjectLink < ActiveRecord::Base
self.table_name = 'forked_project_links'
end
def perform(start_id, end_id)
log("Creating memberships for forks: #{start_id} - #{end_id}")
ActiveRecord::Base.connection.execute <<~INSERT_MEMBERS
INSERT INTO fork_network_members (fork_network_id, project_id, forked_from_project_id)
SELECT fork_network_members.fork_network_id,
forked_project_links.forked_to_project_id,
forked_project_links.forked_from_project_id
FROM forked_project_links
INNER JOIN fork_network_members
ON forked_project_links.forked_from_project_id = fork_network_members.project_id
WHERE forked_project_links.id BETWEEN #{start_id} AND #{end_id}
AND NOT EXISTS (
SELECT true
FROM fork_network_members existing_members
WHERE existing_members.project_id = forked_project_links.forked_to_project_id
)
INSERT_MEMBERS
if missing_members?(start_id, end_id)
BackgroundMigrationWorker.perform_in(RESCHEDULE_DELAY, "CreateForkNetworkMembershipsRange", [start_id, end_id])
end
end
def missing_members?(start_id, end_id)
count_sql = <<~MISSING_MEMBERS
SELECT COUNT(*)
FROM forked_project_links
WHERE NOT EXISTS (
SELECT true
FROM fork_network_members
WHERE fork_network_members.project_id = forked_project_links.forked_to_project_id
)
AND forked_project_links.id BETWEEN #{start_id} AND #{end_id}
MISSING_MEMBERS
ForkNetworkMember.count_by_sql(count_sql) > 0
end
def log(message)
Rails.logger.info("#{self.class.name} - #{message}")
end
end
end
end
module Gitlab
module BackgroundMigration
class PopulateForkNetworksRange
def perform(start_id, end_id)
log("Creating fork networks for forked project links: #{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
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
)
AND NOT EXISTS (
SELECT true
FROM fork_networks
WHERE forked_project_links.forked_from_project_id = fork_networks.root_project_id
)
AND forked_project_links.id BETWEEN #{start_id} AND #{end_id}
INSERT_NETWORKS
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
INNER JOIN forked_project_links
ON forked_project_links.forked_from_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
delay = BackgroundMigration::CreateForkNetworkMembershipsRange::RESCHEDULE_DELAY
BackgroundMigrationWorker.perform_in(delay, "CreateForkNetworkMembershipsRange", [start_id, end_id])
end
def log(message)
Rails.logger.info("#{self.class.name} - #{message}")
end
end
end
end
...@@ -23,7 +23,8 @@ module Gitlab ...@@ -23,7 +23,8 @@ module Gitlab
@extractor.analyze(closing_statements.join(" ")) @extractor.analyze(closing_statements.join(" "))
@extractor.issues.reject do |issue| @extractor.issues.reject do |issue|
@extractor.project.forked_from?(issue.project) # Don't extract issues on original project # Don't extract issues from the project this project was forked from
@extractor.project.forked_from?(issue.project)
end end
end end
end end
......
...@@ -8,8 +8,8 @@ msgid "" ...@@ -8,8 +8,8 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: gitlab 1.0.0\n" "Project-Id-Version: gitlab 1.0.0\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-10-04 23:47+0100\n" "POT-Creation-Date: 2017-10-06 18:33+0200\n"
"PO-Revision-Date: 2017-10-04 23:47+0100\n" "PO-Revision-Date: 2017-10-06 18:33+0200\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n" "Language: \n"
...@@ -23,6 +23,11 @@ msgid_plural "%d commits" ...@@ -23,6 +23,11 @@ msgid_plural "%d commits"
msgstr[0] "" msgstr[0] ""
msgstr[1] "" msgstr[1] ""
msgid "%d layer"
msgid_plural "%d layers"
msgstr[0] ""
msgstr[1] ""
msgid "%s additional commit has been omitted to prevent performance issues." msgid "%s additional commit has been omitted to prevent performance issues."
msgid_plural "%s additional commits have been omitted to prevent performance issues." msgid_plural "%s additional commits have been omitted to prevent performance issues."
msgstr[0] "" msgstr[0] ""
...@@ -59,6 +64,9 @@ msgstr[1] "" ...@@ -59,6 +64,9 @@ msgstr[1] ""
msgid "1st contribution!" msgid "1st contribution!"
msgstr "" msgstr ""
msgid "2FA enabled"
msgstr ""
msgid "A collection of graphs regarding Continuous Integration" msgid "A collection of graphs regarding Continuous Integration"
msgstr "" msgstr ""
...@@ -528,6 +536,51 @@ msgstr "" ...@@ -528,6 +536,51 @@ msgstr ""
msgid "Compare" msgid "Compare"
msgstr "" msgstr ""
msgid "Container Registry"
msgstr ""
msgid "ContainerRegistry|Created"
msgstr ""
msgid "ContainerRegistry|First log in to GitLab&rsquo;s Container Registry using your GitLab username and password. If you have %{link_2fa} you need to use a %{link_token}:"
msgstr ""
msgid "ContainerRegistry|GitLab supports up to 3 levels of image names. The following examples of images are valid for your project:"
msgstr ""
msgid "ContainerRegistry|How to use the Container Registry"
msgstr ""
msgid "ContainerRegistry|Learn more about"
msgstr ""
msgid "ContainerRegistry|No tags in Container Registry for this container image."
msgstr ""
msgid "ContainerRegistry|Once you log in, you&rsquo;re free to create and upload a container image using the common %{build} and %{push} commands"
msgstr ""
msgid "ContainerRegistry|Remove repository"
msgstr ""
msgid "ContainerRegistry|Remove tag"
msgstr ""
msgid "ContainerRegistry|Size"
msgstr ""
msgid "ContainerRegistry|Tag"
msgstr ""
msgid "ContainerRegistry|Tag ID"
msgstr ""
msgid "ContainerRegistry|Use different image names"
msgstr ""
msgid "ContainerRegistry|With the Docker Container Registry integrated into GitLab, every project can have its own space to store its Docker images."
msgstr ""
msgid "Contribution guide" msgid "Contribution guide"
msgstr "" msgstr ""
...@@ -739,6 +792,9 @@ msgstr[1] "" ...@@ -739,6 +792,9 @@ msgstr[1] ""
msgid "ForkedFromProjectPath|Forked from" msgid "ForkedFromProjectPath|Forked from"
msgstr "" msgstr ""
msgid "ForkedFromProjectPath|Forked from %{project_name} (deleted)"
msgstr ""
msgid "Format" msgid "Format"
msgstr "" msgstr ""
...@@ -949,6 +1005,9 @@ msgstr "" ...@@ -949,6 +1005,9 @@ msgstr ""
msgid "New tag" msgid "New tag"
msgstr "" msgstr ""
msgid "No container images stored for this project. Add one by following the instructions above."
msgstr ""
msgid "No repository" msgid "No repository"
msgstr "" msgstr ""
...@@ -1024,6 +1083,9 @@ msgstr "" ...@@ -1024,6 +1083,9 @@ msgstr ""
msgid "OpenedNDaysAgo|Opened" msgid "OpenedNDaysAgo|Opened"
msgstr "" msgstr ""
msgid "Opens in a new window"
msgstr ""
msgid "Options" msgid "Options"
msgstr "" msgstr ""
...@@ -1350,6 +1412,15 @@ msgstr[1] "" ...@@ -1350,6 +1412,15 @@ msgstr[1] ""
msgid "Snippets" msgid "Snippets"
msgstr "" msgstr ""
msgid "Something went wrong on our end."
msgstr ""
msgid "Something went wrong while fetching the projects."
msgstr ""
msgid "Something went wrong while fetching the registry list."
msgstr ""
msgid "SortOptions|Access level, ascending" msgid "SortOptions|Access level, ascending"
msgstr "" msgstr ""
...@@ -1905,3 +1976,6 @@ msgid "parent" ...@@ -1905,3 +1976,6 @@ msgid "parent"
msgid_plural "parents" msgid_plural "parents"
msgstr[0] "" msgstr[0] ""
msgstr[1] "" msgstr[1] ""
msgid "personal access token"
msgstr ""
require 'rails_helper' require 'rails_helper'
describe Projects::BlobController do describe Projects::BlobController do
include ProjectForksHelper
let(:project) { create(:project, :public, :repository) } let(:project) { create(:project, :public, :repository) }
describe "GET show" do describe "GET show" do
...@@ -226,9 +228,8 @@ describe Projects::BlobController do ...@@ -226,9 +228,8 @@ describe Projects::BlobController do
end end
context 'when user has forked project' do context 'when user has forked project' do
let(:forked_project_link) { create(:forked_project_link, forked_from_project: project) } let!(:forked_project) { fork_project(project, guest, namespace: guest.namespace, repository: true) }
let!(:forked_project) { forked_project_link.forked_to_project } let(:guest) { create(:user) }
let(:guest) { forked_project.owner }
before do before do
sign_in(guest) sign_in(guest)
......
require 'spec_helper' require 'spec_helper'
describe Projects::MergeRequests::DiffsController do describe Projects::MergeRequests::DiffsController do
include ProjectForksHelper
let(:project) { create(:project, :repository) } let(:project) { create(:project, :repository) }
let(:user) { project.owner } let(:user) { project.owner }
let(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: project) } let(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: project) }
...@@ -37,12 +39,12 @@ describe Projects::MergeRequests::DiffsController do ...@@ -37,12 +39,12 @@ describe Projects::MergeRequests::DiffsController do
render_views render_views
let(:project) { create(:project, :repository) } let(:project) { create(:project, :repository) }
let(:fork_project) { create(:forked_project_with_submodules) } let(:forked_project) { fork_project_with_submodules(project) }
let(:merge_request) { create(:merge_request_with_diffs, source_project: fork_project, source_branch: 'add-submodule-version-bump', target_branch: 'master', target_project: project) } let(:merge_request) { create(:merge_request_with_diffs, source_project: forked_project, source_branch: 'add-submodule-version-bump', target_branch: 'master', target_project: project) }
before do before do
fork_project.build_forked_project_link(forked_to_project_id: fork_project.id, forked_from_project_id: project.id) project.add_developer(user)
fork_project.save
merge_request.reload merge_request.reload
go go
end end
......
require 'spec_helper' require 'spec_helper'
describe Projects::MergeRequestsController do describe Projects::MergeRequestsController do
include ProjectForksHelper
let(:project) { create(:project, :repository) } let(:project) { create(:project, :repository) }
let(:user) { project.owner } let(:user) { project.owner }
let(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: project) } let(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: project) }
...@@ -204,14 +206,11 @@ describe Projects::MergeRequestsController do ...@@ -204,14 +206,11 @@ describe Projects::MergeRequestsController do
context 'there is no source project' do context 'there is no source project' do
let(:project) { create(:project, :repository) } let(:project) { create(:project, :repository) }
let(:fork_project) { create(:forked_project_with_submodules) } let(:forked_project) { fork_project_with_submodules(project) }
let(:merge_request) { create(:merge_request, source_project: fork_project, source_branch: 'add-submodule-version-bump', target_branch: 'master', target_project: project) } let!(:merge_request) { create(:merge_request, source_project: forked_project, source_branch: 'add-submodule-version-bump', target_branch: 'master', target_project: project) }
before do before do
fork_project.build_forked_project_link(forked_to_project_id: fork_project.id, forked_from_project_id: project.id) forked_project.destroy
fork_project.save
merge_request.reload
fork_project.destroy
end end
it 'closes MR without errors' do it 'closes MR without errors' do
...@@ -599,21 +598,16 @@ describe Projects::MergeRequestsController do ...@@ -599,21 +598,16 @@ describe Projects::MergeRequestsController do
describe 'GET ci_environments_status' do describe 'GET ci_environments_status' do
context 'the environment is from a forked project' do context 'the environment is from a forked project' do
let!(:forked) { create(:project, :repository) } let!(:forked) { fork_project(project, user, repository: true) }
let!(:environment) { create(:environment, project: forked) } let!(:environment) { create(:environment, project: forked) }
let!(:deployment) { create(:deployment, environment: environment, sha: forked.commit.id, ref: 'master') } let!(:deployment) { create(:deployment, environment: environment, sha: forked.commit.id, ref: 'master') }
let(:admin) { create(:admin) } let(:admin) { create(:admin) }
let(:merge_request) do let(:merge_request) do
create(:forked_project_link, forked_to_project: forked,
forked_from_project: project)
create(:merge_request, source_project: forked, target_project: project) create(:merge_request, source_project: forked, target_project: project)
end end
before do before do
forked.team << [user, :master]
get :ci_environments_status, get :ci_environments_status,
namespace_id: merge_request.project.namespace.to_param, namespace_id: merge_request.project.namespace.to_param,
project_id: merge_request.project, project_id: merge_request.project,
......
require 'spec_helper' require 'spec_helper'
describe Projects::NotesController do describe Projects::NotesController do
include ProjectForksHelper
let(:user) { create(:user) } let(:user) { create(:user) }
let(:project) { create(:project) } let(:project) { create(:project) }
let(:issue) { create(:issue, project: project) } let(:issue) { create(:issue, project: project) }
...@@ -210,18 +212,16 @@ describe Projects::NotesController do ...@@ -210,18 +212,16 @@ describe Projects::NotesController do
context 'when creating a commit comment from an MR fork' do context 'when creating a commit comment from an MR fork' do
let(:project) { create(:project, :repository) } let(:project) { create(:project, :repository) }
let(:fork_project) do let(:forked_project) do
create(:project, :repository).tap do |fork| fork_project(project, nil, repository: true)
create(:forked_project_link, forked_to_project: fork, forked_from_project: project)
end
end end
let(:merge_request) do let(:merge_request) do
create(:merge_request, source_project: fork_project, target_project: project, source_branch: 'feature', target_branch: 'master') create(:merge_request, source_project: forked_project, target_project: project, source_branch: 'feature', target_branch: 'master')
end end
let(:existing_comment) do let(:existing_comment) do
create(:note_on_commit, note: 'a note', project: fork_project, commit_id: merge_request.commit_shas.first) create(:note_on_commit, note: 'a note', project: forked_project, commit_id: merge_request.commit_shas.first)
end end
def post_create(extra_params = {}) def post_create(extra_params = {})
...@@ -231,7 +231,7 @@ describe Projects::NotesController do ...@@ -231,7 +231,7 @@ describe Projects::NotesController do
project_id: project, project_id: project,
target_type: 'merge_request', target_type: 'merge_request',
target_id: merge_request.id, target_id: merge_request.id,
note_project_id: fork_project.id, note_project_id: forked_project.id,
in_reply_to_discussion_id: existing_comment.discussion_id in_reply_to_discussion_id: existing_comment.discussion_id
}.merge(extra_params) }.merge(extra_params)
end end
...@@ -253,16 +253,16 @@ describe Projects::NotesController do ...@@ -253,16 +253,16 @@ describe Projects::NotesController do
end end
context 'when the user has access to the fork' do context 'when the user has access to the fork' do
let(:discussion) { fork_project.notes.find_discussion(existing_comment.discussion_id) } let(:discussion) { forked_project.notes.find_discussion(existing_comment.discussion_id) }
before do before do
fork_project.add_developer(user) forked_project.add_developer(user)
existing_comment existing_comment
end end
it 'creates the note' do it 'creates the note' do
expect { post_create }.to change { fork_project.notes.count }.by(1) expect { post_create }.to change { forked_project.notes.count }.by(1)
end end
end end
end end
......
require('spec_helper') require('spec_helper')
describe ProjectsController do describe ProjectsController do
include ProjectForksHelper
let(:project) { create(:project) } let(:project) { create(:project) }
let(:public_project) { create(:project, :public) } let(:public_project) { create(:project, :public) }
let(:user) { create(:user) } let(:user) { create(:user) }
...@@ -377,10 +379,10 @@ describe ProjectsController do ...@@ -377,10 +379,10 @@ describe ProjectsController do
context "when the project is forked" do context "when the project is forked" do
let(:project) { create(:project, :repository) } let(:project) { create(:project, :repository) }
let(:fork_project) { create(:project, :repository, forked_from_project: project) } let(:forked_project) { fork_project(project, nil, repository: true) }
let(:merge_request) do let(:merge_request) do
create(:merge_request, create(:merge_request,
source_project: fork_project, source_project: forked_project,
target_project: project) target_project: project)
end end
...@@ -388,7 +390,7 @@ describe ProjectsController do ...@@ -388,7 +390,7 @@ describe ProjectsController do
project.merge_requests << merge_request project.merge_requests << merge_request
sign_in(admin) sign_in(admin)
delete :destroy, namespace_id: fork_project.namespace, id: fork_project delete :destroy, namespace_id: forked_project.namespace, id: forked_project
expect(merge_request.reload.state).to eq('closed') expect(merge_request.reload.state).to eq('closed')
end end
...@@ -455,18 +457,14 @@ describe ProjectsController do ...@@ -455,18 +457,14 @@ describe ProjectsController do
end end
context 'with forked project' do context 'with forked project' do
let(:project_fork) { create(:project, :repository, namespace: user.namespace) } let(:forked_project) { fork_project(create(:project, :public), user) }
before do
create(:forked_project_link, forked_to_project: project_fork)
end
it 'removes fork from project' do it 'removes fork from project' do
delete(:remove_fork, delete(:remove_fork,
namespace_id: project_fork.namespace.to_param, namespace_id: forked_project.namespace.to_param,
id: project_fork.to_param, format: :js) id: forked_project.to_param, format: :js)
expect(project_fork.forked?).to be_falsey expect(forked_project.reload.forked?).to be_falsey
expect(flash[:notice]).to eq('The fork relationship has been removed.') expect(flash[:notice]).to eq('The fork relationship has been removed.')
expect(response).to render_template(:remove_fork) expect(response).to render_template(:remove_fork)
end end
......
FactoryGirl.define do
factory :fork_network do
association :root_project, factory: :project
end
end
...@@ -3,12 +3,13 @@ require 'spec_helper' ...@@ -3,12 +3,13 @@ require 'spec_helper'
feature 'Dashboard Merge Requests' do feature 'Dashboard Merge Requests' do
include FilterItemSelectHelper include FilterItemSelectHelper
include SortingHelper include SortingHelper
include ProjectForksHelper
let(:current_user) { create :user } let(:current_user) { create :user }
let(:project) { create(:project) } let(:project) { create(:project) }
let(:public_project) { create(:project, :public, :repository) } let(:public_project) { create(:project, :public, :repository) }
let(:forked_project) { Projects::ForkService.new(public_project, current_user).execute } let(:forked_project) { fork_project(public_project, current_user, repository: true) }
before do before do
project.add_master(current_user) project.add_master(current_user)
......
require 'spec_helper'
feature 'Creating a merge request from a fork', :js do
include ProjectForksHelper
let(:user) { create(:user) }
let(:project) { create(:project, :public, :repository) }
let!(:source_project) do
fork_project(project, user,
repository: true,
namespace: user.namespace)
end
before do
source_project.add_master(user)
sign_in(user)
end
shared_examples 'create merge request to other project' do
it 'has all possible target projects' do
visit project_new_merge_request_path(source_project)
first('.js-target-project').click
within('.dropdown-target-project .dropdown-content') do
expect(page).to have_content(project.full_path)
expect(page).to have_content(target_project.full_path)
expect(page).to have_content(source_project.full_path)
end
end
it 'allows creating the merge request to another target project' do
visit project_merge_requests_path(source_project)
page.within '.content' do
click_link 'New merge request'
end
find('.js-source-branch', match: :first).click
find('.dropdown-source-branch .dropdown-content a', match: :first).click
first('.js-target-project').click
find('.dropdown-target-project .dropdown-content a', text: target_project.full_path).click
click_button 'Compare branches and continue'
wait_for_requests
expect { click_button 'Submit merge request' }
.to change { target_project.merge_requests.reload.size }.by(1)
end
it 'updates the branches when selecting a new target project' do
target_project_member = target_project.owner
CreateBranchService.new(target_project, target_project_member)
.execute('a-brand-new-branch-to-test', 'master')
visit project_new_merge_request_path(source_project)
first('.js-target-project').click
find('.dropdown-target-project .dropdown-content a', text: target_project.full_path).click
wait_for_requests
first('.js-target-branch').click
within('.dropdown-target-branch .dropdown-content') do
expect(page).to have_content('a-brand-new-branch-to-test')
end
end
end
context 'creating to the source of a fork' do
let!(:target_project) { project }
it_behaves_like('create merge request to other project')
end
context 'creating to a sibling of a fork' do
let!(:target_project) do
other_user = create(:user)
fork_project(project, other_user,
repository: true,
namespace: other_user.namespace)
end
it_behaves_like('create merge request to other project')
end
end
require 'spec_helper' require 'spec_helper'
feature 'Merge request created from fork' do feature 'Merge request created from fork' do
include ProjectForksHelper
given(:user) { create(:user) } given(:user) { create(:user) }
given(:project) { create(:project, :public, :repository) } given(:project) { create(:project, :public, :repository) }
given(:fork_project) { create(:project, :public, :repository) } given(:forked_project) { fork_project(project, user, repository: true) }
given!(:merge_request) do given!(:merge_request) do
create(:forked_project_link, forked_to_project: fork_project, create(:merge_request_with_diffs, source_project: forked_project,
forked_from_project: project)
create(:merge_request_with_diffs, source_project: fork_project,
target_project: project, target_project: project,
description: 'Test merge request') description: 'Test merge request')
end end
background do background do
fork_project.team << [user, :master] forked_project.team << [user, :master]
sign_in user sign_in user
end end
...@@ -31,7 +30,7 @@ feature 'Merge request created from fork' do ...@@ -31,7 +30,7 @@ feature 'Merge request created from fork' do
background do background do
create(:note_on_commit, note: comment, create(:note_on_commit, note: comment,
project: fork_project, project: forked_project,
commit_id: merge_request.commit_shas.first) commit_id: merge_request.commit_shas.first)
end end
...@@ -55,7 +54,7 @@ feature 'Merge request created from fork' do ...@@ -55,7 +54,7 @@ feature 'Merge request created from fork' do
context 'source project is deleted' do context 'source project is deleted' do
background do background do
MergeRequests::MergeService.new(project, user).execute(merge_request) MergeRequests::MergeService.new(project, user).execute(merge_request)
fork_project.destroy! forked_project.destroy!
end end
scenario 'user can access merge request', js: true do scenario 'user can access merge request', js: true do
...@@ -69,7 +68,7 @@ feature 'Merge request created from fork' do ...@@ -69,7 +68,7 @@ feature 'Merge request created from fork' do
context 'pipeline present in source project' do context 'pipeline present in source project' do
given(:pipeline) do given(:pipeline) do
create(:ci_pipeline, create(:ci_pipeline,
project: fork_project, project: forked_project,
sha: merge_request.diff_head_sha, sha: merge_request.diff_head_sha,
ref: merge_request.source_branch) ref: merge_request.source_branch)
end end
......
require 'spec_helper' require 'spec_helper'
feature 'Diffs URL', js: true do feature 'Diffs URL', js: true do
include ProjectForksHelper
let(:project) { create(:project, :public, :repository) } let(:project) { create(:project, :public, :repository) }
let(:merge_request) { create(:merge_request, source_project: project) } let(:merge_request) { create(:merge_request, source_project: project) }
...@@ -68,7 +70,7 @@ feature 'Diffs URL', js: true do ...@@ -68,7 +70,7 @@ feature 'Diffs URL', js: true do
context 'when editing file' do context 'when editing file' do
let(:author_user) { create(:user) } let(:author_user) { create(:user) }
let(:user) { create(:user) } let(:user) { create(:user) }
let(:forked_project) { Projects::ForkService.new(project, author_user).execute } let(:forked_project) { fork_project(project, author_user, repository: true) }
let(:merge_request) { create(:merge_request_with_diffs, source_project: forked_project, target_project: project, author: author_user) } let(:merge_request) { create(:merge_request_with_diffs, source_project: forked_project, target_project: project, author: author_user) }
let(:changelog_id) { Digest::SHA1.hexdigest("CHANGELOG") } let(:changelog_id) { Digest::SHA1.hexdigest("CHANGELOG") }
......
require 'rails_helper' require 'rails_helper'
describe 'New/edit merge request', :js do describe 'New/edit merge request', :js do
include ProjectForksHelper
let!(:project) { create(:project, :public, :repository) } let!(:project) { create(:project, :public, :repository) }
let(:fork_project) { create(:project, :repository, forked_from_project: project) } let(:forked_project) { fork_project(project, nil, repository: true) }
let!(:user) { create(:user) } let!(:user) { create(:user) }
let!(:user2) { create(:user) } let!(:user2) { create(:user) }
let!(:milestone) { create(:milestone, project: project) } let!(:milestone) { create(:milestone, project: project) }
...@@ -170,16 +172,16 @@ describe 'New/edit merge request', :js do ...@@ -170,16 +172,16 @@ describe 'New/edit merge request', :js do
context 'forked project' do context 'forked project' do
before do before do
fork_project.team << [user, :master] forked_project.team << [user, :master]
sign_in(user) sign_in(user)
end end
context 'new merge request' do context 'new merge request' do
before do before do
visit project_new_merge_request_path( visit project_new_merge_request_path(
fork_project, forked_project,
merge_request: { merge_request: {
source_project_id: fork_project.id, source_project_id: forked_project.id,
target_project_id: project.id, target_project_id: project.id,
source_branch: 'fix', source_branch: 'fix',
target_branch: 'master' target_branch: 'master'
...@@ -238,7 +240,7 @@ describe 'New/edit merge request', :js do ...@@ -238,7 +240,7 @@ describe 'New/edit merge request', :js do
context 'edit merge request' do context 'edit merge request' do
before do before do
merge_request = create(:merge_request, merge_request = create(:merge_request,
source_project: fork_project, source_project: forked_project,
target_project: project, target_project: project,
source_branch: 'fix', source_branch: 'fix',
target_branch: 'master' target_branch: 'master'
......
require 'spec_helper' require 'spec_helper'
feature 'issuable templates', js: true do feature 'issuable templates', js: true do
include ProjectForksHelper
let(:user) { create(:user) } let(:user) { create(:user) }
let(:project) { create(:project, :public, :repository) } let(:project) { create(:project, :public, :repository) }
let(:issue_form_location) { '#content-body .issuable-details .detail-page-description' } let(:issue_form_location) { '#content-body .issuable-details .detail-page-description' }
...@@ -116,15 +118,13 @@ feature 'issuable templates', js: true do ...@@ -116,15 +118,13 @@ feature 'issuable templates', js: true do
context 'user creates a merge request from a forked project using templates' do context 'user creates a merge request from a forked project using templates' do
let(:template_content) { 'this is a test "feature-proposal" template' } let(:template_content) { 'this is a test "feature-proposal" template' }
let(:fork_user) { create(:user) } let(:fork_user) { create(:user) }
let(:fork_project) { create(:project, :public, :repository) } let(:forked_project) { fork_project(project, fork_user) }
let(:merge_request) { create(:merge_request, :with_diffs, source_project: fork_project, target_project: project) } let(:merge_request) { create(:merge_request, :with_diffs, source_project: forked_project, target_project: project) }
background do background do
sign_out(:user) sign_out(:user)
project.team << [fork_user, :developer] project.team << [fork_user, :developer]
fork_project.team << [fork_user, :master]
create(:forked_project_link, forked_to_project: fork_project, forked_from_project: project)
sign_in(fork_user) sign_in(fork_user)
......
...@@ -79,7 +79,7 @@ feature 'User creates a directory', js: true do ...@@ -79,7 +79,7 @@ feature 'User creates a directory', js: true do
fill_in(:commit_message, with: 'New commit message', visible: true) fill_in(:commit_message, with: 'New commit message', visible: true)
click_button('Create directory') click_button('Create directory')
fork = user.fork_of(project2) fork = user.fork_of(project2.reload)
expect(current_path).to eq(project_new_merge_request_path(fork)) expect(current_path).to eq(project_new_merge_request_path(fork))
end end
......
...@@ -142,7 +142,7 @@ describe 'User creates files' do ...@@ -142,7 +142,7 @@ describe 'User creates files' do
fill_in(:commit_message, with: 'New commit message', visible: true) fill_in(:commit_message, with: 'New commit message', visible: true)
click_button('Commit changes') click_button('Commit changes')
fork = user.fork_of(project2) fork = user.fork_of(project2.reload)
expect(current_path).to eq(project_new_merge_request_path(fork)) expect(current_path).to eq(project_new_merge_request_path(fork))
expect(page).to have_content('New commit message') expect(page).to have_content('New commit message')
......
...@@ -59,7 +59,7 @@ describe 'User deletes files' do ...@@ -59,7 +59,7 @@ describe 'User deletes files' do
fill_in(:commit_message, with: 'New commit message', visible: true) fill_in(:commit_message, with: 'New commit message', visible: true)
click_button('Delete file') click_button('Delete file')
fork = user.fork_of(project2) fork = user.fork_of(project2.reload)
expect(current_path).to eq(project_new_merge_request_path(fork)) expect(current_path).to eq(project_new_merge_request_path(fork))
expect(page).to have_content('New commit message') expect(page).to have_content('New commit message')
......
require 'spec_helper' require 'spec_helper'
describe 'User edits files' do describe 'User edits files' do
include ProjectForksHelper
let(:project) { create(:project, :repository, name: 'Shop') } let(:project) { create(:project, :repository, name: 'Shop') }
let(:project2) { create(:project, :repository, name: 'Another Project', path: 'another-project') } let(:project2) { create(:project, :repository, name: 'Another Project', path: 'another-project') }
let(:project_tree_path_root_ref) { project_tree_path(project, project.repository.root_ref) } let(:project_tree_path_root_ref) { project_tree_path(project, project.repository.root_ref) }
...@@ -122,7 +123,7 @@ describe 'User edits files' do ...@@ -122,7 +123,7 @@ describe 'User edits files' do
fill_in(:commit_message, with: 'New commit message', visible: true) fill_in(:commit_message, with: 'New commit message', visible: true)
click_button('Commit changes') click_button('Commit changes')
fork = user.fork_of(project2) fork = user.fork_of(project2.reload)
expect(current_path).to eq(project_new_merge_request_path(fork)) expect(current_path).to eq(project_new_merge_request_path(fork))
...@@ -130,5 +131,34 @@ describe 'User edits files' do ...@@ -130,5 +131,34 @@ describe 'User edits files' do
expect(page).to have_content('New commit message') expect(page).to have_content('New commit message')
end end
context 'when the user already had a fork of the project', :js do
let!(:forked_project) { fork_project(project2, user, namespace: user.namespace, repository: true) }
before do
visit(project2_tree_path_root_ref)
end
it 'links to the forked project for editing' do
click_link('.gitignore')
find('.js-edit-blob').click
expect(page).not_to have_link('Fork')
expect(page).not_to have_button('Cancel')
execute_script("ace.edit('editor').setValue('*.rbca')")
fill_in(:commit_message, with: 'Another commit', visible: true)
click_button('Commit changes')
fork = user.fork_of(project2)
expect(current_path).to eq(project_new_merge_request_path(fork))
wait_for_requests
expect(page).to have_content('Another commit')
expect(page).to have_content("From #{forked_project.full_path}")
expect(page).to have_content("into #{project2.full_path}")
end
end
end end
end end
...@@ -74,7 +74,7 @@ describe 'User replaces files' do ...@@ -74,7 +74,7 @@ describe 'User replaces files' do
expect(page).to have_content('Replacement file commit message') expect(page).to have_content('Replacement file commit message')
fork = user.fork_of(project2) fork = user.fork_of(project2.reload)
expect(current_path).to eq(project_new_merge_request_path(fork)) expect(current_path).to eq(project_new_merge_request_path(fork))
......
...@@ -39,6 +39,9 @@ describe 'User uploads files' do ...@@ -39,6 +39,9 @@ describe 'User uploads files' do
expect(current_path).to eq(project_new_merge_request_path(project)) expect(current_path).to eq(project_new_merge_request_path(project))
click_link('Changes') click_link('Changes')
find("a[data-action='diffs']", text: 'Changes').click
wait_for_requests
expect(page).to have_content('Lorem ipsum dolor sit amet') expect(page).to have_content('Lorem ipsum dolor sit amet')
expect(page).to have_content('Sed ut perspiciatis unde omnis') expect(page).to have_content('Sed ut perspiciatis unde omnis')
...@@ -51,7 +54,7 @@ describe 'User uploads files' do ...@@ -51,7 +54,7 @@ describe 'User uploads files' do
visit(project2_tree_path_root_ref) visit(project2_tree_path_root_ref)
end end
it 'uploads and commit a new fileto a forked project', js: true do it 'uploads and commit a new file to a forked project', js: true do
find('.add-to-tree').click find('.add-to-tree').click
click_link('Upload file') click_link('Upload file')
...@@ -69,11 +72,13 @@ describe 'User uploads files' do ...@@ -69,11 +72,13 @@ describe 'User uploads files' do
expect(page).to have_content('New commit message') expect(page).to have_content('New commit message')
fork = user.fork_of(project2) fork = user.fork_of(project2.reload)
expect(current_path).to eq(project_new_merge_request_path(fork)) expect(current_path).to eq(project_new_merge_request_path(fork))
click_link('Changes') find("a[data-action='diffs']", text: 'Changes').click
wait_for_requests
expect(page).to have_content('Lorem ipsum dolor sit amet') expect(page).to have_content('Lorem ipsum dolor sit amet')
expect(page).to have_content('Sed ut perspiciatis unde omnis') expect(page).to have_content('Sed ut perspiciatis unde omnis')
......
require 'spec_helper' require 'spec_helper'
feature 'Project' do feature 'Project' do
include ProjectForksHelper
describe 'creating from template' do describe 'creating from template' do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:template) { Gitlab::ProjectTemplate.find(:rails) } let(:template) { Gitlab::ProjectTemplate.find(:rails) }
...@@ -57,11 +59,10 @@ feature 'Project' do ...@@ -57,11 +59,10 @@ feature 'Project' do
describe 'remove forked relationship', js: true do describe 'remove forked relationship', js: true do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:project) { create(:project, namespace: user.namespace) } let(:project) { fork_project(create(:project, :public), user, namespace_id: user.namespace) }
before do before do
sign_in user sign_in user
create(:forked_project_link, forked_to_project: project)
visit edit_project_path(project) visit edit_project_path(project)
end end
...@@ -71,11 +72,60 @@ feature 'Project' do ...@@ -71,11 +72,60 @@ feature 'Project' do
remove_with_confirm('Remove fork relationship', project.path) remove_with_confirm('Remove fork relationship', project.path)
expect(page).to have_content 'The fork relationship has been removed.' expect(page).to have_content 'The fork relationship has been removed.'
expect(project.forked?).to be_falsey expect(project.reload.forked?).to be_falsey
expect(page).not_to have_content 'Remove fork relationship' expect(page).not_to have_content 'Remove fork relationship'
end end
end end
describe 'showing information about source of a project fork' do
let(:user) { create(:user) }
let(:base_project) { create(:project, :public, :repository) }
let(:forked_project) { fork_project(base_project, user, repository: true) }
before do
sign_in user
end
it 'shows a link to the source project when it is available' do
visit project_path(forked_project)
expect(page).to have_content('Forked from')
expect(page).to have_link(base_project.full_name)
end
it 'does not contain fork network information for the root project' do
forked_project
visit project_path(base_project)
expect(page).not_to have_content('In fork network of')
expect(page).not_to have_content('Forked from')
end
it 'shows the name of the deleted project when the source was deleted' do
forked_project
Projects::DestroyService.new(base_project, base_project.owner).execute
visit project_path(forked_project)
expect(page).to have_content("Forked from #{base_project.full_name} (deleted)")
end
context 'a fork of a fork' do
let(:fork_of_fork) { fork_project(forked_project, user, repository: true) }
it 'links to the base project if the source project is removed' do
fork_of_fork
Projects::DestroyService.new(forked_project, user).execute
visit project_path(fork_of_fork)
expect(page).to have_content("Forked from")
expect(page).to have_link(base_project.full_name)
end
end
end
describe 'removal', js: true do describe 'removal', js: true do
let(:user) { create(:user, username: 'test', name: 'test') } let(:user) { create(:user, username: 'test', name: 'test') }
let(:project) { create(:project, namespace: user.namespace, name: 'project1') } let(:project) { create(:project, namespace: user.namespace, name: 'project1') }
......
require 'spec_helper'
describe MergeRequestTargetProjectFinder do
include ProjectForksHelper
let(:user) { create(:user) }
subject(:finder) { described_class.new(current_user: user, source_project: forked_project) }
shared_examples 'finding related projects' do
it 'finds sibling projects and base project' do
other_fork
expect(finder.execute).to contain_exactly(base_project, other_fork, forked_project)
end
it 'does not include projects that have merge requests turned off' do
other_fork.project_feature.update!(merge_requests_access_level: ProjectFeature::DISABLED)
base_project.project_feature.update!(merge_requests_access_level: ProjectFeature::DISABLED)
expect(finder.execute).to contain_exactly(forked_project)
end
end
context 'public projects' do
let(:base_project) { create(:project, :public, path: 'base') }
let(:forked_project) { fork_project(base_project) }
let(:other_fork) { fork_project(base_project) }
it_behaves_like 'finding related projects'
end
context 'private projects' do
let(:base_project) { create(:project, :private, path: 'base') }
let(:forked_project) { fork_project(base_project, base_project.owner) }
let(:other_fork) { fork_project(base_project, base_project.owner) }
context 'when the user is a member of all projects' do
before do
base_project.add_developer(user)
forked_project.add_developer(user)
other_fork.add_developer(user)
end
it_behaves_like 'finding related projects'
end
it 'only finds the projects the user is a member of' do
other_fork.add_developer(user)
base_project.add_developer(user)
expect(finder.execute).to contain_exactly(other_fork, base_project)
end
end
end
require 'spec_helper' require 'spec_helper'
describe MergeRequestsFinder do describe MergeRequestsFinder do
include ProjectForksHelper
let(:user) { create :user } let(:user) { create :user }
let(:user2) { create :user } let(:user2) { create :user }
let(:project1) { create(:project) } let(:project1) { create(:project, :public) }
let(:project2) { create(:project, forked_from_project: project1) } let(:project2) { fork_project(project1, user) }
let(:project3) { create(:project, :archived, forked_from_project: project1) } let(:project3) do
p = fork_project(project1, user)
p.update!(archived: true)
p
end
let!(:merge_request1) { create(:merge_request, :simple, author: user, source_project: project2, target_project: project1) } let!(:merge_request1) { create(:merge_request, :simple, author: user, source_project: project2, target_project: project1) }
let!(:merge_request2) { create(:merge_request, :simple, author: user, source_project: project2, target_project: project1, state: 'closed') } let!(:merge_request2) { create(:merge_request, :simple, author: user, source_project: project2, target_project: project1, state: 'closed') }
......
require 'spec_helper' require 'spec_helper'
describe MergeRequestsHelper do describe MergeRequestsHelper do
include ProjectForksHelper
describe 'ci_build_details_path' do describe 'ci_build_details_path' do
let(:project) { create(:project) } let(:project) { create(:project) }
let(:merge_request) { MergeRequest.new } let(:merge_request) { MergeRequest.new }
...@@ -31,10 +32,10 @@ describe MergeRequestsHelper do ...@@ -31,10 +32,10 @@ describe MergeRequestsHelper do
describe 'within different projects' do describe 'within different projects' do
let(:project) { create(:project) } let(:project) { create(:project) }
let(:fork_project) { create(:project, forked_from_project: project) } let(:forked_project) { fork_project(project) }
let(:merge_request) { create(:merge_request, source_project: fork_project, target_project: project) } let(:merge_request) { create(:merge_request, source_project: forked_project, target_project: project) }
subject { format_mr_branch_names(merge_request) } subject { format_mr_branch_names(merge_request) }
let(:source_title) { "#{fork_project.full_path}:#{merge_request.source_branch}" } let(:source_title) { "#{forked_project.full_path}:#{merge_request.source_branch}" }
let(:target_title) { "#{project.full_path}:#{merge_request.target_branch}" } let(:target_title) { "#{project.full_path}:#{merge_request.target_branch}" }
it { is_expected.to eq([source_title, target_title]) } it { is_expected.to eq([source_title, target_title]) }
......
require 'spec_helper'
describe Gitlab::BackgroundMigration::CreateForkNetworkMembershipsRange, :migration, schema: 20170929131201 do
let(:migration) { described_class.new }
let(:base1) { create(:project) }
let(:base1_fork1) { create(:project) }
let(:base1_fork2) { create(:project) }
let(:base2) { create(:project) }
let(:base2_fork1) { create(:project) }
let(:base2_fork2) { create(:project) }
let(:fork_of_fork) { create(:project) }
let(:fork_of_fork2) { create(:project) }
let(:second_level_fork) { create(:project) }
let(:third_level_fork) { create(:project) }
let(:fork_network1) { fork_networks.find_by(root_project_id: base1.id) }
let(:fork_network2) { fork_networks.find_by(root_project_id: base2.id) }
let!(:forked_project_links) { table(:forked_project_links) }
let!(:fork_networks) { table(:fork_networks) }
let!(:fork_network_members) { table(:fork_network_members) }
before do
# The fork-network relation created for the forked project
fork_networks.create(id: 1, root_project_id: base1.id)
fork_network_members.create(project_id: base1.id, fork_network_id: 1)
fork_networks.create(id: 2, root_project_id: base2.id)
fork_network_members.create(project_id: base2.id, fork_network_id: 2)
# Normal fork links
forked_project_links.create(id: 1, forked_from_project_id: base1.id, forked_to_project_id: base1_fork1.id)
forked_project_links.create(id: 2, forked_from_project_id: base1.id, forked_to_project_id: base1_fork2.id)
forked_project_links.create(id: 3, forked_from_project_id: base2.id, forked_to_project_id: base2_fork1.id)
forked_project_links.create(id: 4, forked_from_project_id: base2.id, forked_to_project_id: base2_fork2.id)
# Fork links
forked_project_links.create(id: 5, forked_from_project_id: base1_fork1.id, forked_to_project_id: fork_of_fork.id)
forked_project_links.create(id: 6, forked_from_project_id: base1_fork1.id, forked_to_project_id: fork_of_fork2.id)
# Forks 3 levels down
forked_project_links.create(id: 7, forked_from_project_id: fork_of_fork.id, forked_to_project_id: second_level_fork.id)
forked_project_links.create(id: 8, forked_from_project_id: second_level_fork.id, forked_to_project_id: third_level_fork.id)
migration.perform(1, 8)
end
it 'creates a memberships for the direct forks' do
base1_fork1_membership = fork_network_members.find_by(fork_network_id: fork_network1.id,
project_id: base1_fork1.id)
base1_fork2_membership = fork_network_members.find_by(fork_network_id: fork_network1.id,
project_id: base1_fork2.id)
base2_fork1_membership = fork_network_members.find_by(fork_network_id: fork_network2.id,
project_id: base2_fork1.id)
base2_fork2_membership = fork_network_members.find_by(fork_network_id: fork_network2.id,
project_id: base2_fork2.id)
expect(base1_fork1_membership.forked_from_project_id).to eq(base1.id)
expect(base1_fork2_membership.forked_from_project_id).to eq(base1.id)
expect(base2_fork1_membership.forked_from_project_id).to eq(base2.id)
expect(base2_fork2_membership.forked_from_project_id).to eq(base2.id)
end
it 'adds the fork network members for forks of forks' do
fork_of_fork_membership = fork_network_members.find_by(project_id: fork_of_fork.id,
fork_network_id: fork_network1.id)
fork_of_fork2_membership = fork_network_members.find_by(project_id: fork_of_fork2.id,
fork_network_id: fork_network1.id)
second_level_fork_membership = fork_network_members.find_by(project_id: second_level_fork.id,
fork_network_id: fork_network1.id)
third_level_fork_membership = fork_network_members.find_by(project_id: third_level_fork.id,
fork_network_id: fork_network1.id)
expect(fork_of_fork_membership.forked_from_project_id).to eq(base1_fork1.id)
expect(fork_of_fork2_membership.forked_from_project_id).to eq(base1_fork1.id)
expect(second_level_fork_membership.forked_from_project_id).to eq(fork_of_fork.id)
expect(third_level_fork_membership.forked_from_project_id).to eq(second_level_fork.id)
end
it 'reschedules itself when there are missing members' do
allow(migration).to receive(:missing_members?).and_return(true)
expect(BackgroundMigrationWorker)
.to receive(:perform_in).with(described_class::RESCHEDULE_DELAY, "CreateForkNetworkMembershipsRange", [1, 3])
migration.perform(1, 3)
end
it 'can be repeated without effect' do
expect { fork_network_members.count }.not_to change { migration.perform(1, 7) }
end
it 'knows it is finished for this range' do
expect(migration.missing_members?(1, 7)).to be_falsy
end
context 'with more forks' do
before do
forked_project_links.create(id: 9, forked_from_project_id: fork_of_fork.id, forked_to_project_id: create(:project).id)
forked_project_links.create(id: 10, forked_from_project_id: fork_of_fork.id, forked_to_project_id: create(:project).id)
end
it 'only processes a single batch of links at a time' do
expect(fork_network_members.count).to eq(10)
migration.perform(8, 10)
expect(fork_network_members.count).to eq(12)
end
it 'knows when not all memberships withing a batch have been created' do
expect(migration.missing_members?(8, 10)).to be_truthy
end
end
end
require 'spec_helper'
describe Gitlab::BackgroundMigration::PopulateForkNetworksRange, :migration, schema: 20170929131201 do
let(:migration) { described_class.new }
let(:base1) { create(:project) }
let(:base1_fork1) { create(:project) }
let(:base1_fork2) { create(:project) }
let(:base2) { create(:project) }
let(:base2_fork1) { create(:project) }
let(:base2_fork2) { create(:project) }
let!(:forked_project_links) { table(:forked_project_links) }
let!(:fork_networks) { table(:fork_networks) }
let!(:fork_network_members) { table(:fork_network_members) }
let(:fork_network1) { fork_networks.find_by(root_project_id: base1.id) }
let(:fork_network2) { fork_networks.find_by(root_project_id: base2.id) }
before do
# A normal fork link
forked_project_links.create(id: 1,
forked_from_project_id: base1.id,
forked_to_project_id: base1_fork1.id)
forked_project_links.create(id: 2,
forked_from_project_id: base1.id,
forked_to_project_id: base1_fork2.id)
forked_project_links.create(id: 3,
forked_from_project_id: base2.id,
forked_to_project_id: base2_fork1.id)
forked_project_links.create(id: 4,
forked_from_project_id: base2_fork1.id,
forked_to_project_id: create(:project).id)
forked_project_links.create(id: 5,
forked_from_project_id: base2.id,
forked_to_project_id: base2_fork2.id)
migration.perform(1, 3)
end
it 'it creates the fork network' do
expect(fork_network1).not_to be_nil
expect(fork_network2).not_to be_nil
end
it 'does not create a fork network for a fork-of-fork' do
# perfrom the entire batch
migration.perform(1, 5)
expect(fork_networks.find_by(root_project_id: base2_fork1.id)).to be_nil
end
it 'creates memberships for the root of fork networks' do
base1_membership = fork_network_members.find_by(fork_network_id: fork_network1.id,
project_id: base1.id)
base2_membership = fork_network_members.find_by(fork_network_id: fork_network2.id,
project_id: base2.id)
expect(base1_membership).not_to be_nil
expect(base2_membership).not_to be_nil
end
it 'schedules a job for inserting memberships for forks-of-forks' do
delay = Gitlab::BackgroundMigration::CreateForkNetworkMembershipsRange::RESCHEDULE_DELAY
expect(BackgroundMigrationWorker)
.to receive(:perform_in).with(delay, "CreateForkNetworkMembershipsRange", [1, 3])
migration.perform(1, 3)
end
it 'only processes a single batch of links at a time' do
expect(fork_network_members.count).to eq(5)
migration.perform(3, 5)
expect(fork_network_members.count).to eq(7)
end
it 'can be repeated without effect' do
expect { migration.perform(1, 3) }.not_to change { fork_network_members.count }
end
end
...@@ -272,6 +272,9 @@ project: ...@@ -272,6 +272,9 @@ project:
- uploads - uploads
- members_and_requesters - members_and_requesters
- build_trace_section_names - build_trace_section_names
- root_of_fork_network
- fork_network_member
- fork_network
award_emoji: award_emoji:
- awardable - awardable
- user - user
......
require 'spec_helper' require 'spec_helper'
describe 'forked project import' do describe 'forked project import' do
include ProjectForksHelper
let(:user) { create(:user) } let(:user) { create(:user) }
let!(:project_with_repo) { create(:project, :repository, name: 'test-repo-restorer', path: 'test-repo-restorer') } let!(:project_with_repo) { create(:project, :repository, name: 'test-repo-restorer', path: 'test-repo-restorer') }
let!(:project) { create(:project, name: 'test-repo-restorer-no-repo', path: 'test-repo-restorer-no-repo') } let!(:project) { create(:project, name: 'test-repo-restorer-no-repo', path: 'test-repo-restorer-no-repo') }
let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" } let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" }
let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: project.full_path) } let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: project.full_path) }
let(:forked_from_project) { create(:project, :repository) } let(:forked_from_project) { create(:project, :repository) }
let(:fork_link) { create(:forked_project_link, forked_from_project: project_with_repo) } let(:forked_project) { fork_project(project_with_repo, nil, repository: true) }
let(:repo_saver) { Gitlab::ImportExport::RepoSaver.new(project: project_with_repo, shared: shared) } let(:repo_saver) { Gitlab::ImportExport::RepoSaver.new(project: project_with_repo, shared: shared) }
let(:bundle_path) { File.join(shared.export_path, Gitlab::ImportExport.project_bundle_filename) } let(:bundle_path) { File.join(shared.export_path, Gitlab::ImportExport.project_bundle_filename) }
...@@ -16,7 +18,7 @@ describe 'forked project import' do ...@@ -16,7 +18,7 @@ describe 'forked project import' do
end end
let!(:merge_request) do let!(:merge_request) do
create(:merge_request, source_project: fork_link.forked_to_project, target_project: project_with_repo) create(:merge_request, source_project: forked_project, target_project: project_with_repo)
end end
let(:saver) do let(:saver) do
......
require 'spec_helper' require 'spec_helper'
describe Gitlab::ImportExport::MergeRequestParser do describe Gitlab::ImportExport::MergeRequestParser do
include ProjectForksHelper
let(:user) { create(:user) } let(:user) { create(:user) }
let!(:project) { create(:project, :repository, name: 'test-repo-restorer', path: 'test-repo-restorer') } let!(:project) { create(:project, :repository, name: 'test-repo-restorer', path: 'test-repo-restorer') }
let(:forked_from_project) { create(:project, :repository) } let(:forked_project) { fork_project(project) }
let(:fork_link) { create(:forked_project_link, forked_from_project: project) }
let!(:merge_request) do let!(:merge_request) do
create(:merge_request, source_project: fork_link.forked_to_project, target_project: project) create(:merge_request, source_project: forked_project, target_project: project)
end end
let(:parsed_merge_request) do let(:parsed_merge_request) do
......
require 'spec_helper' require 'spec_helper'
describe Gitlab::SearchResults do describe Gitlab::SearchResults do
include ProjectForksHelper
let(:user) { create(:user) } let(:user) { create(:user) }
let!(:project) { create(:project, name: 'foo') } let!(:project) { create(:project, name: 'foo') }
let!(:issue) { create(:issue, project: project, title: 'foo') } let!(:issue) { create(:issue, project: project, title: 'foo') }
...@@ -42,7 +44,7 @@ describe Gitlab::SearchResults do ...@@ -42,7 +44,7 @@ describe Gitlab::SearchResults do
end end
it 'includes merge requests from source and target projects' do it 'includes merge requests from source and target projects' do
forked_project = create(:project, forked_from_project: project) forked_project = fork_project(project, user)
merge_request_2 = create(:merge_request, target_project: project, source_project: forked_project, title: 'foo') merge_request_2 = create(:merge_request, target_project: project, source_project: forked_project, title: 'foo')
results = described_class.new(user, Project.where(id: forked_project.id), 'foo') results = described_class.new(user, Project.where(id: forked_project.id), 'foo')
......
...@@ -2,11 +2,12 @@ require 'spec_helper' ...@@ -2,11 +2,12 @@ require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20170508170547_add_head_pipeline_for_each_merge_request.rb') require Rails.root.join('db', 'post_migrate', '20170508170547_add_head_pipeline_for_each_merge_request.rb')
describe AddHeadPipelineForEachMergeRequest, :truncate do describe AddHeadPipelineForEachMergeRequest, :truncate do
include ProjectForksHelper
let(:migration) { described_class.new } let(:migration) { described_class.new }
let!(:project) { create(:project) } let!(:project) { create(:project) }
let!(:forked_project_link) { create(:forked_project_link, forked_from_project: project) } let!(:other_project) { fork_project(project) }
let!(:other_project) { forked_project_link.forked_to_project }
let!(:pipeline_1) { create(:ci_pipeline, project: project, ref: "branch_1") } let!(:pipeline_1) { create(:ci_pipeline, project: project, ref: "branch_1") }
let!(:pipeline_2) { create(:ci_pipeline, project: other_project, ref: "branch_1") } let!(:pipeline_2) { create(:ci_pipeline, project: other_project, ref: "branch_1") }
......
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
require 'spec_helper'
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
describe '#find_fork_in' do
it 'finds all fork of the current network in al collection' do
network = create(:fork_network)
root_project = network.root_project
another_project = fork_project(root_project)
create(:project)
expect(network.find_forks_in(Project.all))
.to contain_exactly(another_project, root_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
require 'spec_helper' require 'spec_helper'
describe ForkedProjectLink, "add link on fork" do describe ForkedProjectLink, "add link on fork" do
include ProjectForksHelper
let(:project_from) { create(:project, :repository) } let(:project_from) { create(:project, :repository) }
let(:project_to) { fork_project(project_from, user) } let(:project_to) { fork_project(project_from, user) }
let(:user) { create(:user) } let(:user) { create(:user) }
let(:namespace) { user.namespace }
before do before do
project_from.add_reporter(user) project_from.add_reporter(user)
...@@ -64,13 +65,4 @@ describe ForkedProjectLink, "add link on fork" do ...@@ -64,13 +65,4 @@ describe ForkedProjectLink, "add link on fork" do
expect(ForkedProjectLink.exists?(id: forked_project_link.id)).to eq(false) expect(ForkedProjectLink.exists?(id: forked_project_link.id)).to eq(false)
end end
end end
def fork_project(from_project, user)
service = Projects::ForkService.new(from_project, user)
shell = double('gitlab_shell', fork_repository: true)
allow(service).to receive(:gitlab_shell).and_return(shell)
service.execute
end
end end
...@@ -2,6 +2,7 @@ require 'spec_helper' ...@@ -2,6 +2,7 @@ require 'spec_helper'
describe MergeRequest do describe MergeRequest do
include RepoHelpers include RepoHelpers
include ProjectForksHelper
subject { create(:merge_request) } subject { create(:merge_request) }
...@@ -49,6 +50,19 @@ describe MergeRequest do ...@@ -49,6 +50,19 @@ describe MergeRequest do
expect(subject).to be_valid expect(subject).to be_valid
end end
end end
context 'for forks' do
let(:project) { create(:project) }
let(:fork1) { fork_project(project) }
let(:fork2) { fork_project(project) }
it 'allows merge requests for sibling-forks' do
subject.source_project = fork1
subject.target_project = fork2
expect(subject).to be_valid
end
end
end end
describe 'respond to' do describe 'respond to' do
...@@ -672,7 +686,7 @@ describe MergeRequest do ...@@ -672,7 +686,7 @@ describe MergeRequest do
describe '#diverged_commits_count' do describe '#diverged_commits_count' do
let(:project) { create(:project, :repository) } let(:project) { create(:project, :repository) }
let(:fork_project) { create(:project, :repository, forked_from_project: project) } let(:forked_project) { fork_project(project, nil, repository: true) }
context 'when the target branch does not exist anymore' do context 'when the target branch does not exist anymore' do
subject { create(:merge_request, source_project: project, target_project: project) } subject { create(:merge_request, source_project: project, target_project: project) }
...@@ -700,7 +714,7 @@ describe MergeRequest do ...@@ -700,7 +714,7 @@ describe MergeRequest do
end end
context 'diverged on fork' do context 'diverged on fork' do
subject(:merge_request_fork_with_divergence) { create(:merge_request, :diverged, source_project: fork_project, target_project: project) } subject(:merge_request_fork_with_divergence) { create(:merge_request, :diverged, source_project: forked_project, target_project: project) }
it 'counts commits that are on target branch but not on source branch' do it 'counts commits that are on target branch but not on source branch' do
expect(subject.diverged_commits_count).to eq(29) expect(subject.diverged_commits_count).to eq(29)
...@@ -708,7 +722,7 @@ describe MergeRequest do ...@@ -708,7 +722,7 @@ describe MergeRequest do
end end
context 'rebased on fork' do context 'rebased on fork' do
subject(:merge_request_rebased) { create(:merge_request, :rebased, source_project: fork_project, target_project: project) } subject(:merge_request_rebased) { create(:merge_request, :rebased, source_project: forked_project, target_project: project) }
it 'counts commits that are on target branch but not on source branch' do it 'counts commits that are on target branch but not on source branch' do
expect(subject.diverged_commits_count).to eq(0) expect(subject.diverged_commits_count).to eq(0)
...@@ -1257,11 +1271,7 @@ describe MergeRequest do ...@@ -1257,11 +1271,7 @@ describe MergeRequest do
end end
context 'with environments on source project' do context 'with environments on source project' do
let(:source_project) do let(:source_project) { fork_project(project, nil, repository: true) }
create(:project, :repository) do |fork_project|
fork_project.create_forked_project_link(forked_to_project_id: fork_project.id, forked_from_project_id: project.id)
end
end
let(:merge_request) do let(:merge_request) do
create(:merge_request, create(:merge_request,
...@@ -1425,14 +1435,14 @@ describe MergeRequest do ...@@ -1425,14 +1435,14 @@ describe MergeRequest do
describe "#source_project_missing?" do describe "#source_project_missing?" do
let(:project) { create(:project) } let(:project) { create(:project) }
let(:fork_project) { create(:project, forked_from_project: project) } let(:forked_project) { fork_project(project) }
let(:user) { create(:user) } let(:user) { create(:user) }
let(:unlink_project) { Projects::UnlinkForkService.new(fork_project, user) } let(:unlink_project) { Projects::UnlinkForkService.new(forked_project, user) }
context "when the fork exists" do context "when the fork exists" do
let(:merge_request) do let(:merge_request) do
create(:merge_request, create(:merge_request,
source_project: fork_project, source_project: forked_project,
target_project: project) target_project: project)
end end
...@@ -1446,9 +1456,9 @@ describe MergeRequest do ...@@ -1446,9 +1456,9 @@ describe MergeRequest do
end end
context "when the fork does not exist" do context "when the fork does not exist" do
let(:merge_request) do let!(:merge_request) do
create(:merge_request, create(:merge_request,
source_project: fork_project, source_project: forked_project,
target_project: project) target_project: project)
end end
...@@ -1471,14 +1481,14 @@ describe MergeRequest do ...@@ -1471,14 +1481,14 @@ describe MergeRequest do
describe "#closed_without_fork?" do describe "#closed_without_fork?" do
let(:project) { create(:project) } let(:project) { create(:project) }
let(:fork_project) { create(:project, forked_from_project: project) } let(:forked_project) { fork_project(project) }
let(:user) { create(:user) } let(:user) { create(:user) }
let(:unlink_project) { Projects::UnlinkForkService.new(fork_project, user) } let(:unlink_project) { Projects::UnlinkForkService.new(forked_project, user) }
context "when the merge request is closed" do context "when the merge request is closed" do
let(:closed_merge_request) do let(:closed_merge_request) do
create(:closed_merge_request, create(:closed_merge_request,
source_project: fork_project, source_project: forked_project,
target_project: project) target_project: project)
end end
...@@ -1497,7 +1507,7 @@ describe MergeRequest do ...@@ -1497,7 +1507,7 @@ describe MergeRequest do
context "when the merge request is open" do context "when the merge request is open" do
let(:open_merge_request) do let(:open_merge_request) do
create(:merge_request, create(:merge_request,
source_project: fork_project, source_project: forked_project,
target_project: project) target_project: project)
end end
...@@ -1516,24 +1526,24 @@ describe MergeRequest do ...@@ -1516,24 +1526,24 @@ describe MergeRequest do
end end
context 'forked project' do context 'forked project' do
let(:project) { create(:project) } let(:project) { create(:project, :public) }
let(:user) { create(:user) } let(:user) { create(:user) }
let(:fork_project) { create(:project, forked_from_project: project, namespace: user.namespace) } let(:forked_project) { fork_project(project, user) }
let!(:merge_request) do let!(:merge_request) do
create(:closed_merge_request, create(:closed_merge_request,
source_project: fork_project, source_project: forked_project,
target_project: project) target_project: project)
end end
it 'returns false if unforked' do it 'returns false if unforked' do
Projects::UnlinkForkService.new(fork_project, user).execute Projects::UnlinkForkService.new(forked_project, user).execute
expect(merge_request.reload.reopenable?).to be_falsey expect(merge_request.reload.reopenable?).to be_falsey
end end
it 'returns false if the source project is deleted' do it 'returns false if the source project is deleted' do
Projects::DestroyService.new(fork_project, user).execute Projects::DestroyService.new(forked_project, user).execute
expect(merge_request.reload.reopenable?).to be_falsey expect(merge_request.reload.reopenable?).to be_falsey
end end
......
require 'spec_helper' require 'spec_helper'
describe Namespace do describe Namespace do
include ProjectForksHelper
let!(:namespace) { create(:namespace) } let!(:namespace) { create(:namespace) }
describe 'associations' do describe 'associations' do
...@@ -520,4 +522,25 @@ describe Namespace do ...@@ -520,4 +522,25 @@ describe Namespace do
end end
end end
end end
describe '#has_forks_of?' do
let(:project) { create(:project, :public) }
let!(:forked_project) { fork_project(project, namespace.owner, namespace: namespace) }
before do
# Reset the fork network relation
project.reload
end
it 'knows if there is a direct fork in the namespace' do
expect(namespace.find_fork_of(project)).to eq(forked_project)
end
it 'knows when there is as fork-of-fork in the namespace' do
other_namespace = create(:namespace)
other_fork = fork_project(forked_project, other_namespace.owner, namespace: other_namespace)
expect(other_namespace.find_fork_of(project)).to eq(other_fork)
end
end
end end
...@@ -1818,6 +1818,59 @@ describe Project do ...@@ -1818,6 +1818,59 @@ describe Project do
end end
end end
context 'forks' do
include ProjectForksHelper
let(:project) { create(:project, :public) }
let!(:forked_project) { fork_project(project) }
describe '#fork_network' do
it 'includes a fork of the project' do
expect(project.fork_network.projects).to include(forked_project)
end
it 'includes a fork of a fork' do
other_fork = fork_project(forked_project)
expect(project.fork_network.projects).to include(other_fork)
end
it 'includes sibling forks' do
other_fork = fork_project(project)
expect(forked_project.fork_network.projects).to include(other_fork)
end
it 'includes the base project' do
expect(forked_project.fork_network.projects).to include(project.reload)
end
end
describe '#in_fork_network_of?' do
it 'is true for a real fork' do
expect(forked_project.in_fork_network_of?(project)).to be_truthy
end
it 'is true for a fork of a fork', :postgresql do
other_fork = fork_project(forked_project)
expect(other_fork.in_fork_network_of?(project)).to be_truthy
end
it 'is true for sibling forks' do
sibling = fork_project(project)
expect(sibling.in_fork_network_of?(forked_project)).to be_truthy
end
it 'is false when another project is given' do
other_project = build_stubbed(:project)
expect(forked_project.in_fork_network_of?(other_project)).to be_falsy
end
end
end
describe '#pushes_since_gc' do describe '#pushes_since_gc' do
let(:project) { create(:project) } let(:project) { create(:project) }
......
...@@ -2,6 +2,7 @@ require 'spec_helper' ...@@ -2,6 +2,7 @@ require 'spec_helper'
describe User do describe User do
include Gitlab::CurrentSettings include Gitlab::CurrentSettings
include ProjectForksHelper
describe 'modules' do describe 'modules' do
subject { described_class } subject { described_class }
...@@ -1431,7 +1432,7 @@ describe User do ...@@ -1431,7 +1432,7 @@ describe User do
describe "#contributed_projects" do describe "#contributed_projects" do
subject { create(:user) } subject { create(:user) }
let!(:project1) { create(:project) } let!(:project1) { create(:project) }
let!(:project2) { create(:project, forked_from_project: project3) } let!(:project2) { fork_project(project3) }
let!(:project3) { create(:project) } let!(:project3) { create(:project) }
let!(:merge_request) { create(:merge_request, source_project: project2, target_project: project3, author: subject) } let!(:merge_request) { create(:merge_request, source_project: project2, target_project: project3, author: subject) }
let!(:push_event) { create(:push_event, project: project1, author: subject) } let!(:push_event) { create(:push_event, project: project1, author: subject) }
...@@ -1455,6 +1456,23 @@ describe User do ...@@ -1455,6 +1456,23 @@ describe User do
end end
end end
describe '#fork_of' do
let(:user) { create(:user) }
it "returns a user's fork of a project" do
project = create(:project, :public)
user_fork = fork_project(project, user, namespace: user.namespace)
expect(user.fork_of(project)).to eq(user_fork)
end
it 'returns nil if the project does not have a fork network' do
project = create(:project)
expect(user.fork_of(project)).to be_nil
end
end
describe '#can_be_removed?' do describe '#can_be_removed?' do
subject { create(:user) } subject { create(:user) }
......
require "spec_helper" require "spec_helper"
describe API::MergeRequests do describe API::MergeRequests do
include ProjectForksHelper
let(:base_time) { Time.now } let(:base_time) { Time.now }
let(:user) { create(:user) } let(:user) { create(:user) }
let(:admin) { create(:user, :admin) } let(:admin) { create(:user, :admin) }
...@@ -616,17 +618,17 @@ describe API::MergeRequests do ...@@ -616,17 +618,17 @@ describe API::MergeRequests do
context 'forked projects' do context 'forked projects' do
let!(:user2) { create(:user) } let!(:user2) { create(:user) }
let!(:fork_project) { create(:project, forked_from_project: project, namespace: user2.namespace, creator_id: user2.id) } let!(:forked_project) { fork_project(project, user2) }
let!(:unrelated_project) { create(:project, namespace: create(:user).namespace, creator_id: user2.id) } let!(:unrelated_project) { create(:project, namespace: create(:user).namespace, creator_id: user2.id) }
before do before do
fork_project.add_reporter(user2) forked_project.add_reporter(user2)
allow_any_instance_of(MergeRequest).to receive(:write_ref) allow_any_instance_of(MergeRequest).to receive(:write_ref)
end end
it "returns merge_request" do it "returns merge_request" do
post api("/projects/#{fork_project.id}/merge_requests", user2), post api("/projects/#{forked_project.id}/merge_requests", user2),
title: 'Test merge_request', source_branch: "feature_conflict", target_branch: "master", title: 'Test merge_request', source_branch: "feature_conflict", target_branch: "master",
author: user2, target_project_id: project.id, description: 'Test description for Test merge_request' author: user2, target_project_id: project.id, description: 'Test description for Test merge_request'
expect(response).to have_gitlab_http_status(201) expect(response).to have_gitlab_http_status(201)
...@@ -635,10 +637,10 @@ describe API::MergeRequests do ...@@ -635,10 +637,10 @@ describe API::MergeRequests do
end end
it "does not return 422 when source_branch equals target_branch" do it "does not return 422 when source_branch equals target_branch" do
expect(project.id).not_to eq(fork_project.id) expect(project.id).not_to eq(forked_project.id)
expect(fork_project.forked?).to be_truthy expect(forked_project.forked?).to be_truthy
expect(fork_project.forked_from_project).to eq(project) expect(forked_project.forked_from_project).to eq(project)
post api("/projects/#{fork_project.id}/merge_requests", user2), post api("/projects/#{forked_project.id}/merge_requests", user2),
title: 'Test merge_request', source_branch: "master", target_branch: "master", author: user2, target_project_id: project.id title: 'Test merge_request', source_branch: "master", target_branch: "master", author: user2, target_project_id: project.id
expect(response).to have_gitlab_http_status(201) expect(response).to have_gitlab_http_status(201)
expect(json_response['title']).to eq('Test merge_request') expect(json_response['title']).to eq('Test merge_request')
...@@ -647,7 +649,7 @@ describe API::MergeRequests do ...@@ -647,7 +649,7 @@ describe API::MergeRequests do
it 'returns 422 when target project has disabled merge requests' do it 'returns 422 when target project has disabled merge requests' do
project.project_feature.update(merge_requests_access_level: 0) project.project_feature.update(merge_requests_access_level: 0)
post api("/projects/#{fork_project.id}/merge_requests", user2), post api("/projects/#{forked_project.id}/merge_requests", user2),
title: 'Test', title: 'Test',
target_branch: 'master', target_branch: 'master',
source_branch: 'markdown', source_branch: 'markdown',
...@@ -658,36 +660,26 @@ describe API::MergeRequests do ...@@ -658,36 +660,26 @@ describe API::MergeRequests do
end end
it "returns 400 when source_branch is missing" do it "returns 400 when source_branch is missing" do
post api("/projects/#{fork_project.id}/merge_requests", user2), post api("/projects/#{forked_project.id}/merge_requests", user2),
title: 'Test merge_request', target_branch: "master", author: user2, target_project_id: project.id title: 'Test merge_request', target_branch: "master", author: user2, target_project_id: project.id
expect(response).to have_gitlab_http_status(400) expect(response).to have_gitlab_http_status(400)
end end
it "returns 400 when target_branch is missing" do it "returns 400 when target_branch is missing" do
post api("/projects/#{fork_project.id}/merge_requests", user2), post api("/projects/#{forked_project.id}/merge_requests", user2),
title: 'Test merge_request', target_branch: "master", author: user2, target_project_id: project.id title: 'Test merge_request', target_branch: "master", author: user2, target_project_id: project.id
expect(response).to have_gitlab_http_status(400) expect(response).to have_gitlab_http_status(400)
end end
it "returns 400 when title is missing" do it "returns 400 when title is missing" do
post api("/projects/#{fork_project.id}/merge_requests", user2), post api("/projects/#{forked_project.id}/merge_requests", user2),
target_branch: 'master', source_branch: 'markdown', author: user2, target_project_id: project.id target_branch: 'master', source_branch: 'markdown', author: user2, target_project_id: project.id
expect(response).to have_gitlab_http_status(400) expect(response).to have_gitlab_http_status(400)
end end
context 'when target_branch is specified' do context 'when target_branch is specified' do
it 'returns 422 if not a forked project' do
post api("/projects/#{project.id}/merge_requests", user),
title: 'Test merge_request',
target_branch: 'master',
source_branch: 'markdown',
author: user,
target_project_id: fork_project.id
expect(response).to have_gitlab_http_status(422)
end
it 'returns 422 if targeting a different fork' do it 'returns 422 if targeting a different fork' do
post api("/projects/#{fork_project.id}/merge_requests", user2), post api("/projects/#{forked_project.id}/merge_requests", user2),
title: 'Test merge_request', title: 'Test merge_request',
target_branch: 'master', target_branch: 'master',
source_branch: 'markdown', source_branch: 'markdown',
...@@ -698,8 +690,8 @@ describe API::MergeRequests do ...@@ -698,8 +690,8 @@ describe API::MergeRequests do
end end
it "returns 201 when target_branch is specified and for the same project" do it "returns 201 when target_branch is specified and for the same project" do
post api("/projects/#{fork_project.id}/merge_requests", user2), post api("/projects/#{forked_project.id}/merge_requests", user2),
title: 'Test merge_request', target_branch: 'master', source_branch: 'markdown', author: user2, target_project_id: fork_project.id title: 'Test merge_request', target_branch: 'master', source_branch: 'markdown', author: user2, target_project_id: forked_project.id
expect(response).to have_gitlab_http_status(201) expect(response).to have_gitlab_http_status(201)
end end
end end
......
...@@ -64,9 +64,12 @@ describe API::Projects do ...@@ -64,9 +64,12 @@ describe API::Projects do
create(:project, :public) create(:project, :public)
end end
# TODO: We're currently querying to detect if a project is a fork
# in 2 ways. Lower this back to 8 when `ForkedProjectLink` relation is
# removed
expect do expect do
get api('/projects', current_user) get api('/projects', current_user)
end.not_to exceed_query_limit(control).with_threshold(8) end.not_to exceed_query_limit(control).with_threshold(9)
end end
end end
......
require "spec_helper" require "spec_helper"
describe API::MergeRequests do describe API::MergeRequests do
include ProjectForksHelper
let(:base_time) { Time.now } let(:base_time) { Time.now }
let(:user) { create(:user) } let(:user) { create(:user) }
let(:admin) { create(:user, :admin) } let(:admin) { create(:user, :admin) }
...@@ -312,17 +314,17 @@ describe API::MergeRequests do ...@@ -312,17 +314,17 @@ describe API::MergeRequests do
context 'forked projects' do context 'forked projects' do
let!(:user2) { create(:user) } let!(:user2) { create(:user) }
let!(:fork_project) { create(:project, forked_from_project: project, namespace: user2.namespace, creator_id: user2.id) } let!(:forked_project) { fork_project(project, user2) }
let!(:unrelated_project) { create(:project, namespace: create(:user).namespace, creator_id: user2.id) } let!(:unrelated_project) { create(:project, namespace: create(:user).namespace, creator_id: user2.id) }
before do before do
fork_project.add_reporter(user2) forked_project.add_reporter(user2)
allow_any_instance_of(MergeRequest).to receive(:write_ref) allow_any_instance_of(MergeRequest).to receive(:write_ref)
end end
it "returns merge_request" do it "returns merge_request" do
post v3_api("/projects/#{fork_project.id}/merge_requests", user2), post v3_api("/projects/#{forked_project.id}/merge_requests", user2),
title: 'Test merge_request', source_branch: "feature_conflict", target_branch: "master", title: 'Test merge_request', source_branch: "feature_conflict", target_branch: "master",
author: user2, target_project_id: project.id, description: 'Test description for Test merge_request' author: user2, target_project_id: project.id, description: 'Test description for Test merge_request'
expect(response).to have_gitlab_http_status(201) expect(response).to have_gitlab_http_status(201)
...@@ -331,10 +333,10 @@ describe API::MergeRequests do ...@@ -331,10 +333,10 @@ describe API::MergeRequests do
end end
it "does not return 422 when source_branch equals target_branch" do it "does not return 422 when source_branch equals target_branch" do
expect(project.id).not_to eq(fork_project.id) expect(project.id).not_to eq(forked_project.id)
expect(fork_project.forked?).to be_truthy expect(forked_project.forked?).to be_truthy
expect(fork_project.forked_from_project).to eq(project) expect(forked_project.forked_from_project).to eq(project)
post v3_api("/projects/#{fork_project.id}/merge_requests", user2), post v3_api("/projects/#{forked_project.id}/merge_requests", user2),
title: 'Test merge_request', source_branch: "master", target_branch: "master", author: user2, target_project_id: project.id title: 'Test merge_request', source_branch: "master", target_branch: "master", author: user2, target_project_id: project.id
expect(response).to have_gitlab_http_status(201) expect(response).to have_gitlab_http_status(201)
expect(json_response['title']).to eq('Test merge_request') expect(json_response['title']).to eq('Test merge_request')
...@@ -343,7 +345,7 @@ describe API::MergeRequests do ...@@ -343,7 +345,7 @@ describe API::MergeRequests do
it "returns 422 when target project has disabled merge requests" do it "returns 422 when target project has disabled merge requests" do
project.project_feature.update(merge_requests_access_level: 0) project.project_feature.update(merge_requests_access_level: 0)
post v3_api("/projects/#{fork_project.id}/merge_requests", user2), post v3_api("/projects/#{forked_project.id}/merge_requests", user2),
title: 'Test', title: 'Test',
target_branch: "master", target_branch: "master",
source_branch: 'markdown', source_branch: 'markdown',
...@@ -354,36 +356,26 @@ describe API::MergeRequests do ...@@ -354,36 +356,26 @@ describe API::MergeRequests do
end end
it "returns 400 when source_branch is missing" do it "returns 400 when source_branch is missing" do
post v3_api("/projects/#{fork_project.id}/merge_requests", user2), post v3_api("/projects/#{forked_project.id}/merge_requests", user2),
title: 'Test merge_request', target_branch: "master", author: user2, target_project_id: project.id title: 'Test merge_request', target_branch: "master", author: user2, target_project_id: project.id
expect(response).to have_gitlab_http_status(400) expect(response).to have_gitlab_http_status(400)
end end
it "returns 400 when target_branch is missing" do it "returns 400 when target_branch is missing" do
post v3_api("/projects/#{fork_project.id}/merge_requests", user2), post v3_api("/projects/#{forked_project.id}/merge_requests", user2),
title: 'Test merge_request', target_branch: "master", author: user2, target_project_id: project.id title: 'Test merge_request', target_branch: "master", author: user2, target_project_id: project.id
expect(response).to have_gitlab_http_status(400) expect(response).to have_gitlab_http_status(400)
end end
it "returns 400 when title is missing" do it "returns 400 when title is missing" do
post v3_api("/projects/#{fork_project.id}/merge_requests", user2), post v3_api("/projects/#{forked_project.id}/merge_requests", user2),
target_branch: 'master', source_branch: 'markdown', author: user2, target_project_id: project.id target_branch: 'master', source_branch: 'markdown', author: user2, target_project_id: project.id
expect(response).to have_gitlab_http_status(400) expect(response).to have_gitlab_http_status(400)
end end
context 'when target_branch is specified' do context 'when target_branch is specified' do
it 'returns 422 if not a forked project' do
post v3_api("/projects/#{project.id}/merge_requests", user),
title: 'Test merge_request',
target_branch: 'master',
source_branch: 'markdown',
author: user,
target_project_id: fork_project.id
expect(response).to have_gitlab_http_status(422)
end
it 'returns 422 if targeting a different fork' do it 'returns 422 if targeting a different fork' do
post v3_api("/projects/#{fork_project.id}/merge_requests", user2), post v3_api("/projects/#{forked_project.id}/merge_requests", user2),
title: 'Test merge_request', title: 'Test merge_request',
target_branch: 'master', target_branch: 'master',
source_branch: 'markdown', source_branch: 'markdown',
...@@ -394,8 +386,8 @@ describe API::MergeRequests do ...@@ -394,8 +386,8 @@ describe API::MergeRequests do
end end
it "returns 201 when target_branch is specified and for the same project" do it "returns 201 when target_branch is specified and for the same project" do
post v3_api("/projects/#{fork_project.id}/merge_requests", user2), post v3_api("/projects/#{forked_project.id}/merge_requests", user2),
title: 'Test merge_request', target_branch: 'master', source_branch: 'markdown', author: user2, target_project_id: fork_project.id title: 'Test merge_request', target_branch: 'master', source_branch: 'markdown', author: user2, target_project_id: forked_project.id
expect(response).to have_gitlab_http_status(201) expect(response).to have_gitlab_http_status(201)
end end
end end
......
...@@ -2,6 +2,7 @@ require 'spec_helper' ...@@ -2,6 +2,7 @@ require 'spec_helper'
describe 'Git LFS API and storage' do describe 'Git LFS API and storage' do
include WorkhorseHelpers include WorkhorseHelpers
include ProjectForksHelper
let(:user) { create(:user) } let(:user) { create(:user) }
let!(:lfs_object) { create(:lfs_object, :with_file) } let!(:lfs_object) { create(:lfs_object, :with_file) }
...@@ -1173,11 +1174,6 @@ describe 'Git LFS API and storage' do ...@@ -1173,11 +1174,6 @@ describe 'Git LFS API and storage' do
ActionController::HttpAuthentication::Basic.encode_credentials(user.username, Gitlab::LfsToken.new(user).token) ActionController::HttpAuthentication::Basic.encode_credentials(user.username, Gitlab::LfsToken.new(user).token)
end end
def fork_project(project, user, object = nil)
allow(RepositoryForkWorker).to receive(:perform_async).and_return(true)
Projects::ForkService.new(project, user, {}).execute
end
def post_lfs_json(url, body = nil, headers = nil) def post_lfs_json(url, body = nil, headers = nil)
post(url, body.try(:to_json), (headers || {}).merge('Content-Type' => 'application/vnd.git-lfs+json')) post(url, body.try(:to_json), (headers || {}).merge('Content-Type' => 'application/vnd.git-lfs+json'))
end end
......
require 'spec_helper' require 'spec_helper'
describe BuildDetailsEntity do describe BuildDetailsEntity do
include ProjectForksHelper
set(:user) { create(:admin) } set(:user) { create(:admin) }
it 'inherits from JobEntity' do it 'inherits from JobEntity' do
...@@ -56,18 +58,16 @@ describe BuildDetailsEntity do ...@@ -56,18 +58,16 @@ describe BuildDetailsEntity do
end end
context 'when merge request is from a fork' do context 'when merge request is from a fork' do
let(:fork_project) do let(:forked_project) { fork_project(project) }
create(:project, forked_from_project: project)
end
let(:pipeline) { create(:ci_pipeline, project: fork_project) } let(:pipeline) { create(:ci_pipeline, project: forked_project) }
before do before do
allow(build).to receive(:merge_request).and_return(merge_request) allow(build).to receive(:merge_request).and_return(merge_request)
end end
let(:merge_request) do let(:merge_request) do
create(:merge_request, source_project: fork_project, create(:merge_request, source_project: forked_project,
target_project: project, target_project: project,
source_branch: build.ref) source_branch: build.ref)
end end
......
require 'spec_helper' require 'spec_helper'
describe Ci::CreatePipelineService do describe Ci::CreatePipelineService do
include ProjectForksHelper
set(:project) { create(:project, :repository) } set(:project) { create(:project, :repository) }
let(:user) { create(:admin) } let(:user) { create(:admin) }
let(:ref_name) { 'refs/heads/master' } let(:ref_name) { 'refs/heads/master' }
...@@ -82,13 +84,9 @@ describe Ci::CreatePipelineService do ...@@ -82,13 +84,9 @@ describe Ci::CreatePipelineService do
end end
context 'when merge request target project is different from source project' do context 'when merge request target project is different from source project' do
let!(:project) { fork_project(target_project, nil, repository: true) }
let!(:target_project) { create(:project, :repository) } let!(:target_project) { create(:project, :repository) }
let!(:forked_project_link) do
create(:forked_project_link, forked_to_project: project,
forked_from_project: target_project)
end
it 'updates head pipeline for merge request' do it 'updates head pipeline for merge request' do
merge_request = create(:merge_request, source_branch: 'master', merge_request = create(:merge_request, source_branch: 'master',
target_branch: "branch_1", target_branch: "branch_1",
......
require 'spec_helper' require 'spec_helper'
describe DeleteMergedBranchesService do describe DeleteMergedBranchesService do
include ProjectForksHelper
subject(:service) { described_class.new(project, project.owner) } subject(:service) { described_class.new(project, project.owner) }
let(:project) { create(:project, :repository) } let(:project) { create(:project, :repository) }
...@@ -50,9 +52,9 @@ describe DeleteMergedBranchesService do ...@@ -50,9 +52,9 @@ describe DeleteMergedBranchesService do
context 'open merge requests' do context 'open merge requests' do
it 'does not delete branches from open merge requests' do it 'does not delete branches from open merge requests' do
fork_link = create(:forked_project_link, forked_from_project: project) forked_project = fork_project(project)
create(:merge_request, :opened, source_project: project, target_project: project, source_branch: 'branch-merged', target_branch: 'master') create(:merge_request, :opened, source_project: project, target_project: project, source_branch: 'branch-merged', target_branch: 'master')
create(:merge_request, :opened, source_project: fork_link.forked_to_project, target_project: project, target_branch: 'improve/awesome', source_branch: 'master') create(:merge_request, :opened, source_project: forked_project, target_project: project, target_branch: 'improve/awesome', source_branch: 'master')
service.execute service.execute
......
require 'spec_helper' require 'spec_helper'
describe MergeRequests::Conflicts::ResolveService do describe MergeRequests::Conflicts::ResolveService do
include ProjectForksHelper
let(:user) { create(:user) } let(:user) { create(:user) }
let(:project) { create(:project, :repository) } let(:project) { create(:project, :public, :repository) }
let(:fork_project) do let(:forked_project) do
create(:forked_project_with_submodules) do |fork_project| forked_project = fork_project(project, user)
fork_project.build_forked_project_link(forked_to_project_id: fork_project.id, forked_from_project_id: project.id) TestEnv.copy_repo(forked_project,
fork_project.save bare_repo: TestEnv.forked_repo_path_bare,
end refs: TestEnv::FORKED_BRANCH_SHA)
forked_project
end end
let(:merge_request) do let(:merge_request) do
...@@ -19,7 +21,7 @@ describe MergeRequests::Conflicts::ResolveService do ...@@ -19,7 +21,7 @@ describe MergeRequests::Conflicts::ResolveService do
let(:merge_request_from_fork) do let(:merge_request_from_fork) do
create(:merge_request, create(:merge_request,
source_branch: 'conflict-resolvable-fork', source_project: fork_project, source_branch: 'conflict-resolvable-fork', source_project: forked_project,
target_branch: 'conflict-start', target_project: project) target_branch: 'conflict-start', target_project: project)
end end
...@@ -114,7 +116,7 @@ describe MergeRequests::Conflicts::ResolveService do ...@@ -114,7 +116,7 @@ describe MergeRequests::Conflicts::ResolveService do
end end
it 'gets conflicts from the source project' do it 'gets conflicts from the source project' do
expect(fork_project.repository.rugged).to receive(:merge_commits).and_call_original expect(forked_project.repository.rugged).to receive(:merge_commits).and_call_original
expect(project.repository.rugged).not_to receive(:merge_commits) expect(project.repository.rugged).not_to receive(:merge_commits)
resolve_conflicts resolve_conflicts
......
require "spec_helper" require "spec_helper"
describe MergeRequests::GetUrlsService do describe MergeRequests::GetUrlsService do
include ProjectForksHelper
let(:project) { create(:project, :public, :repository) } let(:project) { create(:project, :public, :repository) }
let(:service) { described_class.new(project) } let(:service) { described_class.new(project) }
let(:source_branch) { "merge-test" } let(:source_branch) { "merge-test" }
...@@ -85,7 +87,7 @@ describe MergeRequests::GetUrlsService do ...@@ -85,7 +87,7 @@ describe MergeRequests::GetUrlsService do
context 'pushing to existing branch from forked project' do context 'pushing to existing branch from forked project' do
let(:user) { create(:user) } let(:user) { create(:user) }
let!(:forked_project) { Projects::ForkService.new(project, user).execute } let!(:forked_project) { fork_project(project, user, repository: true) }
let!(:merge_request) { create(:merge_request, source_project: forked_project, target_project: project, source_branch: source_branch) } let!(:merge_request) { create(:merge_request, source_project: forked_project, target_project: project, source_branch: source_branch) }
let(:changes) { existing_branch_changes } let(:changes) { existing_branch_changes }
# Source project is now the forked one # Source project is now the forked one
......
require 'spec_helper' require 'spec_helper'
describe MergeRequests::RefreshService do describe MergeRequests::RefreshService do
include ProjectForksHelper
let(:project) { create(:project, :repository) } let(:project) { create(:project, :repository) }
let(:user) { create(:user) } let(:user) { create(:user) }
let(:service) { described_class } let(:service) { described_class }
...@@ -12,7 +14,8 @@ describe MergeRequests::RefreshService do ...@@ -12,7 +14,8 @@ describe MergeRequests::RefreshService do
group.add_owner(@user) group.add_owner(@user)
@project = create(:project, :repository, namespace: group) @project = create(:project, :repository, namespace: group)
@fork_project = Projects::ForkService.new(@project, @user).execute @fork_project = fork_project(@project, @user, repository: true)
@merge_request = create(:merge_request, @merge_request = create(:merge_request,
source_project: @project, source_project: @project,
source_branch: 'master', source_branch: 'master',
...@@ -311,8 +314,7 @@ describe MergeRequests::RefreshService do ...@@ -311,8 +314,7 @@ describe MergeRequests::RefreshService do
context 'when the merge request is sourced from a different project' do context 'when the merge request is sourced from a different project' do
it 'creates a `MergeRequestsClosingIssues` record for each issue closed by a commit' do it 'creates a `MergeRequestsClosingIssues` record for each issue closed by a commit' do
forked_project = create(:project, :repository) forked_project = fork_project(@project, @user, repository: true)
create(:forked_project_link, forked_to_project: forked_project, forked_from_project: @project)
merge_request = create(:merge_request, merge_request = create(:merge_request,
target_branch: 'master', target_branch: 'master',
......
...@@ -212,6 +212,19 @@ describe Projects::DestroyService do ...@@ -212,6 +212,19 @@ describe Projects::DestroyService do
end end
end end
context 'as the root of a fork network' do
let!(:fork_network) { create(:fork_network, root_project: project) }
it 'updates the fork network with the project name' do
destroy_project(project, user)
fork_network.reload
expect(fork_network.deleted_root_project_name).to eq(project.full_name)
expect(fork_network.root_project).to be_nil
end
end
def destroy_project(project, user, params = {}) def destroy_project(project, user, params = {})
if async if async
Projects::DestroyService.new(project, user, params).async_execute Projects::DestroyService.new(project, user, params).async_execute
......
require 'spec_helper' require 'spec_helper'
describe Projects::ForkService do describe Projects::ForkService do
include ProjectForksHelper
let(:gitlab_shell) { Gitlab::Shell.new } let(:gitlab_shell) { Gitlab::Shell.new }
describe 'fork by user' do describe 'fork by user' do
...@@ -33,7 +34,7 @@ describe Projects::ForkService do ...@@ -33,7 +34,7 @@ describe Projects::ForkService do
end end
describe "successfully creates project in the user namespace" do describe "successfully creates project in the user namespace" do
let(:to_project) { fork_project(@from_project, @to_user) } let(:to_project) { fork_project(@from_project, @to_user, namespace: @to_user.namespace) }
it { expect(to_project).to be_persisted } it { expect(to_project).to be_persisted }
it { expect(to_project.errors).to be_empty } it { expect(to_project.errors).to be_empty }
...@@ -60,13 +61,40 @@ describe Projects::ForkService do ...@@ -60,13 +61,40 @@ describe Projects::ForkService do
expect(@from_project.forks_count).to eq(1) expect(@from_project.forks_count).to eq(1)
end 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
end end
context 'project already exists' do context 'project already exists' do
it "fails due to validation, not transaction failure" do it "fails due to validation, not transaction failure" do
@existing_project = create(:project, :repository, creator_id: @to_user.id, name: @from_project.name, namespace: @to_namespace) @existing_project = create(:project, :repository, creator_id: @to_user.id, name: @from_project.name, namespace: @to_namespace)
@to_project = fork_project(@from_project, @to_user) @to_project = fork_project(@from_project, @to_user, namespace: @to_namespace)
expect(@existing_project).to be_persisted expect(@existing_project).to be_persisted
expect(@to_project).not_to be_persisted expect(@to_project).not_to be_persisted
...@@ -88,7 +116,7 @@ describe Projects::ForkService do ...@@ -88,7 +116,7 @@ describe Projects::ForkService do
end end
it 'does not allow creation' do it 'does not allow creation' do
to_project = fork_project(@from_project, @to_user) to_project = fork_project(@from_project, @to_user, namespace: @to_user.namespace)
expect(to_project).not_to be_persisted expect(to_project).not_to be_persisted
expect(to_project.errors.messages).to have_key(:base) expect(to_project.errors.messages).to have_key(:base)
...@@ -182,9 +210,4 @@ describe Projects::ForkService do ...@@ -182,9 +210,4 @@ describe Projects::ForkService do
end end
end end
end end
def fork_project(from_project, user, params = {})
allow(RepositoryForkWorker).to receive(:perform_async).and_return(true)
Projects::ForkService.new(from_project, user, params).execute
end
end end
require 'spec_helper' require 'spec_helper'
describe Projects::UnlinkForkService do describe Projects::UnlinkForkService do
subject { described_class.new(fork_project, user) } include ProjectForksHelper
let(:fork_link) { create(:forked_project_link) } subject { described_class.new(forked_project, user) }
let(:fork_project) { fork_link.forked_to_project }
let(:fork_link) { forked_project.forked_project_link }
let(:project) { create(:project, :public) }
let(:forked_project) { fork_project(project, user) }
let(:user) { create(:user) } let(:user) { create(:user) }
context 'with opened merge request on the source project' do context 'with opened merge request on the source project' do
let(:merge_request) { create(:merge_request, source_project: fork_project, target_project: fork_link.forked_from_project) } let(:merge_request) { create(:merge_request, source_project: forked_project, target_project: fork_link.forked_from_project) }
let(:mr_close_service) { MergeRequests::CloseService.new(fork_project, user) } let(:mr_close_service) { MergeRequests::CloseService.new(forked_project, user) }
before do before do
allow(MergeRequests::CloseService).to receive(:new) allow(MergeRequests::CloseService).to receive(:new)
.with(fork_project, user) .with(forked_project, user)
.and_return(mr_close_service) .and_return(mr_close_service)
end end
...@@ -25,13 +28,24 @@ describe Projects::UnlinkForkService do ...@@ -25,13 +28,24 @@ describe Projects::UnlinkForkService do
end end
it 'remove fork relation' do it 'remove fork relation' do
expect(fork_project.forked_project_link).to receive(:destroy) expect(forked_project.forked_project_link).to receive(:destroy)
subject.execute
end
it 'removes the link to the fork network' do
expect(forked_project.fork_network_member).to be_present
expect(forked_project.fork_network).to be_present
subject.execute subject.execute
forked_project.reload
expect(forked_project.fork_network_member).to be_nil
expect(forked_project.reload.fork_network).to be_nil
end end
it 'refreshes the forks count cache of the source project' do it 'refreshes the forks count cache of the source project' do
source = fork_project.forked_from_project source = forked_project.forked_from_project
expect(source.forks_count).to eq(1) expect(source.forks_count).to eq(1)
......
require 'spec_helper' require 'spec_helper'
describe Projects::UpdateService, '#execute' do describe Projects::UpdateService, '#execute' do
include ProjectForksHelper
let(:gitlab_shell) { Gitlab::Shell.new } let(:gitlab_shell) { Gitlab::Shell.new }
let(:user) { create(:user) } let(:user) { create(:user) }
let(:admin) { create(:admin) } let(:admin) { create(:admin) }
...@@ -76,13 +78,7 @@ describe Projects::UpdateService, '#execute' do ...@@ -76,13 +78,7 @@ describe Projects::UpdateService, '#execute' do
describe 'when updating project that has forks' do describe 'when updating project that has forks' do
let(:project) { create(:project, :internal) } let(:project) { create(:project, :internal) }
let(:forked_project) { create(:forked_project_with_submodules, :internal) } let(:forked_project) { fork_project(project) }
before do
forked_project.build_forked_project_link(forked_to_project_id: forked_project.id,
forked_from_project_id: project.id)
forked_project.save
end
it 'updates forks visibility level when parent set to more restrictive' do it 'updates forks visibility level when parent set to more restrictive' do
opts = { visibility_level: Gitlab::VisibilityLevel::PRIVATE } opts = { visibility_level: Gitlab::VisibilityLevel::PRIVATE }
......
module ProjectForksHelper
def fork_project(project, user = nil, params = {})
# Load the `fork_network` for the project to fork as there might be one that
# wasn't loaded yet.
project.reload unless project.fork_network
unless user
user = create(:user)
project.add_developer(user)
end
unless params[:namespace] || params[:namespace_id]
params[:namespace] = create(:group)
params[:namespace].add_owner(user)
end
service = Projects::ForkService.new(project, user, params)
create_repository = params.delete(:repository)
# Avoid creating a repository
unless create_repository
allow(RepositoryForkWorker).to receive(:perform_async).and_return(true)
shell = double('gitlab_shell', fork_repository: true)
allow(service).to receive(:gitlab_shell).and_return(shell)
end
forked_project = service.execute
# Reload the both projects so they know about their newly created fork_network
if forked_project.persisted?
project.reload
forked_project.reload
end
if create_repository
# The call to project.repository.after_import in RepositoryForkWorker does
# not reset the @exists variable of this forked_project.repository
# so we have to explicitely call this method to clear the @exists variable.
# of the instance we're returning here.
forked_project.repository.after_import
# We can't leave the hooks in place after a fork, as those would fail in tests
# The "internal" API is not available
FileUtils.rm_rf("#{forked_project.repository.path}/hooks")
end
forked_project
end
def fork_project_with_submodules(project, user = nil, params = {})
forked_project = fork_project(project, user, params)
TestEnv.copy_repo(forked_project,
bare_repo: TestEnv.forked_repo_path_bare,
refs: TestEnv::FORKED_BRANCH_SHA)
forked_project
end
end
...@@ -2,10 +2,11 @@ require 'spec_helper' ...@@ -2,10 +2,11 @@ require 'spec_helper'
describe 'projects/merge_requests/_commits.html.haml' do describe 'projects/merge_requests/_commits.html.haml' do
include Devise::Test::ControllerHelpers include Devise::Test::ControllerHelpers
include ProjectForksHelper
let(:user) { create(:user) } let(:user) { create(:user) }
let(:target_project) { create(:project, :repository) } let(:target_project) { create(:project, :public, :repository) }
let(:source_project) { create(:project, :repository, forked_from_project: target_project) } let(:source_project) { fork_project(target_project, user, repository: true) }
let(:merge_request) do let(:merge_request) do
create(:merge_request, :simple, create(:merge_request, :simple,
......
...@@ -2,16 +2,19 @@ require 'spec_helper' ...@@ -2,16 +2,19 @@ require 'spec_helper'
describe 'projects/merge_requests/edit.html.haml' do describe 'projects/merge_requests/edit.html.haml' do
include Devise::Test::ControllerHelpers include Devise::Test::ControllerHelpers
include ProjectForksHelper
let(:user) { create(:user) } let(:user) { create(:user) }
let(:project) { create(:project, :repository) } let(:project) { create(:project, :repository) }
let(:fork_project) { create(:project, :repository, forked_from_project: project) } let(:forked_project) { fork_project(project, user, repository: true) }
let(:unlink_project) { Projects::UnlinkForkService.new(fork_project, user) } let(:unlink_project) { Projects::UnlinkForkService.new(forked_project, user) }
let(:milestone) { create(:milestone, project: project) } let(:milestone) { create(:milestone, project: project) }
let(:closed_merge_request) do let(:closed_merge_request) do
project.add_developer(user)
create(:closed_merge_request, create(:closed_merge_request,
source_project: fork_project, source_project: forked_project,
target_project: project, target_project: project,
author: user, author: user,
assignee: user, assignee: user,
......
...@@ -2,16 +2,17 @@ require 'spec_helper' ...@@ -2,16 +2,17 @@ require 'spec_helper'
describe 'projects/merge_requests/show.html.haml' do describe 'projects/merge_requests/show.html.haml' do
include Devise::Test::ControllerHelpers include Devise::Test::ControllerHelpers
include ProjectForksHelper
let(:user) { create(:user) } let(:user) { create(:user) }
let(:project) { create(:project, :repository) } let(:project) { create(:project, :public, :repository) }
let(:fork_project) { create(:project, :repository, forked_from_project: project) } let(:forked_project) { fork_project(project, user, repository: true) }
let(:unlink_project) { Projects::UnlinkForkService.new(fork_project, user) } let(:unlink_project) { Projects::UnlinkForkService.new(forked_project, user) }
let(:note) { create(:note_on_merge_request, project: project, noteable: closed_merge_request) } let(:note) { create(:note_on_merge_request, project: project, noteable: closed_merge_request) }
let(:closed_merge_request) do let(:closed_merge_request) do
create(:closed_merge_request, create(:closed_merge_request,
source_project: fork_project, source_project: forked_project,
target_project: project, target_project: project,
author: user) author: user)
end end
...@@ -52,7 +53,7 @@ describe 'projects/merge_requests/show.html.haml' do ...@@ -52,7 +53,7 @@ describe 'projects/merge_requests/show.html.haml' do
context 'when the merge request is open' do context 'when the merge request is open' do
it 'closes the merge request if the source project does not exist' do it 'closes the merge request if the source project does not exist' do
closed_merge_request.update_attributes(state: 'open') closed_merge_request.update_attributes(state: 'open')
fork_project.destroy forked_project.destroy
render render
......
require 'spec_helper' require 'spec_helper'
describe NamespacelessProjectDestroyWorker do describe NamespacelessProjectDestroyWorker do
include ProjectForksHelper
subject { described_class.new } subject { described_class.new }
before do before do
...@@ -55,9 +57,11 @@ describe NamespacelessProjectDestroyWorker do ...@@ -55,9 +57,11 @@ describe NamespacelessProjectDestroyWorker do
context 'project forked from another' do context 'project forked from another' do
let!(:parent_project) { create(:project) } let!(:parent_project) { create(:project) }
let(:project) do
before do namespaceless_project = fork_project(parent_project)
create(:forked_project_link, forked_to_project: project, forked_from_project: parent_project) namespaceless_project.namespace_id = nil
namespaceless_project.save(validate: false)
namespaceless_project
end end
it 'closes open merge requests' do it 'closes open merge requests' do
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment