Commit ea5a006f27cfd3013f94652e0e0f0e63091036ad
1 parent
7ebbb6e3
Exists in
master
and in
4 other branches
Additon of apis for fork administration.
Added ability to add and remove the forked from/to relatioinship between existing repos.
Showing
5 changed files
with
138 additions
and
3 deletions
Show diff stats
doc/api/projects.md
| @@ -453,3 +453,28 @@ Parameters: | @@ -453,3 +453,28 @@ Parameters: | ||
| 453 | + `id` (required) - The ID of the project. | 453 | + `id` (required) - The ID of the project. |
| 454 | + `branch` (required) - The name of the branch. | 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 | \ No newline at end of file | 481 | \ No newline at end of file |
lib/api/entities.rb
| @@ -25,6 +25,12 @@ module API | @@ -25,6 +25,12 @@ module API | ||
| 25 | expose :id, :url, :created_at | 25 | expose :id, :url, :created_at |
| 26 | end | 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 | class Project < Grape::Entity | 34 | class Project < Grape::Entity |
| 29 | expose :id, :description, :default_branch, :public, :ssh_url_to_repo, :http_url_to_repo, :web_url | 35 | expose :id, :description, :default_branch, :public, :ssh_url_to_repo, :http_url_to_repo, :web_url |
| 30 | expose :owner, using: Entities::UserBasic | 36 | expose :owner, using: Entities::UserBasic |
| @@ -32,6 +38,7 @@ module API | @@ -32,6 +38,7 @@ module API | ||
| 32 | expose :path, :path_with_namespace | 38 | expose :path, :path_with_namespace |
| 33 | expose :issues_enabled, :merge_requests_enabled, :wall_enabled, :wiki_enabled, :created_at, :last_activity_at | 39 | expose :issues_enabled, :merge_requests_enabled, :wall_enabled, :wiki_enabled, :created_at, :last_activity_at |
| 34 | expose :namespace | 40 | expose :namespace |
| 41 | + expose :forked_from_project, using: Entities::ForkedFromProject, :if => lambda{ | project, options | project.forked? } | ||
| 35 | end | 42 | end |
| 36 | 43 | ||
| 37 | class ProjectMember < UserBasic | 44 | class ProjectMember < UserBasic |
lib/api/helpers.rb
| @@ -5,12 +5,12 @@ module API | @@ -5,12 +5,12 @@ module API | ||
| 5 | end | 5 | end |
| 6 | 6 | ||
| 7 | def user_project | 7 | def user_project |
| 8 | - @project ||= find_project | 8 | + @project ||= find_project(params[:id]) |
| 9 | @project || not_found! | 9 | @project || not_found! |
| 10 | end | 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 | if project && can?(current_user, :read_project, project) | 15 | if project && can?(current_user, :read_project, project) |
| 16 | project | 16 | project |
lib/api/projects.rb
| @@ -121,6 +121,42 @@ module API | @@ -121,6 +121,42 @@ module API | ||
| 121 | end | 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 | # Get a project team members | 160 | # Get a project team members |
| 125 | # | 161 | # |
| 126 | # Parameters: | 162 | # Parameters: |
spec/requests/api/projects_spec.rb
| @@ -595,4 +595,71 @@ describe API::API do | @@ -595,4 +595,71 @@ describe API::API do | ||
| 595 | end | 595 | end |
| 596 | end | 596 | end |
| 597 | end | 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 | end | 665 | end |