summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG1
-rw-r--r--doc/api/projects.md12
-rw-r--r--lib/api/projects.rb17
-rw-r--r--spec/requests/api/fork_spec.rb73
4 files changed, 103 insertions, 0 deletions
diff --git a/CHANGELOG b/CHANGELOG
index cca1dc03c2b..2f71891a7ba 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -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
+ - API: Add support for forking a project via the API (Bernhard Kaindl)
- API: filter project issues by milestone (Julien Bianchi)
v 7.3.2
diff --git a/doc/api/projects.md b/doc/api/projects.md
index 9f6f6741093..8385e11b805 100644
--- a/doc/api/projects.md
+++ b/doc/api/projects.md
@@ -281,6 +281,18 @@ Parameters:
- `visibility_level` (optional)
- `import_url` (optional)
+### Fork project
+
+Forks a project into the user namespace of the authenticated user.
+
+```
+POST /projects/fork/:id
+```
+
+Parameters:
+
+- `id` (required) - The ID of the project to be forked
+
### Remove project
Removes a project including all associated resources (issues, merge requests etc.)
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index f555819df1b..7f7d2f8e9a8 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -153,6 +153,23 @@ module API
end
end
+ # Fork new project for the current user.
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # Example Request
+ # POST /projects/fork/:id
+ post 'fork/:id' do
+ @forked_project =
+ ::Projects::ForkService.new(user_project,
+ current_user).execute
+ if @forked_project.errors.any?
+ conflict!(@forked_project.errors.messages)
+ else
+ present @forked_project, with: Entities::Project
+ end
+ end
+
# Remove project
#
# Parameters:
diff --git a/spec/requests/api/fork_spec.rb b/spec/requests/api/fork_spec.rb
new file mode 100644
index 00000000000..cbbd1e7de5a
--- /dev/null
+++ b/spec/requests/api/fork_spec.rb
@@ -0,0 +1,73 @@
+require 'spec_helper'
+
+describe API::API, api: true do
+ include ApiHelpers
+ let(:user) { create(:user) }
+ let(:user2) { create(:user) }
+ let(:user3) { create(:user) }
+ let(:admin) { create(:admin) }
+ let(:project) {
+ create(:project, creator_id: user.id,
+ namespace: user.namespace)
+ }
+ let(:project_user2) {
+ create(:project_member, user: user2,
+ project: project,
+ access_level: ProjectMember::GUEST)
+ }
+
+ describe 'POST /projects/fork/:id' do
+ before { project_user2 }
+ before { user3 }
+
+ context 'when authenticated' do
+ it 'should fork if user has sufficient access to project' do
+ post api("/projects/fork/#{project.id}", user2)
+ response.status.should == 201
+ json_response['name'].should == project.name
+ json_response['path'].should == project.path
+ json_response['owner']['id'].should == user2.id
+ json_response['namespace']['id'].should == user2.namespace.id
+ json_response['forked_from_project']['id'].should == project.id
+ end
+
+ it 'should fork if user is admin' do
+ post api("/projects/fork/#{project.id}", admin)
+ response.status.should == 201
+ json_response['name'].should == project.name
+ json_response['path'].should == project.path
+ json_response['owner']['id'].should == admin.id
+ json_response['namespace']['id'].should == admin.namespace.id
+ json_response['forked_from_project']['id'].should == project.id
+ end
+
+ it 'should fail on missing project access for the project to fork' do
+ post api("/projects/fork/#{project.id}", user3)
+ response.status.should == 404
+ json_response['message'].should == '404 Not Found'
+ end
+
+ it 'should fail if forked project exists in the user namespace' do
+ post api("/projects/fork/#{project.id}", user)
+ response.status.should == 409
+ json_response['message']['base'].should == ['Invalid fork destination']
+ json_response['message']['name'].should == ['has already been taken']
+ json_response['message']['path'].should == ['has already been taken']
+ end
+
+ it 'should fail if project to fork from does not exist' do
+ post api('/projects/fork/424242', user)
+ response.status.should == 404
+ json_response['message'].should == '404 Not Found'
+ end
+ end
+
+ context 'when unauthenticated' do
+ it 'should return authentication error' do
+ post api("/projects/fork/#{project.id}")
+ response.status.should == 401
+ json_response['message'].should == '401 Unauthorized'
+ end
+ end
+ end
+end