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
end
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
elsif params[:target_project_id].present?
MergeRequestTargetProjectFinder.new(current_user: current_user, source_project: @project)
.execute.find(params[:target_project_id])
else
@project.forked_project_link.forked_from_project
@project.forked_from_project
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
end
def target_projects(project)
[project, project.default_merge_request_target].uniq
MergeRequestTargetProjectFinder.new(current_user: current_user, source_project: project)
.execute
end
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
return false unless for_fork?
return true unless source_project
!source_project.forked_from?(target_project)
!source_project.in_fork_network_of?(target_project)
end
def reopenable?
......
......@@ -139,7 +139,9 @@ class Namespace < ActiveRecord::Base
end
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
def lfs_enabled?
......
......@@ -118,11 +118,20 @@ class Project < ActiveRecord::Base
has_one :mock_monitoring_service
has_one :microsoft_teams_service
# TODO: replace these relations with the fork network versions
has_one :forked_project_link, foreign_key: "forked_to_project_id"
has_one :forked_from_project, through: :forked_project_link
has_many :forked_project_links, foreign_key: "forked_from_project_id"
has_many :forks, through: :forked_project_links, source: :forked_to_project
# TODO: replace these relations with the fork network versions
has_one :root_of_fork_network,
foreign_key: 'root_project_id',
inverse_of: :root_project,
class_name: 'ForkNetwork'
has_one :fork_network_member
has_one :fork_network, through: :fork_network_member
# Merge Requests for target project should be removed with it
has_many :merge_requests, foreign_key: 'target_project_id'
......@@ -1000,6 +1009,11 @@ class Project < ActiveRecord::Base
end
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?)
end
......@@ -1119,8 +1133,19 @@ class Project < ActiveRecord::Base
end
end
def forked_from?(project)
forked? && project == forked_from_project
def forked_from?(other_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
def origin_merge_requests
......
......@@ -697,15 +697,7 @@ class User < ActiveRecord::Base
end
def fork_of(project)
links = ForkedProjectLink.where(
forked_from_project_id: project,
forked_to_project_id: personal_projects.unscope(:order)
)
if links.any?
links.first.forked_to_project
else
nil
end
namespace.find_fork_of(project)
end
def ldap_user?
......
......@@ -22,6 +22,13 @@ module Projects
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)
system_hook_service.execute_hooks_for(project, :destroy)
......
......@@ -23,11 +23,31 @@ module Projects
refresh_forks_count
link_fork_network(new_project)
new_project
end
private
def fork_network
if @project.fork_network
@project.fork_network
elsif forked_from_project = @project.forked_from_project
# TODO: remove this case when all background migrations have completed
# this only happens when a project had a `forked_project_link` that was
# not migrated to the `fork_network` relation
forked_from_project.fork_network || forked_from_project.create_root_of_fork_network
else
@project.create_root_of_fork_network
end
end
def link_fork_network(new_project)
fork_network.fork_network_members.create(project: new_project,
forked_from_project: @project)
end
def refresh_forks_count
Projects::ForksCountService.new(@project).refresh_cache
end
......
......@@ -16,6 +16,7 @@ module Projects
refresh_forks_count(@project.forked_from_project)
@project.forked_project_link.destroy
@project.fork_network_member.destroy
end
def refresh_forks_count(project)
......
- 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) }
.limit-container-width{ class: container_class }
.avatar-container.s70.project-avatar
......@@ -12,11 +14,15 @@
- if @project.description.present?
= markdown_field(@project, :description)
- if forked_from_project = @project.forked_from_project
- if @project.forked?
%p
#{ s_('ForkedFromProjectPath|Forked from') }
= link_to project_path(forked_from_project) do
= forked_from_project.namespace.try(:name)
- if forked_from_project
#{ s_('ForkedFromProjectPath|Forked from') }
= 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
.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
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|
t.integer "forked_to_project_id", null: false
t.integer "forked_from_project_id", null: false
......@@ -1793,6 +1809,10 @@ ActiveRecord::Schema.define(version: 20171006091000) do
add_foreign_key "environments", "projects", name: "fk_d1c8c1da6a", on_delete: :cascade
add_foreign_key "events", "projects", on_delete: :cascade
add_foreign_key "events", "users", column: "author_id", name: "fk_edfd187b6f", on_delete: :cascade
add_foreign_key "fork_network_members", "fork_networks", on_delete: :cascade
add_foreign_key "fork_network_members", "projects", column: "forked_from_project_id", name: "fk_b01280dae4", on_delete: :nullify
add_foreign_key "fork_network_members", "projects", on_delete: :cascade
add_foreign_key "fork_networks", "projects", column: "root_project_id", name: "fk_e7b436b2b5", on_delete: :nullify
add_foreign_key "forked_project_links", "projects", column: "forked_to_project_id", name: "fk_434510edb0", on_delete: :cascade
add_foreign_key "gcp_clusters", "projects", on_delete: :cascade
add_foreign_key "gcp_clusters", "services", on_delete: :nullify
......
......@@ -58,13 +58,13 @@ class Spinach::Features::ProjectFork < Spinach::FeatureSteps
step 'I should see my fork on the list' 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}")
end
end
step 'I make forked repo invalid' do
project = @user.fork_of(@project)
project = @user.fork_of(@project.reload)
project.path = 'test-crappy-path'
project.save!
end
......
......@@ -5,6 +5,7 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps
include SharedPaths
include Select2Helper
include WaitForRequests
include ProjectForksHelper
step 'I am a member of project "Shop"' do
@project = ::Project.find_by(name: "Shop")
......@@ -13,7 +14,9 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps
end
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
step 'I click link "New Merge Request"' do
......
......@@ -10,7 +10,7 @@ if ENV['CI']
Knapsack::Adapters::SpinachAdapter.bind
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)
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
@extractor.analyze(closing_statements.join(" "))
@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
......
......@@ -8,8 +8,8 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab 1.0.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-10-04 23:47+0100\n"
"PO-Revision-Date: 2017-10-04 23:47+0100\n"
"POT-Creation-Date: 2017-10-06 18:33+0200\n"
"PO-Revision-Date: 2017-10-06 18:33+0200\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
......@@ -23,6 +23,11 @@ msgid_plural "%d commits"
msgstr[0] ""
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_plural "%s additional commits have been omitted to prevent performance issues."
msgstr[0] ""
......@@ -59,6 +64,9 @@ msgstr[1] ""
msgid "1st contribution!"
msgstr ""
msgid "2FA enabled"
msgstr ""
msgid "A collection of graphs regarding Continuous Integration"
msgstr ""
......@@ -528,6 +536,51 @@ msgstr ""
msgid "Compare"
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"
msgstr ""
......@@ -739,6 +792,9 @@ msgstr[1] ""
msgid "ForkedFromProjectPath|Forked from"
msgstr ""
msgid "ForkedFromProjectPath|Forked from %{project_name} (deleted)"
msgstr ""
msgid "Format"
msgstr ""
......@@ -949,6 +1005,9 @@ msgstr ""
msgid "New tag"
msgstr ""
msgid "No container images stored for this project. Add one by following the instructions above."
msgstr ""
msgid "No repository"
msgstr ""
......@@ -1024,6 +1083,9 @@ msgstr ""
msgid "OpenedNDaysAgo|Opened"
msgstr ""
msgid "Opens in a new window"
msgstr ""
msgid "Options"
msgstr ""
......@@ -1350,6 +1412,15 @@ msgstr[1] ""
msgid "Snippets"
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"
msgstr ""
......@@ -1905,3 +1976,6 @@ msgid "parent"
msgid_plural "parents"
msgstr[0] ""
msgstr[1] ""
msgid "personal access token"
msgstr ""
require 'rails_helper'
describe Projects::BlobController do
include ProjectForksHelper
let(:project) { create(:project, :public, :repository) }
describe "GET show" do
......@@ -226,9 +228,8 @@ describe Projects::BlobController do
end
context 'when user has forked project' do
let(:forked_project_link) { create(:forked_project_link, forked_from_project: project) }
let!(:forked_project) { forked_project_link.forked_to_project }
let(:guest) { forked_project.owner }
let!(:forked_project) { fork_project(project, guest, namespace: guest.namespace, repository: true) }
let(:guest) { create(:user) }
before do
sign_in(guest)
......
require 'spec_helper'
describe Projects::MergeRequests::DiffsController do
include ProjectForksHelper
let(:project) { create(:project, :repository) }
let(:user) { project.owner }
let(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: project) }
......@@ -37,12 +39,12 @@ describe Projects::MergeRequests::DiffsController do
render_views
let(:project) { create(:project, :repository) }
let(:fork_project) { create(:forked_project_with_submodules) }
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(:forked_project) { fork_project_with_submodules(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
fork_project.build_forked_project_link(forked_to_project_id: fork_project.id, forked_from_project_id: project.id)
fork_project.save
project.add_developer(user)
merge_request.reload
go
end
......
require 'spec_helper'
describe Projects::MergeRequestsController do
include ProjectForksHelper
let(:project) { create(:project, :repository) }
let(:user) { project.owner }
let(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: project) }
......@@ -204,14 +206,11 @@ describe Projects::MergeRequestsController do
context 'there is no source project' do
let(:project) { create(:project, :repository) }
let(:fork_project) { create(:forked_project_with_submodules) }
let(:merge_request) { create(:merge_request, source_project: fork_project, source_branch: 'add-submodule-version-bump', target_branch: 'master', target_project: project) }
let(:forked_project) { fork_project_with_submodules(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
fork_project.build_forked_project_link(forked_to_project_id: fork_project.id, forked_from_project_id: project.id)
fork_project.save
merge_request.reload
fork_project.destroy
forked_project.destroy
end
it 'closes MR without errors' do
......@@ -599,21 +598,16 @@ describe Projects::MergeRequestsController do
describe 'GET ci_environments_status' 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!(:deployment) { create(:deployment, environment: environment, sha: forked.commit.id, ref: 'master') }
let(:admin) { create(:admin) }
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)
end
before do
forked.team << [user, :master]
get :ci_environments_status,
namespace_id: merge_request.project.namespace.to_param,
project_id: merge_request.project,
......
require 'spec_helper'
describe Projects::NotesController do
include ProjectForksHelper
let(:user) { create(:user) }
let(:project) { create(:project) }
let(:issue) { create(:issue, project: project) }
......@@ -210,18 +212,16 @@ describe Projects::NotesController do
context 'when creating a commit comment from an MR fork' do
let(:project) { create(:project, :repository) }
let(:fork_project) do
create(:project, :repository).tap do |fork|
create(:forked_project_link, forked_to_project: fork, forked_from_project: project)
end
let(:forked_project) do
fork_project(project, nil, repository: true)
end
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
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
def post_create(extra_params = {})
......@@ -231,7 +231,7 @@ describe Projects::NotesController do
project_id: project,
target_type: 'merge_request',
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
}.merge(extra_params)
end
......@@ -253,16 +253,16 @@ describe Projects::NotesController do
end
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
fork_project.add_developer(user)
forked_project.add_developer(user)
existing_comment
end
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
......
require('spec_helper')
describe ProjectsController do
include ProjectForksHelper
let(:project) { create(:project) }
let(:public_project) { create(:project, :public) }
let(:user) { create(:user) }
......@@ -377,10 +379,10 @@ describe ProjectsController do
context "when the project is forked" do
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
create(:merge_request,
source_project: fork_project,
source_project: forked_project,
target_project: project)
end
......@@ -388,7 +390,7 @@ describe ProjectsController do
project.merge_requests << merge_request
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')
end
......@@ -455,18 +457,14 @@ describe ProjectsController do
end
context 'with forked project' do
let(:project_fork) { create(:project, :repository, namespace: user.namespace) }
before do
create(:forked_project_link, forked_to_project: project_fork)
end
let(:forked_project) { fork_project(create(:project, :public), user) }
it 'removes fork from project' do
delete(:remove_fork,
namespace_id: project_fork.namespace.to_param,
id: project_fork.to_param, format: :js)
namespace_id: forked_project.namespace.to_param,
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(response).to render_template(:remove_fork)
end
......
FactoryGirl.define do
factory :fork_network do
association :root_project, factory: :project
end
end
......@@ -3,12 +3,13 @@ require 'spec_helper'
feature 'Dashboard Merge Requests' do
include FilterItemSelectHelper
include SortingHelper
include ProjectForksHelper
let(:current_user) { create :user }
let(:project) { create(:project) }
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
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'
feature 'Merge request created from fork' do
include ProjectForksHelper
given(:user) { create(:user) }
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
create(:forked_project_link, forked_to_project: fork_project,
forked_from_project: project)
create(:merge_request_with_diffs, source_project: fork_project,
create(:merge_request_with_diffs, source_project: forked_project,
target_project: project,
description: 'Test merge request')
end
background do
fork_project.team << [user, :master]
forked_project.team << [user, :master]
sign_in user
end
......@@ -31,7 +30,7 @@ feature 'Merge request created from fork' do
background do
create(:note_on_commit, note: comment,
project: fork_project,
project: forked_project,
commit_id: merge_request.commit_shas.first)
end
......@@ -55,7 +54,7 @@ feature 'Merge request created from fork' do
context 'source project is deleted' do
background do
MergeRequests::MergeService.new(project, user).execute(merge_request)
fork_project.destroy!
forked_project.destroy!
end
scenario 'user can access merge request', js: true do
......@@ -69,7 +68,7 @@ feature 'Merge request created from fork' do
context 'pipeline present in source project' do
given(:pipeline) do
create(:ci_pipeline,
project: fork_project,
project: forked_project,
sha: merge_request.diff_head_sha,
ref: merge_request.source_branch)
end
......
require 'spec_helper'
feature 'Diffs URL', js: true do
include ProjectForksHelper
let(:project) { create(:project, :public, :repository) }
let(:merge_request) { create(:merge_request, source_project: project) }
......@@ -68,7 +70,7 @@ feature 'Diffs URL', js: true do
context 'when editing file' do
let(:author_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(:changelog_id) { Digest::SHA1.hexdigest("CHANGELOG") }
......
require 'rails_helper'
describe 'New/edit merge request', :js do
include ProjectForksHelper
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!(:user2) { create(:user) }
let!(:milestone) { create(:milestone, project: project) }
......@@ -170,16 +172,16 @@ describe 'New/edit merge request', :js do
context 'forked project' do
before do
fork_project.team << [user, :master]
forked_project.team << [user, :master]
sign_in(user)
end
context 'new merge request' do
before do
visit project_new_merge_request_path(
fork_project,
forked_project,
merge_request: {
source_project_id: fork_project.id,
source_project_id: forked_project.id,
target_project_id: project.id,
source_branch: 'fix',
target_branch: 'master'
......@@ -238,7 +240,7 @@ describe 'New/edit merge request', :js do
context 'edit merge request' do
before do
merge_request = create(:merge_request,
source_project: fork_project,
source_project: forked_project,
target_project: project,
source_branch: 'fix',
target_branch: 'master'
......
require 'spec_helper'
feature 'issuable templates', js: true do
include ProjectForksHelper
let(:user) { create(:user) }
let(:project) { create(:project, :public, :repository) }
let(:issue_form_location) { '#content-body .issuable-details .detail-page-description' }
......@@ -116,15 +118,13 @@ feature 'issuable templates', js: true 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(:fork_user) { create(:user) }
let(:fork_project) { create(:project, :public, :repository) }
let(:merge_request) { create(:merge_request, :with_diffs, source_project: fork_project, target_project: project) }
let(:forked_project) { fork_project(project, fork_user) }
let(:merge_request) { create(:merge_request, :with_diffs, source_project: forked_project, target_project: project) }
background do
sign_out(:user)
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)
......
......@@ -79,7 +79,7 @@ feature 'User creates a directory', js: true do
fill_in(:commit_message, with: 'New commit message', visible: true)
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))
end
......
......@@ -142,7 +142,7 @@ describe 'User creates files' do
fill_in(:commit_message, with: 'New commit message', visible: true)
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(page).to have_content('New commit message')
......
......@@ -59,7 +59,7 @@ describe 'User deletes files' do
fill_in(:commit_message, with: 'New commit message', visible: true)
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(page).to have_content('New commit message')
......
require 'spec_helper'
describe 'User edits files' do
include ProjectForksHelper
let(:project) { create(:project, :repository, name: 'Shop') }
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) }
......@@ -122,7 +123,7 @@ describe 'User edits files' do
fill_in(:commit_message, with: 'New commit message', visible: true)
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))
......@@ -130,5 +131,34 @@ describe 'User edits files' do
expect(page).to have_content('New commit message')
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
......@@ -74,7 +74,7 @@ describe 'User replaces files' do
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))
......
......@@ -39,6 +39,9 @@ describe 'User uploads files' do
expect(current_path).to eq(project_new_merge_request_path(project))
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('Sed ut perspiciatis unde omnis')
......@@ -51,7 +54,7 @@ describe 'User uploads files' do
visit(project2_tree_path_root_ref)
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
click_link('Upload file')
......@@ -69,11 +72,13 @@ describe 'User uploads files' do
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))
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('Sed ut perspiciatis unde omnis')
......
require 'spec_helper'
feature 'Project' do
include ProjectForksHelper
describe 'creating from template' do
let(:user) { create(:user) }
let(:template) { Gitlab::ProjectTemplate.find(:rails) }
......@@ -57,11 +59,10 @@ feature 'Project' do
describe 'remove forked relationship', js: true do
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
sign_in user
create(:forked_project_link, forked_to_project: project)
visit edit_project_path(project)
end
......@@ -71,11 +72,60 @@ feature 'Project' do
remove_with_confirm('Remove fork relationship', project.path)
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'
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
let(:user) { create(:user, username: 'test', name: 'test') }
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'
describe MergeRequestsFinder do
include ProjectForksHelper
let(:user) { create :user }
let(:user2) { create :user }
let(:project1) { create(:project) }
let(:project2) { create(:project, forked_from_project: project1) }
let(:project3) { create(:project, :archived, forked_from_project: project1) }
let(:project1) { create(:project, :public) }
let(:project2) { fork_project(project1, user) }
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_request2) { create(:merge_request, :simple, author: user, source_project: project2, target_project: project1, state: 'closed') }
......
require 'spec_helper'
describe MergeRequestsHelper do
include ProjectForksHelper
describe 'ci_build_details_path' do
let(:project) { create(:project) }
let(:merge_request) { MergeRequest.new }
......@@ -31,10 +32,10 @@ describe MergeRequestsHelper do
describe 'within different projects' do
let(:project) { create(:project) }
let(:fork_project) { create(:project, forked_from_project: project) }
let(:merge_request) { create(:merge_request, source_project: fork_project, target_project: project) }
let(:forked_project) { fork_project(project) }
let(:merge_request) { create(:merge_request, source_project: forked_project, target_project: project) }
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}" }
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:
- uploads
- members_and_requesters
- build_trace_section_names
- root_of_fork_network
- fork_network_member
- fork_network
award_emoji:
- awardable
- user
......
require 'spec_helper'
describe 'forked project import' do
include ProjectForksHelper
let(:user) { create(:user) }
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(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" }
let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: project.full_path) }
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(:bundle_path) { File.join(shared.export_path, Gitlab::ImportExport.project_bundle_filename) }
......@@ -16,7 +18,7 @@ describe 'forked project import' do
end
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
let(:saver) do
......
require 'spec_helper'
describe Gitlab::ImportExport::MergeRequestParser do
include ProjectForksHelper
let(:user) { create(:user) }
let!(:project) { create(:project, :repository, name: 'test-repo-restorer', path: 'test-repo-restorer') }
let(:forked_from_project) { create(:project, :repository) }
let(:fork_link) { create(:forked_project_link, forked_from_project: project) }
let(:forked_project) { fork_project(project) }
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
let(:parsed_merge_request) do
......
require 'spec_helper'
describe Gitlab::SearchResults do
include ProjectForksHelper
let(:user) { create(:user) }
let!(:project) { create(:project, name: 'foo') }
let!(:issue) { create(:issue, project: project, title: 'foo') }
......@@ -42,7 +44,7 @@ describe Gitlab::SearchResults do
end
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')
results = described_class.new(user, Project.where(id: forked_project.id), 'foo')
......
......@@ -2,11 +2,12 @@ require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20170508170547_add_head_pipeline_for_each_merge_request.rb')
describe AddHeadPipelineForEachMergeRequest, :truncate do
include ProjectForksHelper
let(:migration) { described_class.new }
let!(:project) { create(:project) }
let!(:forked_project_link) { create(:forked_project_link, forked_from_project: project) }
let!(:other_project) { forked_project_link.forked_to_project }
let!(:other_project) { fork_project(project) }
let!(:pipeline_1) { create(:ci_pipeline, project: 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'
describe ForkedProjectLink, "add link on fork" do
include ProjectForksHelper
let(:project_from) { create(:project, :repository) }
let(:project_to) { fork_project(project_from, user) }
let(:user) { create(:user) }
let(:namespace) { user.namespace }
before do
project_from.add_reporter(user)
......@@ -64,13 +65,4 @@ describe ForkedProjectLink, "add link on fork" do
expect(ForkedProjectLink.exists?(id: forked_project_link.id)).to eq(false)
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
......@@ -2,6 +2,7 @@ require 'spec_helper'
describe MergeRequest do
include RepoHelpers
include ProjectForksHelper
subject { create(:merge_request) }
......@@ -49,6 +50,19 @@ describe MergeRequest do
expect(subject).to be_valid
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
describe 'respond to' do
......@@ -672,7 +686,7 @@ describe MergeRequest do
describe '#diverged_commits_count' do
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
subject { create(:merge_request, source_project: project, target_project: project) }
......@@ -700,7 +714,7 @@ describe MergeRequest do
end
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
expect(subject.diverged_commits_count).to eq(29)
......@@ -708,7 +722,7 @@ describe MergeRequest do
end
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
expect(subject.diverged_commits_count).to eq(0)
......@@ -1257,11 +1271,7 @@ describe MergeRequest do
end
context 'with environments on source project' do
let(:source_project) do
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(:source_project) { fork_project(project, nil, repository: true) }
let(:merge_request) do
create(:merge_request,
......@@ -1425,14 +1435,14 @@ describe MergeRequest do
describe "#source_project_missing?" do
let(:project) { create(:project) }
let(:fork_project) { create(:project, forked_from_project: project) }
let(:forked_project) { fork_project(project) }
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
let(:merge_request) do
create(:merge_request,
source_project: fork_project,
source_project: forked_project,
target_project: project)
end
......@@ -1446,9 +1456,9 @@ describe MergeRequest do
end
context "when the fork does not exist" do
let(:merge_request) do
let!(:merge_request) do
create(:merge_request,
source_project: fork_project,
source_project: forked_project,
target_project: project)
end
......@@ -1471,14 +1481,14 @@ describe MergeRequest do
describe "#closed_without_fork?" do
let(:project) { create(:project) }
let(:fork_project) { create(:project, forked_from_project: project) }
let(:forked_project) { fork_project(project) }
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
let(:closed_merge_request) do
create(:closed_merge_request,
source_project: fork_project,
source_project: forked_project,
target_project: project)
end
......@@ -1497,7 +1507,7 @@ describe MergeRequest do
context "when the merge request is open" do
let(:open_merge_request) do
create(:merge_request,
source_project: fork_project,
source_project: forked_project,
target_project: project)
end
......@@ -1516,24 +1526,24 @@ describe MergeRequest do
end
context 'forked project' do
let(:project) { create(:project) }
let(:project) { create(:project, :public) }
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
create(:closed_merge_request,
source_project: fork_project,
source_project: forked_project,
target_project: project)
end
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
end
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
end
......
require 'spec_helper'
describe Namespace do
include ProjectForksHelper
let!(:namespace) { create(:namespace) }
describe 'associations' do
......@@ -520,4 +522,25 @@ describe Namespace do
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
......@@ -1818,6 +1818,59 @@ describe Project do
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
let(:project) { create(:project) }
......
......@@ -2,6 +2,7 @@ require 'spec_helper'
describe User do
include Gitlab::CurrentSettings
include ProjectForksHelper
describe 'modules' do
subject { described_class }
......@@ -1431,7 +1432,7 @@ describe User do
describe "#contributed_projects" do
subject { create(:user) }
let!(:project1) { create(:project) }
let!(:project2) { create(:project, forked_from_project: project3) }
let!(:project2) { fork_project(project3) }
let!(:project3) { create(:project) }
let!(:merge_request) { create(:merge_request, source_project: project2, target_project: project3, author: subject) }
let!(:push_event) { create(:push_event, project: project1, author: subject) }
......@@ -1455,6 +1456,23 @@ describe User do
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
subject { create(:user) }
......
require "spec_helper"
describe API::MergeRequests do
include ProjectForksHelper
let(:base_time) { Time.now }
let(:user) { create(:user) }
let(:admin) { create(:user, :admin) }
......@@ -616,17 +618,17 @@ describe API::MergeRequests do
context 'forked projects' do
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) }
before do
fork_project.add_reporter(user2)
forked_project.add_reporter(user2)
allow_any_instance_of(MergeRequest).to receive(:write_ref)
end
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",
author: user2, target_project_id: project.id, description: 'Test description for Test merge_request'
expect(response).to have_gitlab_http_status(201)
......@@ -635,10 +637,10 @@ describe API::MergeRequests do
end
it "does not return 422 when source_branch equals target_branch" do
expect(project.id).not_to eq(fork_project.id)
expect(fork_project.forked?).to be_truthy
expect(fork_project.forked_from_project).to eq(project)
post api("/projects/#{fork_project.id}/merge_requests", user2),
expect(project.id).not_to eq(forked_project.id)
expect(forked_project.forked?).to be_truthy
expect(forked_project.forked_from_project).to eq(project)
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
expect(response).to have_gitlab_http_status(201)
expect(json_response['title']).to eq('Test merge_request')
......@@ -647,7 +649,7 @@ describe API::MergeRequests do
it 'returns 422 when target project has disabled merge requests' do
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',
target_branch: 'master',
source_branch: 'markdown',
......@@ -658,36 +660,26 @@ describe API::MergeRequests do
end
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
expect(response).to have_gitlab_http_status(400)
end
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
expect(response).to have_gitlab_http_status(400)
end
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
expect(response).to have_gitlab_http_status(400)
end
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
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',
......@@ -698,8 +690,8 @@ describe API::MergeRequests do
end
it "returns 201 when target_branch is specified and for the same project" do
post api("/projects/#{fork_project.id}/merge_requests", user2),
title: 'Test merge_request', target_branch: 'master', source_branch: 'markdown', author: user2, target_project_id: fork_project.id
post api("/projects/#{forked_project.id}/merge_requests", user2),
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)
end
end
......
......@@ -64,9 +64,12 @@ describe API::Projects do
create(:project, :public)
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
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
......
require "spec_helper"
describe API::MergeRequests do
include ProjectForksHelper
let(:base_time) { Time.now }
let(:user) { create(:user) }
let(:admin) { create(:user, :admin) }
......@@ -312,17 +314,17 @@ describe API::MergeRequests do
context 'forked projects' do
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) }
before do
fork_project.add_reporter(user2)
forked_project.add_reporter(user2)
allow_any_instance_of(MergeRequest).to receive(:write_ref)
end
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",
author: user2, target_project_id: project.id, description: 'Test description for Test merge_request'
expect(response).to have_gitlab_http_status(201)
......@@ -331,10 +333,10 @@ describe API::MergeRequests do
end
it "does not return 422 when source_branch equals target_branch" do
expect(project.id).not_to eq(fork_project.id)
expect(fork_project.forked?).to be_truthy
expect(fork_project.forked_from_project).to eq(project)
post v3_api("/projects/#{fork_project.id}/merge_requests", user2),
expect(project.id).not_to eq(forked_project.id)
expect(forked_project.forked?).to be_truthy
expect(forked_project.forked_from_project).to eq(project)
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
expect(response).to have_gitlab_http_status(201)
expect(json_response['title']).to eq('Test merge_request')
......@@ -343,7 +345,7 @@ describe API::MergeRequests do
it "returns 422 when target project has disabled merge requests" do
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',
target_branch: "master",
source_branch: 'markdown',
......@@ -354,36 +356,26 @@ describe API::MergeRequests do
end
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
expect(response).to have_gitlab_http_status(400)
end
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
expect(response).to have_gitlab_http_status(400)
end
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
expect(response).to have_gitlab_http_status(400)
end
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
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',
......@@ -394,8 +386,8 @@ describe API::MergeRequests do
end
it "returns 201 when target_branch is specified and for the same project" do
post v3_api("/projects/#{fork_project.id}/merge_requests", user2),
title: 'Test merge_request', target_branch: 'master', source_branch: 'markdown', author: user2, target_project_id: fork_project.id
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: forked_project.id
expect(response).to have_gitlab_http_status(201)
end
end
......
......@@ -2,6 +2,7 @@ require 'spec_helper'
describe 'Git LFS API and storage' do
include WorkhorseHelpers
include ProjectForksHelper
let(:user) { create(:user) }
let!(:lfs_object) { create(:lfs_object, :with_file) }
......@@ -1173,11 +1174,6 @@ describe 'Git LFS API and storage' do
ActionController::HttpAuthentication::Basic.encode_credentials(user.username, Gitlab::LfsToken.new(user).token)
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)
post(url, body.try(:to_json), (headers || {}).merge('Content-Type' => 'application/vnd.git-lfs+json'))
end
......
require 'spec_helper'
describe BuildDetailsEntity do
include ProjectForksHelper
set(:user) { create(:admin) }
it 'inherits from JobEntity' do
......@@ -56,18 +58,16 @@ describe BuildDetailsEntity do
end
context 'when merge request is from a fork' do
let(:fork_project) do
create(:project, forked_from_project: project)
end
let(:forked_project) { fork_project(project) }
let(:pipeline) { create(:ci_pipeline, project: fork_project) }
let(:pipeline) { create(:ci_pipeline, project: forked_project) }
before do
allow(build).to receive(:merge_request).and_return(merge_request)
end
let(:merge_request) do
create(:merge_request, source_project: fork_project,
create(:merge_request, source_project: forked_project,
target_project: project,
source_branch: build.ref)
end
......
require 'spec_helper'
describe Ci::CreatePipelineService do
include ProjectForksHelper
set(:project) { create(:project, :repository) }
let(:user) { create(:admin) }
let(:ref_name) { 'refs/heads/master' }
......@@ -82,13 +84,9 @@ describe Ci::CreatePipelineService do
end
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!(: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
merge_request = create(:merge_request, source_branch: 'master',
target_branch: "branch_1",
......
require 'spec_helper'
describe DeleteMergedBranchesService do
include ProjectForksHelper
subject(:service) { described_class.new(project, project.owner) }
let(:project) { create(:project, :repository) }
......@@ -50,9 +52,9 @@ describe DeleteMergedBranchesService do
context '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: 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
......
require 'spec_helper'
describe MergeRequests::Conflicts::ResolveService do
include ProjectForksHelper
let(:user) { create(:user) }
let(:project) { create(:project, :repository) }
let(:fork_project) do
create(:forked_project_with_submodules) do |fork_project|
fork_project.build_forked_project_link(forked_to_project_id: fork_project.id, forked_from_project_id: project.id)
fork_project.save
end
let(:project) { create(:project, :public, :repository) }
let(:forked_project) do
forked_project = fork_project(project, user)
TestEnv.copy_repo(forked_project,
bare_repo: TestEnv.forked_repo_path_bare,
refs: TestEnv::FORKED_BRANCH_SHA)
forked_project
end
let(:merge_request) do
......@@ -19,7 +21,7 @@ describe MergeRequests::Conflicts::ResolveService do
let(:merge_request_from_fork) do
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)
end
......@@ -114,7 +116,7 @@ describe MergeRequests::Conflicts::ResolveService do
end
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)
resolve_conflicts
......
require "spec_helper"
describe MergeRequests::GetUrlsService do
include ProjectForksHelper
let(:project) { create(:project, :public, :repository) }
let(:service) { described_class.new(project) }
let(:source_branch) { "merge-test" }
......@@ -85,7 +87,7 @@ describe MergeRequests::GetUrlsService do
context 'pushing to existing branch from forked project' do
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(:changes) { existing_branch_changes }
# Source project is now the forked one
......
require 'spec_helper'
describe MergeRequests::RefreshService do
include ProjectForksHelper
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
let(:service) { described_class }
......@@ -12,7 +14,8 @@ describe MergeRequests::RefreshService do
group.add_owner(@user)
@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,
source_project: @project,
source_branch: 'master',
......@@ -311,8 +314,7 @@ describe MergeRequests::RefreshService 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
forked_project = create(:project, :repository)
create(:forked_project_link, forked_to_project: forked_project, forked_from_project: @project)
forked_project = fork_project(@project, @user, repository: true)
merge_request = create(:merge_request,
target_branch: 'master',
......
......@@ -212,6 +212,19 @@ describe Projects::DestroyService do
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 = {})
if async
Projects::DestroyService.new(project, user, params).async_execute
......
require 'spec_helper'
describe Projects::ForkService do
include ProjectForksHelper
let(:gitlab_shell) { Gitlab::Shell.new }
describe 'fork by user' do
......@@ -33,7 +34,7 @@ describe Projects::ForkService do
end
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.errors).to be_empty }
......@@ -60,13 +61,40 @@ describe Projects::ForkService do
expect(@from_project.forks_count).to eq(1)
end
it 'creates a fork network with the new project and the root project set' do
to_project
fork_network = @from_project.reload.fork_network
expect(fork_network).not_to be_nil
expect(fork_network.root_project).to eq(@from_project)
expect(fork_network.projects).to contain_exactly(@from_project, to_project)
end
end
context 'creating a fork of a fork' do
let(:from_forked_project) { fork_project(@from_project, @to_user) }
let(:other_namespace) do
group = create(:group)
group.add_owner(@to_user)
group
end
let(:to_project) { fork_project(from_forked_project, @to_user, namespace: other_namespace) }
it 'sets the root of the network to the root project' do
expect(to_project.fork_network.root_project).to eq(@from_project)
end
it 'sets the forked_from_project on the membership' do
expect(to_project.fork_network_member.forked_from_project).to eq(from_forked_project)
end
end
end
context 'project already exists' 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)
@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(@to_project).not_to be_persisted
......@@ -88,7 +116,7 @@ describe Projects::ForkService do
end
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.errors.messages).to have_key(:base)
......@@ -182,9 +210,4 @@ describe Projects::ForkService do
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
require 'spec_helper'
describe Projects::UnlinkForkService do
subject { described_class.new(fork_project, user) }
include ProjectForksHelper
let(:fork_link) { create(:forked_project_link) }
let(:fork_project) { fork_link.forked_to_project }
subject { described_class.new(forked_project, user) }
let(:fork_link) { forked_project.forked_project_link }
let(:project) { create(:project, :public) }
let(:forked_project) { fork_project(project, user) }
let(:user) { create(:user) }
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(:mr_close_service) { MergeRequests::CloseService.new(fork_project, user) }
let(:merge_request) { create(:merge_request, source_project: forked_project, target_project: fork_link.forked_from_project) }
let(:mr_close_service) { MergeRequests::CloseService.new(forked_project, user) }
before do
allow(MergeRequests::CloseService).to receive(:new)
.with(fork_project, user)
.with(forked_project, user)
.and_return(mr_close_service)
end
......@@ -25,13 +28,24 @@ describe Projects::UnlinkForkService do
end
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
forked_project.reload
expect(forked_project.fork_network_member).to be_nil
expect(forked_project.reload.fork_network).to be_nil
end
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)
......
require 'spec_helper'
describe Projects::UpdateService, '#execute' do
include ProjectForksHelper
let(:gitlab_shell) { Gitlab::Shell.new }
let(:user) { create(:user) }
let(:admin) { create(:admin) }
......@@ -76,13 +78,7 @@ describe Projects::UpdateService, '#execute' do
describe 'when updating project that has forks' do
let(:project) { create(:project, :internal) }
let(:forked_project) { create(:forked_project_with_submodules, :internal) }
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
let(:forked_project) { fork_project(project) }
it 'updates forks visibility level when parent set to more restrictive' do
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'
describe 'projects/merge_requests/_commits.html.haml' do
include Devise::Test::ControllerHelpers
include ProjectForksHelper
let(:user) { create(:user) }
let(:target_project) { create(:project, :repository) }
let(:source_project) { create(:project, :repository, forked_from_project: target_project) }
let(:target_project) { create(:project, :public, :repository) }
let(:source_project) { fork_project(target_project, user, repository: true) }
let(:merge_request) do
create(:merge_request, :simple,
......
......@@ -2,16 +2,19 @@ require 'spec_helper'
describe 'projects/merge_requests/edit.html.haml' do
include Devise::Test::ControllerHelpers
include ProjectForksHelper
let(:user) { create(:user) }
let(:project) { create(:project, :repository) }
let(:fork_project) { create(:project, :repository, forked_from_project: project) }
let(:unlink_project) { Projects::UnlinkForkService.new(fork_project, user) }
let(:forked_project) { fork_project(project, user, repository: true) }
let(:unlink_project) { Projects::UnlinkForkService.new(forked_project, user) }
let(:milestone) { create(:milestone, project: project) }
let(:closed_merge_request) do
project.add_developer(user)
create(:closed_merge_request,
source_project: fork_project,
source_project: forked_project,
target_project: project,
author: user,
assignee: user,
......
......@@ -2,16 +2,17 @@ require 'spec_helper'
describe 'projects/merge_requests/show.html.haml' do
include Devise::Test::ControllerHelpers
include ProjectForksHelper
let(:user) { create(:user) }
let(:project) { create(:project, :repository) }
let(:fork_project) { create(:project, :repository, forked_from_project: project) }
let(:unlink_project) { Projects::UnlinkForkService.new(fork_project, user) }
let(:project) { create(:project, :public, :repository) }
let(:forked_project) { fork_project(project, user, repository: true) }
let(:unlink_project) { Projects::UnlinkForkService.new(forked_project, user) }
let(:note) { create(:note_on_merge_request, project: project, noteable: closed_merge_request) }
let(:closed_merge_request) do
create(:closed_merge_request,
source_project: fork_project,
source_project: forked_project,
target_project: project,
author: user)
end
......@@ -52,7 +53,7 @@ describe 'projects/merge_requests/show.html.haml' do
context 'when the merge request is open' do
it 'closes the merge request if the source project does not exist' do
closed_merge_request.update_attributes(state: 'open')
fork_project.destroy
forked_project.destroy
render
......
require 'spec_helper'
describe NamespacelessProjectDestroyWorker do
include ProjectForksHelper
subject { described_class.new }
before do
......@@ -55,9 +57,11 @@ describe NamespacelessProjectDestroyWorker do
context 'project forked from another' do
let!(:parent_project) { create(:project) }
before do
create(:forked_project_link, forked_to_project: project, forked_from_project: parent_project)
let(:project) do
namespaceless_project = fork_project(parent_project)
namespaceless_project.namespace_id = nil
namespaceless_project.save(validate: false)
namespaceless_project
end
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