diff --git a/CHANGELOG b/CHANGELOG index 3f52fab74d71ccf576dae27a338d79cefa008f2e..49c0c906e62b5d4b14851932f145ce515bb5be0b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.12.0 (unreleased) + - Add abillity to fork to a specific namespace using API. @ritave <olaf@tomalka.me> - Prepend blank line to `Closes` message on merge request linked to issue (lukehowell) - Filter tags by name !6121 - Make push events have equal vertical spacing. diff --git a/doc/api/projects.md b/doc/api/projects.md index a62aaee14d79061fe112705a18914c8419b2384a..fe3c8709d1391caae9068820b3dcc4a99d913f36 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -514,7 +514,7 @@ invalid, 400 is returned. ### Fork project -Forks a project into the user namespace of the authenticated user. +Forks a project into the user namespace of the authenticated user or the one provided. ``` POST /projects/fork/:id @@ -523,6 +523,7 @@ POST /projects/fork/:id Parameters: - `id` (required) - The ID or NAMESPACE/PROJECT_NAME of the project to be forked +- `namespace` (optional) - The ID or path of the namespace that the project will be forked to ### Star a project diff --git a/lib/api/projects.rb b/lib/api/projects.rb index a1fd598414abcf4d67e36804694f64edfc602caf..d35ec73c8c15b09c9601f214be8c01f03d38c8d7 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -189,16 +189,28 @@ module API end end - # Fork new project for the current user. + # Fork new project for the current user or provided namespace. # # Parameters: # id (required) - The ID of a project + # namespace (optional) - The ID or name of the namespace that the project will be forked into. # Example Request # POST /projects/fork/:id post 'fork/:id' do + attrs = {} + namespace_id = params[:namespace] + if namespace_id.present? + namespace = Namespace.find_by(id: namespace_id) || Namespace.find_by_path_or_name(namespace_id) + if namespace.nil? + not_found!('Target Namespace') + end + authorize! :create_projects, namespace + attrs[:namespace] = namespace + end @forked_project = ::Projects::ForkService.new(user_project, - current_user).execute + current_user, + attrs).execute if @forked_project.errors.any? conflict!(@forked_project.errors.messages) else diff --git a/spec/requests/api/fork_spec.rb b/spec/requests/api/fork_spec.rb index f802fcd2d2e590607baabd4a40d7b143e9780e9f..97f17efc053959b8f2636245c554088a72dc5cac 100644 --- a/spec/requests/api/fork_spec.rb +++ b/spec/requests/api/fork_spec.rb @@ -3,9 +3,15 @@ require 'spec_helper' describe API::API, api: true do include ApiHelpers let(:user) { create(:user) } - let(:user2) { create(:user) } + let(:user2) { create(:user, username: 'user2_name') } let(:user3) { create(:user) } let(:admin) { create(:admin) } + let(:group) { create(:group, name: 'group_name') } + let(:group2) do + group = create(:group, name: 'group2_name') + group.add_owner(user2) + group + end let(:project) do create(:project, creator_id: user.id, namespace: user.namespace) @@ -58,6 +64,52 @@ describe API::API, api: true do expect(response).to have_http_status(404) expect(json_response['message']).to eq('404 Project Not Found') end + + it 'forks with explicit own user namespace id' do + post api("/projects/fork/#{project.id}?namespace=#{user2.namespace.id}", user2) + expect(response).to have_http_status(201) + expect(json_response['owner']['id']).to eq(user2.id) + end + + it 'forks with explicit own user name as namespace' do + post api("/projects/fork/#{project.id}?namespace=#{user2.username}", user2) + expect(response).to have_http_status(201) + expect(json_response['owner']['id']).to eq(user2.id) + end + + it 'forks to another user when admin' do + post api("/projects/fork/#{project.id}?namespace=#{user2.username}", admin) + expect(response).to have_http_status(201) + expect(json_response['owner']['id']).to eq(user2.id) + end + + it 'fails if trying to fork to another user when not admin' do + post api("/projects/fork/#{project.id}?namespace=#{admin.namespace.id}", user2) + expect(response).to have_http_status(403) + end + + it 'fails if trying to fork to non-existent namespace' do + post api("/projects/fork/#{project.id}?namespace=42424242", user2) + expect(response).to have_http_status(404) + expect(json_response['message']).to eq('404 Target Namespace Not Found') + end + + it 'forks to owned group' do + post api("/projects/fork/#{project.id}?namespace=#{group2.name}", user2) + expect(response).to have_http_status(201) + expect(json_response['namespace']['name']).to eq(group2.name) + end + + it 'fails to fork to not owned group' do + post api("/projects/fork/#{project.id}?namespace=#{group.name}", user2) + expect(response).to have_http_status(403) + end + + it 'forks to not owned group when admin' do + post api("/projects/fork/#{project.id}?namespace=#{group.name}", admin) + expect(response).to have_http_status(201) + expect(json_response['namespace']['name']).to eq(group.name) + end end context 'when unauthenticated' do