Commit c44764f5 authored by Bernhard Kaindl's avatar Bernhard Kaindl
Browse files

Prepare ForkService to support forking projects to given namespaces

Remove overload of BaseService.initialize, so initialize gains params,
which is used to pass the namespace (like e.g. in TransferService).

The namespace is checked for permission to create projects in it.
parent 0e2f4186b3cf
......@@ -9,6 +9,7 @@ v 7.4.0
- Do not delete tmp/repositories itself during clean-up, only its contents
- Support for backup uploads to remote storage
- Prevent notes polling when there are not notes
- Internal ForkService: Prepare support for fork to a given namespace
- API: Add support for forking a project via the API (Bernhard Kaindl)
- API: filter project issues by milestone (Julien Bianchi)
- Fail harder in the backup script
......@@ -2,11 +2,9 @@ module Projects
class ForkService < BaseService
include Gitlab::ShellAdapter
def initialize(project, user)
@from_project, @current_user = project, user
def execute
@from_project = @project
project_params = {
visibility_level: @from_project.visibility_level,
description: @from_project.description,
......@@ -15,8 +13,15 @@ def execute
project = =
project.path = @from_project.path
project.namespace = current_user.namespace
project.creator = current_user
project.namespace = @current_user.namespace
if namespace = @params[:namespace]
project.namespace = namespace
project.creator = @current_user
unless @current_user.can?(:create_projects, project.namespace)
project.errors.add(:namespace, 'insufficient access rights')
return project
# If the project cannot save, we do not want to trigger the project destroy
# as this can have the side effect of deleting a repo attached to an existing
......@@ -27,7 +32,7 @@ def execute
#First save the DB entries as they can be rolled back if the repo fork fails
project.build_forked_project_link(forked_to_project_id:, forked_from_project_id:
if << [current_user, :master] << [@current_user, :master]
#Now fork the repo
unless gitlab_shell.fork_repository(@from_project.path_with_namespace, project.namespace.path)
......@@ -42,10 +42,54 @@
def fork_project(from_project, user, fork_success = true)
context =, user)
shell = double("gitlab_shell")
shell.stub(fork_repository: fork_success)
describe :fork_to_namespace do
before do
@group_owner = create(:user)
@developer = create(:user)
@project = create(:project, creator_id:,
star_count: 777,
description: 'Wow, such a cool project!')
@group = create(:group)
@group.add_user(@group_owner, GroupMember::OWNER)
@group.add_user(@developer, GroupMember::DEVELOPER)
@opts = { namespace: @group }
context 'fork project for group' do
it 'group owner successfully forks project into the group' do
to_project = fork_project(@project, @group_owner, true, @opts)
to_project.owner.should == @group
to_project.namespace.should == @group ==
to_project.path.should == @project.path
to_project.description.should == @project.description
to_project.star_count.should be_zero
context 'fork project for group when user not owner' do
it 'group developer should fail to fork project into the group' do
to_project = fork_project(@project, @developer, true, @opts)
to_project.errors[:namespace].should == ['insufficient access rights']
context 'project already exists in group' do
it 'should fail due to validation, not transaction failure' do
existing_project = create(:project, name:,
namespace: @group)
to_project = fork_project(@project, @group_owner, true, @opts)
existing_project.persisted?.should be_true
to_project.errors[:base].should == ['Invalid fork destination']
to_project.errors[:name].should == ['has already been taken']
to_project.errors[:path].should == ['has already been taken']
def fork_project(from_project, user, fork_success = true, params = {})
context =, user, params)
shell = double('gitlab_shell').stub(fork_repository: fork_success)
context.stub(gitlab_shell: shell)
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