Commit ea5a006f27cfd3013f94652e0e0f0e63091036ad

Authored by Angus MacArthur
1 parent 7ebbb6e3

Additon of apis for fork administration.

Added ability to add and remove the forked from/to relatioinship
between existing repos.
doc/api/projects.md
... ... @@ -453,3 +453,28 @@ Parameters:
453 453 + `id` (required) - The ID of the project.
454 454 + `branch` (required) - The name of the branch.
455 455  
  456 +
  457 +## Admin fork relation
  458 +
  459 +Allows modification of the forked relationship between existing projects. . Available only for admins.
  460 +
  461 +### Create a forked from/to relation between existing projects.
  462 +
  463 +```
  464 +POST /projects/:id/fork/:forked_from_id
  465 +```
  466 +
  467 +Parameters:
  468 +
  469 ++ `id` (required) - The ID of the project
  470 ++ `forked_from_id:` (required) - The ID of the project that was forked from
  471 +
  472 +### Delete an existing forked from relationship
  473 +
  474 +```
  475 +DELETE /projects/:id/fork
  476 +```
  477 +
  478 +Parameter:
  479 +
  480 ++ `id` (required) - The ID of the project
456 481 \ No newline at end of file
... ...
lib/api/entities.rb
... ... @@ -25,6 +25,12 @@ module API
25 25 expose :id, :url, :created_at
26 26 end
27 27  
  28 + class ForkedFromProject < Grape::Entity
  29 + expose :id
  30 + expose :name, :name_with_namespace
  31 + expose :path, :path_with_namespace
  32 + end
  33 +
28 34 class Project < Grape::Entity
29 35 expose :id, :description, :default_branch, :public, :ssh_url_to_repo, :http_url_to_repo, :web_url
30 36 expose :owner, using: Entities::UserBasic
... ... @@ -32,6 +38,7 @@ module API
32 38 expose :path, :path_with_namespace
33 39 expose :issues_enabled, :merge_requests_enabled, :wall_enabled, :wiki_enabled, :created_at, :last_activity_at
34 40 expose :namespace
  41 + expose :forked_from_project, using: Entities::ForkedFromProject, :if => lambda{ | project, options | project.forked? }
35 42 end
36 43  
37 44 class ProjectMember < UserBasic
... ...
lib/api/helpers.rb
... ... @@ -5,12 +5,12 @@ module API
5 5 end
6 6  
7 7 def user_project
8   - @project ||= find_project
  8 + @project ||= find_project(params[:id])
9 9 @project || not_found!
10 10 end
11 11  
12   - def find_project
13   - project = Project.find_by_id(params[:id]) || Project.find_with_namespace(params[:id])
  12 + def find_project(id)
  13 + project = Project.find_by_id(id) || Project.find_with_namespace(id)
14 14  
15 15 if project && can?(current_user, :read_project, project)
16 16 project
... ...
lib/api/projects.rb
... ... @@ -121,6 +121,42 @@ module API
121 121 end
122 122  
123 123  
  124 + # Mark this project as forked from another
  125 + #
  126 + # Parameters:
  127 + # id: (required) - The ID of the project being marked as a fork
  128 + # forked_from_id: (required) - The ID of the project it was forked from
  129 + # Example Request:
  130 + # POST /projects/:id/fork/:forked_from_id
  131 + post ":id/fork/:forked_from_id" do
  132 + authenticated_as_admin!
  133 + forked_from_project = find_project(params[:forked_from_id])
  134 + unless forked_from_project.nil?
  135 + if user_project.forked_from_project.nil?
  136 + user_project.create_forked_project_link(forked_to_project_id: user_project.id, forked_from_project_id: forked_from_project.id)
  137 + else
  138 + render_api_error!("Project already forked", 409)
  139 + end
  140 + else
  141 + not_found!
  142 + end
  143 +
  144 + end
  145 +
  146 + # Remove a forked_from relationship
  147 + #
  148 + # Parameters:
  149 + # id: (required) - The ID of the project being marked as a fork
  150 + # Example Request:
  151 + # DELETE /projects/:id/fork
  152 + delete ":id/fork" do
  153 + authenticated_as_admin!
  154 + unless user_project.forked_project_link.nil?
  155 + user_project.forked_project_link.destroy
  156 + end
  157 + end
  158 +
  159 +
124 160 # Get a project team members
125 161 #
126 162 # Parameters:
... ...
spec/requests/api/projects_spec.rb
... ... @@ -595,4 +595,71 @@ describe API::API do
595 595 end
596 596 end
597 597 end
  598 +
  599 + describe :fork_admin do
  600 + let(:project_fork_target) { create(:project) }
  601 + let(:project_fork_source) { create(:project, public: true) }
  602 +
  603 + describe "POST /projects/:id/fork/:forked_from_id" do
  604 + let(:new_project_fork_source) { create(:project, public: true) }
  605 +
  606 + it "shouldn't available for non admin users" do
  607 + post api("/projects/#{project_fork_target.id}/fork/#{project_fork_source.id}", user)
  608 + response.status.should == 403
  609 + end
  610 +
  611 + it "should allow project to be forked from an existing project" do
  612 + project_fork_target.forked?.should_not be_true
  613 + post api("/projects/#{project_fork_target.id}/fork/#{project_fork_source.id}", admin)
  614 + response.status.should == 201
  615 + project_fork_target.reload
  616 + project_fork_target.forked_from_project.id.should == project_fork_source.id
  617 + project_fork_target.forked_project_link.should_not be_nil
  618 + project_fork_target.forked?.should be_true
  619 + end
  620 +
  621 + it "should fail if forked_from project which does not exist" do
  622 + post api("/projects/#{project_fork_target.id}/fork/9999", admin)
  623 + response.status.should == 404
  624 + end
  625 +
  626 + it "should fail with 409 if already forked" do
  627 + post api("/projects/#{project_fork_target.id}/fork/#{project_fork_source.id}", admin)
  628 + project_fork_target.reload
  629 + project_fork_target.forked_from_project.id.should == project_fork_source.id
  630 + post api("/projects/#{project_fork_target.id}/fork/#{new_project_fork_source.id}", admin)
  631 + response.status.should == 409
  632 + project_fork_target.reload
  633 + project_fork_target.forked_from_project.id.should == project_fork_source.id
  634 + project_fork_target.forked?.should be_true
  635 + end
  636 + end
  637 +
  638 + describe "DELETE /projects/:id/fork" do
  639 +
  640 + it "shouldn't available for non admin users" do
  641 + delete api("/projects/#{project_fork_target.id}/fork", user)
  642 + response.status.should == 403
  643 + end
  644 +
  645 + it "should make forked project unforked" do
  646 + post api("/projects/#{project_fork_target.id}/fork/#{project_fork_source.id}", admin)
  647 + project_fork_target.reload
  648 + project_fork_target.forked_from_project.should_not be_nil
  649 + project_fork_target.forked?.should be_true
  650 + delete api("/projects/#{project_fork_target.id}/fork", admin)
  651 + response.status.should == 200
  652 + project_fork_target.reload
  653 + project_fork_target.forked_from_project.should be_nil
  654 + project_fork_target.forked?.should_not be_true
  655 + end
  656 +
  657 + it "should be idempotent if not forked" do
  658 + project_fork_target.forked_from_project.should be_nil
  659 + delete api("/projects/#{project_fork_target.id}/fork", admin)
  660 + response.status.should == 200
  661 + project_fork_target.reload.forked_from_project.should be_nil
  662 + end
  663 + end
  664 + end
598 665 end
... ...