Commit cb931752 authored by Nick Thomas's avatar Nick Thomas

Support setting more attributes when forking a project

Name, description, path, and visibility level, are all simple to set
when forking a project and open up a range of use cases (especially
path). Add support for these four attributes to the API and to the
Rails controller backend.
parent 458c0d35
......@@ -86,7 +86,7 @@ class Projects::ForksController < Projects::ApplicationController
def fork_service
strong_memoize(:fork_service) do
::Projects::ForkService.new(project, current_user, namespace: fork_namespace)
::Projects::ForkService.new(project, current_user, fork_params)
end
end
......@@ -96,6 +96,12 @@ class Projects::ForksController < Projects::ApplicationController
end
end
def fork_params
params.permit(:path, :name, :description, :visibility).tap do |param|
param[:namespace] = fork_namespace
end
end
def authorize_fork_namespace!
access_denied! unless fork_namespace && fork_service.valid_fork_target?
end
......
......@@ -43,8 +43,8 @@ module Projects
def new_fork_params
new_params = {
forked_from_project: @project,
visibility_level: allowed_visibility_level,
description: @project.description,
visibility_level: target_visibility_level,
description: target_description,
name: target_name,
path: target_path,
shared_runners_enabled: @project.shared_runners_enabled,
......@@ -107,6 +107,10 @@ module Projects
@target_name ||= @params[:name] || @project.name
end
def target_description
@target_description ||= @params[:description] || @project.description
end
def target_namespace
@target_namespace ||= @params[:namespace] || current_user.namespace
end
......@@ -115,8 +119,9 @@ module Projects
@skip_disk_validation ||= @params[:skip_disk_validation] || false
end
def allowed_visibility_level
def target_visibility_level
target_level = [@project.visibility_level, target_namespace.visibility_level].min
target_level = [target_level, Gitlab::VisibilityLevel.level_value(params[:visibility])].min if params.key?(:visibility)
Gitlab::VisibilityLevel.closest_allowed_level(target_level)
end
......
---
title: Support setting more attributes when forking a project
merge_request: 51962
author:
type: added
......@@ -1280,6 +1280,8 @@ POST /projects/:id/fork
| `namespace_path` | string | **{dotted-circle}** No | The path of the namespace that the project is forked to. |
| `namespace` | integer/string | **{dotted-circle}** No | _(Deprecated)_ The ID or path of the namespace that the project is forked to. |
| `path` | string | **{dotted-circle}** No | The path assigned to the resultant project after forking. |
| `description` | string | **{dotted-circle}** No | The description assigned to the resultant project after forking. |
| `visibility` | string | **{dotted-circle}** No | The [visibility level](#project-visibility-level) assigned to the resultant project after forking. |
## List Forks of a project
......
......@@ -295,6 +295,8 @@ module API
optional :namespace_path, type: String, desc: 'The path of the namespace that the project will be forked into'
optional :path, type: String, desc: 'The path that will be assigned to the fork'
optional :name, type: String, desc: 'The name that will be assigned to the fork'
optional :description, type: String, desc: 'The description that will be assigned to the fork'
optional :visibility, type: String, values: Gitlab::VisibilityLevel.string_values, desc: 'The visibility of the fork'
end
post ':id/fork', feature_category: :source_code_management do
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-foss/issues/42284')
......
......@@ -209,6 +209,13 @@ RSpec.describe Projects::ForksController do
}
end
let(:created_project) do
Namespace
.find_by_id(params[:namespace_key])
.projects
.find_by_path(params.fetch(:path, project.path))
end
subject do
post :create, params: params
end
......@@ -260,6 +267,21 @@ RSpec.describe Projects::ForksController do
expect(response).to redirect_to(namespace_project_import_path(user.namespace, project, continue: continue_params))
end
end
context 'custom attributes set' do
let(:params) { super().merge(path: 'something_custom', name: 'Something Custom', description: 'Something Custom', visibility: 'private') }
it 'creates a project with custom values' do
subject
expect(response).to have_gitlab_http_status(:found)
expect(response).to redirect_to(namespace_project_import_path(user.namespace, params[:path]))
expect(created_project.path).to eq(params[:path])
expect(created_project.name).to eq(params[:name])
expect(created_project.description).to eq(params[:description])
expect(created_project.visibility).to eq(params[:visibility])
end
end
end
context 'when user is not signed in' do
......
......@@ -3328,8 +3328,8 @@ RSpec.describe API::Projects do
expect(json_response['message']['path']).to eq(['has already been taken'])
end
it 'accepts a name for the target project' do
post api("/projects/#{project.id}/fork", user2), params: { name: 'My Random Project' }
it 'accepts custom parameters for the target project' do
post api("/projects/#{project.id}/fork", user2), params: { name: 'My Random Project', description: 'A description', visibility: 'private' }
expect(response).to have_gitlab_http_status(:created)
expect(json_response['name']).to eq('My Random Project')
......@@ -3337,6 +3337,8 @@ RSpec.describe API::Projects do
expect(json_response['owner']['id']).to eq(user2.id)
expect(json_response['namespace']['id']).to eq(user2.namespace.id)
expect(json_response['forked_from_project']['id']).to eq(project.id)
expect(json_response['description']).to eq('A description')
expect(json_response['visibility']).to eq('private')
expect(json_response['import_status']).to eq('scheduled')
expect(json_response).to include("import_error")
end
......@@ -3368,6 +3370,13 @@ RSpec.describe API::Projects do
expect(json_response['message']['path']).to eq(['has already been taken'])
expect(json_response['message']['name']).to eq(['has already been taken'])
end
it 'fails to fork with an unknown visibility level' do
post api("/projects/#{project.id}/fork", user2), params: { visibility: 'something' }
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response['error']).to eq('visibility does not have a valid value')
end
end
context 'when unauthenticated' do
......
......@@ -323,6 +323,50 @@ RSpec.describe Projects::ForkService do
end
end
end
describe 'fork with optional attributes' do
let(:public_project) { create(:project, :public) }
it 'sets optional attributes to specified values' do
forked_project = fork_project(
public_project,
nil,
namespace: public_project.namespace,
path: 'forked',
name: 'My Fork',
description: 'Description',
visibility: 'internal',
using_service: true
)
expect(forked_project.path).to eq('forked')
expect(forked_project.name).to eq('My Fork')
expect(forked_project.description).to eq('Description')
expect(forked_project.visibility_level).to eq(Gitlab::VisibilityLevel::INTERNAL)
end
it 'sets visibility level to private if an unknown visibility is requested' do
forked_project = fork_project(public_project, nil, using_service: true, visibility: 'unknown')
expect(forked_project.visibility_level).to eq(Gitlab::VisibilityLevel::PRIVATE)
end
it 'sets visibility level to project visibility level if requested visibility is greater' do
private_project = create(:project, :private)
forked_project = fork_project(private_project, nil, using_service: true, visibility: 'public')
expect(forked_project.visibility_level).to eq(Gitlab::VisibilityLevel::PRIVATE)
end
it 'sets visibility level to target namespace visibility level if requested visibility is greater' do
private_group = create(:group, :private)
forked_project = fork_project(public_project, nil, namespace: private_group, using_service: true, visibility: 'public')
expect(forked_project.visibility_level).to eq(Gitlab::VisibilityLevel::PRIVATE)
end
end
end
context 'when a project is already forked' 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