Commit 57c4036d authored by Rémy Coutable's avatar Rémy Coutable

Merge branch 'feature/fork-to-group-api' into 'master'

API: Add the ability to fork to a specific namespace

Browser interface allows forking to an owned grup.
This commit brings API up to speed by providing optional namespace
parameter to fork API. This allows forking to users and groups under
forker's control using their id or unique name.

Fixes #21591 

## Does this MR meet the acceptance criteria?

- [x] [CHANGELOG](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CHANGELOG) entry added
- [x] [Documentation created/updated](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/development/doc_styleguide.md)
- [x] API support added
- Tests
  - [x] Added for this feature/bug
  - [x] All builds are passing
- [x] Conform by the [merge request performance guides](http://docs.gitlab.com/ce/development/merge_request_performance_guidelines.html)
- [x] Conform by the [style guides](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#style-guides)
- [x] Branch has no merge conflicts with `master` (if you do - rebase it please)
- [x] [Squashed related commits together](https://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits)

See merge request !6213
parents 1e042ac2 a3fdd76d
Please view this file on the master branch, on stable branches it's out of date. Please view this file on the master branch, on stable branches it's out of date.
v 8.12.0 (unreleased) v 8.12.0 (unreleased)
- Add ability to fork to a specific namespace using API. (ritave)
- Cleanup misalignments in Issue list view !6206 - Cleanup misalignments in Issue list view !6206
- Prepend blank line to `Closes` message on merge request linked to issue (lukehowell) - Prepend blank line to `Closes` message on merge request linked to issue (lukehowell)
- Filter tags by name !6121 - Filter tags by name !6121
......
...@@ -514,7 +514,7 @@ invalid, 400 is returned. ...@@ -514,7 +514,7 @@ invalid, 400 is returned.
### Fork project ### 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 POST /projects/fork/:id
...@@ -523,6 +523,7 @@ POST /projects/fork/:id ...@@ -523,6 +523,7 @@ POST /projects/fork/:id
Parameters: Parameters:
- `id` (required) - The ID or NAMESPACE/PROJECT_NAME of the project to be forked - `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 ### Star a project
......
...@@ -189,16 +189,30 @@ module API ...@@ -189,16 +189,30 @@ module API
end end
end end
# Fork new project for the current user. # Fork new project for the current user or provided namespace.
# #
# Parameters: # Parameters:
# id (required) - The ID of a project # 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 # Example Request
# POST /projects/fork/:id # POST /projects/fork/:id
post 'fork/:id' do 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)
not_found!('Target Namespace') unless namespace
attrs[:namespace] = namespace
end
@forked_project = @forked_project =
::Projects::ForkService.new(user_project, ::Projects::ForkService.new(user_project,
current_user).execute current_user,
attrs).execute
if @forked_project.errors.any? if @forked_project.errors.any?
conflict!(@forked_project.errors.messages) conflict!(@forked_project.errors.messages)
else else
......
...@@ -6,6 +6,12 @@ describe API::API, api: true do ...@@ -6,6 +6,12 @@ describe API::API, api: true do
let(:user2) { create(:user) } let(:user2) { create(:user) }
let(:user3) { create(:user) } let(:user3) { create(:user) }
let(:admin) { create(:admin) } let(:admin) { create(:admin) }
let(:group) { create(:group) }
let(:group2) do
group = create(:group, name: 'group2_name')
group.add_owner(user2)
group
end
let(:project) do let(:project) do
create(:project, creator_id: user.id, namespace: user.namespace) create(:project, creator_id: user.id, namespace: user.namespace)
...@@ -22,6 +28,7 @@ describe API::API, api: true do ...@@ -22,6 +28,7 @@ describe API::API, api: true do
context 'when authenticated' do context 'when authenticated' do
it 'forks if user has sufficient access to project' do it 'forks if user has sufficient access to project' do
post api("/projects/fork/#{project.id}", user2) post api("/projects/fork/#{project.id}", user2)
expect(response).to have_http_status(201) expect(response).to have_http_status(201)
expect(json_response['name']).to eq(project.name) expect(json_response['name']).to eq(project.name)
expect(json_response['path']).to eq(project.path) expect(json_response['path']).to eq(project.path)
...@@ -32,6 +39,7 @@ describe API::API, api: true do ...@@ -32,6 +39,7 @@ describe API::API, api: true do
it 'forks if user is admin' do it 'forks if user is admin' do
post api("/projects/fork/#{project.id}", admin) post api("/projects/fork/#{project.id}", admin)
expect(response).to have_http_status(201) expect(response).to have_http_status(201)
expect(json_response['name']).to eq(project.name) expect(json_response['name']).to eq(project.name)
expect(json_response['path']).to eq(project.path) expect(json_response['path']).to eq(project.path)
...@@ -42,12 +50,14 @@ describe API::API, api: true do ...@@ -42,12 +50,14 @@ describe API::API, api: true do
it 'fails on missing project access for the project to fork' do it 'fails on missing project access for the project to fork' do
post api("/projects/fork/#{project.id}", user3) post api("/projects/fork/#{project.id}", user3)
expect(response).to have_http_status(404) expect(response).to have_http_status(404)
expect(json_response['message']).to eq('404 Project Not Found') expect(json_response['message']).to eq('404 Project Not Found')
end end
it 'fails if forked project exists in the user namespace' do it 'fails if forked project exists in the user namespace' do
post api("/projects/fork/#{project.id}", user) post api("/projects/fork/#{project.id}", user)
expect(response).to have_http_status(409) expect(response).to have_http_status(409)
expect(json_response['message']['name']).to eq(['has already been taken']) expect(json_response['message']['name']).to eq(['has already been taken'])
expect(json_response['message']['path']).to eq(['has already been taken']) expect(json_response['message']['path']).to eq(['has already been taken'])
...@@ -55,14 +65,70 @@ describe API::API, api: true do ...@@ -55,14 +65,70 @@ describe API::API, api: true do
it 'fails if project to fork from does not exist' do it 'fails if project to fork from does not exist' do
post api('/projects/fork/424242', user) post api('/projects/fork/424242', user)
expect(response).to have_http_status(404) expect(response).to have_http_status(404)
expect(json_response['message']).to eq('404 Project Not Found') expect(json_response['message']).to eq('404 Project Not Found')
end end
it 'forks with explicit own user namespace id' do
post api("/projects/fork/#{project.id}", user2), namespace: user2.namespace.id
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}", user2), namespace: user2.username
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}", admin), namespace: user2.username
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}", user2), namespace: admin.namespace.id
expect(response).to have_http_status(409)
end
it 'fails if trying to fork to non-existent namespace' do
post api("/projects/fork/#{project.id}", user2), namespace: 42424242
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}", user2), namespace: group2.name
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}", user2), namespace: group.name
expect(response).to have_http_status(409)
end
it 'forks to not owned group when admin' do
post api("/projects/fork/#{project.id}", admin), namespace: group.name
expect(response).to have_http_status(201)
expect(json_response['namespace']['name']).to eq(group.name)
end
end end
context 'when unauthenticated' do context 'when unauthenticated' do
it 'returns authentication error' do it 'returns authentication error' do
post api("/projects/fork/#{project.id}") post api("/projects/fork/#{project.id}")
expect(response).to have_http_status(401) expect(response).to have_http_status(401)
expect(json_response['message']).to eq('401 Unauthorized') expect(json_response['message']).to eq('401 Unauthorized')
end end
......
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